[
  {
    "path": ".github/CODEOWNERS",
    "content": "\n# https://help.github.com/en/articles/about-code-owners\n*       @urbanairship/mobile\n"
  },
  {
    "path": ".github/CONTRIBUTING.md",
    "content": "## Contributing Code\n\nWe accept pull requests! If you would like to submit a pull request, please fill out and submit our\n[Contributor License Agreement](https://docs.google.com/forms/d/e/1FAIpQLScErfiz-fXSPpVZ9r8Di2Tr2xDFxt5MgzUel0__9vqUgvko7Q/viewform).\n\nOne of our engineers will verify receipt of the agreement before approving your pull request.\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE.md",
    "content": "\n\n❗For how-to inquiries involving Airship functionality or use cases, please\ncontact (support)[https://support.airship.com/].\n\n<!---\n\nPlease fill out this template when submitting an issue.\n\nAll lines beginning with an ℹ symbol indicate information that's\nimportant for you to provide to ensure your issue is resolved as\nquickly and efficiently as possible.\n\n-->\n\n# Preliminary Info\n\n### What Airship dependencies are you using?\n<!-- ℹ Please include Airship dependencies and their versions here. -->\n\n### What are the versions of any relevant development tools you are using?\n<!-- ℹ If applicable, please include any relevant information about the\nversion of your IDE, plugin(s), or package manager(s) here. -->\n\n# Report\n\n### What unexpected behavior are you seeing?\n<!-- ℹ Please provide a brief description of the unexpected behavior here. -->\n\n### What is the expected behavior?\n<!-- ℹ Please provide a brief description of the expected behavior here. -->\n\n### What are the steps to reproduce the unexpected behavior?\n<!-- ℹ Please provide reproduction steps here. -->\n\n### Do you have logging for the issue?\n<!-- ℹ Please provide links to gists of any pertinent logs here. -->\n"
  },
  {
    "path": ".github/PULL_REQUEST_TEMPLATE.md",
    "content": "\n<!--\n\nPlease fill out this template when submitting an pull request.\n\nAll lines beginning with an ℹ symbol indicate information that's\nimportant for you to provide to ensure your pull request is reviewed\nas quickly and efficiently as possible.\n\n-->\n\n### What do these changes do?\n<!-- ℹ Please provide a description of your changes here. -->\n\n### Why are these changes necessary?\n<!-- ℹ Please provide a concise description of why your changes are\nnecessary here. -->\n\n### How did you verify these changes?\n<!-- ℹ Please provide any relevant steps to verify or test your work. Make\nthis description clear enough so someone unfamiliar with your work could\nverify for you. -->\n\n#### Verification Screenshots:\n<!-- ℹ If applicable, please provide screenshots here. -->\n\n### Anything else a reviewer should know?\n<!-- ℹ Please provide any additional notes for the reviewer here. -->\n"
  },
  {
    "path": ".github/workflows/check-cert.yml",
    "content": "name: Check Certificate Expiration\n\non:\n  workflow_dispatch:\n\njobs:\n  check-cert:\n    runs-on: macos-latest\n    steps:\n      - name: Check Certificate Details\n        env:\n          CERT_BASE64: ${{ secrets.CERTIFICATEXC }}\n          CERT_PASS: ${{ secrets.CERTIFICATEXC_PASS }}\n        run: |\n          # Decode the certificate from base64\n          echo \"$CERT_BASE64\" | base64 --decode > cert.p12\n\n          # Extract certificate info without importing to keychain\n          # This will show expiration date and other details\n          openssl pkcs12 -in cert.p12 -passin pass:\"$CERT_PASS\" -nokeys -clcerts | openssl x509 -noout -subject -issuer -dates -fingerprint\n\n          # Also show the certificate name as it appears for codesigning\n          openssl pkcs12 -in cert.p12 -passin pass:\"$CERT_PASS\" -nokeys -clcerts | openssl x509 -noout -subject | sed 's/subject=//'\n\n          # Clean up\n          rm cert.p12"
  },
  {
    "path": ".github/workflows/check_framework_size.yml",
    "content": "name: Check Framework Size\n\non:\n  workflow_dispatch:\n\nenv:\n  BUNDLE_PATH: vendor/bundle\n\njobs:\n \n  framework-size:\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Restore Size\n        uses: actions/cache@v4\n        with:\n            path: build/previous-size.txt\n            key: size-cache-${{ github.head_ref }}\n            restore-keys: |\n             size-cache-\n      - name: Install Apple Certificate\n        uses: apple-actions/import-codesign-certs@v3\n        with:\n          p12-file-base64: ${{ secrets.CERTIFICATEXC }}\n          p12-password: ${{ secrets.CERTIFICATEXC_PASS }}\n      - name: Framework size\n        run: make compare-framework-size\n      - name: Save Size\n        uses: actions/cache@v4\n        with:\n           path: build/previous-size.txt\n           key: size-cache-${{ github.head_ref }}-${{ github.run_id }}\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI Pull Request\n\non: [pull_request]\n\nenv:\n  BUNDLE_PATH: vendor/bundle\n\nconcurrency:\n  group: ${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  run-tests-core:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 5\n    steps:\n      - uses: actions/checkout@v4\n      - name: Test\n        run: make test-core\n\n  run-tests-message-center:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 5\n    steps:\n      - uses: actions/checkout@v4\n      - name: Test\n        run: make test-message-center\n\n  run-tests-preference-center:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 5\n    steps:\n      - uses: actions/checkout@v4\n      - name: Test\n        run: make test-preference-center\n\n  run-tests-feature-flags:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 5\n    steps:\n      - uses: actions/checkout@v4\n      - name: Test\n        run: make test-feature-flags\n\n  run-tests-automation:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 5\n    steps:\n      - uses: actions/checkout@v4\n      - name: Test\n        run: make test-automation\n\n  run-tests-extensions:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 5\n    steps:\n      - uses: actions/checkout@v4\n      - name: Test\n        run: make test-service-extension\n\n  build-airship-objectiveC:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@v4\n      - name: Build AirshipObjectiveC target\n        run: make build-airship-objectiveC\n        \n  build-samples:\n    runs-on: macos-15-xlarge\n    timeout-minutes: 15\n    steps:\n      - uses: actions/checkout@v4\n      - name: Build samples\n        run: make build-samples\n      \n\n  # run-tests-watchos:\n  #   runs-on: macos-15-xlarge\n  #   steps:\n  #     - uses: actions/checkout@v4\n  #     - uses: ruby/setup-ruby@v1\n  #       with:\n  #         bundler-cache: true\n  #     - name: Install xcodegen\n  #       run: brew install xcodegen\n  #     - name: Install Apple Certificate\n  #       uses: apple-actions/import-codesign-certs@v1\n  #       with:\n  #         p12-file-base64: ${{ secrets.CERTIFICATE_P12_BASE64 }}\n  #         p12-password: ${{ secrets.CERTIFICATE_P12_PASSWORD }}\n  #     - name: Install the provisioning profile\n  #       env:\n  #         PROVISIONING_APP_BASE64: ${{ secrets.PROVISIONING_PROFILE_APP_BASE64 }}\n  #         PROVISIONING_EXT_BASE64: ${{ secrets.PROVISIONING_PROFILE_EXT_BASE64 }}\n  #       run: |\n  #         PP_APP_PATH=$RUNNER_TEMP/wkapp_prof.mobileprovision\n  #         PP_EXT_PATH=$RUNNER_TEMP/wkext_prof.mobileprovision\n\n  #         echo -n \"$PROVISIONING_APP_BASE64\" | base64 --decode > $PP_APP_PATH\n  #         echo -n \"$PROVISIONING_EXT_BASE64\" | base64 --decode > $PP_EXT_PATH\n\n  #         mkdir -p ~/Library/MobileDevice/Provisioning\\ Profiles\n  #         cp $PP_APP_PATH ~/Library/MobileDevice/Provisioning\\ Profiles\n  #         cp $PP_EXT_PATH ~/Library/MobileDevice/Provisioning\\ Profiles\n  #     - name: Test\n  #       run: make build-sample-watchos\n        \n  pod-lib-lint-watchos:\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Pod lint\n        run: make pod-lint-watchos\n        \n  pod-lib-lint-tvos:\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Pod lint\n        run: make pod-lint-tvos\n\n  pod-lib-lint-ios:\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Pod lint\n        run: make pod-lint-ios\n\n  pod-lib-lint-extensions:\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Pod lint\n        run: make pod-lint-extensions\n"
  },
  {
    "path": ".github/workflows/deploy_docC.yml",
    "content": "name: Deploy docC to Pages\n\non:\n  push:\n    tags:\n      - \"[0-9]+.[0-9]+.[0-9]+**\"\n      \n  # Allows you to run this workflow manually from the Actions tab\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: write\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: \"pages\"\n  cancel-in-progress: false\n\njobs:\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: macos-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n        \n      - name: Extract version\n        id: extract_version\n        run: |\n          set -euo pipefail\n          VERSION=\"${GITHUB_REF#refs/tags/}\"\n          MAJOR_VERSION=$(echo \"$VERSION\" | cut -d. -f1)\n          echo \"VERSION=$VERSION\" >> $GITHUB_OUTPUT\n          echo \"VERSION_DIR=v$MAJOR_VERSION\" >> $GITHUB_OUTPUT\n          echo \"VERSION_DIR=v$MAJOR_VERSION\" >> $GITHUB_ENV\n\n      - name: Check Version\n        run: |\n          set -euo pipefail\n          bash ./scripts/check_version.sh \"${{ steps.extract_version.outputs.VERSION }}\"\n\n      - name: Build DocC\n        env:\n          VERSION_DIR: ${{ steps.extract_version.outputs.VERSION_DIR }}\n        run: |\n          set -euo pipefail\n          make build-docC version=\"$VERSION_DIR\"\n\n      - name: Deploy to GitHub Pages\n        env:\n          VERSION_DIR: ${{ steps.extract_version.outputs.VERSION_DIR }}\n        uses: peaceiris/actions-gh-pages@v4\n        with:\n          github_token: ${{ secrets.GITHUB_TOKEN }}\n          publish_branch: gh-pages\n          publish_dir: ./docs\n          destination_dir: $VERSION_DIR\n          keep_files: true\n          enable_jekyll: false\n"
  },
  {
    "path": ".github/workflows/merge.yml",
    "content": "name: CI Merge\n\non:\n    push:\n      branches:\n        - main\n        - next\n\nenv:\n  BUNDLE_PATH: vendor/bundle\n\nconcurrency:\n  group: ${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  build-sdk:\n    runs-on: macos-15-xlarge\n    steps:\n      - name: Install Apple Certificate\n        uses: apple-actions/import-codesign-certs@v3\n        with:\n          p12-file-base64: ${{ secrets.CERTIFICATEXC }}\n          p12-password: ${{ secrets.CERTIFICATEXC_PASS }}\n      - uses: actions/checkout@v4\n      - name: Build SDK\n        run: make build-xcframeworks-no-sign\n\n  finished:\n    runs-on: ubuntu-latest\n    needs: [build-sdk]\n    steps:\n      - name: Slack Notification\n        uses: lazy-actions/slatify@master\n        if: ${{ failure() }}\n        with:\n          type: ${{ job.status }}\n          job_name: \"Merge things busted!\"\n          url: ${{ secrets.SLACK_WEBHOOK }}"
  },
  {
    "path": ".github/workflows/pr-review.yml",
    "content": "name: Gemini PR Review\n\non:\n  pull_request:\n    types: [opened, edited, synchronize]\n\nconcurrency: pr-${{ github.event.pull_request.number }}\n\njobs:\n  review:\n    runs-on: ubuntu-latest\n    permissions:\n      pull-requests: write\n      contents: read\n\n    steps:\n    - name: ⬇️  Checkout\n      uses: actions/checkout@v4\n\n    - name: 🪄  Save PR diff\n      env:\n        GH_TOKEN: ${{ secrets.MOBILE_REVIEW_PAT }}\n      run: gh pr diff ${{ github.event.pull_request.number }} > diff.patch\n\n    - name: 🛠️  Set up Node 20\n      uses: actions/setup-node@v4\n      with:\n        node-version: 20\n\n    - name: 🤖 Run Gemini review script\n      continue-on-error: true\n      env:\n        GEMINI_API_KEY: ${{ secrets.GEMINI_API_KEY }}\n        MODEL_ID:       gemini-2.5-pro-preview-05-06\n        GITHUB_TOKEN:   ${{ secrets.MOBILE_REVIEW_PAT }}\n        PR_NUMBER:      ${{ github.event.pull_request.number }}\n        REPO:           ${{ github.repository }}\n        DEBUG:          true\n      run: node scripts/pr-review.mjs\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release\n\non:\n  push:\n    tags:\n      - \"[0-9]+.[0-9]+.[0-9]+**\"\nenv:\n  BUNDLE_PATH: vendor/bundle\n\njobs:\n  check-version:\n    if: github.repository == 'urbanairship/ios-library'\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Get the version\n        id: get_version\n        run: |\n          set -euo pipefail\n          VERSION=\"${GITHUB_REF#refs/tags/}\"\n          echo \"VERSION=$VERSION\" >> $GITHUB_OUTPUT\n\n      - name: Check Version\n        run: |\n          set -euo pipefail\n          bash ./scripts/check_version.sh \"${{ steps.get_version.outputs.VERSION }}\"\n\n      - name: Slack Notification\n        uses: lazy-actions/slatify@master\n        with:\n          type: ${{ job.status }}\n          job_name: \"iOS SDK Release Started :apple_og:\"\n          url: ${{ secrets.SLACK_WEBHOOK }}\n\n  build-package:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install Coreutils\n        run: brew install coreutils\n      - name: Install Apple Certificate\n        uses: apple-actions/import-codesign-certs@v3\n        with:\n          p12-file-base64: ${{ secrets.CERTIFICATEXC }}\n          p12-password: ${{ secrets.CERTIFICATEXC_PASS }}\n      - name: Build SDK\n        run: make build-package\n      - name: Upload zip distribution\n        uses: actions/upload-artifact@v4\n        with:\n          name: airship\n          path: ./build/Airship.zip\n\n      - name: Upload zip distribution (Carthage)\n        uses: actions/upload-artifact@v4\n        with:\n          name: airship-carthage\n          path: ./build/Airship.xcframeworks.zip\n\n      - name: Upload .NET xcframeworks\n        uses: actions/upload-artifact@v4\n        with:\n          name: airship-dotnet\n          path: ./build/Airship.dotnet.xcframeworks.zip\n\n\n  build-samples:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    steps:\n      - uses: actions/checkout@v4\n      - name: Build samples\n        run: make build-samples\n\n  # Test jobs, all depend on check-version\n  run-tests-core:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install xcodegen\n        run: brew install xcodegen\n      - name: Test Core\n        run: make test-core\n        \n  run-tests-preference-center:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install xcodegen\n        run: brew install xcodegen\n      - name: Test Preference Center\n        run: make test-preference-center\n        \n  run-tests-message-center:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install xcodegen\n        run: brew install xcodegen\n      - name: Test Message Center\n        run: make test-message-center\n\n  run-tests-automation:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install xcodegen\n        run: brew install xcodegen\n      - name: Test Automation\n        run: make test-automation\n\n  run-tests-feature-flags:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install xcodegen\n        run: brew install xcodegen\n      - name: Test Feature Flags\n        run: make test-feature-flags\n\n  run-tests-service-extension:\n    needs: check-version\n    runs-on: macos-15-xlarge\n    timeout-minutes: 10\n    steps:\n      - uses: actions/checkout@v4\n      - name: Install xcodegen\n        run: brew install xcodegen\n      - name: Test Service Extension\n        run: make test-service-extension\n\n  deploy-github:\n    permissions:\n      contents: write\n    runs-on: macos-15-xlarge\n    needs:\n      - run-tests-core\n      - run-tests-preference-center\n      - run-tests-message-center\n      - run-tests-automation\n      - run-tests-feature-flags\n      - run-tests-service-extension\n      - build-package\n      - build-samples\n    steps:\n      - uses: actions/checkout@v4\n      - name: Get the version\n        id: get_version\n        run: |\n          set -euo pipefail\n          VERSION=\"${GITHUB_REF#refs/tags/}\"\n          echo \"VERSION=$VERSION\" >> $GITHUB_OUTPUT\n\n      - name: Get the release notes\n        id: get_release_notes\n        run: |\n          set -euo pipefail\n          \n          VERSION=\"${{ steps.get_version.outputs.VERSION }}\"\n          \n          # Match line starting with '## Version [VERSION]' \n          # but allow for the date suffix that follows.\n          NOTES=$(awk -v ver=\"$VERSION\" '\n            $0 ~ \"^## Version \" ver \"($|[ ]-)\" {flag=1; next} \n            $0 ~ \"^## Version \" {flag=0} \n            flag\n          ' CHANGELOG.md)\n\n          # Strip potential leading/trailing whitespace/newlines\n          NOTES=\"$(echo \"$NOTES\" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//')\"\n\n          if [ -z \"$NOTES\" ]; then\n            echo \"::error::Could not find notes for Version $VERSION in CHANGELOG.md\"\n            exit 1\n          fi\n\n          DELIMITER=\"EOF_$(uuidgen)\"\n          {\n            echo \"NOTES<<$DELIMITER\"\n            echo \"$NOTES\"\n            echo \"$DELIMITER\"\n          } >> \"$GITHUB_OUTPUT\"\n\n      - name: Download zip distribution\n        uses: actions/download-artifact@v4\n        with:\n          name: airship\n          path: ./build\n      - name: Download Carthage zip distribution\n        uses: actions/download-artifact@v4\n        with:\n          name: airship-carthage\n          path: ./build\n\n      - name: Download .NET xcframeworks\n        uses: actions/download-artifact@v4\n        with:\n          name: airship-dotnet\n          path: ./build\n\n      - name: Create GitHub Release\n        id: create_release\n        uses: softprops/action-gh-release@v2\n        with:\n          tag_name: ${{ steps.get_version.outputs.VERSION }}\n          release_name: ${{ steps.get_version.outputs.VERSION }}\n          body: ${{ steps.get_release_notes.outputs.NOTES }}\n          draft: false\n          prerelease: false\n          files: |\n            ./build/Airship.zip\n            ./build/Airship.xcframeworks.zip\n            ./build/Airship.dotnet.xcframeworks.zip\n      - name: Kickoff prebuilt repo\n        env:\n          GITHUB_TOKEN: ${{ secrets.IOS_DEPLOY_PREBUILT_PAT }}\n        run: gh --repo urbanairship/ios-library-prebuilt workflow run release.yml\n\n  deploy-pods:\n    runs-on: macos-15-xlarge\n    needs:\n      - run-tests-core\n      - run-tests-preference-center\n      - run-tests-message-center\n      - run-tests-automation\n      - run-tests-feature-flags\n      - run-tests-service-extension\n      - build-package\n      - build-samples\n    steps:\n      - uses: actions/checkout@v4\n      \n      - name: Get the version\n        id: get_version\n        run: |\n          set -euo pipefail\n          VERSION=\"${GITHUB_REF#refs/tags/}\"\n          echo \"VERSION=$VERSION\" >> $GITHUB_OUTPUT\n\n      - name: Publish Pods\n        env:\n          COCOAPODS_TRUNK_TOKEN: ${{ secrets.COCOAPODS_TRUNK_TOKEN }}\n        run: make pod-publish\n\n      - name: Slack Notification\n        uses: lazy-actions/slatify@master\n        if: always()\n        with:\n          type: ${{ job.status }}\n          job_name: \"Publish the Pods ${{ steps.get_version.outputs.VERSION }} :tidepod:\"\n          url: ${{ secrets.SLACK_WEBHOOK }}\n\n  finished:\n    runs-on: ubuntu-latest\n    needs: [deploy-github, deploy-pods]\n    steps:\n      - name: Get the version\n        id: get_version\n        run: |\n          set -euo pipefail\n          VERSION=\"${GITHUB_REF#refs/tags/}\"\n          echo \"VERSION=$VERSION\" >> $GITHUB_OUTPUT\n\n      - name: Slack Notification\n        uses: lazy-actions/slatify@master\n        if: always()\n        with:\n          type: ${{ job.status }}\n          job_name: \":raised_hands: iOS SDK Released! :raised_hands:\"\n          url: ${{ secrets.SLACK_WEBHOOK }}\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\n*.orig\n.hg*\n*.mode1v3\n*.pbxuser\n*.perspective*\ndistribution_binaries/\ndistribution_package/\ndocs/html/\ndocs/docset/\nDebug/\nRelease/\nDistribution/\nbuild/\nUANativeBridge.c\nxcuserdata/\nAirshipConfig.plist\nAirshipDevelopment.plist\nfabfile.py*\n*.build_output\n# Undo files for MacVim\n*.un~\n# CoverStory tool script\ncover.py\nOCMockLibrary\n*.swp\nDeploy/output\ntest-output/\nPods/\nCarthage/\nAirship.framework.zip\nDerivedData\n**/.claude/settings.local.json\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".ruby-version",
    "content": "3.2.0"
  },
  {
    "path": ".swift-format.json",
    "content": "{\n  \"fileScopedDeclarationPrivacy\" : {\n    \"accessLevel\" : \"private\"\n  },\n  \"indentation\" : {\n    \"spaces\" : 4\n  },\n  \"indentConditionalCompilationBlocks\" : false,\n  \"indentSwitchCaseLabels\" : false,\n  \"lineBreakAroundMultilineExpressionChainComponents\" : true,\n  \"lineBreakBeforeControlFlowKeywords\" : false,\n  \"lineBreakBeforeEachArgument\" : true,\n  \"lineBreakBeforeEachGenericRequirement\" : true,\n  \"lineLength\" : 80,\n  \"maximumBlankLines\" : 1,\n  \"prioritizeKeepingFunctionOutputTogether\" : false,\n  \"respectsExistingLineBreaks\" : true,\n  \"rules\" : {\n    \"AllPublicDeclarationsHaveDocumentation\" : true,\n    \"AlwaysUseLowerCamelCase\" : true,\n    \"AmbiguousTrailingClosureOverload\" : true,\n    \"BeginDocumentationCommentWithOneLineSummary\" : false,\n    \"DoNotUseSemicolons\" : true,\n    \"DontRepeatTypeInStaticProperties\" : true,\n    \"FileScopedDeclarationPrivacy\" : true,\n    \"FullyIndirectEnum\" : true,\n    \"GroupNumericLiterals\" : true,\n    \"IdentifiersMustBeASCII\" : true,\n    \"NeverForceUnwrap\" : true,\n    \"NeverUseForceTry\" : true,\n    \"NeverUseImplicitlyUnwrappedOptionals\" : true,\n    \"NoAccessLevelOnExtensionDeclaration\" : true,\n    \"NoBlockComments\" : true,\n    \"NoCasesWithOnlyFallthrough\" : true,\n    \"NoEmptyTrailingClosureParentheses\" : true,\n    \"NoLabelsInCasePatterns\" : true,\n    \"NoLeadingUnderscores\" : false,\n    \"NoParensAroundConditions\" : true,\n    \"NoVoidReturnOnFunctionSignature\" : true,\n    \"OneCasePerLine\" : true,\n    \"OneVariableDeclarationPerLine\" : true,\n    \"OnlyOneTrailingClosureArgument\" : true,\n    \"OrderedImports\" : true,\n    \"ReturnVoidInsteadOfEmptyTuple\" : true,\n    \"UseEarlyExits\" : true,\n    \"UseLetInEveryBoundCaseVariable\" : true,\n    \"UseShorthandTypeNames\" : true,\n    \"UseSingleLinePropertyGetter\" : true,\n    \"UseSynthesizedInitializer\" : true,\n    \"UseTripleSlashForDocumentationComments\" : true,\n    \"UseWhereClausesInForLoops\" : false,\n    \"ValidateDocumentationComments\" : false\n  },\n  \"tabWidth\" : 8,\n  \"version\" : 1\n}\n\n\n"
  },
  {
    "path": ".swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": ".whitesource",
    "content": "{\n  \"scanSettings\": {\n    \"configMode\": \"AUTO\",\n    \"configExternalURL\": \"\",\n    \"projectToken\": \"\",\n    \"baseBranches\": []\n  },\n  \"checkRunSettings\": {\n    \"vulnerableCheckRunConclusionLevel\": \"success\",\n    \"displayMode\": \"diff\"\n  },\n  \"issueSettings\": {\n    \"minSeverityLevel\": \"NONE\"\n  }\n}"
  },
  {
    "path": "Airship/Airship.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 70;\n\tobjects = {\n\n/* Begin PBXAggregateTarget section */\n\t\t6EAAE85D28C2AD3A003CAE53 /* AirshipRelease */ = {\n\t\t\tisa = PBXAggregateTarget;\n\t\t\tbuildConfigurationList = 6EAAE85E28C2AD3A003CAE53 /* Build configuration list for PBXAggregateTarget \"AirshipRelease\" */;\n\t\t\tbuildPhases = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E128B9D2D305C4600733024 /* PBXTargetDependency */,\n\t\t\t\t6E5917892B28E93A0084BBBF /* PBXTargetDependency */,\n\t\t\t\t6E6B493E2A787D0A00AF98D8 /* PBXTargetDependency */,\n\t\t\t\t6EAAE87728C2AD80003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAE87928C2AD80003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAE87B28C2AD80003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAE87D28C2AD80003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAE87F28C2AD80003CAE53 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipRelease;\n\t\t\tproductName = AirshipRelease;\n\t\t};\n\t\t6ECCAD252CF55BC700423D86 /* AirshipRelease tvOS */ = {\n\t\t\tisa = PBXAggregateTarget;\n\t\t\tbuildConfigurationList = 6ECCAD342CF55BC700423D86 /* Build configuration list for PBXAggregateTarget \"AirshipRelease tvOS\" */;\n\t\t\tbuildPhases = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6ECCAD282CF55BC700423D86 /* PBXTargetDependency */,\n\t\t\t\t6ECCAD2A2CF55BC700423D86 /* PBXTargetDependency */,\n\t\t\t\t6ECCAD2C2CF55BC700423D86 /* PBXTargetDependency */,\n\t\t\t\t6ECCAD322CF55BC700423D86 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = \"AirshipRelease tvOS\";\n\t\t\tproductName = AirshipRelease;\n\t\t};\n/* End PBXAggregateTarget section */\n\n/* Begin PBXBuildFile section */\n\t\t27051CD72EE75E3300C770D5 /* AutomationEventsHistoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27051CD62EE75E3300C770D5 /* AutomationEventsHistoryTest.swift */; };\n\t\t27077E4C2EE7531C0027A282 /* AutomationEventsHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27077E4B2EE7531C0027A282 /* AutomationEventsHistory.swift */; };\n\t\t271B38652DB2866200495D9F /* TagActionMutation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 271B38642DB2866200495D9F /* TagActionMutation.swift */; };\n\t\t27264FB32E81B064000B6FA3 /* AirshipSceneController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27264FB22E81B064000B6FA3 /* AirshipSceneController.swift */; };\n\t\t2726505B2E81B80E000B6FA3 /* PagerControllerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2726505A2E81B80E000B6FA3 /* PagerControllerTest.swift */; };\n\t\t2753F6422F6C5BB50073882C /* MessageCenterMessageError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2753F6412F6C5BB50073882C /* MessageCenterMessageError.swift */; };\n\t\t275D32AC2EF957F200B75760 /* AirshipSimpleLayoutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 275D32AA2EF955AD00B75760 /* AirshipSimpleLayoutView.swift */; };\n\t\t275D32AD2EF957F200B75761 /* AirshipSimpleLayoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 275D32AB2EF955AD00B75761 /* AirshipSimpleLayoutViewModel.swift */; };\n\t\t2797B4192F47687800A7F848 /* NativeLayoutPersistentDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2797B4182F47687800A7F848 /* NativeLayoutPersistentDataStore.swift */; };\n\t\t27AFE70F2E733F4400767044 /* ModifyTagsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AFE70E2E733F4400767044 /* ModifyTagsAction.swift */; };\n\t\t27AFE7112E73477200767044 /* ModifyTagsActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27AFE7102E73477200767044 /* ModifyTagsActionTest.swift */; };\n\t\t27CCF77D2F1656150018058F /* MessageViewAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CCF77C2F1656150018058F /* MessageViewAnalytics.swift */; };\n\t\t27CCF77F2F16DA500018058F /* MessageViewAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CCF77E2F16DA500018058F /* MessageViewAnalyticsTest.swift */; };\n\t\t27CCF8D32F2382750018058F /* ThomasStateStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27CCF8D22F2382750018058F /* ThomasStateStorage.swift */; };\n\t\t27E4194A2EF59F9800D5C1A6 /* MessageCenterThomasView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27E419492EF59F9800D5C1A6 /* MessageCenterThomasView.swift */; };\n\t\t27F1E1332F0E7AA400E317DB /* ThomasLayoutEventContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAFB2B58AA4F002FEA75 /* ThomasLayoutEventContext.swift */; };\n\t\t27F1E1342F0E7C9C00E317DB /* ThomasLayoutEventRecorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AABA2B58A946002FEA75 /* ThomasLayoutEventRecorder.swift */; };\n\t\t27F1E1352F0E7D2C00E317DB /* ThomasLayoutEventMessageID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAB62B58A945002FEA75 /* ThomasLayoutEventMessageID.swift */; };\n\t\t27F1E1362F0E7D7B00E317DB /* ThomasLayoutEventSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAFA2B58AA4F002FEA75 /* ThomasLayoutEventSource.swift */; };\n\t\t27F1E18C2F0E828D00E317DB /* ThomasLayoutEventMessageIDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADD2B58A9D2002FEA75 /* ThomasLayoutEventMessageIDTest.swift */; };\n\t\t27F1E18D2F0E828D00E317DB /* ThomasLayoutEventRecorderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAD72B58A9D1002FEA75 /* ThomasLayoutEventRecorderTest.swift */; };\n\t\t27F1E18E2F0E828D00E317DB /* ThomasLayoutEventContextTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADF2B58A9D2002FEA75 /* ThomasLayoutEventContextTest.swift */; };\n\t\t27F1E18F2F0E82C700E317DB /* ThomasLayoutResolutionEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADB2B58A9D1002FEA75 /* ThomasLayoutResolutionEventTest.swift */; };\n\t\t27F1E1902F0E82C700E317DB /* ThomasLayoutEventTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADA2B58A9D1002FEA75 /* ThomasLayoutEventTestUtils.swift */; };\n\t\t27F1E1912F0E82C700E317DB /* ThomasLayoutFormResultEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE02B58A9D2002FEA75 /* ThomasLayoutFormResultEventTest.swift */; };\n\t\t27F1E1922F0E82C700E317DB /* ThomasLayoutPageSwipeEventAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE72B58A9D4002FEA75 /* ThomasLayoutPageSwipeEventAction.swift */; };\n\t\t27F1E1932F0E82C700E317DB /* ThomasLayoutDisplayEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAD62B58A9D1002FEA75 /* ThomasLayoutDisplayEventTest.swift */; };\n\t\t27F1E1942F0E82C700E317DB /* ThomasLayoutButtonTapEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADE2B58A9D2002FEA75 /* ThomasLayoutButtonTapEventTest.swift */; };\n\t\t27F1E1952F0E82C700E317DB /* ThomasLayoutPagerSummaryEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE12B58A9D2002FEA75 /* ThomasLayoutPagerSummaryEventTest.swift */; };\n\t\t27F1E1962F0E82C700E317DB /* ThomasLayoutPageActionEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADC2B58A9D2002FEA75 /* ThomasLayoutPageActionEventTest.swift */; };\n\t\t27F1E1972F0E82C700E317DB /* ThomasLayoutGestureEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE42B58A9D3002FEA75 /* ThomasLayoutGestureEventTest.swift */; };\n\t\t27F1E1992F0E82C700E317DB /* ThomasLayoutFormDisplayEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE32B58A9D3002FEA75 /* ThomasLayoutFormDisplayEventTest.swift */; };\n\t\t27F1E19A2F0E82C700E317DB /* ThomasLayoutPermissionResultEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAD92B58A9D1002FEA75 /* ThomasLayoutPermissionResultEventTest.swift */; };\n\t\t27F1E19B2F0E82C700E317DB /* ThomasLayoutPageViewEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE22B58A9D2002FEA75 /* ThomasLayoutPageViewEventTest.swift */; };\n\t\t27F1E19C2F0E82C700E317DB /* ThomasLayoutPagerCompletedEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAD82B58A9D1002FEA75 /* ThomasLayoutPagerCompletedEventTest.swift */; };\n\t\t27F1E19D2F0E836000E317DB /* InAppMessageAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAE52B58A9D3002FEA75 /* InAppMessageAnalyticsTest.swift */; };\n\t\t27F1E19E2F0E846B00E317DB /* TestThomasLayoutEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAD42B58A977002FEA75 /* TestThomasLayoutEvent.swift */; };\n\t\t27F1E19F2F0E848B00E317DB /* TestThomasLayoutEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAD42B58A977002FEA75 /* TestThomasLayoutEvent.swift */; };\n\t\t27F1E1A02F0E84C300E317DB /* ThomasLayoutEventTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AADA2B58A9D1002FEA75 /* ThomasLayoutEventTestUtils.swift */; };\n\t\t27F1E2012F0E910B00E317DB /* ThomasLayoutButtonTapEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F32F0E910B00E317DB /* ThomasLayoutButtonTapEvent.swift */; };\n\t\t27F1E2022F0E910B00E317DB /* ThomasLayoutDisplayEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F42F0E910B00E317DB /* ThomasLayoutDisplayEvent.swift */; };\n\t\t27F1E2032F0E910B00E317DB /* ThomasLayoutEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F52F0E910B00E317DB /* ThomasLayoutEvent.swift */; };\n\t\t27F1E2042F0E910B00E317DB /* ThomasLayoutFormDisplayEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F62F0E910B00E317DB /* ThomasLayoutFormDisplayEvent.swift */; };\n\t\t27F1E2052F0E910B00E317DB /* ThomasLayoutFormResultEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F72F0E910B00E317DB /* ThomasLayoutFormResultEvent.swift */; };\n\t\t27F1E2062F0E910B00E317DB /* ThomasLayoutGestureEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F82F0E910B00E317DB /* ThomasLayoutGestureEvent.swift */; };\n\t\t27F1E2072F0E910B00E317DB /* ThomasLayoutPageActionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1F92F0E910B00E317DB /* ThomasLayoutPageActionEvent.swift */; };\n\t\t27F1E2082F0E910B00E317DB /* ThomasLayoutPagerCompletedEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1FA2F0E910B00E317DB /* ThomasLayoutPagerCompletedEvent.swift */; };\n\t\t27F1E2092F0E910B00E317DB /* ThomasLayoutPagerSummaryEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1FB2F0E910B00E317DB /* ThomasLayoutPagerSummaryEvent.swift */; };\n\t\t27F1E20A2F0E910B00E317DB /* ThomasLayoutPageSwipeEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1FC2F0E910B00E317DB /* ThomasLayoutPageSwipeEvent.swift */; };\n\t\t27F1E20B2F0E910B00E317DB /* ThomasLayoutPageViewEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1FD2F0E910B00E317DB /* ThomasLayoutPageViewEvent.swift */; };\n\t\t27F1E20C2F0E910B00E317DB /* ThomasLayoutPermissionResultEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1FE2F0E910B00E317DB /* ThomasLayoutPermissionResultEvent.swift */; };\n\t\t27F1E20D2F0E910B00E317DB /* ThomasLayoutResolutionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 27F1E1FF2F0E910B00E317DB /* ThomasLayoutResolutionEvent.swift */; };\n\t\t27F1E2112F0FF5FE00E317DB /* ThomasDisplayListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AB172B59E80E002FEA75 /* ThomasDisplayListener.swift */; };\n\t\t27F1E2122F0FF6EB00E317DB /* ThomasDisplayListenerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BBC2B5B290200A6489B /* ThomasDisplayListenerTest.swift */; };\n\t\t320AD3A629E7FA2000D66106 /* PagerGestureMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = 320AD3A529E7FA2000D66106 /* PagerGestureMap.swift */; };\n\t\t3215CA9D2739349800B7D97E /* ModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3215CA9C2739349700B7D97E /* ModalView.swift */; };\n\t\t322AAB1E2B5AB65700652DAC /* AddChannelPromptView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32DDC0562AF1055300D23EBE /* AddChannelPromptView.swift */; };\n\t\t322AAB212B5ACB2800652DAC /* ChannelListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322AAB202B5ACB2800652DAC /* ChannelListView.swift */; };\n\t\t322AAB222B5FCB6B00652DAC /* ContactManagementView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 322AAB1C2B5A869000652DAC /* ContactManagementView.swift */; };\n\t\t3231126A29D5E4F600CF0D86 /* AirshipAutomationResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231126929D5E4F600CF0D86 /* AirshipAutomationResources.swift */; };\n\t\t3231128229D5E67200CF0D86 /* FrequencyLimitStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231127B29D5E67200CF0D86 /* FrequencyLimitStore.swift */; };\n\t\t3231128329D5E67200CF0D86 /* FrequencyLimitManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231127C29D5E67200CF0D86 /* FrequencyLimitManager.swift */; };\n\t\t3231128429D5E67200CF0D86 /* Occurrence.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231127D29D5E67200CF0D86 /* Occurrence.swift */; };\n\t\t3231128729D5E67200CF0D86 /* FrequencyConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231128029D5E67200CF0D86 /* FrequencyConstraint.swift */; };\n\t\t3231128829D5E67200CF0D86 /* FrequencyChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231128129D5E67200CF0D86 /* FrequencyChecker.swift */; };\n\t\t3231128C29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3231128A29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodeld */; };\n\t\t3231129129D5E6D900CF0D86 /* FrequencyLimitManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3231128F29D5E6C600CF0D86 /* FrequencyLimitManagerTest.swift */; };\n\t\t3237D5F22B865D990055932B /* JSONValueTransformer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3237D5F12B865D990055932B /* JSONValueTransformer.swift */; };\n\t\t3243EC632D93109C00B43B25 /* AirshipSwitchToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3243EC622D93109C00B43B25 /* AirshipSwitchToggleStyle.swift */; };\n\t\t3243EC642D93109C00B43B25 /* AirshipCheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3243EC612D93109C00B43B25 /* AirshipCheckboxToggleStyle.swift */; };\n\t\t324D3BFF273E6B4500058EE4 /* BannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 324D3BFE273E6B4500058EE4 /* BannerView.swift */; };\n\t\t32515869272AFB2E00DF8B44 /* Media.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32515866272AFB2E00DF8B44 /* Media.swift */; };\n\t\t3251586B272AFB2E00DF8B44 /* VideoMediaWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32515868272AFB2E00DF8B44 /* VideoMediaWebView.swift */; };\n\t\t325D53DA295C7979003421B4 /* ActionRegistryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 325D53D9295C7979003421B4 /* ActionRegistryTest.swift */; };\n\t\t325D53F629646E53003421B4 /* TestChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E48426A60CDF0038CFDD /* TestChannel.swift */; };\n\t\t325D53F729648150003421B4 /* TestDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1422683B8FA00A2CBD0 /* TestDate.swift */; };\n\t\t325D53FA29648818003421B4 /* TestAirshipRequestSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */; };\n\t\t325D53FB2964885B003421B4 /* AirshipBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3299EF212949EC3E00251E70 /* AirshipBaseTest.swift */; };\n\t\t3261A7F6243CD7F900ADBF6B /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3261A7F4243CD73100ADBF6B /* CoreTelephony.framework */; platformFilter = maccatalyst; };\n\t\t3299EF172948CBC100251E70 /* RemoteDataAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3299EF162948CBC100251E70 /* RemoteDataAPIClientTest.swift */; };\n\t\t3299EF222949EC3E00251E70 /* AirshipBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3299EF212949EC3E00251E70 /* AirshipBaseTest.swift */; };\n\t\t3299EF26294B222F00251E70 /* RemoteDataStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3299EF25294B222F00251E70 /* RemoteDataStoreTest.swift */; };\n\t\t329DFCCF2B7E4DDA0039C8C0 /* UARemoteDataMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329DFCCE2B7E4DDA0039C8C0 /* UARemoteDataMapping.swift */; };\n\t\t329DFCD52B7E59700039C8C0 /* UAInboxDataMapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = 329DFCD42B7E59700039C8C0 /* UAInboxDataMapping.swift */; };\n\t\t329DFCFF2B7FB8810039C8C0 /* UARemoteDataMappingV3toV4.xcmappingmodel in Resources */ = {isa = PBXBuildFile; fileRef = 329DFCCA2B7E4DA10039C8C0 /* UARemoteDataMappingV3toV4.xcmappingmodel */; };\n\t\t32B513562B9F53A500BBE780 /* MessageCenterPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B513552B9F53A500BBE780 /* MessageCenterPredicate.swift */; };\n\t\t32B5BE3B28F8A7EB00F2254B /* MessageCenterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE3A28F8A7EB00F2254B /* MessageCenterController.swift */; };\n\t\t32B5BE3E28F8A8C000F2254B /* MessageCenterListItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE2728F8A7D600F2254B /* MessageCenterListItemView.swift */; };\n\t\t32B5BE3F28F8A8C200F2254B /* MessageCenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE2828F8A7D600F2254B /* MessageCenterView.swift */; };\n\t\t32B5BE4028F8A8C500F2254B /* MessageCenterMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE2928F8A7D600F2254B /* MessageCenterMessageView.swift */; };\n\t\t32B5BE4128F8A8C700F2254B /* MessageCenterListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE2A28F8A7D600F2254B /* MessageCenterListView.swift */; };\n\t\t32B5BE4228F8A8CA00F2254B /* MessageCenterListItemViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE2B28F8A7D600F2254B /* MessageCenterListItemViewModel.swift */; };\n\t\t32B5BE4928F8B66500F2254B /* MessageCenterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE4828F8B66500F2254B /* MessageCenterViewController.swift */; };\n\t\t32B632882906CA17000D3E34 /* MessageCenterThemeLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B632862906CA17000D3E34 /* MessageCenterThemeLoader.swift */; };\n\t\t32B632892906CA17000D3E34 /* MessageCenterTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B632872906CA17000D3E34 /* MessageCenterTheme.swift */; };\n\t\t32BBFB402B274C8600C6A998 /* ContactChannelsAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32BBFB3F2B274C8600C6A998 /* ContactChannelsAPIClient.swift */; };\n\t\t32C68D0529424449006BBB29 /* RemoteDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32C68D0429424449006BBB29 /* RemoteDataTest.swift */; };\n\t\t32CF81E2275627F4003009D1 /* AirshipAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32CF81E1275627F4003009D1 /* AirshipAsyncImage.swift */; };\n\t\t32D6E87B2727F7060077C784 /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32D6E87A2727F7060077C784 /* Image.swift */; };\n\t\t32E339E32A334A2000CD3BE5 /* AddCustomEventActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32E339E22A334A2000CD3BE5 /* AddCustomEventActionTest.swift */; };\n\t\t32F293D5295AFD94004A7D9C /* ActionArgumentsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F293D4295AFD94004A7D9C /* ActionArgumentsTest.swift */; };\n\t\t32F615A728F708980015696D /* MessageCenterListTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F615A628F708980015696D /* MessageCenterListTests.swift */; };\n\t\t32F615A828F708AD0015696D /* TestChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E48426A60CDF0038CFDD /* TestChannel.swift */; };\n\t\t32F68CDC28F02A7100F7F52A /* MessageCenterStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F68CDA28F02A6B00F7F52A /* MessageCenterStoreTest.swift */; };\n\t\t32F68CEE28F07C2C00F7F52A /* AirshipMessageCenterResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F68CE828F07C2B00F7F52A /* AirshipMessageCenterResources.swift */; };\n\t\t32F68CEF28F07C2C00F7F52A /* MessageCenterSDKModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F68CE928F07C2C00F7F52A /* MessageCenterSDKModule.swift */; };\n\t\t32F68CF328F07C2C00F7F52A /* MessageCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F68CED28F07C2C00F7F52A /* MessageCenter.swift */; };\n\t\t32F68CF528F07C4900F7F52A /* MessageCenterList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F68CF428F07C4800F7F52A /* MessageCenterList.swift */; };\n\t\t32F97AC129E5986B00FED65F /* StoryIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32F97AC029E5986B00FED65F /* StoryIndicator.swift */; };\n\t\t32FD4C782D8079910056D141 /* BasicToggleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32FD4C772D8079910056D141 /* BasicToggleLayout.swift */; };\n\t\t3C39D3092384C8BE003C50D4 /* AirshipMessageCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CA0E423237E4A7B00EE76CF /* AirshipMessageCenter.framework */; settings = {ATTRIBUTES = (Weak, ); }; };\n\t\t3C63556026CDD4F8006E9916 /* AirshipPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C63555F26CDD4F8006E9916 /* AirshipPush.swift */; };\n\t\t3C693E4E25141CAC00EBFB88 /* AirshipDebugEventData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 3CA0E222237CCBA600EE76CF /* AirshipDebugEventData.xcdatamodeld */; };\n\t\t3C693E4F25141CAC00EBFB88 /* AirshipDebugPushData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 83A674F623AA7AA4005C0C8F /* AirshipDebugPushData.xcdatamodeld */; };\n\t\t3CA0E2A0237CCE2600EE76CF /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 494DD9571B0EB677009C134E /* AirshipCore.framework */; settings = {ATTRIBUTES = (Weak, ); }; };\n\t\t3CA0E2BF237CD05F00EE76CF /* AirshipEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA0E241237CCBA600EE76CF /* AirshipEvent.swift */; };\n\t\t3CA0E2CA237CD05F00EE76CF /* EventDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA0E24D237CCBA600EE76CF /* EventDataManager.swift */; };\n\t\t3CA0E2CD237CD05F00EE76CF /* AirshipDebugManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA0E233237CCBA600EE76CF /* AirshipDebugManager.swift */; };\n\t\t3CA0E479237E4E3000EE76CF /* UAInbox.xcdatamodeld in Resources */ = {isa = PBXBuildFile; fileRef = 3CA0E304237E396100EE76CF /* UAInbox.xcdatamodeld */; };\n\t\t3CA84AAE26DE255200A59685 /* EventStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA84AA626DE255200A59685 /* EventStore.swift */; };\n\t\t3CA84AB826DE257200A59685 /* DefaultAirshipAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA84AB626DE257200A59685 /* DefaultAirshipAnalytics.swift */; };\n\t\t3CA84ABA26DE257200A59685 /* AirshipAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CA84AB726DE257200A59685 /* AirshipAnalytics.swift */; };\n\t\t3CB37A1E251151A400E60392 /* AirshipDebugResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CB37A1D251151A400E60392 /* AirshipDebugResources.swift */; };\n\t\t3CC8AA0626BB3C7900405614 /* DefaultAirshipPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC8AA0526BB3C7900405614 /* DefaultAirshipPush.swift */; };\n\t\t3CC95B1F268E785900FE2ACD /* NotificationCategories.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC95B1E268E785900FE2ACD /* NotificationCategories.swift */; };\n\t\t3CC95B2B2696549B00FE2ACD /* AirshipPushableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3CC95B2A2696549B00FE2ACD /* AirshipPushableComponent.swift */; };\n\t\t45A8ADF023134B38004AD8CA /* testMCColorsCatalog.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 45A8ADD123133E51004AD8CA /* testMCColorsCatalog.xcassets */; };\n\t\t6014AD672C1B5F540072DCF0 /* ChallengeResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6014AD662C1B5F540072DCF0 /* ChallengeResolver.swift */; };\n\t\t6014AD6C2C2032730072DCF0 /* ChallengeResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6014AD6A2C2032360072DCF0 /* ChallengeResolverTest.swift */; };\n\t\t6014AD752C20410B0072DCF0 /* airship.der in Resources */ = {isa = PBXBuildFile; fileRef = 6014AD742C20410A0072DCF0 /* airship.der */; };\n\t\t6018AF572B29C20A008E528B /* SearchEventTemplateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6018AF562B29C20A008E528B /* SearchEventTemplateTest.swift */; };\n\t\t602AD0D52D7242B300C7D566 /* ThomasSmsLocale.swift in Sources */ = {isa = PBXBuildFile; fileRef = 602AD0D42D7242B300C7D566 /* ThomasSmsLocale.swift */; };\n\t\t603269532BF4B141007F7F75 /* AdditionalAudienceCheckerApiClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 603269522BF4B141007F7F75 /* AdditionalAudienceCheckerApiClient.swift */; };\n\t\t603269552BF4B8D5007F7F75 /* AdditionalAudienceCheckerResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 603269542BF4B8D5007F7F75 /* AdditionalAudienceCheckerResolver.swift */; };\n\t\t603269582BF7550E007F7F75 /* AdditionalAudienceCheckerResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 603269572BF7550E007F7F75 /* AdditionalAudienceCheckerResolverTest.swift */; };\n\t\t603269592BF75976007F7F75 /* AirshipCacheTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7EACD02AF4192400DA286B /* AirshipCacheTest.swift */; };\n\t\t6032695B2BF75D69007F7F75 /* AirshipHTTPResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6032695A2BF75D69007F7F75 /* AirshipHTTPResponseTest.swift */; };\n\t\t6032695C2BF75E39007F7F75 /* AirshipHTTPResponseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6032695A2BF75D69007F7F75 /* AirshipHTTPResponseTest.swift */; };\n\t\t605073832B2CD38200209B51 /* ActiveTimerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605073822B2CD38200209B51 /* ActiveTimerTest.swift */; };\n\t\t605073842B2CD46D00209B51 /* TestAppStateTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E61267C03C700654DB2 /* TestAppStateTracker.swift */; };\n\t\t6050738A2B32F85100209B51 /* ThomasViewModelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 605073892B32F85100209B51 /* ThomasViewModelTest.swift */; };\n\t\t605073902B347B6400209B51 /* ThomasPresentationModelCodingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6050738F2B347B6400209B51 /* ThomasPresentationModelCodingTest.swift */; };\n\t\t6058771D2AC73C940021628E /* AirshipMeteredUsageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6058771C2AC73C7E0021628E /* AirshipMeteredUsageTest.swift */; };\n\t\t605877202ACAC8700021628E /* MeteredUsageApiClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6058771E2ACAC86A0021628E /* MeteredUsageApiClientTest.swift */; };\n\t\t60653FC22CBD2CD4009CD9A7 /* PushData+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60653FC02CBD2CD4009CD9A7 /* PushData+CoreDataClass.swift */; };\n\t\t60653FC32CBD2CD4009CD9A7 /* PushData+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60653FC12CBD2CD4009CD9A7 /* PushData+CoreDataProperties.swift */; };\n\t\t6068E0062B2A190300349E82 /* CustomEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6068E0052B2A190300349E82 /* CustomEventTest.swift */; };\n\t\t6068E0082B2A2A6700349E82 /* AccountEventTemplateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6068E0072B2A2A6700349E82 /* AccountEventTemplateTest.swift */; };\n\t\t6068E0322B2B785A00349E82 /* MediaEventTemplateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6068E0312B2B785A00349E82 /* MediaEventTemplateTest.swift */; };\n\t\t6068E0342B2B7CA100349E82 /* RetailEventTemplateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6068E0332B2B7CA100349E82 /* RetailEventTemplateTest.swift */; };\n\t\t6068E03B2B2CBCF200349E82 /* ActiveTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6068E03A2B2CBCF200349E82 /* ActiveTimer.swift */; };\n\t\t607951222A1CD1A50086578F /* ExperimentManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6079511F2A1CD19F0086578F /* ExperimentManagerTest.swift */; };\n\t\t6087DB882B278F7600449BA8 /* JsonValueMatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6087DB872B278F7600449BA8 /* JsonValueMatcherTest.swift */; };\n\t\t608B16E62C2C1138005298FA /* SubscriptionListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608B16E52C2C1137005298FA /* SubscriptionListProvider.swift */; };\n\t\t608B16E82C2C6DDB005298FA /* ChannelSubscriptionListProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608B16E72C2C6DDB005298FA /* ChannelSubscriptionListProvider.swift */; };\n\t\t608B16F12C2D5F0D005298FA /* BaseCachingRemoteDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 608B16F02C2D5F0D005298FA /* BaseCachingRemoteDataProvider.swift */; };\n\t\t60956D862CBE7CFA00950172 /* AirshipLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8E1C9A26447B3800B11791 /* AirshipLock.swift */; };\n\t\t609843562D6F518900690371 /* SmsLocalePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 609843552D6F518900690371 /* SmsLocalePicker.swift */; };\n\t\t60A292112CB7C5C30096F5EB /* TestDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1422683B8FA00A2CBD0 /* TestDate.swift */; };\n\t\t60A364ED2C3479BF00B05E26 /* ExecutionWindowTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A364EC2C3479BF00B05E26 /* ExecutionWindowTest.swift */; };\n\t\t60A5CC082B28DC500017EDB2 /* NotificationCategoriesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A5CC072B28DC500017EDB2 /* NotificationCategoriesTest.swift */; };\n\t\t60A5CC0C2B29AE890017EDB2 /* ProximityRegionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A5CC0B2B29AE890017EDB2 /* ProximityRegionTest.swift */; };\n\t\t60A5CC0E2B29B1B80017EDB2 /* CircularRegionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A5CC0D2B29B1B80017EDB2 /* CircularRegionTest.swift */; };\n\t\t60A5CC102B29B4100017EDB2 /* RegionEventTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60A5CC0F2B29B4100017EDB2 /* RegionEventTest.swift */; };\n\t\t60C1DB0F2A8B743C00A1D3DA /* AirshipEmbeddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C1DB0B2A8B743B00A1D3DA /* AirshipEmbeddedView.swift */; };\n\t\t60C1DB102A8B743C00A1D3DA /* AirshipEmbeddedViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C1DB0C2A8B743B00A1D3DA /* AirshipEmbeddedViewManager.swift */; };\n\t\t60C1DB112A8B743C00A1D3DA /* AirshipEmbeddedObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C1DB0D2A8B743B00A1D3DA /* AirshipEmbeddedObserver.swift */; };\n\t\t60C1DB122A8B743C00A1D3DA /* EmbeddedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60C1DB0E2A8B743C00A1D3DA /* EmbeddedView.swift */; };\n\t\t60CE9BDE2D0B6A0900A8B625 /* ThomasPagerControllerBranching.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60CE9BDD2D0B6A0900A8B625 /* ThomasPagerControllerBranching.swift */; };\n\t\t60D1D9B82B68FB6400EBE0A4 /* PreparedTrigger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D1D9B72B68FB6400EBE0A4 /* PreparedTrigger.swift */; };\n\t\t60D1D9BB2B6A53F000EBE0A4 /* PreparedTriggerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D1D9BA2B6A53F000EBE0A4 /* PreparedTriggerTest.swift */; };\n\t\t60D1D9BD2B6AB2D100EBE0A4 /* AutomationTriggerProcessorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D1D9BC2B6AB2D100EBE0A4 /* AutomationTriggerProcessorTest.swift */; };\n\t\t60D2B3352D9F0FCF00B0752D /* PagerDisableSwipeSelectorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D2B3342D9F0FCF00B0752D /* PagerDisableSwipeSelectorTest.swift */; };\n\t\t60D3BCC42A1529D800E07524 /* ExperimentDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BCC32A1529D800E07524 /* ExperimentDataProvider.swift */; };\n\t\t60D3BCC62A152A0D00E07524 /* ExperimentManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BCC52A152A0D00E07524 /* ExperimentManager.swift */; };\n\t\t60D3BCCC2A153C0700E07524 /* Experiment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BCCB2A153C0700E07524 /* Experiment.swift */; };\n\t\t60D3BCCE2A15471C00E07524 /* AudienceHashSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BCCD2A15471C00E07524 /* AudienceHashSelector.swift */; };\n\t\t60D3BCD02A154D9400E07524 /* MessageCriteria.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60D3BCCF2A154D9400E07524 /* MessageCriteria.swift */; };\n\t\t60E09FDB2B2780DB005A16EA /* JsonMatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60E09FDA2B2780DB005A16EA /* JsonMatcherTest.swift */; };\n\t\t60EACF542B7BF2EA00CAFDBB /* AirshipApptimizeIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60EACF532B7BF2EA00CAFDBB /* AirshipApptimizeIntegration.swift */; };\n\t\t60F8E75C2B8F3D4B00460EDF /* CancelSchedulesAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E75B2B8F3D4B00460EDF /* CancelSchedulesAction.swift */; };\n\t\t60F8E75E2B8FA12800460EDF /* CancelSchedulesActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E75D2B8FA12800460EDF /* CancelSchedulesActionTest.swift */; };\n\t\t60F8E7602B8FAF5400460EDF /* ScheduleAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E75F2B8FAF5400460EDF /* ScheduleAction.swift */; };\n\t\t60F8E7622B8FB2CC00460EDF /* ScheduleActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E7612B8FB2CC00460EDF /* ScheduleActionTest.swift */; };\n\t\t60FCA3052B4F1110005C9232 /* LegacyInAppMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FCA3042B4F1110005C9232 /* LegacyInAppMessaging.swift */; };\n\t\t60FCA3072B4F1C73005C9232 /* LegacyInAppMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FCA3062B4F1C73005C9232 /* LegacyInAppMessage.swift */; };\n\t\t60FCA30A2B51364A005C9232 /* LegacyInAppMessageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FCA3092B51364A005C9232 /* LegacyInAppMessageTest.swift */; };\n\t\t60FCA30C2B51492A005C9232 /* LegacyInAppMessagingTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FCA30B2B51492A005C9232 /* LegacyInAppMessagingTest.swift */; };\n\t\t60FCA30D2B5534DB005C9232 /* TestAirshipInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE1526E29BC90005D20D /* TestAirshipInstance.swift */; };\n\t\t60FCA30E2B5535F4005C9232 /* TestAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE1726E2C5940005D20D /* TestAnalytics.swift */; };\n\t\t60FCA3252B5EF3A8005C9232 /* AutomationEventFeedTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60FCA3242B5EF3A8005C9232 /* AutomationEventFeedTest.swift */; };\n\t\t6329102E2DD8103200B13C6C /* NativeVideoPlayer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6329102D2DD8103200B13C6C /* NativeVideoPlayer.swift */; };\n\t\t632913FA2DE547A500B13C6C /* VideoMediaNativeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 632913F92DE547A500B13C6C /* VideoMediaNativeView.swift */; };\n\t\t6E0031AB2D08CC920004F53E /* AirshipAuthorizedNotificationSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0031AA2D08CC8A0004F53E /* AirshipAuthorizedNotificationSettings.swift */; };\n\t\t6E0104FF2DDF9B26009D651F /* IconView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0104FE2DDF9B26009D651F /* IconView.swift */; };\n\t\t6E0105012DDFA5E9009D651F /* ScoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0105002DDFA5E6009D651F /* ScoreController.swift */; };\n\t\t6E0105032DDFA719009D651F /* ScoreToggleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0105022DDFA719009D651F /* ScoreToggleLayout.swift */; };\n\t\t6E0105052DDFA735009D651F /* ScoreState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0105042DDFA735009D651F /* ScoreState.swift */; };\n\t\t6E032A502B210E6000404630 /* RemoteConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E032A4F2B210E6000404630 /* RemoteConfigTest.swift */; };\n\t\t6E062D03271656DE001A74A1 /* Container.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E062D02271656DE001A74A1 /* Container.swift */; };\n\t\t6E062D05271656F8001A74A1 /* LinearLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E062D04271656F8001A74A1 /* LinearLayout.swift */; };\n\t\t6E062D0727165709001A74A1 /* Label.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E062D0627165709001A74A1 /* Label.swift */; };\n\t\t6E062D092716571F001A74A1 /* LabelButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E062D082716571F001A74A1 /* LabelButton.swift */; };\n\t\t6E062D0D2718B505001A74A1 /* ViewConstraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E062D0C2718B505001A74A1 /* ViewConstraints.swift */; };\n\t\t6E07688829F9D28A0014E2A9 /* AirshipNotificationCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07688729F9D28A0014E2A9 /* AirshipNotificationCenter.swift */; };\n\t\t6E07688C29F9F0830014E2A9 /* AirshipLocaleManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07688B29F9F0830014E2A9 /* AirshipLocaleManagerTest.swift */; };\n\t\t6E07689229FB39440014E2A9 /* AirshipUnsafeSendableWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07689129FB39440014E2A9 /* AirshipUnsafeSendableWrapper.swift */; };\n\t\t6E07B5F82D925ED60087EC47 /* TestPrivacyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */; };\n\t\t6E07B5F92D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */; };\n\t\t6E07B5FA2D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */; };\n\t\t6E07B5FB2D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */; };\n\t\t6E07B5FC2D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */; };\n\t\t6E0B8732294A9C130064B7BD /* AirshipAutomation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E0B872A294A9C120064B7BD /* AirshipAutomation.framework */; };\n\t\t6E0B8744294A9C950064B7BD /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 494DD9571B0EB677009C134E /* AirshipCore.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); };\n\t\t6E0B8760294CE0BF0064B7BD /* FarmHashFingerprint64Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0B875F294CE0BF0064B7BD /* FarmHashFingerprint64Test.swift */; };\n\t\t6E0B8762294CE0DC0064B7BD /* FarmHashFingerprint64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0B8761294CE0DC0064B7BD /* FarmHashFingerprint64.swift */; };\n\t\t6E0F4BE22B32190400673CA4 /* AutomationSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0F4BE12B32190400673CA4 /* AutomationSchedule.swift */; };\n\t\t6E0F4BE52B32645600673CA4 /* AutomationTrigger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0F4BE42B32645600673CA4 /* AutomationTrigger.swift */; };\n\t\t6E0F4BE72B32646000673CA4 /* AutomationDelay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0F4BE62B32646000673CA4 /* AutomationDelay.swift */; };\n\t\t6E0F4BE92B3264A400673CA4 /* DeferredAutomationData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0F4BE82B3264A400673CA4 /* DeferredAutomationData.swift */; };\n\t\t6E0F557F2AE03BB900E7CB8C /* ThomasAsyncImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E0F557E2AE03BB900E7CB8C /* ThomasAsyncImage.swift */; };\n\t\t6E10A1482C2B825200ED9556 /* DefaultTaskSleeperTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E10A1472C2B825200ED9556 /* DefaultTaskSleeperTest.swift */; };\n\t\t6E1185C62C3328A10071334E /* ExecutionWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1185C52C3328A10071334E /* ExecutionWindow.swift */; };\n\t\t6E12539129A81ACE0009EE58 /* AirshipCoreDataPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E12539029A81ACE0009EE58 /* AirshipCoreDataPredicate.swift */; };\n\t\t6E146C502F5214D900320A36 /* AirshipDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E146C4F2F5214D900320A36 /* AirshipDevice.swift */; };\n\t\t6E146D682F523DB900320A36 /* AirshipFont.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E146D672F523DB700320A36 /* AirshipFont.swift */; };\n\t\t6E146D6A2F5241BC00320A36 /* AirshipColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E146D692F5241BA00320A36 /* AirshipColor.swift */; };\n\t\t6E146EDD2F52537000320A36 /* AishipFontTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E146EDC2F52536C00320A36 /* AishipFontTests.swift */; };\n\t\t6E146FF32F525E7300320A36 /* AirshipPasteboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E146FF22F525E7300320A36 /* AirshipPasteboard.swift */; };\n\t\t6E1472D52F526DCD00320A36 /* AirshipNativePlatform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1472D42F526DC600320A36 /* AirshipNativePlatform.swift */; };\n\t\t6E1473EA2F527C4D00320A36 /* TestURLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECB62812A36A45F0095C85C /* TestURLOpener.swift */; };\n\t\t6E1476CC2F5643A100320A36 /* MessageCenterNavigationAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1476CB2F56439A00320A36 /* MessageCenterNavigationAppearance.swift */; };\n\t\t6E14C9A128B5E4AF00A55E65 /* PushNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E14C9A028B5E4AF00A55E65 /* PushNotification.swift */; };\n\t\t6E1528172B4DC3C000DF1377 /* ActionAutomationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528162B4DC3C000DF1377 /* ActionAutomationExecutor.swift */; };\n\t\t6E1528192B4DC3D000DF1377 /* InAppMessageAutomationPreparer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528182B4DC3D000DF1377 /* InAppMessageAutomationPreparer.swift */; };\n\t\t6E15281B2B4DC3DF00DF1377 /* InAppMessageAutomationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15281A2B4DC3DF00DF1377 /* InAppMessageAutomationExecutor.swift */; };\n\t\t6E15281D2B4DC43100DF1377 /* ActionAutomationPreparer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15281C2B4DC43100DF1377 /* ActionAutomationPreparer.swift */; };\n\t\t6E1528202B4DC59C00DF1377 /* InAppMessageSceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15281F2B4DC59C00DF1377 /* InAppMessageSceneDelegate.swift */; };\n\t\t6E1528222B4DC5C000DF1377 /* InAppMessageDisplayDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528212B4DC5C000DF1377 /* InAppMessageDisplayDelegate.swift */; };\n\t\t6E1528242B4DC60200DF1377 /* DisplayCoordinatorManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528232B4DC60200DF1377 /* DisplayCoordinatorManager.swift */; };\n\t\t6E1528262B4DC64B00DF1377 /* DisplayAdapterFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528252B4DC64B00DF1377 /* DisplayAdapterFactory.swift */; };\n\t\t6E1528282B4DCFCB00DF1377 /* AirshipActorValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528272B4DCFCB00DF1377 /* AirshipActorValue.swift */; };\n\t\t6E15282C2B4DE81E00DF1377 /* AutomationSDKModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15282B2B4DE81E00DF1377 /* AutomationSDKModule.swift */; };\n\t\t6E15282F2B4DED7A00DF1377 /* InAppMessageAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15282E2B4DED7A00DF1377 /* InAppMessageAnalytics.swift */; };\n\t\t6E1528312B4DED8900DF1377 /* InAppMessageAnalyticsFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528302B4DED8900DF1377 /* InAppMessageAnalyticsFactory.swift */; };\n\t\t6E1528332B4DF2E600DF1377 /* ScheduleConditionsChangedNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528322B4DF2E600DF1377 /* ScheduleConditionsChangedNotifier.swift */; };\n\t\t6E1528352B4E11DB00DF1377 /* CustomDisplayAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528342B4E11DB00DF1377 /* CustomDisplayAdapter.swift */; };\n\t\t6E1528372B4E11E800DF1377 /* CustomDisplayAdapterWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528362B4E11E800DF1377 /* CustomDisplayAdapterWrapper.swift */; };\n\t\t6E1528392B4E13D400DF1377 /* AirshipLayoutDisplayAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528382B4E13D400DF1377 /* AirshipLayoutDisplayAdapter.swift */; };\n\t\t6E1528402B4F153900DF1377 /* TestDisplayAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15283F2B4F153900DF1377 /* TestDisplayAdapter.swift */; };\n\t\t6E1528422B4F156200DF1377 /* TestDisplayCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1528412B4F156200DF1377 /* TestDisplayCoordinator.swift */; };\n\t\t6E152BCA2743235800788402 /* Icons.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E152BC92743235800788402 /* Icons.swift */; };\n\t\t6E1589502AFEF19F00954A04 /* SessionTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15894F2AFEF19F00954A04 /* SessionTracker.swift */; };\n\t\t6E1589542AFF021D00954A04 /* SessionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1589532AFF021D00954A04 /* SessionState.swift */; };\n\t\t6E1589582AFF023400954A04 /* SessionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1589572AFF023400954A04 /* SessionEvent.swift */; };\n\t\t6E15B6DB26CC749F0099C92D /* RemoteConfigManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B6D826CC749F0099C92D /* RemoteConfigManager.swift */; };\n\t\t6E15B6F426CD85C40099C92D /* RuntimeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B6F326CD85C40099C92D /* RuntimeConfig.swift */; };\n\t\t6E15B6FA26CDCA6A0099C92D /* RuntimeConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B6F926CDCA6A0099C92D /* RuntimeConfigTest.swift */; };\n\t\t6E15B70326CDE40E0099C92D /* RemoteConfigManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70226CDE40E0099C92D /* RemoteConfigManagerTest.swift */; };\n\t\t6E15B70526CE07180099C92D /* TestRemoteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70426CE07180099C92D /* TestRemoteData.swift */; };\n\t\t6E15B71426CEB4190099C92D /* RemoteDataStorePayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70A26CEB4190099C92D /* RemoteDataStorePayload.swift */; };\n\t\t6E15B71826CEB4190099C92D /* RemoteDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70C26CEB4190099C92D /* RemoteDataStore.swift */; };\n\t\t6E15B72326CEC7030099C92D /* RemoteDataPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B72026CEC7030099C92D /* RemoteDataPayload.swift */; };\n\t\t6E15B72A26CEDBA50099C92D /* RemoteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B72926CEDBA50099C92D /* RemoteData.swift */; };\n\t\t6E15B72D26CF13BC0099C92D /* RemoteDataProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B72C26CF13BC0099C92D /* RemoteDataProviderDelegate.swift */; };\n\t\t6E15B73026CF4F6B0099C92D /* TestRemoteDataAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B72F26CF4F6B0099C92D /* TestRemoteDataAPIClient.swift */; };\n\t\t6E16208A2B311219009240B2 /* DisplayCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1620892B311219009240B2 /* DisplayCoordinator.swift */; };\n\t\t6E16208D2B3116AE009240B2 /* ImmediateDisplayCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E16208C2B3116AE009240B2 /* ImmediateDisplayCoordinator.swift */; };\n\t\t6E16208F2B3116BA009240B2 /* DefaultDisplayCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E16208E2B3116BA009240B2 /* DefaultDisplayCoordinator.swift */; };\n\t\t6E1620932B3118D9009240B2 /* ImmediateDisplayCoordinatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1620912B3118D5009240B2 /* ImmediateDisplayCoordinatorTest.swift */; };\n\t\t6E1620952B311D8A009240B2 /* DefaultDisplayCoordinatorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1620942B311D8A009240B2 /* DefaultDisplayCoordinatorTest.swift */; };\n\t\t6E1767F629B923D100D65F60 /* ChannelAuthTokenProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1767F329B923D100D65F60 /* ChannelAuthTokenProviderTest.swift */; };\n\t\t6E1767F729B923D100D65F60 /* ChannelAuthTokenAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1767F429B923D100D65F60 /* ChannelAuthTokenAPIClientTest.swift */; };\n\t\t6E1767F829B923D100D65F60 /* TestChannelAuthTokenAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1767F529B923D100D65F60 /* TestChannelAuthTokenAPIClient.swift */; };\n\t\t6E1767FA29B92F1700D65F60 /* AirshipUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1767F929B92F1700D65F60 /* AirshipUtilsTest.swift */; };\n\t\t6E1802F92C5C2DEC00198D0D /* AirshipAnalyticFeedTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1802F82C5C2DEC00198D0D /* AirshipAnalyticFeedTest.swift */; };\n\t\t6E1892B1268CE8FE00417887 /* AirshipLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8E1C9A26447B3800B11791 /* AirshipLock.swift */; };\n\t\t6E1892C8268D15C300417887 /* PreferenceCenterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1892C7268D15C300417887 /* PreferenceCenterTest.swift */; };\n\t\t6E1892D5268E3D8500417887 /* PreferenceCenterDecoder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1892D4268E3D8500417887 /* PreferenceCenterDecoder.swift */; };\n\t\t6E1892D7268E3F1800417887 /* PreferenceCenterConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1892D6268E3F1800417887 /* PreferenceCenterConfigTest.swift */; };\n\t\t6E1892DE2694F1C100417887 /* ChannelRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1892DD2694F1C100417887 /* ChannelRegistrar.swift */; };\n\t\t6E1A15062D6EA3A50056418B /* ThomasFormState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A15052D6EA3A50056418B /* ThomasFormState.swift */; };\n\t\t6E1A19222D6F875A0056418B /* ThomasFormValidationMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A19212D6F87550056418B /* ThomasFormValidationMode.swift */; };\n\t\t6E1A19242D6F8BDD0056418B /* AirshipInputValidationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A19232D6F8BD50056418B /* AirshipInputValidationTest.swift */; };\n\t\t6E1A1BB32D6F9D0F0056418B /* ThomasFormFieldProcessorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A1BB22D6F9D090056418B /* ThomasFormFieldProcessorTest.swift */; };\n\t\t6E1A1D852D70F3700056418B /* ThomasState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A1D842D70F36D0056418B /* ThomasState.swift */; };\n\t\t6E1A9BAB2B5AE38A00A6489B /* InAppMessageDisplayListener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BAA2B5AE38A00A6489B /* InAppMessageDisplayListener.swift */; };\n\t\t6E1A9BB02B5B0C4C00A6489B /* AutomationActionRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BAF2B5B0C4C00A6489B /* AutomationActionRunner.swift */; };\n\t\t6E1A9BB22B5B172F00A6489B /* TestActionRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BB12B5B172F00A6489B /* TestActionRunner.swift */; };\n\t\t6E1A9BB72B5B1D9E00A6489B /* TestActiveTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BB62B5B1D9E00A6489B /* TestActiveTimer.swift */; };\n\t\t6E1A9BB92B5B20A500A6489B /* InAppMessageDisplayListenerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BB82B5B20A500A6489B /* InAppMessageDisplayListenerTest.swift */; };\n\t\t6E1A9BBB2B5B20D700A6489B /* TestInAppMessageAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BBA2B5B20D700A6489B /* TestInAppMessageAnalytics.swift */; };\n\t\t6E1A9BBF2B5EE19000A6489B /* PreparedSchedule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BBE2B5EE19000A6489B /* PreparedSchedule.swift */; };\n\t\t6E1A9BC12B5EE1CF00A6489B /* SchedulePrepareResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BC02B5EE1CF00A6489B /* SchedulePrepareResult.swift */; };\n\t\t6E1A9BC32B5EE1DE00A6489B /* ScheduleReadyResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BC22B5EE1DE00A6489B /* ScheduleReadyResult.swift */; };\n\t\t6E1A9BC52B5EE1EE00A6489B /* ScheduleExecuteResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BC42B5EE1EE00A6489B /* ScheduleExecuteResult.swift */; };\n\t\t6E1A9BC72B5EE32E00A6489B /* AutomationTriggerProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BC62B5EE32E00A6489B /* AutomationTriggerProcessor.swift */; };\n\t\t6E1A9BC92B5EE34600A6489B /* AutomationEventFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BC82B5EE34600A6489B /* AutomationEventFeed.swift */; };\n\t\t6E1A9BD12B5EE84600A6489B /* AutomationScheduleData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BD02B5EE84600A6489B /* AutomationScheduleData.swift */; };\n\t\t6E1A9BD32B5EE8A400A6489B /* AutomationScheduleState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BD22B5EE8A400A6489B /* AutomationScheduleState.swift */; };\n\t\t6E1A9BD52B5EE97000A6489B /* TriggeringInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BD42B5EE97000A6489B /* TriggeringInfo.swift */; };\n\t\t6E1A9BF72B606CF200A6489B /* AutomationDelayProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1A9BF62B606CF200A6489B /* AutomationDelayProcessor.swift */; };\n\t\t6E1B7B132B714FFC00695561 /* LandingPageAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1B7B122B714FFC00695561 /* LandingPageAction.swift */; };\n\t\t6E1B7B162B715FFE00695561 /* LandingPageActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1B7B152B715FFE00695561 /* LandingPageActionTest.swift */; };\n\t\t6E1BACDB2719ED7D0038399E /* ScrollLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1BACDA2719ED7D0038399E /* ScrollLayout.swift */; };\n\t\t6E1BACDD2719FC0A0038399E /* ViewFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1BACDC2719FC0A0038399E /* ViewFactory.swift */; };\n\t\t6E1C9C3A271E90EB009EF9EF /* LayoutModelsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1C9C39271E90EB009EF9EF /* LayoutModelsTest.swift */; };\n\t\t6E1C9C4B271F7878009EF9EF /* BackgroundColorViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1C9C4A271F7878009EF9EF /* BackgroundColorViewModifier.swift */; };\n\t\t6E1CBD812BA3A30300519D9C /* AirshipEmbeddedInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1CBD802BA3A30300519D9C /* AirshipEmbeddedInfo.swift */; };\n\t\t6E1CBDE32BA51ED100519D9C /* InAppDisplayImpressionRuleProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1CBDE22BA51ED100519D9C /* InAppDisplayImpressionRuleProvider.swift */; };\n\t\t6E1CBDFF2BAA1DF200519D9C /* DefaultInAppDisplayImpressionRuleProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1CBDFE2BAA1DF200519D9C /* DefaultInAppDisplayImpressionRuleProviderTest.swift */; };\n\t\t6E1CBE2D2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6E1CBE2A2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodeld */; };\n\t\t6E1D8AD126CC5D490049DACB /* RemoteConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1D8AB226CC5D490049DACB /* RemoteConfig.swift */; };\n\t\t6E1D8AD826CC66BE0049DACB /* RemoteConfigCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1D8AD726CC66BE0049DACB /* RemoteConfigCache.swift */; };\n\t\t6E1D90022B2D1AB4004BA130 /* RetryingQueueTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1D90012B2D1AB4004BA130 /* RetryingQueueTests.swift */; };\n\t\t6E1EEE902BD81AF300B45A87 /* ContactChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E1EEE8F2BD81AF300B45A87 /* ContactChannel.swift */; };\n\t\t6E1F6E842BE6835400CFC7A7 /* UARemoteDataMappingV2toV4.xcmappingmodel in Resources */ = {isa = PBXBuildFile; fileRef = 6E1F6E832BE6835400CFC7A7 /* UARemoteDataMappingV2toV4.xcmappingmodel */; };\n\t\t6E1F6E882BE683E600CFC7A7 /* UARemoteDataMappingV1toV4.xcmappingmodel in Resources */ = {isa = PBXBuildFile; fileRef = 6E1F6E872BE683E600CFC7A7 /* UARemoteDataMappingV1toV4.xcmappingmodel */; };\n\t\t6E213B182BC60AF100BF24AE /* AirshipWeakValueHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E213B172BC60AF100BF24AE /* AirshipWeakValueHolder.swift */; };\n\t\t6E213B1E2BC7054500BF24AE /* InAppActionRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E213B1D2BC7054500BF24AE /* InAppActionRunner.swift */; };\n\t\t6E21852B237D32B30084933A /* EventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E21852A237D32B30084933A /* EventData.swift */; };\n\t\t6E2486DF28945D3900657CE4 /* PreferenceCenterState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2486DE28945D3900657CE4 /* PreferenceCenterState.swift */; };\n\t\t6E2486EC2894901E00657CE4 /* ConditionsViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2486EB2894901E00657CE4 /* ConditionsViewModifier.swift */; };\n\t\t6E2486F22898341400657CE4 /* ConditionsMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2486F02898341400657CE4 /* ConditionsMonitor.swift */; };\n\t\t6E2486F728984D0D00657CE4 /* PreferenceCenterTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2486F628984D0D00657CE4 /* PreferenceCenterTheme.swift */; };\n\t\t6E2486FD2899C06100657CE4 /* PreferenceCenterContentLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2486FC2899C06100657CE4 /* PreferenceCenterContentLoader.swift */; };\n\t\t6E25DD052D515F33009CF1A4 /* ThomasEmailRegistrationOptionsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FA28B2D515C5A005893E2 /* ThomasEmailRegistrationOptionsTest.swift */; };\n\t\t6E2811682BE406A50040D928 /* FeatureFlagDeferredResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7EACD22AF4220E00DA286B /* FeatureFlagDeferredResolverTest.swift */; };\n\t\t6E28116C2BE40E860040D928 /* FeatureFlagVariablesTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E28116B2BE40E860040D928 /* FeatureFlagVariablesTest.swift */; };\n\t\t6E29474B2AD47E15009EC6DD /* AirshipPreferenceCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 847BFFF4267CD739007CD249 /* AirshipPreferenceCenter.framework */; };\n\t\t6E29474D2AD5DA3B009EC6DD /* LiveActivityRegistrationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E29474C2AD5DA3B009EC6DD /* LiveActivityRegistrationStatus.swift */; };\n\t\t6E2947512AD5DB5A009EC6DD /* LiveActivityRegistrationStatusUpdates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2947502AD5DB5A009EC6DD /* LiveActivityRegistrationStatusUpdates.swift */; };\n\t\t6E299FD528D13D00001305A7 /* DefaultAirshipRequestSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E299FD428D13D00001305A7 /* DefaultAirshipRequestSessionTest.swift */; };\n\t\t6E299FD728D13E54001305A7 /* AirshipRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E299FD628D13E54001305A7 /* AirshipRequest.swift */; };\n\t\t6E299FDB28D14208001305A7 /* AirshipResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E299FDA28D14208001305A7 /* AirshipResponse.swift */; };\n\t\t6E299FDF28D14258001305A7 /* AirshipRequestSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E299FDE28D14258001305A7 /* AirshipRequestSession.swift */; };\n\t\t6E2D6AEE26B083DB00B7C226 /* ChannelAudienceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2D6AED26B083DB00B7C226 /* ChannelAudienceManager.swift */; };\n\t\t6E2D6AF226B0B64E00B7C226 /* SubscriptionListAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2D6AF126B0B64E00B7C226 /* SubscriptionListAPIClientTest.swift */; };\n\t\t6E2D6AF426B0C3C500B7C226 /* ChannelAudienceManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2D6AF326B0C3C500B7C226 /* ChannelAudienceManagerTest.swift */; };\n\t\t6E2D6AF626B0C6CA00B7C226 /* TestSubscriptionListAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2D6AF526B0C6CA00B7C226 /* TestSubscriptionListAPIClient.swift */; };\n\t\t6E2E3CA22B32723C00B8515B /* InAppMessageNativeBridgeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2E3CA12B32723C00B8515B /* InAppMessageNativeBridgeExtension.swift */; };\n\t\t6E2E3CA62B327A6C00B8515B /* InAppMessageNativeBridgeExtensionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2E3CA42B32726400B8515B /* InAppMessageNativeBridgeExtensionTest.swift */; };\n\t\t6E2F5A742A60833700CABD3D /* FeatureFlagManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5A732A60833700CABD3D /* FeatureFlagManager.swift */; };\n\t\t6E2F5A762A60871E00CABD3D /* FeatureFlagPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5A752A60871E00CABD3D /* FeatureFlagPayload.swift */; };\n\t\t6E2F5A862A65F00200CABD3D /* RemoteDataSourceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5A852A65F00200CABD3D /* RemoteDataSourceStatus.swift */; };\n\t\t6E2F5A8A2A66088100CABD3D /* AudienceDeviceInfoProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5A892A66088100CABD3D /* AudienceDeviceInfoProvider.swift */; };\n\t\t6E2F5A8E2A66FE8900CABD3D /* AirshipTimeCriteria.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5A8D2A66FE8900CABD3D /* AirshipTimeCriteria.swift */; };\n\t\t6E2F5A932A67316C00CABD3D /* AirshipFeatureFlags.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A62058692A5841330041FBF9 /* AirshipFeatureFlags.framework */; settings = {ATTRIBUTES = (Weak, ); }; };\n\t\t6E2F5AB12A67434B00CABD3D /* FeatureFlag.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5AB02A67434B00CABD3D /* FeatureFlag.swift */; };\n\t\t6E2F5AB22A67589400CABD3D /* TestDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1422683B8FA00A2CBD0 /* TestDate.swift */; };\n\t\t6E2F5AB32A6758ED00CABD3D /* TestNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED171268448EC00A2CBD0 /* TestNetworkMonitor.swift */; };\n\t\t6E2F5AB52A67599400CABD3D /* TestRemoteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70426CE07180099C92D /* TestRemoteData.swift */; };\n\t\t6E2F5AB72A675ADC00CABD3D /* TestAudienceChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5AB62A675ADC00CABD3D /* TestAudienceChecker.swift */; };\n\t\t6E2F5AB82A675ADC00CABD3D /* TestAudienceChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5AB62A675ADC00CABD3D /* TestAudienceChecker.swift */; };\n\t\t6E2F5ABA2A675D3600CABD3D /* FeatureFlagsRemoteDataAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5AB92A675D3600CABD3D /* FeatureFlagsRemoteDataAccess.swift */; };\n\t\t6E2FA2892D51519B005893E2 /* ThomasEmailRegistrationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2FA2882D515189005893E2 /* ThomasEmailRegistrationOptions.swift */; };\n\t\t6E34C4B12C7D4B6400B00506 /* ExecutionWindowProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E34C4B02C7D4B6400B00506 /* ExecutionWindowProcessor.swift */; };\n\t\t6E34C4B32C7D4C6600B00506 /* ExecutionWindowProcessorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E34C4B22C7D4C6600B00506 /* ExecutionWindowProcessorTest.swift */; };\n\t\t6E382C21276D3E990091A351 /* ThomasValidationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E382C20276D3E990091A351 /* ThomasValidationTests.swift */; };\n\t\t6E3B230F28A318CD0005D46E /* PreferenceCenterThemeLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B230E28A318CD0005D46E /* PreferenceCenterThemeLoader.swift */; };\n\t\t6E3B231328A32EC30005D46E /* PreferenceCenterViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B231228A32EC30005D46E /* PreferenceCenterViewExtensions.swift */; };\n\t\t6E3B32CC27559D8B00B89C7B /* FormInputViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B32CB27559D8B00B89C7B /* FormInputViewModifier.swift */; };\n\t\t6E3B32CF2755D8C700B89C7B /* LayoutState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3B32CE2755D8C700B89C7B /* LayoutState.swift */; };\n\t\t6E3CA5412ECB9B7900210C32 /* AirshipDisplayTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E3CA5402ECB9B7400210C32 /* AirshipDisplayTarget.swift */; };\n\t\t6E4007142A153AB20013C2DE /* AppRemoteDataProviderDelegateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4007132A153AB20013C2DE /* AppRemoteDataProviderDelegateTest.swift */; };\n\t\t6E4007162A153ABE0013C2DE /* ContactRemoteDataProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4007152A153ABE0013C2DE /* ContactRemoteDataProviderTest.swift */; };\n\t\t6E4007182A153AFE0013C2DE /* RemoteDataURLFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4007172A153AFE0013C2DE /* RemoteDataURLFactoryTest.swift */; };\n\t\t6E40868C2B8931C900435E2C /* AirshipViewSizeReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E40868B2B8931C900435E2C /* AirshipViewSizeReader.swift */; };\n\t\t6E40868E2B8D036600435E2C /* AirshipEmbeddedSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E40868D2B8D036600435E2C /* AirshipEmbeddedSize.swift */; };\n\t\t6E411B782538C4E600FEE4E8 /* UANativeBridge in Resources */ = {isa = PBXBuildFile; fileRef = 6E411B6C2538C4E500FEE4E8 /* UANativeBridge */; };\n\t\t6E411B7C2538C4E600FEE4E8 /* UANotificationCategories.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6E411B6D2538C4E600FEE4E8 /* UANotificationCategories.plist */; };\n\t\t6E411C782538C60900FEE4E8 /* UrbanAirship.strings in Resources */ = {isa = PBXBuildFile; fileRef = 6E411C742538C60900FEE4E8 /* UrbanAirship.strings */; };\n\t\t6E411CAB2538C6A600FEE4E8 /* UARemoteData.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6E411CA42538C6A500FEE4E8 /* UARemoteData.xcdatamodeld */; };\n\t\t6E411CAF2538C6A600FEE4E8 /* UAEvents.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6E411CA72538C6A500FEE4E8 /* UAEvents.xcdatamodeld */; };\n\t\t6E43202226EA814F009228AB /* CoreTelephony.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3261A7F4243CD73100ADBF6B /* CoreTelephony.framework */; platformFilter = maccatalyst; };\n\t\t6E43219226EA89B6009228AB /* NativeBridgeDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E43218F26EA89B6009228AB /* NativeBridgeDelegate.swift */; };\n\t\t6E4325C32B7A9D9A00A9B000 /* AirshipPrivacyManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325C22B7A9D9A00A9B000 /* AirshipPrivacyManagerTest.swift */; };\n\t\t6E4325C52B7AC3F700A9B000 /* TestPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325C42B7AC3F700A9B000 /* TestPush.swift */; };\n\t\t6E4325C62B7AC40D00A9B000 /* TestPush.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325C42B7AC3F700A9B000 /* TestPush.swift */; };\n\t\t6E4325CE2B7AD5A200A9B000 /* AirshipComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325CD2B7AD5A200A9B000 /* AirshipComponent.swift */; };\n\t\t6E4325D32B7AD96800A9B000 /* AirshipTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325D22B7AD96800A9B000 /* AirshipTest.swift */; };\n\t\t6E4325E92B7AEB1F00A9B000 /* AirshipEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325E82B7AEB1F00A9B000 /* AirshipEvent.swift */; };\n\t\t6E4325F22B7B1EDA00A9B000 /* SessionEventFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325F12B7B1EDA00A9B000 /* SessionEventFactory.swift */; };\n\t\t6E4325F62B7B2F5800A9B000 /* FeatureFlagAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325F52B7B2F5800A9B000 /* FeatureFlagAnalytics.swift */; };\n\t\t6E4325F82B7C08A600A9B000 /* AirshipAnalyticsFeed.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4325F72B7C08A600A9B000 /* AirshipAnalyticsFeed.swift */; };\n\t\t6E4326012B7C327C00A9B000 /* AirshipEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4326002B7C327C00A9B000 /* AirshipEvents.swift */; };\n\t\t6E4326072B7C364300A9B000 /* AssociatedIdentifiersTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4326042B7C361F00A9B000 /* AssociatedIdentifiersTest.swift */; };\n\t\t6E4326092B7C396F00A9B000 /* TestAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE1726E2C5940005D20D /* TestAnalytics.swift */; };\n\t\t6E4339EF2DFA03A3000A7741 /* JSONValueMatcherPredicates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4339EE2DFA039B000A7741 /* JSONValueMatcherPredicates.swift */; };\n\t\t6E4339F12DFA099F000A7741 /* AirshipIvyVersionMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4339F02DFA099C000A7741 /* AirshipIvyVersionMatcher.swift */; };\n\t\t6E44626729E6813A00CB2B56 /* AsyncSerialQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E44626629E6813A00CB2B56 /* AsyncSerialQueue.swift */; };\n\t\t6E46A273272B19760089CDE3 /* ViewExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E46A272272B19760089CDE3 /* ViewExtensions.swift */; };\n\t\t6E46A27C272B63680089CDE3 /* ThomasEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E46A27B272B63680089CDE3 /* ThomasEnvironment.swift */; };\n\t\t6E46A27F272B68660089CDE3 /* ThomasDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E46A27E272B68660089CDE3 /* ThomasDelegate.swift */; };\n\t\t6E475BFE2F5A3709003D8E42 /* VideoGroupState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E475BFD2F5A3709003D8E42 /* VideoGroupState.swift */; };\n\t\t6E475CBA2F5B3E45003D8E42 /* VideoMediaWebViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E475CB92F5B3E45003D8E42 /* VideoMediaWebViewTests.swift */; };\n\t\t6E49D7B228401D2E00C7BB9D /* Permission.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7A628401D2D00C7BB9D /* Permission.swift */; };\n\t\t6E49D7B428401D2E00C7BB9D /* PermissionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7A728401D2D00C7BB9D /* PermissionDelegate.swift */; };\n\t\t6E49D7B628401D2E00C7BB9D /* NotificationRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7A828401D2D00C7BB9D /* NotificationRegistrar.swift */; };\n\t\t6E49D7B828401D2E00C7BB9D /* Atomic.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7A928401D2D00C7BB9D /* Atomic.swift */; };\n\t\t6E49D7BA28401D2E00C7BB9D /* NotificationPermissionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7AA28401D2D00C7BB9D /* NotificationPermissionDelegate.swift */; };\n\t\t6E49D7BC28401D2E00C7BB9D /* PermissionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7AB28401D2D00C7BB9D /* PermissionsManager.swift */; };\n\t\t6E49D7BE28401D2E00C7BB9D /* RegistrationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7AC28401D2D00C7BB9D /* RegistrationDelegate.swift */; };\n\t\t6E49D7C028401D2E00C7BB9D /* UNNotificationRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7AD28401D2D00C7BB9D /* UNNotificationRegistrar.swift */; };\n\t\t6E49D7C228401D2E00C7BB9D /* PermissionStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7AE28401D2D00C7BB9D /* PermissionStatus.swift */; };\n\t\t6E49D7C428401D2E00C7BB9D /* PushNotificationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7AF28401D2E00C7BB9D /* PushNotificationDelegate.swift */; };\n\t\t6E49D7C628401D2E00C7BB9D /* Badger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7B028401D2E00C7BB9D /* Badger.swift */; };\n\t\t6E49D7C828401D2E00C7BB9D /* APNSRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7B128401D2E00C7BB9D /* APNSRegistrar.swift */; };\n\t\t6E49D7CD284028C600C7BB9D /* PermissionsManagerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E49D7CC284028C600C7BB9D /* PermissionsManagerTests.swift */; };\n\t\t6E4A466128EF447C00A25617 /* MessageCenterUser.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A466028EF447C00A25617 /* MessageCenterUser.swift */; };\n\t\t6E4A466528EF448600A25617 /* MessageCenterStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A466228EF448600A25617 /* MessageCenterStore.swift */; };\n\t\t6E4A466628EF448600A25617 /* MessageCenterAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A466328EF448600A25617 /* MessageCenterAPIClient.swift */; };\n\t\t6E4A466728EF448600A25617 /* MessageCenterMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A466428EF448600A25617 /* MessageCenterMessage.swift */; };\n\t\t6E4A467228EF44F600A25617 /* AirshipMessageCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CA0E423237E4A7B00EE76CF /* AirshipMessageCenter.framework */; };\n\t\t6E4A467928EF453400A25617 /* MessageCenterAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A467828EF453400A25617 /* MessageCenterAPIClientTest.swift */; };\n\t\t6E4A468028EF4FAF00A25617 /* TestAirshipRequestSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */; };\n\t\t6E4A468128EF4FAF00A25617 /* TestAirshipRequestSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */; };\n\t\t6E4A469F28F4A7DF00A25617 /* MessageCenterAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A469E28F4A7DF00A25617 /* MessageCenterAction.swift */; };\n\t\t6E4A46A128F4AEDF00A25617 /* MessageCenterNativeBridgeExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A46A028F4AEDF00A25617 /* MessageCenterNativeBridgeExtension.swift */; };\n\t\t6E4A4FDA2A30358F0049FEFC /* TagsActionArgs.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A4FD92A30358F0049FEFC /* TagsActionArgs.swift */; };\n\t\t6E4A4FDE2A3132850049FEFC /* AirshipSDKModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A4FDD2A3132850049FEFC /* AirshipSDKModule.swift */; };\n\t\t6E4AEE0C2B6B24D7008AEAC1 /* AirshipAutomation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E0B872A294A9C120064B7BD /* AirshipAutomation.framework */; };\n\t\t6E4AEE272B6B2E0A008AEAC1 /* alternate-airship.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6E4AEE222B6B2E09008AEAC1 /* alternate-airship.jpg */; };\n\t\t6E4AEE282B6B2E0A008AEAC1 /* DefaultAssetFileManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEE232B6B2E09008AEAC1 /* DefaultAssetFileManagerTest.swift */; };\n\t\t6E4AEE292B6B2E0A008AEAC1 /* airship.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6E4AEE242B6B2E0A008AEAC1 /* airship.jpg */; };\n\t\t6E4AEE2A2B6B2E0A008AEAC1 /* AssetCacheManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEE252B6B2E0A008AEAC1 /* AssetCacheManagerTest.swift */; };\n\t\t6E4AEE2B2B6B2E0A008AEAC1 /* DefaultAssetDownloaderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEE262B6B2E0A008AEAC1 /* DefaultAssetDownloaderTest.swift */; };\n\t\t6E4AEE2C2B6B302D008AEAC1 /* ActionAutomationPreparerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15283D2B4F0B8200DF1377 /* ActionAutomationPreparerTest.swift */; };\n\t\t6E4AEE302B6B3041008AEAC1 /* InAppMessageThemeTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D79C2B4F9E830099B6F3 /* InAppMessageThemeTest.swift */; };\n\t\t6E4AEE312B6B3A6A008AEAC1 /* Valid-UAInAppMessageModalStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 459D40542092474300C40E2D /* Valid-UAInAppMessageModalStyle.plist */; };\n\t\t6E4AEE322B6B3A6A008AEAC1 /* Valid-UAInAppMessageBannerStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 459D40562092474A00C40E2D /* Valid-UAInAppMessageBannerStyle.plist */; };\n\t\t6E4AEE332B6B3A6A008AEAC1 /* Valid-UAInAppMessageFullScreenStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 459D4049208FE64D00C40E2D /* Valid-UAInAppMessageFullScreenStyle.plist */; };\n\t\t6E4AEE342B6B3A6A008AEAC1 /* Invalid-UAInAppMessageBannerStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 459D40582092475500C40E2D /* Invalid-UAInAppMessageBannerStyle.plist */; };\n\t\t6E4AEE352B6B3A6A008AEAC1 /* Valid-UAInAppMessageHTMLStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6EE6529222A7E3B800F7D54D /* Valid-UAInAppMessageHTMLStyle.plist */; };\n\t\t6E4AEE362B6B3A6A008AEAC1 /* Invalid-UAInAppMessageModalStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 459D405A2092475C00C40E2D /* Invalid-UAInAppMessageModalStyle.plist */; };\n\t\t6E4AEE372B6B3A6A008AEAC1 /* Invalid-UAInAppMessageFullScreenStyle.plist in Resources */ = {isa = PBXBuildFile; fileRef = 459D404B208FE6BA00C40E2D /* Invalid-UAInAppMessageFullScreenStyle.plist */; };\n\t\t6E4AEE572B6B4358008AEAC1 /* UAAutomation.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEE492B6B4358008AEAC1 /* UAAutomation.xcdatamodeld */; };\n\t\t6E4AEE642B6B44EA008AEAC1 /* LegacyAutomationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEE622B6B44EA008AEAC1 /* LegacyAutomationStore.swift */; };\n\t\t6E4AEE652B6B44EA008AEAC1 /* AutomationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEE632B6B44EA008AEAC1 /* AutomationStore.swift */; };\n\t\t6E4AEEBC2B6D6380008AEAC1 /* TriggerData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4AEEBB2B6D6380008AEAC1 /* TriggerData.swift */; };\n\t\t6E4D20722E6B761200A8D641 /* MessageCenterListViewWithNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D20712E6B760C00A8D641 /* MessageCenterListViewWithNavigation.swift */; };\n\t\t6E4D224A2E6F814000A8D641 /* MessageCenterMessageViewWithNavigation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D22492E6F813700A8D641 /* MessageCenterMessageViewWithNavigation.swift */; };\n\t\t6E4D224C2E6F968A00A8D641 /* MessageCenterNavigationStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D224B2E6F968000A8D641 /* MessageCenterNavigationStack.swift */; };\n\t\t6E4D224E2E6F96B800A8D641 /* MessageCenterSplitNavigationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D224D2E6F96B100A8D641 /* MessageCenterSplitNavigationView.swift */; };\n\t\t6E4D22502E6F9CF200A8D641 /* MessageCenterContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D224F2E6F9CD700A8D641 /* MessageCenterContent.swift */; };\n\t\t6E4D22522E6FA2F700A8D641 /* MessageCenterBackButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D22512E6FA2F300A8D641 /* MessageCenterBackButton.swift */; };\n\t\t6E4D22542E6FA5ED00A8D641 /* MessageCenterWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D22532E6FA5EA00A8D641 /* MessageCenterWebView.swift */; };\n\t\t6E4D225D2E70ADE100A8D641 /* MessageCenterMessageViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D225C2E70ADDB00A8D641 /* MessageCenterMessageViewModel.swift */; };\n\t\t6E4D225F2E70AFE800A8D641 /* MessageCenterListViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4D225E2E70AFE300A8D641 /* MessageCenterListViewModel.swift */; };\n\t\t6E4E2E2829CEB222002E7682 /* ContactManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4E2E2729CEB222002E7682 /* ContactManagerProtocol.swift */; };\n\t\t6E4E5B3B26E7F91600198175 /* AirshipLocalizationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4E5B3926E7F91600198175 /* AirshipLocalizationUtils.swift */; };\n\t\t6E4E5B3D26E7F91600198175 /* Attributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4E5B3A26E7F91600198175 /* Attributes.swift */; };\n\t\t6E5213E32DCA7A3B00CF64B9 /* ThomasEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5213E22DCA7A3800CF64B9 /* ThomasEvent.swift */; };\n\t\t6E5214672DCAB03900CF64B9 /* ThomasFormResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5214662DCAB03600CF64B9 /* ThomasFormResult.swift */; };\n\t\t6E5214692DCABFCE00CF64B9 /* ThomasLayoutContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5214682DCABFCA00CF64B9 /* ThomasLayoutContext.swift */; };\n\t\t6E52146B2DCBF9BD00CF64B9 /* AirshipTimerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E52146A2DCBF9BA00CF64B9 /* AirshipTimerProtocol.swift */; };\n\t\t6E52146D2DCBFABE00CF64B9 /* ThomasPagerTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E52146C2DCBFAB900CF64B9 /* ThomasPagerTracker.swift */; };\n\t\t6E52146F2DCC075500CF64B9 /* ThomasPagerTrackerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E52146E2DCC075100CF64B9 /* ThomasPagerTrackerTest.swift */; };\n\t\t6E5215222DCEA12A00CF64B9 /* ThomasViewedPageInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5215212DCEA10F00CF64B9 /* ThomasViewedPageInfo.swift */; };\n\t\t6E524C732C126F5F002CA094 /* AirshipEventType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E524C722C126F5F002CA094 /* AirshipEventType.swift */; };\n\t\t6E524D022C1A2CAE002CA094 /* InAppMessageThemeManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E524D012C1A2CAE002CA094 /* InAppMessageThemeManager.swift */; };\n\t\t6E524D042C1A454E002CA094 /* InAppMessageThemeShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E524D032C1A454E002CA094 /* InAppMessageThemeShadow.swift */; };\n\t\t6E55A4D72E1DB4F700B07DF8 /* ThomasAssociatedLabelResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E55A4D62E1DB4CB00B07DF8 /* ThomasAssociatedLabelResolver.swift */; };\n\t\t6E57CE3228DB8BDA00287601 /* LiveActivityUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E57CE3128DB8BDA00287601 /* LiveActivityUpdate.swift */; };\n\t\t6E57CE3728DBBD9A00287601 /* LiveActivityRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E57CE3628DBBD9A00287601 /* LiveActivityRegistry.swift */; };\n\t\t6E590E6E29A94CA90036DFAB /* AppStateTrackerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E590E6D29A94CA90036DFAB /* AppStateTrackerTest.swift */; };\n\t\t6E5A64C42AAB7D5C00574085 /* AirshipMeteredUsage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5A64C32AAB7D5C00574085 /* AirshipMeteredUsage.swift */; };\n\t\t6E5A64C82AABBE7100574085 /* MeteredUsageAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5A64C72AABBE7100574085 /* MeteredUsageAPIClient.swift */; };\n\t\t6E5A64D02AABBEAF00574085 /* AirshipMeteredUsageEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5A64CF2AABBEAF00574085 /* AirshipMeteredUsageEvent.swift */; };\n\t\t6E5A64D42AABBED600574085 /* MeteredUsageStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5A64D32AABBED600574085 /* MeteredUsageStore.swift */; };\n\t\t6E5A64D92AABC5A400574085 /* UAMeteredUsage.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6E5A64D72AABC5A400574085 /* UAMeteredUsage.xcdatamodeld */; };\n\t\t6E5ADF822D7682A300A03799 /* StateSubscriptionsModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5ADF812D7682A200A03799 /* StateSubscriptionsModifier.swift */; };\n\t\t6E5ADF842D7682D600A03799 /* ThomasStateTrigger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5ADF832D7682D300A03799 /* ThomasStateTrigger.swift */; };\n\t\t6E5B1A052AFF090B0019CA61 /* SessionTrackerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5B1A042AFF090B0019CA61 /* SessionTrackerTest.swift */; };\n\t\t6E60EF6A29DF542B003F7A8D /* AnonContactData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E60EF6929DF542B003F7A8D /* AnonContactData.swift */; };\n\t\t6E6363E229DCD0CF009C358A /* ContactSubscriptionListClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6363E129DCD0CF009C358A /* ContactSubscriptionListClient.swift */; };\n\t\t6E6363E629DCE9A2009C358A /* ContactSubscriptionListAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6363E529DCE9A2009C358A /* ContactSubscriptionListAPIClientTest.swift */; };\n\t\t6E6363E829DCEB84009C358A /* ContactManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6363E729DCEB84009C358A /* ContactManagerTest.swift */; };\n\t\t6E6363EA29DCECA1009C358A /* TestContactSubscriptionListAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6363E929DCECA1009C358A /* TestContactSubscriptionListAPIClient.swift */; };\n\t\t6E6363EC29DDF84B009C358A /* SerialQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6363EB29DDF84B009C358A /* SerialQueue.swift */; };\n\t\t6E64C88027331ABA000EB887 /* PreferenceDataStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E64C87F27331ABA000EB887 /* PreferenceDataStoreTest.swift */; };\n\t\t6E65244C2A4FD4270019F353 /* DeviceTagSelectorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E65244B2A4FD4270019F353 /* DeviceTagSelectorTest.swift */; };\n\t\t6E65244E2A4FD69F0019F353 /* DeviceAudienceSelectorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E65244D2A4FD69F0019F353 /* DeviceAudienceSelectorTest.swift */; };\n\t\t6E6524502A4FD8D30019F353 /* JSONPredicateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E65244F2A4FD8D30019F353 /* JSONPredicateTest.swift */; };\n\t\t6E6541E02758976D009676CA /* AirshipProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6541DF2758976D009676CA /* AirshipProgressView.swift */; };\n\t\t6E65FB602C753CB400D9F341 /* EmbeddedViewSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E65FB5F2C753CB400D9F341 /* EmbeddedViewSelector.swift */; };\n\t\t6E664BA126C43F5400A2C8E5 /* ActivityViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BA026C43F5400A2C8E5 /* ActivityViewController.swift */; };\n\t\t6E664BA726C4417400A2C8E5 /* ShareAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BA426C4417400A2C8E5 /* ShareAction.swift */; };\n\t\t6E664BCA26C4852B00A2C8E5 /* AddCustomEventAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BC926C4852B00A2C8E5 /* AddCustomEventAction.swift */; };\n\t\t6E664BD026C4916600A2C8E5 /* AddTagsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BCF26C4916600A2C8E5 /* AddTagsAction.swift */; };\n\t\t6E664BD326C4917000A2C8E5 /* RemoveTagsAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BD226C4917000A2C8E5 /* RemoveTagsAction.swift */; };\n\t\t6E664BD926C4CD8700A2C8E5 /* PasteboardAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BD526C4CD8700A2C8E5 /* PasteboardAction.swift */; };\n\t\t6E664BDB26C4CD8700A2C8E5 /* EnableFeatureAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BD626C4CD8700A2C8E5 /* EnableFeatureAction.swift */; };\n\t\t6E664BDD26C4CD8700A2C8E5 /* OpenExternalURLAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BD726C4CD8700A2C8E5 /* OpenExternalURLAction.swift */; };\n\t\t6E664BDF26C4CD8700A2C8E5 /* FetchDeviceInfoAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BD826C4CD8700A2C8E5 /* FetchDeviceInfoAction.swift */; };\n\t\t6E664BE526C5817B00A2C8E5 /* TestContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BE426C5817B00A2C8E5 /* TestContact.swift */; };\n\t\t6E664BE726C5B21600A2C8E5 /* ModifyAttributesAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BE626C5B21600A2C8E5 /* ModifyAttributesAction.swift */; };\n\t\t6E664BEA26C6DB7600A2C8E5 /* AirshipUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E664BE926C6DB7500A2C8E5 /* AirshipUtils.swift */; };\n\t\t6E66BA7F2D14B61A0083A9FD /* WrappingLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E66BA7E2D14B61A0083A9FD /* WrappingLayout.swift */; };\n\t\t6E66DDA62E95A68300D44555 /* WorkRateLimiterTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E66DDA52E95A67C00D44555 /* WorkRateLimiterTests.swift */; };\n\t\t6E68028B2B850DDE00F4591F /* ApplicationMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1512683DBC300A2CBD0 /* ApplicationMetrics.swift */; };\n\t\t6E68028C2B85149900F4591F /* ApplicationMetricsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E60EF6529DF4BB5003F7A8D /* ApplicationMetricsTest.swift */; };\n\t\t6E68028E2B852F6A00F4591F /* AutomationScheduleDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E68028D2B852F6A00F4591F /* AutomationScheduleDataTest.swift */; };\n\t\t6E6802902B8671E700F4591F /* InAppAutomationComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E68028F2B8671E700F4591F /* InAppAutomationComponent.swift */; };\n\t\t6E6802922B86732200F4591F /* PreferenceCenterComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6802912B86732200F4591F /* PreferenceCenterComponent.swift */; };\n\t\t6E6802942B8673F900F4591F /* FeatureFlagComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6802932B8673F900F4591F /* FeatureFlagComponent.swift */; };\n\t\t6E6802962B86749900F4591F /* MessageCenterComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6802952B86749900F4591F /* MessageCenterComponent.swift */; };\n\t\t6E6802982B8675A200F4591F /* DebugComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6802972B8675A200F4591F /* DebugComponent.swift */; };\n\t\t6E68203228EDE3E200A4F90B /* LiveActivityRestorer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E68203128EDE3E200A4F90B /* LiveActivityRestorer.swift */; };\n\t\t6E692AFD29E0CB2F00D96CCC /* JavaScriptCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E692AFC29E0CB2F00D96CCC /* JavaScriptCommand.swift */; };\n\t\t6E692AFF29E0CB4100D96CCC /* JavaScriptCommandDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E692AFE29E0CB4100D96CCC /* JavaScriptCommandDelegate.swift */; };\n\t\t6E692B0329E0CBB500D96CCC /* NativeBridgeExtensionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E692B0229E0CBB500D96CCC /* NativeBridgeExtensionDelegate.swift */; };\n\t\t6E698DEC26790AC300654DB2 /* PreferenceDataStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698DE826790AC300654DB2 /* PreferenceDataStore.swift */; };\n\t\t6E698DF226790AC300654DB2 /* AirshipPrivacyManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698DEB26790AC300654DB2 /* AirshipPrivacyManager.swift */; };\n\t\t6E698E03267A799500654DB2 /* AirshipErrors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E02267A799500654DB2 /* AirshipErrors.swift */; };\n\t\t6E698E09267A7DD900654DB2 /* RemoteDataAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E08267A7DD900654DB2 /* RemoteDataAPIClient.swift */; };\n\t\t6E698E0C267A88D600654DB2 /* EventAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E0B267A88D600654DB2 /* EventAPIClient.swift */; };\n\t\t6E698E3D267BEDC300654DB2 /* DefaultAirshipContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E3A267BEDC300654DB2 /* DefaultAirshipContact.swift */; };\n\t\t6E698E3F267BEDC300654DB2 /* AttributesEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E3B267BEDC300654DB2 /* AttributesEditor.swift */; };\n\t\t6E698E41267BEDC300654DB2 /* TagGroupsEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E3C267BEDC300654DB2 /* TagGroupsEditor.swift */; };\n\t\t6E698E57267BF63B00654DB2 /* AppStateTrackerAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E52267BF63A00654DB2 /* AppStateTrackerAdapter.swift */; };\n\t\t6E698E59267BF63B00654DB2 /* AppStateTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E53267BF63A00654DB2 /* AppStateTracker.swift */; };\n\t\t6E698E5F267BF63B00654DB2 /* ApplicationState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E56267BF63B00654DB2 /* ApplicationState.swift */; };\n\t\t6E698E62267C03C700654DB2 /* TestAppStateTracker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E61267C03C700654DB2 /* TestAppStateTracker.swift */; };\n\t\t6E6A848D2B6854FC006FFB35 /* AutomationDelayProcessorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6A848C2B6854FC006FFB35 /* AutomationDelayProcessorTest.swift */; };\n\t\t6E6A84932B68A57E006FFB35 /* AutomationStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6A84912B68A571006FFB35 /* AutomationStoreTest.swift */; };\n\t\t6E6B2DBE2B33B768008BF788 /* AutomationScheduleTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6B2DBD2B33B768008BF788 /* AutomationScheduleTest.swift */; };\n\t\t6E6BD2422AE995DA00B9DFC9 /* DeferredResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2412AE995DA00B9DFC9 /* DeferredResolver.swift */; };\n\t\t6E6BD2462AEAFE7E00B9DFC9 /* DeferredAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2452AEAFE7E00B9DFC9 /* DeferredAPIClient.swift */; };\n\t\t6E6BD24A2AEAFEB700B9DFC9 /* AirsihpTriggerContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2492AEAFEB700B9DFC9 /* AirsihpTriggerContext.swift */; };\n\t\t6E6BD24E2AEAFEC500B9DFC9 /* AirshipStateOverrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD24D2AEAFEC500B9DFC9 /* AirshipStateOverrides.swift */; };\n\t\t6E6BD2582AEC598C00B9DFC9 /* DeferredAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2572AEC598C00B9DFC9 /* DeferredAPIClientTest.swift */; };\n\t\t6E6BD25A2AEC626B00B9DFC9 /* DeferredResolverTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2592AEC626B00B9DFC9 /* DeferredResolverTest.swift */; };\n\t\t6E6BD26D2AF1AC5700B9DFC9 /* AirshipCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD26C2AF1AC5700B9DFC9 /* AirshipCache.swift */; };\n\t\t6E6BD2722AF1B05500B9DFC9 /* UAirshipCache.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2702AF1B05500B9DFC9 /* UAirshipCache.xcdatamodeld */; };\n\t\t6E6BD2762AF1C1D800B9DFC9 /* DeferredFlagResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2752AF1C1D800B9DFC9 /* DeferredFlagResolver.swift */; };\n\t\t6E6BD2782AF2B97300B9DFC9 /* AirshipTaskSleeper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6BD2772AF2B97300B9DFC9 /* AirshipTaskSleeper.swift */; };\n\t\t6E6C3F7F27A20C3C007F55C7 /* ChannelScope.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C3F7E27A20C3C007F55C7 /* ChannelScope.swift */; };\n\t\t6E6C3F8A27A266C0007F55C7 /* CachedValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C3F8927A266C0007F55C7 /* CachedValue.swift */; };\n\t\t6E6C3F8D27A26992007F55C7 /* CachedValueTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C3F8C27A26992007F55C7 /* CachedValueTest.swift */; };\n\t\t6E6C3F9A27A47DB4007F55C7 /* PreferenceCenterConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C3F9927A47DB4007F55C7 /* PreferenceCenterConfig.swift */; };\n\t\t6E6C3F9E27A4C3D4007F55C7 /* AirshipJSON.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C3F9D27A4C3D4007F55C7 /* AirshipJSON.swift */; };\n\t\t6E6C84462A5C8CFE00DD83A2 /* AirshipConfigTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6C84452A5C8CFD00DD83A2 /* AirshipConfigTest.swift */; };\n\t\t6E6CC38623A3F9B4003D583C /* PushDataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6CC38023A3F9B4003D583C /* PushDataManager.swift */; };\n\t\t6E6ED13B2683A58D00A2CBD0 /* Dispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1352683A58D00A2CBD0 /* Dispatcher.swift */; };\n\t\t6E6ED1402683A9F200A2CBD0 /* AirshipDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED13F2683A9F200A2CBD0 /* AirshipDate.swift */; };\n\t\t6E6ED1432683B8FA00A2CBD0 /* TestDate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1422683B8FA00A2CBD0 /* TestDate.swift */; };\n\t\t6E6ED1452683BC7F00A2CBD0 /* TestDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1442683BC7F00A2CBD0 /* TestDispatcher.swift */; };\n\t\t6E6ED1492683D8E200A2CBD0 /* LocaleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1482683D8E200A2CBD0 /* LocaleManager.swift */; };\n\t\t6E6ED15B2683DBC300A2CBD0 /* AirshipBase64.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED14D2683DBC200A2CBD0 /* AirshipBase64.swift */; };\n\t\t6E6ED15F2683DBC300A2CBD0 /* AirshipCoreResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED14F2683DBC200A2CBD0 /* AirshipCoreResources.swift */; };\n\t\t6E6ED1612683DBC300A2CBD0 /* AirshipVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1502683DBC300A2CBD0 /* AirshipVersion.swift */; };\n\t\t6E6ED1672683DBC300A2CBD0 /* UACoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1532683DBC300A2CBD0 /* UACoreData.swift */; };\n\t\t6E6ED1692683DBC300A2CBD0 /* AirshipNetworkChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED1542683DBC300A2CBD0 /* AirshipNetworkChecker.swift */; };\n\t\t6E6ED172268448EC00A2CBD0 /* TestNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED171268448EC00A2CBD0 /* TestNetworkMonitor.swift */; };\n\t\t6E6EF9E7270625C400D30C35 /* AirshipEventsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6EF9E6270625C400D30C35 /* AirshipEventsTest.swift */; };\n\t\t6E71129D2880DACB004942E4 /* EventHandlerViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7112962880DACB004942E4 /* EventHandlerViewModifier.swift */; };\n\t\t6E7112A12880DACB004942E4 /* EnableBehaviorModifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7112982880DACB004942E4 /* EnableBehaviorModifiers.swift */; };\n\t\t6E7112A32880DACB004942E4 /* VisibilityViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7112992880DACB004942E4 /* VisibilityViewModifier.swift */; };\n\t\t6E7112A52880DACB004942E4 /* StateController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E71129A2880DACB004942E4 /* StateController.swift */; };\n\t\t6E739D6626B9BDC100BC6F6D /* ChannelBulkUpdateAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E739D6526B9BDC100BC6F6D /* ChannelBulkUpdateAPIClient.swift */; };\n\t\t6E739D6B26B9DFFB00BC6F6D /* AttributePendingMutations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E739D6A26B9DFFB00BC6F6D /* AttributePendingMutations.swift */; };\n\t\t6E739D6E26B9F58700BC6F6D /* TagGroupMutations.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E739D6D26B9F58700BC6F6D /* TagGroupMutations.swift */; };\n\t\t6E739D7F26BAFCB800BC6F6D /* TestChannelBulkUpdateAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E739D7E26BAFCB800BC6F6D /* TestChannelBulkUpdateAPIClient.swift */; };\n\t\t6E739D8226BB33A200BC6F6D /* ChannelBulkUpdateAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E739D8126BB33A200BC6F6D /* ChannelBulkUpdateAPIClientTest.swift */; };\n\t\t6E75F50529C4EAF600E3585A /* AudienceOverridesProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E75F50429C4EAF600E3585A /* AudienceOverridesProvider.swift */; };\n\t\t6E77CD4A2D8A225E0057A52C /* SMSValidatorAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E77CD492D8A225E0057A52C /* SMSValidatorAPIClient.swift */; };\n\t\t6E77CD4B2D8A225E0057A52C /* AirshipInputValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E77CD472D8A225E0057A52C /* AirshipInputValidator.swift */; };\n\t\t6E77CE472D8A28B90057A52C /* CachingSMSValidatorAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E77CE462D8A28B10057A52C /* CachingSMSValidatorAPIClient.swift */; };\n\t\t6E78848F29B9643C00ACAE45 /* AirshipContact.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E78848E29B9643C00ACAE45 /* AirshipContact.swift */; };\n\t\t6E7DB38328ECDC41002725F6 /* LiveActivity.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DB38228ECDC41002725F6 /* LiveActivity.swift */; };\n\t\t6E7DB38E28ECFCED002725F6 /* AirshipJSONTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DB38D28ECFCED002725F6 /* AirshipJSONTest.swift */; };\n\t\t6E7DB39228ED0D7C002725F6 /* LiveActivityRegistryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DB38A28ECDDD9002725F6 /* LiveActivityRegistryTest.swift */; };\n\t\t6E7E770D2DDFD0D80042086D /* AirshipAsyncSemaphore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7E770C2DDFD0D80042086D /* AirshipAsyncSemaphore.swift */; };\n\t\t6E7E770F2DDFD10A0042086D /* AirshipAsyncSemaphoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7E770E2DDFD1040042086D /* AirshipAsyncSemaphoreTest.swift */; };\n\t\t6E7EACD12AF4192400DA286B /* AirshipCacheTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7EACD02AF4192400DA286B /* AirshipCacheTest.swift */; };\n\t\t6E82482229A6D9DF00136EA0 /* CancellableValueHolder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E82482129A6D9DF00136EA0 /* CancellableValueHolder.swift */; };\n\t\t6E82483829A6E1BE00136EA0 /* AirshipCancellable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E82483729A6E1BE00136EA0 /* AirshipCancellable.swift */; };\n\t\t6E8746492D8A3C71002469D7 /* TestSMSValidatorAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8746482D8A3C64002469D7 /* TestSMSValidatorAPIClient.swift */; };\n\t\t6E87BD6426D594870005D20D /* ChannelRegistrationPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD6326D594870005D20D /* ChannelRegistrationPayload.swift */; };\n\t\t6E87BD6726D6A39A0005D20D /* AirshipConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD6626D6A39A0005D20D /* AirshipConfig.swift */; };\n\t\t6E87BD8326D757CA0005D20D /* CloudSite.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD8226D757CA0005D20D /* CloudSite.swift */; };\n\t\t6E87BD8D26D815780005D20D /* AppIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD8C26D815780005D20D /* AppIntegration.swift */; };\n\t\t6E87BD9226D963B60005D20D /* DefaultAppIntegrationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD9126D963B60005D20D /* DefaultAppIntegrationDelegate.swift */; };\n\t\t6E87BD9D26DD78CC0005D20D /* DefaultAppIntegrationDelegateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD9C26DD78CC0005D20D /* DefaultAppIntegrationDelegateTest.swift */; };\n\t\t6E87BD9F26DDDB250005D20D /* AppIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BD9E26DDDB250005D20D /* AppIntegrationTests.swift */; };\n\t\t6E87BDBD26E01FF40005D20D /* ModuleLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BDBC26E01FF40005D20D /* ModuleLoader.swift */; };\n\t\t6E87BE0126E283850005D20D /* Airship.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BDFD26E283840005D20D /* Airship.swift */; };\n\t\t6E87BE0726E283850005D20D /* DeepLinkDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE0026E283850005D20D /* DeepLinkDelegate.swift */; };\n\t\t6E87BE1326E28F570005D20D /* AirshipInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE1226E28F570005D20D /* AirshipInstance.swift */; };\n\t\t6E87BE1626E29BC90005D20D /* TestAirshipInstance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE1526E29BC90005D20D /* TestAirshipInstance.swift */; };\n\t\t6E87BE1B26E2C59D0005D20D /* TestAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BE1726E2C5940005D20D /* TestAnalytics.swift */; };\n\t\t6E88739A2763D8AB00AC248A /* AirshipImageProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8873992763D8AB00AC248A /* AirshipImageProvider.swift */; };\n\t\t6E887CD1272C5E8400E83363 /* CheckboxState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E887CD0272C5E8400E83363 /* CheckboxState.swift */; };\n\t\t6E887CD3272C5F5000E83363 /* Checkbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E887CD2272C5F5000E83363 /* Checkbox.swift */; };\n\t\t6E887CD5272C5F5A00E83363 /* CheckboxController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E887CD4272C5F5A00E83363 /* CheckboxController.swift */; };\n\t\t6E892F2E2E7A193E00FB0EC4 /* PreferenceCenterContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E892F2D2E7A193200FB0EC4 /* PreferenceCenterContent.swift */; };\n\t\t6E8932982E7B666000FB0EC4 /* APNSRegistrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8932972E7B665200FB0EC4 /* APNSRegistrationResult.swift */; };\n\t\t6E89329A2E7B66C300FB0EC4 /* NotificationRegistrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8932992E7B66B600FB0EC4 /* NotificationRegistrationResult.swift */; };\n\t\t6E8B4BF12888606D00AA336E /* ChannelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8B4BEF2888606400AA336E /* ChannelTest.swift */; };\n\t\t6E8BDA172B62EC9F00711DB8 /* AutomationRemoteDataSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8BDA162B62EC9F00711DB8 /* AutomationRemoteDataSubscriber.swift */; };\n\t\t6E8BDEFE2A67938200F816D9 /* FeatureFlagInfoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8BDEFC2A67937E00F816D9 /* FeatureFlagInfoTest.swift */; };\n\t\t6E8BDF012A679E5000F816D9 /* FeatureFlagRemoteDataAccessTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8BDEFF2A679CD100F816D9 /* FeatureFlagRemoteDataAccessTest.swift */; };\n\t\t6E8BDF022A684FC700F816D9 /* TestRemoteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70426CE07180099C92D /* TestRemoteData.swift */; };\n\t\t6E8CE762284137D600CF4B11 /* AirshipPushTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E8CE761284137D600CF4B11 /* AirshipPushTest.swift */; };\n\t\t6E916C572DB30DA200C676FA /* AirshipWindowFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E916C562DB30D9E00C676FA /* AirshipWindowFactory.swift */; };\n\t\t6E91B43C26868A6300DDB1A8 /* CircularRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91B43B26868A6300DDB1A8 /* CircularRegion.swift */; };\n\t\t6E91B44026868C3400DDB1A8 /* RegionEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91B43E26868C3400DDB1A8 /* RegionEvent.swift */; };\n\t\t6E91B44226868C3400DDB1A8 /* ProximityRegion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91B43F26868C3400DDB1A8 /* ProximityRegion.swift */; };\n\t\t6E91B4452686911B00DDB1A8 /* EventUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91B4442686911B00DDB1A8 /* EventUtils.swift */; };\n\t\t6E91B4692689327D00DDB1A8 /* CustomEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91B4662689327D00DDB1A8 /* CustomEvent.swift */; };\n\t\t6E91E43A28EF423400B6F25E /* WorkBackgroundTasks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E42F28EF423300B6F25E /* WorkBackgroundTasks.swift */; };\n\t\t6E91E43D28EF423400B6F25E /* WorkConditionsMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43028EF423300B6F25E /* WorkConditionsMonitor.swift */; };\n\t\t6E91E44028EF423400B6F25E /* Worker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43128EF423300B6F25E /* Worker.swift */; };\n\t\t6E91E44328EF423400B6F25E /* WorkRateLimiterActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43228EF423300B6F25E /* WorkRateLimiterActor.swift */; };\n\t\t6E91E44628EF423400B6F25E /* AirshipWorkManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43328EF423300B6F25E /* AirshipWorkManagerProtocol.swift */; };\n\t\t6E91E44C28EF423400B6F25E /* AirshipWorkRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43528EF423300B6F25E /* AirshipWorkRequest.swift */; };\n\t\t6E91E45228EF423400B6F25E /* AirshipWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43728EF423300B6F25E /* AirshipWorkManager.swift */; };\n\t\t6E91E45828EF423400B6F25E /* AirshipWorkResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E91E43928EF423400B6F25E /* AirshipWorkResult.swift */; };\n\t\t6E92EC8A284933750038802D /* PromptPermissionAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92EC89284933750038802D /* PromptPermissionAction.swift */; };\n\t\t6E92EC8D2849378E0038802D /* PermissionPrompter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92EC8C2849378E0038802D /* PermissionPrompter.swift */; };\n\t\t6E92EC90284954B10038802D /* ButtonState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92EC8F284954B10038802D /* ButtonState.swift */; };\n\t\t6E92ECA1284A79AB0038802D /* PromptPermissionActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92ECA0284A79AB0038802D /* PromptPermissionActionTest.swift */; };\n\t\t6E92ECA5284A7A5C0038802D /* TestPermissionPrompter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92ECA2284A7A2A0038802D /* TestPermissionPrompter.swift */; };\n\t\t6E92ECA7284AC1120038802D /* EnableFeatureActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92ECA6284AC1120038802D /* EnableFeatureActionTest.swift */; };\n\t\t6E92ECAC284EA7DB0038802D /* AnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92ECAB284EA7DA0038802D /* AnalyticsTest.swift */; };\n\t\t6E92ECB1284ECE590038802D /* CachedList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92ECB0284ECE590038802D /* CachedList.swift */; };\n\t\t6E92ECB6284ED7330038802D /* CachedListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E92ECB3284ED6F10038802D /* CachedListTest.swift */; };\n\t\t6E938DBC2AC39A0500F691D9 /* FeatureFlagAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E938DBB2AC39A0500F691D9 /* FeatureFlagAnalyticsTest.swift */; };\n\t\t6E94760F29BA8FA30025F364 /* ContactManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E94760E29BA8FA30025F364 /* ContactManager.swift */; };\n\t\t6E94761529BBC0240025F364 /* AirshipButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E94761429BBC0230025F364 /* AirshipButton.swift */; };\n\t\t6E952920268A6C1500398B54 /* SearchEventTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E95291F268A6C1500398B54 /* SearchEventTemplate.swift */; };\n\t\t6E952923268B812000398B54 /* AccountEventTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E952922268B812000398B54 /* AccountEventTemplate.swift */; };\n\t\t6E952926268B8F6600398B54 /* AssociatedIdentifiers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E952925268B8F6500398B54 /* AssociatedIdentifiers.swift */; };\n\t\t6E95292C268B98A200398B54 /* MediaEventTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E95292B268B98A200398B54 /* MediaEventTemplate.swift */; };\n\t\t6E95292F268BBD7D00398B54 /* RetailEventTemplate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E95292E268BBD7D00398B54 /* RetailEventTemplate.swift */; };\n\t\t6E96ECF2293EB7900053CC91 /* AirshipEventData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ECF1293EB7900053CC91 /* AirshipEventData.swift */; };\n\t\t6E96ECF6293FCE080053CC91 /* EventUploadScheduler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ECF5293FCE080053CC91 /* EventUploadScheduler.swift */; };\n\t\t6E96ECFA293FDDD90053CC91 /* AirshipSDKExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ECF9293FDDD90053CC91 /* AirshipSDKExtension.swift */; };\n\t\t6E96ED02294115210053CC91 /* AsyncStream.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED01294115210053CC91 /* AsyncStream.swift */; };\n\t\t6E96ED0A294135500053CC91 /* EventManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED09294135500053CC91 /* EventManager.swift */; };\n\t\t6E96ED0E29416E820053CC91 /* EventManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED0D29416E820053CC91 /* EventManagerTest.swift */; };\n\t\t6E96ED1029416E8F0053CC91 /* EventAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED0F29416E8F0053CC91 /* EventAPIClientTest.swift */; };\n\t\t6E96ED1229416E990053CC91 /* EventSchedulerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED1129416E990053CC91 /* EventSchedulerTest.swift */; };\n\t\t6E96ED1429417A600053CC91 /* EventStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED1329417A600053CC91 /* EventStoreTest.swift */; };\n\t\t6E96ED16294197D90053CC91 /* EventUploadTuningInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED15294197D90053CC91 /* EventUploadTuningInfo.swift */; };\n\t\t6E96ED1A2941A0EC0053CC91 /* EventTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E96ED192941A0EC0053CC91 /* EventTestUtils.swift */; };\n\t\t6E9752562A5F79E200E67B1A /* ExperimentTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9752552A5F79E200E67B1A /* ExperimentTest.swift */; };\n\t\t6E97D6AD2D84B1660001CF7F /* ThomasFormStateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E97D6AC2D84B1610001CF7F /* ThomasFormStateTest.swift */; };\n\t\t6E97D6AF2D84B17D0001CF7F /* ThomasFormDataCollectorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E97D6AE2D84B1780001CF7F /* ThomasFormDataCollectorTest.swift */; };\n\t\t6E97D6B12D84B18E0001CF7F /* ThomasFormDataCollector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E97D6B02D84B1890001CF7F /* ThomasFormDataCollector.swift */; };\n\t\t6E97D6B42D84B1D70001CF7F /* ThomasStateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E97D6B32D84B1D10001CF7F /* ThomasStateTest.swift */; };\n\t\t6E97D6B62D84B2350001CF7F /* ThomasFormFieldTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E97D6B52D84B2330001CF7F /* ThomasFormFieldTest.swift */; };\n\t\t6E986EE42B448D3C00FBE6A0 /* AutomationEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E986EE32B448D3C00FBE6A0 /* AutomationEngine.swift */; };\n\t\t6E986EF92B44D41E00FBE6A0 /* InAppAutomation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E986EF82B44D41E00FBE6A0 /* InAppAutomation.swift */; };\n\t\t6E986EFB2B44D48C00FBE6A0 /* InAppMessaging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E986EFA2B44D48C00FBE6A0 /* InAppMessaging.swift */; };\n\t\t6E986F062B47319E00FBE6A0 /* DeferredScheduleResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E986F052B47319E00FBE6A0 /* DeferredScheduleResult.swift */; };\n\t\t6E986F0E2B473EC700FBE6A0 /* AutomationRemoteDataAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E986F0D2B473EC700FBE6A0 /* AutomationRemoteDataAccess.swift */; };\n\t\t6E9B4874288F0CE000C905B1 /* RateAppAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B4873288F0CE000C905B1 /* RateAppAction.swift */; };\n\t\t6E9B4878288F360C00C905B1 /* RateAppActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B4877288F360C00C905B1 /* RateAppActionTest.swift */; };\n\t\t6E9B488B2891962000C905B1 /* PreferenceCenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B488A2891962000C905B1 /* PreferenceCenterView.swift */; };\n\t\t6E9B488D2891B43F00C905B1 /* LabeledSectionBreakView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B488C2891B43F00C905B1 /* LabeledSectionBreakView.swift */; };\n\t\t6E9B488F2891B57300C905B1 /* CommonSectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B488E2891B57300C905B1 /* CommonSectionView.swift */; };\n\t\t6E9B48912891B68C00C905B1 /* PreferenceCenterAlertView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B48902891B68B00C905B1 /* PreferenceCenterAlertView.swift */; };\n\t\t6E9B48932891B6A700C905B1 /* ChannelSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B48922891B6A700C905B1 /* ChannelSubscriptionView.swift */; };\n\t\t6E9B48952891B6B400C905B1 /* ContactSubscriptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B48942891B6B400C905B1 /* ContactSubscriptionView.swift */; };\n\t\t6E9B48972891B6BF00C905B1 /* ContactSubscriptionGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9B48962891B6BF00C905B1 /* ContactSubscriptionGroupView.swift */; };\n\t\t6E9C2B7D2D014438000089A9 /* APNSEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2B7C2D014426000089A9 /* APNSEnvironment.swift */; };\n\t\t6E9C2BD02D02321D000089A9 /* RuntimeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */; };\n\t\t6E9C2BD32D02683D000089A9 /* RuntimeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */; };\n\t\t6E9C2BD42D02683D000089A9 /* RuntimeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */; };\n\t\t6E9C2BD52D02683D000089A9 /* RuntimeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */; };\n\t\t6E9C2BD62D02683D000089A9 /* RuntimeConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */; };\n\t\t6E9C2BD72D0269AE000089A9 /* TestAirshipRequestSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */; };\n\t\t6E9C2BD82D0269AE000089A9 /* TestAirshipRequestSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */; };\n\t\t6E9C2BDA2D027B5F000089A9 /* AirshipAppCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BD92D027B5A000089A9 /* AirshipAppCredentials.swift */; };\n\t\t6E9C2BDC2D028034000089A9 /* APNSEnvironmentTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9C2BDB2D028030000089A9 /* APNSEnvironmentTest.swift */; };\n\t\t6E9D529826C195F7004EA16B /* ActionRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D529726C195F7004EA16B /* ActionRunner.swift */; };\n\t\t6E9D529B26C1A77C004EA16B /* ActionRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E9D529A26C1A77C004EA16B /* ActionRegistry.swift */; };\n\t\t6EA5202327D1364E003011CA /* AirshipDateFormatter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EA5202227D1364E003011CA /* AirshipDateFormatter.swift */; };\n\t\t6EAA61492D5297A7006602F7 /* SubjectExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAA61482D5297A2006602F7 /* SubjectExtension.swift */; };\n\t\t6EAC295A27580063006DFA63 /* ChannelRegistrarTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAC295927580063006DFA63 /* ChannelRegistrarTest.swift */; };\n\t\t6EAD3AF82F45305E00FF274E /* AirshipSwizzler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAD3AF72F45305B00FF274E /* AirshipSwizzler.swift */; };\n\t\t6EAD3AFA2F4530BE00FF274E /* AutoIntegration.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAD3AF92F4530AD00FF274E /* AutoIntegration.swift */; };\n\t\t6EAD3AFC2F4530F600FF274E /* UAAppIntegrationDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAD3AFB2F4530E400FF274E /* UAAppIntegrationDelegate.swift */; };\n\t\t6EAD7CE526B216DB00B88EA7 /* DeepLinkAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAD7CE426B216DB00B88EA7 /* DeepLinkAction.swift */; };\n\t\t6EB11C872697ACBF00DC698F /* ContactOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB11C862697ACBF00DC698F /* ContactOperation.swift */; };\n\t\t6EB11C892697AF5600DC698F /* TagGroupUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB11C882697AF5600DC698F /* TagGroupUpdate.swift */; };\n\t\t6EB11C8B2697AFC700DC698F /* AttributeUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB11C8A2697AFC700DC698F /* AttributeUpdate.swift */; };\n\t\t6EB11C8D2698C50F00DC698F /* AudienceUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB11C8C2698C50F00DC698F /* AudienceUtils.swift */; };\n\t\t6EB1B3F326EAA4D6000421B9 /* ChannelAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698E11267A98AB00654DB2 /* ChannelAPIClient.swift */; };\n\t\t6EB214D22E7DBA61001A5660 /* FeatureFlagManagerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB214D12E7DBA5E001A5660 /* FeatureFlagManagerProtocol.swift */; };\n\t\t6EB21A682E81BB6E001A5660 /* AirshipDebugAddEmailChannelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A4C2E81BB6E001A5660 /* AirshipDebugAddEmailChannelView.swift */; };\n\t\t6EB21A692E81BB6E001A5660 /* AirshipDebugChannelTagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A412E81BB6E001A5660 /* AirshipDebugChannelTagView.swift */; };\n\t\t6EB21A6A2E81BB6E001A5660 /* AirshipDebugAutomationsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A3E2E81BB6E001A5660 /* AirshipDebugAutomationsView.swift */; };\n\t\t6EB21A6B2E81BB6E001A5660 /* AirshipDebugFeatureFlagDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A532E81BB6E001A5660 /* AirshipDebugFeatureFlagDetailsView.swift */; };\n\t\t6EB21A6C2E81BB6E001A5660 /* AirshipDebugExperimentsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A3C2E81BB6E001A5660 /* AirshipDebugExperimentsView.swift */; };\n\t\t6EB21A6D2E81BB6E001A5660 /* AirshipDebugExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A452E81BB6E001A5660 /* AirshipDebugExtensions.swift */; };\n\t\t6EB21A6E2E81BB6E001A5660 /* AirshipDebugAddEventView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A342E81BB6E001A5660 /* AirshipDebugAddEventView.swift */; };\n\t\t6EB21A6F2E81BB6E001A5660 /* AirshipDebugPushDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A5D2E81BB6E001A5660 /* AirshipDebugPushDetailsView.swift */; };\n\t\t6EB21A702E81BB6E001A5660 /* AirshipDebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A662E81BB6E001A5660 /* AirshipDebugView.swift */; };\n\t\t6EB21A712E81BB6E001A5660 /* AirshipDebugAddSMSChannelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A4E2E81BB6E001A5660 /* AirshipDebugAddSMSChannelView.swift */; };\n\t\t6EB21A722E81BB6E001A5660 /* AirshipJSONDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A472E81BB6E001A5660 /* AirshipJSONDetailsView.swift */; };\n\t\t6EB21A732E81BB6E001A5660 /* AirshipDebugAnalyticIdentifierEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A352E81BB6E001A5660 /* AirshipDebugAnalyticIdentifierEditorView.swift */; };\n\t\t6EB21A742E81BB6E001A5660 /* AirshipDebugContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A642E81BB6E001A5660 /* AirshipDebugContentView.swift */; };\n\t\t6EB21A752E81BB6E001A5660 /* AirshipDebugRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A652E81BB6E001A5660 /* AirshipDebugRoute.swift */; };\n\t\t6EB21A772E81BB6E001A5660 /* AirshipDebugPreferencCenterItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A572E81BB6E001A5660 /* AirshipDebugPreferencCenterItemView.swift */; };\n\t\t6EB21A792E81BB6E001A5660 /* AirshipDebugAttributesEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A442E81BB6E001A5660 /* AirshipDebugAttributesEditorView.swift */; };\n\t\t6EB21A7A2E81BB6E001A5660 /* AirshipToast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A492E81BB6E001A5660 /* AirshipToast.swift */; };\n\t\t6EB21A7B2E81BB6E001A5660 /* AirshipDebugFeatureFlagView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A552E81BB6E001A5660 /* AirshipDebugFeatureFlagView.swift */; };\n\t\t6EB21A7C2E81BB6E001A5660 /* Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A4A2E81BB6E001A5660 /* Extensions.swift */; };\n\t\t6EB21A7D2E81BB6E001A5660 /* AirshipDebugTagGroupsEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A462E81BB6E001A5660 /* AirshipDebugTagGroupsEditorView.swift */; };\n\t\t6EB21A7E2E81BB6E001A5660 /* AirshipDebugAppInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A3A2E81BB6E001A5660 /* AirshipDebugAppInfoView.swift */; };\n\t\t6EB21A7F2E81BB6E001A5660 /* AirshipDebugEventDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A372E81BB6E001A5660 /* AirshipDebugEventDetailsView.swift */; };\n\t\t6EB21A802E81BB6E001A5660 /* AirshipDebugPrivacyManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A5B2E81BB6E001A5660 /* AirshipDebugPrivacyManagerView.swift */; };\n\t\t6EB21A812E81BB6E001A5660 /* AirshipDebugEventsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A382E81BB6E001A5660 /* AirshipDebugEventsView.swift */; };\n\t\t6EB21A822E81BB6E001A5660 /* AirshipDebugContactSubscriptionEditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A4F2E81BB6E001A5660 /* AirshipDebugContactSubscriptionEditorView.swift */; };\n\t\t6EB21A832E81BB6E001A5660 /* AirshipDebugPreferenceCenterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A582E81BB6E001A5660 /* AirshipDebugPreferenceCenterView.swift */; };\n\t\t6EB21A842E81BB6E001A5660 /* AirshipDebugAnalyticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A362E81BB6E001A5660 /* AirshipDebugAnalyticsView.swift */; };\n\t\t6EB21A852E81BB6E001A5660 /* AirshipDebugReceivedPushView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A5F2E81BB6E001A5660 /* AirshipDebugReceivedPushView.swift */; };\n\t\t6EB21A862E81BB6E001A5660 /* AirshipDebugPushView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A5E2E81BB6E001A5660 /* AirshipDebugPushView.swift */; };\n\t\t6EB21A872E81BB6E001A5660 /* AirshipDebugNamedUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A512E81BB6E001A5660 /* AirshipDebugNamedUserView.swift */; };\n\t\t6EB21A882E81BB6E001A5660 /* AirshipJSONView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A482E81BB6E001A5660 /* AirshipJSONView.swift */; };\n\t\t6EB21A892E81BB6E001A5660 /* AirshipDebugChannelSubscriptionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A402E81BB6E001A5660 /* AirshipDebugChannelSubscriptionsView.swift */; };\n\t\t6EB21A8A2E81BB6E001A5660 /* AirshipDebugInAppExperiencesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A3D2E81BB6E001A5660 /* AirshipDebugInAppExperiencesView.swift */; };\n\t\t6EB21A8B2E81BB6E001A5660 /* TVSlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A622E81BB6E001A5660 /* TVSlider.swift */; };\n\t\t6EB21A8C2E81BB6E001A5660 /* TVDatePicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A612E81BB6E001A5660 /* TVDatePicker.swift */; };\n\t\t6EB21A8D2E81BB6E001A5660 /* AirshipDebugChannelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A422E81BB6E001A5660 /* AirshipDebugChannelView.swift */; };\n\t\t6EB21A8E2E81BB6E001A5660 /* AirshipDebugContactView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A502E81BB6E001A5660 /* AirshipDebugContactView.swift */; };\n\t\t6EB21A8F2E81BB6E001A5660 /* AirshipDebugAddOpenChannelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A4D2E81BB6E001A5660 /* AirshipDebugAddOpenChannelView.swift */; };\n\t\t6EB21A912E81BFC1001A5660 /* AirshipDebugAddPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A902E81BFB9001A5660 /* AirshipDebugAddPropertyView.swift */; };\n\t\t6EB21A932E81C7AB001A5660 /* AirshipDebugAddStringPropertyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A922E81C7A2001A5660 /* AirshipDebugAddStringPropertyView.swift */; };\n\t\t6EB21AFC2E8216A4001A5660 /* AirshipoDebugTriggers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21AFB2E82169F001A5660 /* AirshipoDebugTriggers.swift */; };\n\t\t6EB21B5F2E82FE9F001A5660 /* AirshipDebugAudienceSubject.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21B5E2E82FE98001A5660 /* AirshipDebugAudienceSubject.swift */; };\n\t\t6EB3FCEF2ABCFA680018594E /* RemoteDataProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB3FCEE2ABCFA680018594E /* RemoteDataProtocol.swift */; };\n\t\t6EB5156E28A42B5800870C5A /* AirshipPreferenceCenterResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB5156D28A42B5800870C5A /* AirshipPreferenceCenterResources.swift */; };\n\t\t6EB5157128A4608C00870C5A /* PreferenceCenterViewControllerFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB5157028A4608C00870C5A /* PreferenceCenterViewControllerFactory.swift */; };\n\t\t6EB5158128A47BD700870C5A /* SubscriptionListEdit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB5158028A47BD700870C5A /* SubscriptionListEdit.swift */; };\n\t\t6EB5158328A47C7100870C5A /* ScopedSubscriptionListEdit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB5158228A47C7100870C5A /* ScopedSubscriptionListEdit.swift */; };\n\t\t6EB5159228A5B1B400870C5A /* TestLegacyTheme.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6EB5159128A5B1B400870C5A /* TestLegacyTheme.plist */; };\n\t\t6EB5159428A5B8E900870C5A /* TestTheme.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6EB5159328A5B8E900870C5A /* TestTheme.plist */; };\n\t\t6EB5159528A5B96300870C5A /* PreferenceThemeLoaderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB5158E28A5B15C00870C5A /* PreferenceThemeLoaderTest.swift */; };\n\t\t6EB5159728A5C54400870C5A /* TestThemeEmpty.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6EB5159628A5C54400870C5A /* TestThemeEmpty.plist */; };\n\t\t6EB5159928A5C61D00870C5A /* TestThemeInvalid.plist in Resources */ = {isa = PBXBuildFile; fileRef = 6EB5159828A5C61D00870C5A /* TestThemeInvalid.plist */; };\n\t\t6EB515A328A5F1C600870C5A /* PreferenceCenterStateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB515A228A5F1C600870C5A /* PreferenceCenterStateTest.swift */; };\n\t\t6EB839472BC83B9D006611C4 /* DefaultInAppActionRunnerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB839452BC83B96006611C4 /* DefaultInAppActionRunnerTest.swift */; };\n\t\t6EB839492BC8898E006611C4 /* AirshipAsyncChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB839482BC8898E006611C4 /* AirshipAsyncChannel.swift */; };\n\t\t6EB8394C2BC8AB51006611C4 /* TestWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63A567528F449D8004B8951 /* TestWorkManager.swift */; };\n\t\t6EB8394E2BC8B1F4006611C4 /* AirshipAsyncChannelTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB8394D2BC8B1F4006611C4 /* AirshipAsyncChannelTest.swift */; };\n\t\t6EBD12052DA73FDA00F678AB /* ValidatableHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBD12042DA73FD300F678AB /* ValidatableHelper.swift */; };\n\t\t6EBFA9AD2D15DA73002BA3E9 /* HashChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBFA9AC2D15DA70002BA3E9 /* HashChecker.swift */; };\n\t\t6EBFA9AF2D15E04D002BA3E9 /* AirshipDeviceAudienceResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBFA9AE2D15E04B002BA3E9 /* AirshipDeviceAudienceResult.swift */; };\n\t\t6EBFA9B12D15F499002BA3E9 /* HashCheckerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EBFA9B02D15F491002BA3E9 /* HashCheckerTest.swift */; };\n\t\t6EC0CA4F2B48987700333A87 /* AutomationRemoteDataAccessTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA4E2B48987700333A87 /* AutomationRemoteDataAccessTest.swift */; };\n\t\t6EC0CA502B4899CC00333A87 /* TestRemoteData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E15B70426CE07180099C92D /* TestRemoteData.swift */; };\n\t\t6EC0CA512B4899DB00333A87 /* TestNetworkMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E6ED171268448EC00A2CBD0 /* TestNetworkMonitor.swift */; };\n\t\t6EC0CA532B48A2C300333A87 /* AutomationAudience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA522B48A2C300333A87 /* AutomationAudience.swift */; };\n\t\t6EC0CA562B48B05600333A87 /* ActionAutomationExecutorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA542B48B05000333A87 /* ActionAutomationExecutorTest.swift */; };\n\t\t6EC0CA5C2B48C2F500333A87 /* AutomationPreparer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA5B2B48C2F500333A87 /* AutomationPreparer.swift */; };\n\t\t6EC0CA682B49287100333A87 /* AutomationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA672B49287100333A87 /* AutomationExecutor.swift */; };\n\t\t6EC0CA6B2B4B698000333A87 /* AutomationExecutorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA6A2B4B698000333A87 /* AutomationExecutorTest.swift */; };\n\t\t6EC0CA6D2B4B879800333A87 /* AutomationPreparerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA6C2B4B879800333A87 /* AutomationPreparerTest.swift */; };\n\t\t6EC0CA6F2B4B893500333A87 /* TestDeferredResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA6E2B4B893500333A87 /* TestDeferredResolver.swift */; };\n\t\t6EC0CA702B4B895A00333A87 /* TestDeferredResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA6E2B4B893500333A87 /* TestDeferredResolver.swift */; };\n\t\t6EC0CA722B4B897B00333A87 /* TestExperimentDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA712B4B897B00333A87 /* TestExperimentDataProvider.swift */; };\n\t\t6EC0CA732B4B897B00333A87 /* TestExperimentDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA712B4B897B00333A87 /* TestExperimentDataProvider.swift */; };\n\t\t6EC0CA762B4B8A3A00333A87 /* TestRemoteDataAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA752B4B8A3A00333A87 /* TestRemoteDataAccess.swift */; };\n\t\t6EC0CA782B4B8A4700333A87 /* TestFrequencyLimitsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA772B4B8A4700333A87 /* TestFrequencyLimitsManager.swift */; };\n\t\t6EC0CA792B4B8C2B00333A87 /* TestAudienceChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E2F5AB62A675ADC00CABD3D /* TestAudienceChecker.swift */; };\n\t\t6EC0CA812B4C812A00333A87 /* DisplayAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC0CA802B4C812A00333A87 /* DisplayAdapter.swift */; };\n\t\t6EC755992A4E115400851ABB /* DeviceAudienceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC755982A4E115400851ABB /* DeviceAudienceSelector.swift */; };\n\t\t6EC7559B2A4E129000851ABB /* DeviceTagSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7559A2A4E129000851ABB /* DeviceTagSelector.swift */; };\n\t\t6EC7559F2A4E5AB200851ABB /* DeviceAudienceChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7559E2A4E5AB200851ABB /* DeviceAudienceChecker.swift */; };\n\t\t6EC755AF2A4FCD8800851ABB /* AudienceHashSelectorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC755AE2A4FCD8800851ABB /* AudienceHashSelectorTest.swift */; };\n\t\t6EC7E46E269E2A4C0038CFDD /* AttributeEditorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E46D269E2A4C0038CFDD /* AttributeEditorTest.swift */; };\n\t\t6EC7E470269E33290038CFDD /* TagGroupsEditorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E46F269E33290038CFDD /* TagGroupsEditorTest.swift */; };\n\t\t6EC7E472269E51030038CFDD /* AttributeUpdateTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E471269E51030038CFDD /* AttributeUpdateTest.swift */; };\n\t\t6EC7E474269E52600038CFDD /* ContactOperationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E473269E52600038CFDD /* ContactOperationTest.swift */; };\n\t\t6EC7E47626A5EE910038CFDD /* AudienceUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E47526A5EE910038CFDD /* AudienceUtilsTest.swift */; };\n\t\t6EC7E47826A604080038CFDD /* AirshipContactTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E47726A604080038CFDD /* AirshipContactTest.swift */; };\n\t\t6EC7E48226A60C060038CFDD /* AirshipChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E48126A60C060038CFDD /* AirshipChannel.swift */; };\n\t\t6EC7E48526A60CDF0038CFDD /* TestChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E48426A60CDF0038CFDD /* TestChannel.swift */; };\n\t\t6EC7E48726A60DD60038CFDD /* TestContactAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E48626A60DD60038CFDD /* TestContactAPIClient.swift */; };\n\t\t6EC7E48D26A738C80038CFDD /* ContactConflictEvent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC7E48C26A738C70038CFDD /* ContactConflictEvent.swift */; };\n\t\t6EC815AF2F2BBFD500E1C0C6 /* BundleExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC815AE2F2BBFD100E1C0C6 /* BundleExtensions.swift */; };\n\t\t6EC81D032F2D445500E1C0C6 /* UAInboxDataMappingV1toV4.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 6EC81D022F2D445500E1C0C6 /* UAInboxDataMappingV1toV4.xcmappingmodel */; };\n\t\t6EC81D052F2D448B00E1C0C6 /* UAInboxDataMappingV2toV4.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 6EC81D042F2D448B00E1C0C6 /* UAInboxDataMappingV2toV4.xcmappingmodel */; };\n\t\t6EC81D082F2D44D700E1C0C6 /* UAInboxDataMappingV3toV4.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = 6EC81D072F2D44D700E1C0C6 /* UAInboxDataMappingV3toV4.xcmappingmodel */; };\n\t\t6EC824A02F33A4DD00E1C0C6 /* MessageDisplayHistory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC8249F2F33A4DD00E1C0C6 /* MessageDisplayHistory.swift */; };\n\t\t6EC824A22F33A5EC00E1C0C6 /* MessageCenterThemeLoaderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC824A12F33A5EC00E1C0C6 /* MessageCenterThemeLoaderTest.swift */; };\n\t\t6EC824A42F33A5F600E1C0C6 /* MessageCenterMessageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC824A32F33A5F600E1C0C6 /* MessageCenterMessageTest.swift */; };\n\t\t6EC9214E2D82144A000A3A59 /* ThomasFormField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC9214D2D82144A000A3A59 /* ThomasFormField.swift */; };\n\t\t6EC922E12D832BB8000A3A59 /* ThomasFormFieldProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC922E02D832BAF000A3A59 /* ThomasFormFieldProcessor.swift */; };\n\t\t6EC922E32D838DFF000A3A59 /* ThomasFormPayloadGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EC922E22D838DFA000A3A59 /* ThomasFormPayloadGenerator.swift */; };\n\t\t6ECB627C2A369F5B0095C85C /* OpenExternalURLActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECB627B2A369F5B0095C85C /* OpenExternalURLActionTest.swift */; };\n\t\t6ECB627E2A36A0770095C85C /* ExternalURLProcessor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECB627D2A36A0770095C85C /* ExternalURLProcessor.swift */; };\n\t\t6ECB62822A36A45F0095C85C /* TestURLOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECB62812A36A45F0095C85C /* TestURLOpener.swift */; };\n\t\t6ECB62842A36A7510095C85C /* DeepLinkActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECB62832A36A7510095C85C /* DeepLinkActionTest.swift */; };\n\t\t6ECB62862A36C1EE0095C85C /* NativeBridgeActionHandlerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECB62852A36C1EE0095C85C /* NativeBridgeActionHandlerTest.swift */; };\n\t\t6ECD4F6D2DD7A7060060EE72 /* RadioInputToggleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECD4F6C2DD7A7060060EE72 /* RadioInputToggleLayout.swift */; };\n\t\t6ECD4F6F2DD7A7090060EE72 /* CheckboxToggleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECD4F6E2DD7A7090060EE72 /* CheckboxToggleLayout.swift */; };\n\t\t6ECD4F712DD7A7CD0060EE72 /* ToggleLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECD4F702DD7A7C90060EE72 /* ToggleLayout.swift */; };\n\t\t6ECDDE6C29B7EEE9009D79DB /* AuthToken.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECDDE6B29B7EEE9009D79DB /* AuthToken.swift */; };\n\t\t6ECDDE7429B80462009D79DB /* ChannelAuthTokenProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECDDE7329B80462009D79DB /* ChannelAuthTokenProvider.swift */; };\n\t\t6ECDDE7929B804FB009D79DB /* ChannelAuthTokenAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ECDDE7829B804FB009D79DB /* ChannelAuthTokenAPIClient.swift */; };\n\t\t6ED040EB278B5D7C00FCF773 /* ThomasFormPayloadGeneratorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED040EA278B5D7C00FCF773 /* ThomasFormPayloadGeneratorTest.swift */; };\n\t\t6ED2F5252B7EE648000AFC80 /* AirshipBase64Test.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F5242B7EE648000AFC80 /* AirshipBase64Test.swift */; };\n\t\t6ED2F5272B7EE82B000AFC80 /* AirshipJSONUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F5262B7EE82B000AFC80 /* AirshipJSONUtilsTest.swift */; };\n\t\t6ED2F5292B7FC59F000AFC80 /* AirshipColorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F5282B7FC59F000AFC80 /* AirshipColorTests.swift */; };\n\t\t6ED2F52B2B7FC5C8000AFC80 /* AirshipIvyVersionMatcherTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F52A2B7FC5C8000AFC80 /* AirshipIvyVersionMatcherTest.swift */; };\n\t\t6ED2F52D2B7FD403000AFC80 /* JavaScriptCommandTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F52C2B7FD403000AFC80 /* JavaScriptCommandTest.swift */; };\n\t\t6ED2F52F2B7FD49B000AFC80 /* AirshipURLAllowListTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F52E2B7FD49B000AFC80 /* AirshipURLAllowListTest.swift */; };\n\t\t6ED2F5312B7FF819000AFC80 /* AirshipViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F5302B7FF819000AFC80 /* AirshipViewUtils.swift */; };\n\t\t6ED2F5382B7FFCEB000AFC80 /* AirshipDateFormatterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED2F5342B7FFCD7000AFC80 /* AirshipDateFormatterTest.swift */; };\n\t\t6ED2F5392B7FFF68000AFC80 /* AirshipSceneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99D1B3272B44F08900447840 /* AirshipSceneManager.swift */; };\n\t\t6ED562A02EA9434B00C20B55 /* StackImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED5629F2EA9434900C20B55 /* StackImageButton.swift */; };\n\t\t6ED6ECA426ADCA6F00973364 /* BlockAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED6ECA326ADCA6F00973364 /* BlockAction.swift */; };\n\t\t6ED6ECA726AE05B700973364 /* EmptyAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED6ECA626AE05B700973364 /* EmptyAction.swift */; };\n\t\t6ED735DA26C73DC5003B0A7D /* DefaultAirshipChannel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED735D926C73DC5003B0A7D /* DefaultAirshipChannel.swift */; };\n\t\t6ED735DD26C7401D003B0A7D /* TagEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED735DC26C7401D003B0A7D /* TagEditor.swift */; };\n\t\t6ED735E026C74321003B0A7D /* TagEditorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED735DF26C74321003B0A7D /* TagEditorTest.swift */; };\n\t\t6ED735E226CAE2D7003B0A7D /* TestChannelRegistrar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED735E126CAE2D7003B0A7D /* TestChannelRegistrar.swift */; };\n\t\t6ED735E426CAE8AA003B0A7D /* TestChannelAudienceManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED735E326CAE8AA003B0A7D /* TestChannelAudienceManager.swift */; };\n\t\t6ED735E626CAEABC003B0A7D /* TestLocaleManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED735E526CAEABC003B0A7D /* TestLocaleManager.swift */; };\n\t\t6ED7BE602D13D9E300B6A124 /* TestCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED7BE5F2D13D9E300B6A124 /* TestCache.swift */; };\n\t\t6ED7BE612D13D9E300B6A124 /* TestCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED7BE5F2D13D9E300B6A124 /* TestCache.swift */; };\n\t\t6ED7BE632D13D9FE00B6A124 /* FeatureFlagResultCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED7BE622D13D9FE00B6A124 /* FeatureFlagResultCache.swift */; };\n\t\t6ED7BE652D13DA0400B6A124 /* FeatureFlagResultCacheTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED7BE642D13DA0400B6A124 /* FeatureFlagResultCacheTest.swift */; };\n\t\t6ED80793273CA0C800D1F455 /* EnvironmentValues.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED80792273CA0C800D1F455 /* EnvironmentValues.swift */; };\n\t\t6ED8079A273DA56000D1F455 /* ThomasViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED80799273DA56000D1F455 /* ThomasViewController.swift */; };\n\t\t6ED838AB2D0CE9D6009CBB0C /* CompoundDeviceAudienceSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED838AA2D0CE9D6009CBB0C /* CompoundDeviceAudienceSelector.swift */; };\n\t\t6ED838AD2D0CEF4A009CBB0C /* CompoundDeviceAudienceSelectorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED838AC2D0CEF4A009CBB0C /* CompoundDeviceAudienceSelectorTest.swift */; };\n\t\t6ED838D02D0D118B009CBB0C /* AutomationCompoundAudience.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6ED838CF2D0D118B009CBB0C /* AutomationCompoundAudience.swift */; };\n\t\t6EDAFB262CB463C5000BD4AA /* ButtonLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDAFB252CB463C1000BD4AA /* ButtonLayout.swift */; };\n\t\t6EDE293F2A9802BF00235738 /* NativeBridgeActionRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE293E2A9802BF00235738 /* NativeBridgeActionRunner.swift */; };\n\t\t6EDE5F192B9BD7E700E33D04 /* InboxMessageData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE5F182B9BD7E700E33D04 /* InboxMessageData.swift */; };\n\t\t6EDE5F4F2BA248FF00E33D04 /* TouchViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE5F4E2BA248FF00E33D04 /* TouchViewModifier.swift */; };\n\t\t6EDE5FC22BADDD96003ADF55 /* PreparedScheduleInfoTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDE5FC12BADDD96003ADF55 /* PreparedScheduleInfoTest.swift */; };\n\t\t6EDF1D932B292FB100E23BC4 /* InAppMessageTextInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1D922B292FB000E23BC4 /* InAppMessageTextInfo.swift */; };\n\t\t6EDF1D962B2A25B400E23BC4 /* InAppMessageButtonInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1D952B2A25B400E23BC4 /* InAppMessageButtonInfo.swift */; };\n\t\t6EDF1D982B2A25C800E23BC4 /* InAppMessageMediaInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1D972B2A25C800E23BC4 /* InAppMessageMediaInfo.swift */; };\n\t\t6EDF1D9C2B2A287A00E23BC4 /* InAppMessageButtonLayoutType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1D9B2B2A287A00E23BC4 /* InAppMessageButtonLayoutType.swift */; };\n\t\t6EDF1D9E2B2A2A5900E23BC4 /* InAppMessageDisplayContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1D9D2B2A2A5900E23BC4 /* InAppMessageDisplayContent.swift */; };\n\t\t6EDF1DA42B2A2C6F00E23BC4 /* InAppMessageColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1DA32B2A2C6F00E23BC4 /* InAppMessageColor.swift */; };\n\t\t6EDF1DA62B2A300100E23BC4 /* InAppMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1DA52B2A300100E23BC4 /* InAppMessage.swift */; };\n\t\t6EDF1DAD2B2A73FC00E23BC4 /* InAppMessageTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1DAA2B2A6D9900E23BC4 /* InAppMessageTest.swift */; };\n\t\t6EDF1DB82B2BB2B800E23BC4 /* RetryingQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDF1DB72B2BB2B800E23BC4 /* RetryingQueue.swift */; };\n\t\t6EDFBBC32F5780BC0043D9EF /* BasementImport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDFBBC22F5780BA0043D9EF /* BasementImport.swift */; };\n\t\t6EDFBBC42F5780EA0043D9EF /* AirshipLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF1933B2838062B005F192A /* AirshipLogHandler.swift */; };\n\t\t6EDFBBC52F5780EA0043D9EF /* AirshipLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E698DEA26790AC300654DB2 /* AirshipLogger.swift */; };\n\t\t6EDFBBC62F5780EA0043D9EF /* AirshipLogPrivacyLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E524CC22C180A39002CA094 /* AirshipLogPrivacyLevel.swift */; };\n\t\t6EDFBBC72F5780EA0043D9EF /* DefaultLogHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF1933D28380644005F192A /* DefaultLogHandler.swift */; };\n\t\t6EDFBBC82F5780EA0043D9EF /* LogLevel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E87BDFE26E283840005D20D /* LogLevel.swift */; };\n\t\t6EE49BDD2A09AD3600AB1CF4 /* AirshipNotificationStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49BDC2A09AD3600AB1CF4 /* AirshipNotificationStatus.swift */; };\n\t\t6EE49BE12A0AADC900AB1CF4 /* AppRemoteDataProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49BE02A0AADC900AB1CF4 /* AppRemoteDataProviderDelegate.swift */; };\n\t\t6EE49C082A0BE9F600AB1CF4 /* RemoteDataURLFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C072A0BE9F600AB1CF4 /* RemoteDataURLFactory.swift */; };\n\t\t6EE49C0C2A0C141800AB1CF4 /* RemoteDataSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C0B2A0C141800AB1CF4 /* RemoteDataSource.swift */; };\n\t\t6EE49C102A0C142F00AB1CF4 /* RemoteDataInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C0F2A0C142F00AB1CF4 /* RemoteDataInfo.swift */; };\n\t\t6EE49C182A0C3CC600AB1CF4 /* ContactRemoteDataProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C172A0C3CC600AB1CF4 /* ContactRemoteDataProviderDelegate.swift */; };\n\t\t6EE49C1D2A0D9D8000AB1CF4 /* RemoteDataProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C1C2A0D9D8000AB1CF4 /* RemoteDataProvider.swift */; };\n\t\t6EE49C222A13E32B00AB1CF4 /* RemoteDataProviderProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C212A13E32B00AB1CF4 /* RemoteDataProviderProtocol.swift */; };\n\t\t6EE49C262A1446B100AB1CF4 /* RemoteDataTestUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE49C252A1446B100AB1CF4 /* RemoteDataTestUtils.swift */; };\n\t\t6EE6AA132B4F3009002FEA75 /* InAppMessageAutomationPreparerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA112B4F3003002FEA75 /* InAppMessageAutomationPreparerTest.swift */; };\n\t\t6EE6AA162B4F302D002FEA75 /* InAppMessageAutomationExecutorTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA142B4F302A002FEA75 /* InAppMessageAutomationExecutorTest.swift */; };\n\t\t6EE6AA1B2B4F3062002FEA75 /* DisplayCoordinatorManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA1A2B4F3062002FEA75 /* DisplayCoordinatorManagerTest.swift */; };\n\t\t6EE6AA1C2B4F3066002FEA75 /* DisplayAdapterFactoryTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA182B4F304B002FEA75 /* DisplayAdapterFactoryTest.swift */; };\n\t\t6EE6AA1E2B4F31B1002FEA75 /* TestCachedAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA1D2B4F31B1002FEA75 /* TestCachedAssets.swift */; };\n\t\t6EE6AA202B4F5246002FEA75 /* InAppMessageSceneManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA1F2B4F5246002FEA75 /* InAppMessageSceneManager.swift */; };\n\t\t6EE6AA282B50C91E002FEA75 /* AutomationRemoteDataSubscriberTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA272B50C91E002FEA75 /* AutomationRemoteDataSubscriberTest.swift */; };\n\t\t6EE6AA2A2B50C976002FEA75 /* TestAutomationEngine.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA292B50C976002FEA75 /* TestAutomationEngine.swift */; };\n\t\t6EE6AA2C2B51DB1E002FEA75 /* AutomationSourceInfoStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA2B2B51DB1E002FEA75 /* AutomationSourceInfoStore.swift */; };\n\t\t6EE6AA382B572897002FEA75 /* AutomationSourceInfoStoreTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AA372B572897002FEA75 /* AutomationSourceInfoStoreTest.swift */; };\n\t\t6EE6AAFF2B58AB66002FEA75 /* LegacyInAppAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AAFE2B58AB66002FEA75 /* LegacyInAppAnalytics.swift */; };\n\t\t6EE6AB022B58B6E9002FEA75 /* LegacyInAppAnalyticsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AB002B58B5AE002FEA75 /* LegacyInAppAnalyticsTest.swift */; };\n\t\t6EE6AB062B59C231002FEA75 /* AirshipLayoutDisplayAdapterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AB052B59C231002FEA75 /* AirshipLayoutDisplayAdapterTest.swift */; };\n\t\t6EE6AB092B59C236002FEA75 /* CustomDisplayAdapterWrapperTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE6AB032B59C21A002FEA75 /* CustomDisplayAdapterWrapperTest.swift */; };\n\t\t6EE7725B238F179300E79944 /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 494DD9571B0EB677009C134E /* AirshipCore.framework */; settings = {ATTRIBUTES = (Required, ); }; };\n\t\t6EED67652CDEE7900087CDCB /* ThomasViewInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67632CDEE75D0087CDCB /* ThomasViewInfo.swift */; };\n\t\t6EED676E2CDEE7EB0087CDCB /* ThomasPresentationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED676C2CDEE7E50087CDCB /* ThomasPresentationInfo.swift */; };\n\t\t6EED67722CDEE8390087CDCB /* ThomasOrientation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67702CDEE8370087CDCB /* ThomasOrientation.swift */; };\n\t\t6EED67752CDEE8460087CDCB /* ThomasWindowSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67742CDEE8460087CDCB /* ThomasWindowSize.swift */; };\n\t\t6EED677A2CDEE87D0087CDCB /* ThomasShadow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67782CDEE8790087CDCB /* ThomasShadow.swift */; };\n\t\t6EED677D2CDEE9040087CDCB /* ThomasSerializable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED677C2CDEE8FE0087CDCB /* ThomasSerializable.swift */; };\n\t\t6EED67972CDEEA2E0087CDCB /* ThomasShapeInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67952CDEEA2B0087CDCB /* ThomasShapeInfo.swift */; };\n\t\t6EED679C2CDEEA380087CDCB /* ThomasToggleStyleInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67992CDEEA380087CDCB /* ThomasToggleStyleInfo.swift */; };\n\t\t6EED67A02CDEEAAB0087CDCB /* AirshipLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED679D2CDEEAA90087CDCB /* AirshipLayout.swift */; };\n\t\t6EED67A22CE1A47C0087CDCB /* ThomasColor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67A12CE1A4780087CDCB /* ThomasColor.swift */; };\n\t\t6EED67A72CE1A5000087CDCB /* ThomasBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67A52CE1A4FC0087CDCB /* ThomasBorder.swift */; };\n\t\t6EED67AB2CE1A5D00087CDCB /* ThomasMarkdownOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67A92CE1A5CC0087CDCB /* ThomasMarkdownOptions.swift */; };\n\t\t6EED67AE2CE1A6B90087CDCB /* ThomasPosition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67AD2CE1A6B70087CDCB /* ThomasPosition.swift */; };\n\t\t6EED67B32CE1A8330087CDCB /* ThomasEventHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67B12CE1A8300087CDCB /* ThomasEventHandler.swift */; };\n\t\t6EED67B62CE1B4420087CDCB /* ThomasTextAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67B52CE1B43C0087CDCB /* ThomasTextAppearance.swift */; };\n\t\t6EED67BA2CE1B4E90087CDCB /* ThomasPlatform.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67B92CE1B4E60087CDCB /* ThomasPlatform.swift */; };\n\t\t6EED67C02CE1B5160087CDCB /* ThomasActionsPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67BD2CE1B5120087CDCB /* ThomasActionsPayload.swift */; };\n\t\t6EED67C32CE1B5890087CDCB /* ThomasIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67C12CE1B5850087CDCB /* ThomasIcon.swift */; };\n\t\t6EED67C82CE1B6020087CDCB /* ThomasMediaFit.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67C52CE1B5FF0087CDCB /* ThomasMediaFit.swift */; };\n\t\t6EED67E32CE268680087CDCB /* ThomasButtonTapEffect.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67E22CE268630087CDCB /* ThomasButtonTapEffect.swift */; };\n\t\t6EED67E82CE268BF0087CDCB /* ThomasButtonClickBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67E62CE268BB0087CDCB /* ThomasButtonClickBehavior.swift */; };\n\t\t6EED67EB2CE269970087CDCB /* ThomasDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67EA2CE269930087CDCB /* ThomasDirection.swift */; };\n\t\t6EED67F02CE26CA40087CDCB /* ThomasSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67EF2CE26CA10087CDCB /* ThomasSize.swift */; };\n\t\t6EED67F62CE26CB80087CDCB /* ThomasConstrainedSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67F32CE26CB50087CDCB /* ThomasConstrainedSize.swift */; };\n\t\t6EED67F82CE26CE40087CDCB /* ThomasSizeConstraint.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67F72CE26CE20087CDCB /* ThomasSizeConstraint.swift */; };\n\t\t6EED67FC2CE26DB60087CDCB /* ThomasAttributeName.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67FB2CE26DAF0087CDCB /* ThomasAttributeName.swift */; };\n\t\t6EED68012CE26DCD0087CDCB /* ThomasAttributeValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED67FF2CE26DCA0087CDCB /* ThomasAttributeValue.swift */; };\n\t\t6EED68042CE26E1A0087CDCB /* ThomasMargin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68032CE26E180087CDCB /* ThomasMargin.swift */; };\n\t\t6EED680A2CE26E550087CDCB /* ThomasAutomatedAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68072CE26E510087CDCB /* ThomasAutomatedAction.swift */; };\n\t\t6EED680D2CE2707F0087CDCB /* ThomasStateAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED680C2CE2707E0087CDCB /* ThomasStateAction.swift */; };\n\t\t6EED68122CE271E50087CDCB /* ThomasEnableBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68102CE271E10087CDCB /* ThomasEnableBehavior.swift */; };\n\t\t6EED68162CE271F80087CDCB /* ThomasFormSubmitBehavior.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68142CE271F30087CDCB /* ThomasFormSubmitBehavior.swift */; };\n\t\t6EED68192CE272790087CDCB /* ThomasAutomatedAccessibilityAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68182CE272710087CDCB /* ThomasAutomatedAccessibilityAction.swift */; };\n\t\t6EED681D2CE274300087CDCB /* ThomasAccessibilityAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED681C2CE274290087CDCB /* ThomasAccessibilityAction.swift */; };\n\t\t6EED68222CE2806D0087CDCB /* ThomasAccessibleInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68202CE2806B0087CDCB /* ThomasAccessibleInfo.swift */; };\n\t\t6EED68292CE28C9A0087CDCB /* ThomasVisibilityInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68282CE28C960087CDCB /* ThomasVisibilityInfo.swift */; };\n\t\t6EED682D2CE28CC10087CDCB /* ThomasValidationInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED682C2CE28CBF0087CDCB /* ThomasValidationInfo.swift */; };\n\t\t6EED68332CE28FAC0087CDCB /* ThomasConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68302CE28FA70087CDCB /* ThomasConstants.swift */; };\n\t\t6EED68E72CE3ECCB0087CDCB /* ThomasPropertyOverride.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EED68E42CE3ECC50087CDCB /* ThomasPropertyOverride.swift */; };\n\t\t6EEE8BA2290B3EDE00230528 /* AirshipKeychainAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EEE8BA1290B3EDE00230528 /* AirshipKeychainAccess.swift */; };\n\t\t6EF02DF02714EB500008B6C9 /* Thomas.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF02DEF2714EB500008B6C9 /* Thomas.swift */; };\n\t\t6EF13FFD2A16F390009A125D /* AirshipBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3299EF212949EC3E00251E70 /* AirshipBaseTest.swift */; };\n\t\t6EF13FFE2A16F391009A125D /* AirshipBaseTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3299EF212949EC3E00251E70 /* AirshipBaseTest.swift */; };\n\t\t6EF1401B2A2671ED009A125D /* AirshipDeviceID.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF1401A2A2671ED009A125D /* AirshipDeviceID.swift */; };\n\t\t6EF1401F2A268CE6009A125D /* TestKeychainAccess.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF1401E2A268CE6009A125D /* TestKeychainAccess.swift */; };\n\t\t6EF140212A269074009A125D /* AirshipDeviceIDTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF140202A269074009A125D /* AirshipDeviceIDTest.swift */; };\n\t\t6EF1E9282CD005E4005EAA07 /* PagerUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF1E9252CD005E2005EAA07 /* PagerUtils.swift */; };\n\t\t6EF1E92A2CD0069B005EAA07 /* PagerSwipeDirection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF1E9292CD00698005EAA07 /* PagerSwipeDirection.swift */; };\n\t\t6EF27DD927306C9100548DA3 /* AirshipToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF27DD827306C9100548DA3 /* AirshipToggle.swift */; };\n\t\t6EF27DE32730E6F900548DA3 /* RadioInputController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF27DE22730E6F900548DA3 /* RadioInputController.swift */; };\n\t\t6EF27DE62730E77300548DA3 /* RadioInputState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF27DE52730E77300548DA3 /* RadioInputState.swift */; };\n\t\t6EF27DE92730E85700548DA3 /* RadioInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF27DE82730E85700548DA3 /* RadioInput.swift */; };\n\t\t6EF553E32B7EE40B00901A22 /* AirshipLocalizationUtilsTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF553E22B7EE40B00901A22 /* AirshipLocalizationUtilsTest.swift */; };\n\t\t6EF66D8D276461DA00ABCB76 /* UrlInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF66D8C276461DA00ABCB76 /* UrlInfo.swift */; };\n\t\t6EF66D912769B69C00ABCB76 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EF66D902769B69C00ABCB76 /* RootView.swift */; };\n\t\t6EFAFB78295525C3008AD187 /* ChannelAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB77295525C3008AD187 /* ChannelAPIClientTest.swift */; };\n\t\t6EFAFB7A295525CD008AD187 /* ChannelCaptureTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB79295525CD008AD187 /* ChannelCaptureTest.swift */; };\n\t\t6EFAFB7C295525DF008AD187 /* ChannelRegistrationPayloadTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB7B295525DF008AD187 /* ChannelRegistrationPayloadTest.swift */; };\n\t\t6EFAFB8229555174008AD187 /* FetchDeviceInfoActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB8129555174008AD187 /* FetchDeviceInfoActionTest.swift */; };\n\t\t6EFAFB8429561F23008AD187 /* ModifyAttributesActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB8329561F23008AD187 /* ModifyAttributesActionTest.swift */; };\n\t\t6EFAFB8A29562474008AD187 /* AddTagsActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB8929562474008AD187 /* AddTagsActionTest.swift */; };\n\t\t6EFAFB8C29562866008AD187 /* RemoveTagsActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFAFB8B29562866008AD187 /* RemoveTagsActionTest.swift */; };\n\t\t6EFB7B342A14A0F400133115 /* RemoteDataProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFB7B322A14A0EC00133115 /* RemoteDataProviderTest.swift */; };\n\t\t6EFD6D4B27272333005B26F1 /* EmptyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D4A27272333005B26F1 /* EmptyView.swift */; };\n\t\t6EFD6D5C27273257005B26F1 /* Shapes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D5B27273257005B26F1 /* Shapes.swift */; };\n\t\t6EFD6D6E27290C0B005B26F1 /* FormController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D6D27290C0B005B26F1 /* FormController.swift */; };\n\t\t6EFD6D7127290C16005B26F1 /* TextInput.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D7027290C16005B26F1 /* TextInput.swift */; };\n\t\t6EFD6D82272A53AE005B26F1 /* PagerState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D81272A53AE005B26F1 /* PagerState.swift */; };\n\t\t6EFD6D8B272A53FB005B26F1 /* PagerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D86272A53FB005B26F1 /* PagerController.swift */; };\n\t\t6EFE7E3F2A97ED660064AC31 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 6EFE7E3E2A97ED600064AC31 /* PrivacyInfo.xcprivacy */; };\n\t\t8401769426C5671100373AF7 /* JSONMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401769326C5671100373AF7 /* JSONMatcher.swift */; };\n\t\t8401769826C5722400373AF7 /* JSONPredicate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401769726C5722400373AF7 /* JSONPredicate.swift */; };\n\t\t8401769A26C5725800373AF7 /* AirshipJSONUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401769926C5725800373AF7 /* AirshipJSONUtils.swift */; };\n\t\t8401769C26C5729E00373AF7 /* JSONValueMatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8401769B26C5729E00373AF7 /* JSONValueMatcher.swift */; };\n\t\t841E7D12268617C800EA0317 /* PreferenceCenterResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 841E7D11268617C800EA0317 /* PreferenceCenterResponse.swift */; };\n\t\t84483A68267CF0C000D0DA7D /* PreferenceCenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84483A67267CF0C000D0DA7D /* PreferenceCenter.swift */; };\n\t\t847B000B267CD85E007CD249 /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 494DD9571B0EB677009C134E /* AirshipCore.framework */; };\n\t\t847B0013267CE558007CD249 /* PreferenceCenterSDKModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = 847B0012267CE558007CD249 /* PreferenceCenterSDKModule.swift */; };\n\t\t847BFFFD267CD73A007CD249 /* AirshipPreferenceCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 847BFFF4267CD739007CD249 /* AirshipPreferenceCenter.framework */; };\n\t\t9908E60E2B000DBA00DB3E2E /* CustomView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9908E60D2B000DBA00DB3E2E /* CustomView.swift */; };\n\t\t9908E6122B0189F800DB3E2E /* ArishipCustomViewManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9908E6112B0189F800DB3E2E /* ArishipCustomViewManager.swift */; };\n\t\t990A09592B5C677C00244D90 /* InAppMessageWebView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990A09582B5C677C00244D90 /* InAppMessageWebView.swift */; };\n\t\t990A09942B5CA5B700244D90 /* InAppMessageExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990A09932B5CA5B700244D90 /* InAppMessageExtensions.swift */; };\n\t\t990A09AF2B5DBD0400244D90 /* InAppMessageViewUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990A09AE2B5DBD0400244D90 /* InAppMessageViewUtils.swift */; };\n\t\t990EB3B12BF59A1500315EAC /* ContactChannelsProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 990EB3B02BF59A1500315EAC /* ContactChannelsProvider.swift */; };\n\t\t99104DF32BA6689A0040C0FD /* PreferenceCloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99104DF22BA6689A0040C0FD /* PreferenceCloseButton.swift */; };\n\t\t99303B062BD97F89002174CA /* ChannelListViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99303B052BD97F89002174CA /* ChannelListViewCell.swift */; };\n\t\t993AFDFE2C1B2D9A00AA875B /* PreferenceCenterConfig+ContactManagement.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993AFDFD2C1B2D9A00AA875B /* PreferenceCenterConfig+ContactManagement.swift */; };\n\t\t993F91FB2CA37874001B1C2E /* FooterView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 993F91FA2CA37874001B1C2E /* FooterView.swift */; };\n\t\t99560C1E2BAE2FFA00F28BDC /* ChannelTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99560C1D2BAE2FFA00F28BDC /* ChannelTextField.swift */; };\n\t\t99560C282BB3843600F28BDC /* PreferenceCenterUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99560C272BB3843600F28BDC /* PreferenceCenterUtils.swift */; };\n\t\t99560C2B2BB384A700F28BDC /* BackgroundShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99560C2A2BB384A700F28BDC /* BackgroundShape.swift */; };\n\t\t99560C2D2BB3855800F28BDC /* EmptySectionLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99560C2C2BB3855800F28BDC /* EmptySectionLabel.swift */; };\n\t\t99560C372BB38A5F00F28BDC /* ErrorLabel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99560C362BB38A5F00F28BDC /* ErrorLabel.swift */; };\n\t\t9971A8852C125C0200092ED1 /* ContactChannelsProviderTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9971A8842C125C0200092ED1 /* ContactChannelsProviderTest.swift */; };\n\t\t998572BF2B3CF95D0091E9C9 /* DefaultAssetDownloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 998572BE2B3CF95D0091E9C9 /* DefaultAssetDownloader.swift */; };\n\t\t998572C12B3CF97B0091E9C9 /* DefaultAssetFileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 998572C02B3CF97B0091E9C9 /* DefaultAssetFileManager.swift */; };\n\t\t999DC85E2B5B721D0048C6AF /* HTMLView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 999DC85D2B5B721D0048C6AF /* HTMLView.swift */; };\n\t\t99C3CC792BCF3E5B00B5BED5 /* SMSValidatorAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C3CC772BCF3DF700B5BED5 /* SMSValidatorAPIClientTest.swift */; };\n\t\t99C3CC7E2BCF40B200B5BED5 /* CachingSMSValidatorAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99C3CC7C2BCF401B00B5BED5 /* CachingSMSValidatorAPIClientTest.swift */; };\n\t\t99CC0D952BC87868001D93D0 /* AddChannelPromptViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99CC0D942BC87868001D93D0 /* AddChannelPromptViewModel.swift */; };\n\t\t99CF46182B3217C300B6FD9B /* AirshipCachedAssets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99CF46172B3217C300B6FD9B /* AirshipCachedAssets.swift */; };\n\t\t99CF461A2B3217DE00B6FD9B /* AssetCacheManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99CF46192B3217DE00B6FD9B /* AssetCacheManager.swift */; };\n\t\t99E0BD0D2B4DD4AB00465B37 /* FullscreenView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E0BD0C2B4DD4AB00465B37 /* FullscreenView.swift */; };\n\t\t99E0BD0F2B4DD71A00465B37 /* InAppMessageHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E0BD0E2B4DD71A00465B37 /* InAppMessageHostingController.swift */; };\n\t\t99E433932C9A0362006436B9 /* PagerIndicator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D85272A53FA005B26F1 /* PagerIndicator.swift */; };\n\t\t99E433942C9A03D9006436B9 /* Pager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EFD6D84272A53FA005B26F1 /* Pager.swift */; };\n\t\t99E433982C9A044C006436B9 /* AirshipResources.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E433972C9A044C006436B9 /* AirshipResources.swift */; };\n\t\t99E6EF6A2B8E36BA0006326A /* InAppMessageValidation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E6EF692B8E36BA0006326A /* InAppMessageValidation.swift */; };\n\t\t99E6EF6D2B8E3C250006326A /* InAppMessageContentValidationTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E6EF6B2B8E3AF60006326A /* InAppMessageContentValidationTest.swift */; };\n\t\t99E8D7972B4F17260099B6F3 /* CloseButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7962B4F17260099B6F3 /* CloseButton.swift */; };\n\t\t99E8D7992B4F19BA0099B6F3 /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7982B4F19BA0099B6F3 /* TextView.swift */; };\n\t\t99E8D79B2B4F2FCE0099B6F3 /* InAppMessageTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D79A2B4F2FCE0099B6F3 /* InAppMessageTheme.swift */; };\n\t\t99E8D7BB2B50A7C20099B6F3 /* InAppMessageViewDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7BA2B50A7C20099B6F3 /* InAppMessageViewDelegate.swift */; };\n\t\t99E8D7BD2B50AA060099B6F3 /* InAppMessageEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7BC2B50AA060099B6F3 /* InAppMessageEnvironment.swift */; };\n\t\t99E8D7BF2B50C2C10099B6F3 /* ButtonGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7BE2B50C2C10099B6F3 /* ButtonGroup.swift */; };\n\t\t99E8D7C12B50E5F40099B6F3 /* InAppMessageRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7C02B50E5F40099B6F3 /* InAppMessageRootView.swift */; };\n\t\t99E8D7C52B5192D40099B6F3 /* MediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7C42B5192D40099B6F3 /* MediaView.swift */; };\n\t\t99E8D7C92B54A5CB0099B6F3 /* InAppMessageThemeModal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7C82B54A5CB0099B6F3 /* InAppMessageThemeModal.swift */; };\n\t\t99E8D7CB2B54A6340099B6F3 /* InAppMessageThemeFullscreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7CA2B54A6340099B6F3 /* InAppMessageThemeFullscreen.swift */; };\n\t\t99E8D7CE2B54A66E0099B6F3 /* InAppMessageThemeBanner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7CD2B54A66E0099B6F3 /* InAppMessageThemeBanner.swift */; };\n\t\t99E8D7D02B54A68F0099B6F3 /* InAppMessageThemeHTML.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7CF2B54A68F0099B6F3 /* InAppMessageThemeHTML.swift */; };\n\t\t99E8D7D52B55B0300099B6F3 /* InAppMessageThemeAdditionalPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7D42B55B0300099B6F3 /* InAppMessageThemeAdditionalPadding.swift */; };\n\t\t99E8D7D82B55B0440099B6F3 /* InAppMessageThemeButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7D72B55B0440099B6F3 /* InAppMessageThemeButton.swift */; };\n\t\t99E8D7DA2B55B05D0099B6F3 /* InAppMessageThemeText.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7D92B55B05D0099B6F3 /* InAppMessageThemeText.swift */; };\n\t\t99E8D7DC2B55C4C20099B6F3 /* InAppMessageThemeMedia.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7DB2B55C4C20099B6F3 /* InAppMessageThemeMedia.swift */; };\n\t\t99E8D7DE2B55C73B0099B6F3 /* ThemeExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99E8D7DD2B55C73B0099B6F3 /* ThemeExtensions.swift */; };\n\t\t99F4FE5B2BC36A6700754F0F /* PreferenceCenterContentStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F4FE5A2BC36A6700754F0F /* PreferenceCenterContentStyle.swift */; };\n\t\t99F662B02B5DDC2900696098 /* BeveledLoadingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F662AF2B5DDC2900696098 /* BeveledLoadingView.swift */; };\n\t\t99F662B22B60425E00696098 /* InAppMessageModalView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F662B12B60425E00696098 /* InAppMessageModalView.swift */; };\n\t\t99F662D22B63047300696098 /* InAppMessageBannerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99F662D12B63047300696098 /* InAppMessageBannerView.swift */; };\n\t\t99FD20A52DEFC35900242551 /* MessageCenterUIKitAppearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99FD20A42DEFC35900242551 /* MessageCenterUIKitAppearance.swift */; };\n\t\tA1B2C3D4E5F60002VIDEOST /* VideoState.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60001VIDEOST /* VideoState.swift */; };\n\t\tA1B2C3D4E5F60004VIDEOCR /* VideoController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A1B2C3D4E5F60003VIDEOCR /* VideoController.swift */; };\n\t\tA61517B226A9C4C3008A41C4 /* SubscriptionListEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61517B126A9C4C3008A41C4 /* SubscriptionListEditor.swift */; };\n\t\tA61517B526AEEAAB008A41C4 /* SubscriptionListUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61517B426AEEAAB008A41C4 /* SubscriptionListUpdate.swift */; };\n\t\tA61517C426B009D6008A41C4 /* SubscriptionListAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61517C026B009D6008A41C4 /* SubscriptionListAPIClient.swift */; };\n\t\tA61F3A752A5DA58500EE94CC /* FeatureFlagManagerTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A61F3A732A5D9D6800EE94CC /* FeatureFlagManagerTest.swift */; };\n\t\tA61F3A782A5DBA1800EE94CC /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 494DD9571B0EB677009C134E /* AirshipCore.framework */; platformFilters = (ios, maccatalyst, macos, tvos, xros, ); };\n\t\tA62058712A5841330041FBF9 /* AirshipFeatureFlags.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A62058692A5841330041FBF9 /* AirshipFeatureFlags.framework */; };\n\t\tA62058812A5842200041FBF9 /* AirshipFeatureFlagsSDKModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = A62058802A5842200041FBF9 /* AirshipFeatureFlagsSDKModule.swift */; };\n\t\tA629F7DA295B514C00671647 /* PasteboardActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A629F7D9295B514C00671647 /* PasteboardActionTest.swift */; };\n\t\tA62C3354299FD509004DB0DA /* ShareActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A62C3353299FD509004DB0DA /* ShareActionTest.swift */; };\n\t\tA63A567628F449D8004B8951 /* TestWorkManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63A567528F449D8004B8951 /* TestWorkManager.swift */; };\n\t\tA63A567828F457FE004B8951 /* TestWorkRateLimiterActor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A63A567728F457FE004B8951 /* TestWorkRateLimiterActor.swift */; };\n\t\tA658DE0C2727020200007672 /* ImageButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = A658DE0A2727020100007672 /* ImageButton.swift */; };\n\t\tA658DE192728498900007672 /* AirshipWebview.swift in Sources */ = {isa = PBXBuildFile; fileRef = A658DE182728498900007672 /* AirshipWebview.swift */; };\n\t\tA658DE2B272AFB0400007672 /* AirshipImageLoader.swift in Sources */ = {isa = PBXBuildFile; fileRef = A658DE2A272AFB0400007672 /* AirshipImageLoader.swift */; };\n\t\tA67EC249279B1A40009089E1 /* ScopedSubscriptionListEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67EC248279B1A40009089E1 /* ScopedSubscriptionListEditor.swift */; };\n\t\tA67EC24B279B1C34009089E1 /* ScopedSubscriptionListUpdate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67EC24A279B1C34009089E1 /* ScopedSubscriptionListUpdate.swift */; };\n\t\tA67F87D2268DECCE00EF5F43 /* ContactAPIClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = A67F87D1268DECCE00EF5F43 /* ContactAPIClient.swift */; };\n\t\tA6849387273290520021675E /* Score.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6849386273290520021675E /* Score.swift */; };\n\t\tA684939D273436370021675E /* FontViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = A684939C273436370021675E /* FontViewModifier.swift */; };\n\t\tA69C987F27E247B20063A101 /* SubscriptionListAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A69C987E27E247B20063A101 /* SubscriptionListAction.swift */; };\n\t\tA6A5530A26D548AF002B20F6 /* NativeBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6A5530926D548AF002B20F6 /* NativeBridge.swift */; };\n\t\tA6A5531026D548D6002B20F6 /* JavaScriptEnvironment.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6A5530F26D548D6002B20F6 /* JavaScriptEnvironment.swift */; };\n\t\tA6A5531326D548FF002B20F6 /* NativeBridgeActionHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6A5531226D548FF002B20F6 /* NativeBridgeActionHandler.swift */; };\n\t\tA6AC44832B923ACB00769ED2 /* TestInAppMessageAutomationExecutor.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6AC44822B923ACB00769ED2 /* TestInAppMessageAutomationExecutor.swift */; };\n\t\tA6AF8D2D27E8D4910068C7EE /* SubscriptionListActionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6AF8D2C27E8D4910068C7EE /* SubscriptionListActionTest.swift */; };\n\t\tA6CDD8D0269491BE0040A673 /* ContactAPIClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6CDD8CF269491BE0040A673 /* ContactAPIClientTest.swift */; };\n\t\tA6D6D48F2A0253AA0072A5CA /* ActionArguments.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D6D48E2A0253AA0072A5CA /* ActionArguments.swift */; };\n\t\tA6D6D49D2A0260780072A5CA /* AirshipAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D6D49C2A0260780072A5CA /* AirshipAction.swift */; };\n\t\tA6D6D49F2A02608C0072A5CA /* ActionResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6D6D49E2A02608C0072A5CA /* ActionResult.swift */; };\n\t\tA6E9AD982D4D12D00091BBAF /* FeatureFlagUpdateStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E9AD972D4D12C60091BBAF /* FeatureFlagUpdateStatus.swift */; };\n\t\tA6E9ADED2D4D204B0091BBAF /* InAppAutomationUpdateStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6E9ADEC2D4D20300091BBAF /* InAppAutomationUpdateStatus.swift */; };\n\t\tA6F0B1912B83CD9B002D10A4 /* AutomationEngineTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = A6F0B18F2B837E36002D10A4 /* AutomationEngineTest.swift */; };\n\t\tC00ED4CF26C729390040C5D0 /* URLAllowList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C00ED4CE26C729390040C5D0 /* URLAllowList.swift */; };\n\t\tC02D0B6626C1A3E200F673E6 /* ChannelCapture.swift in Sources */ = {isa = PBXBuildFile; fileRef = C02D0B6526C1A3E200F673E6 /* ChannelCapture.swift */; };\n\t\tC088383726E0244C00D40838 /* TestURLAllowList.swift in Sources */ = {isa = PBXBuildFile; fileRef = C088383526E0244C00D40838 /* TestURLAllowList.swift */; };\n\t\tCC64F0591D8B77E3009CEF27 /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 494DD9571B0EB677009C134E /* AirshipCore.framework */; };\n\t\tCC64F0CE1D8B781C009CEF27 /* CustomNotificationCategories.plist in Resources */ = {isa = PBXBuildFile; fileRef = CC64F0611D8B781C009CEF27 /* CustomNotificationCategories.plist */; };\n\t\tCC64F14D1D8B7954009CEF27 /* AirshipConfig-Valid-Legacy.plist in Resources */ = {isa = PBXBuildFile; fileRef = CC64F1451D8B7954009CEF27 /* AirshipConfig-Valid-Legacy.plist */; };\n\t\tCC64F14F1D8B7954009CEF27 /* AirshipConfig-Valid.plist in Resources */ = {isa = PBXBuildFile; fileRef = CC64F1471D8B7954009CEF27 /* AirshipConfig-Valid.plist */; };\n\t\tCC64F1511D8B7954009CEF27 /* development-embedded.mobileprovision in Resources */ = {isa = PBXBuildFile; fileRef = CC64F1491D8B7954009CEF27 /* development-embedded.mobileprovision */; };\n\t\tCC64F1521D8B7954009CEF27 /* production-embedded.mobileprovision in Resources */ = {isa = PBXBuildFile; fileRef = CC64F14A1D8B7954009CEF27 /* production-embedded.mobileprovision */; };\n\t\tDFD2464E2473404C000FD565 /* DebugSDKModule.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD2464D2473404C000FD565 /* DebugSDKModule.swift */; };\n\t\tE976486F27A46CC50024518D /* ChannelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = E976486E27A46CC50024518D /* ChannelType.swift */; };\n\t\tE99605A127A071EA00365AE4 /* EmailRegistrationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99605A027A071EA00365AE4 /* EmailRegistrationOptions.swift */; };\n\t\tE99605A427A075B800365AE4 /* SMSRegistrationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99605A327A075B800365AE4 /* SMSRegistrationOptions.swift */; };\n\t\tE99605A727A075C600365AE4 /* OpenRegistrationOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E99605A627A075C600365AE4 /* OpenRegistrationOptions.swift */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t3C39D3072384C8B6003C50D4 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E346237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\t3CA0E2D9237CD59100EE76CF /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t3CA0E348237E4A7B00EE76CF /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6E0B8733294A9C130064B7BD /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E0B8729294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomationSwift;\n\t\t};\n\t\t6E0B8742294A9C780064B7BD /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6E107F032B30B887007AFC4D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6E128B9C2D305C4600733024 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A641E1462BDBBDB400DE6FAA;\n\t\t\tremoteInfo = AirshipObjectiveC;\n\t\t};\n\t\t6E2947492AD47E0C009EC6DD /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\t6E2F5A912A67314A00CABD3D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6E2F5A952A67316C00CABD3D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6E43218A26EA891F009228AB /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E431F6B26EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\t6E4A467328EF44F600A25617 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E346237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\t6E4AEE0A2B6B24D1008AEAC1 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E0B8729294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomationSwift;\n\t\t};\n\t\t6E5917882B28E93A0084BBBF /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E0B8729294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomationSwift;\n\t\t};\n\t\t6E6B493D2A787D0A00AF98D8 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6EAAE87628C2AD80003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E431F6B26EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\t6EAAE87828C2AD80003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6EAAE87A28C2AD80003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E298237CCE2600EE76CF;\n\t\t\tremoteInfo = AirshipDebug;\n\t\t};\n\t\t6EAAE87C28C2AD80003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E346237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\t6EAAE87E28C2AD80003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\t6ECCAD292CF55BC700423D86 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6ECCAD2B2CF55BC700423D86 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E431F6B26EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\t6ECCAD2D2CF55BC700423D86 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6ECCAD332CF55BC700423D86 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\t847B000D267CD85E007CD249 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t847BFFFE267CD73A007CD249 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\tA60235352CCB9E3C00CF412B /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E431F6B26EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\tA61F3A762A5DBA0E00EE94CC /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\tA61F3A7A2A5DBA1800EE94CC /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\tA62058722A5841330041FBF9 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\tA641E1562BDBF5FF00DE6FAA /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E0B8729294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomation;\n\t\t};\n\t\tA641E1582BDBF5FF00DE6FAA /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\tA641E15A2BDBF5FF00DE6FAA /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\tA641E15C2BDBF5FF00DE6FAA /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E346237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\tA641E15E2BDBF5FF00DE6FAA /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\tCC64F05A1D8B77E3009CEF27 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 494DD94E1B0EB677009C134E /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipKit;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\t03525B87280DE48100320AA9 /* af */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = af; path = af.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B88280DE49B00320AA9 /* am */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = am; path = am.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B89280DE4E200320AA9 /* bg */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = bg; path = bg.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B8A280DE4ED00320AA9 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B8B280DE4F800320AA9 /* el */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = el; path = el.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B8C280DE50D00320AA9 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B8D280DE54900320AA9 /* fa */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fa; path = fa.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B8E280DE55900320AA9 /* fr-CA */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"fr-CA\"; path = \"fr-CA.lproj/UrbanAirship.strings\"; sourceTree = \"<group>\"; };\n\t\t03525B8F280DE58800320AA9 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B90280DE5A200320AA9 /* lt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lt; path = lt.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B91280DE5B800320AA9 /* lv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lv; path = lv.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B92280DE5CE00320AA9 /* sl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sl; path = sl.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B93280DE5DD00320AA9 /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sr; path = sr.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B94280DE5EA00320AA9 /* sw */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sw; path = sw.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B95280DE5F400320AA9 /* uk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = uk; path = uk.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t03525B96280DE60200320AA9 /* zh-HK */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-HK\"; path = \"zh-HK.lproj/UrbanAirship.strings\"; sourceTree = \"<group>\"; };\n\t\t03525B97280DE61200320AA9 /* zu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = zu; path = zu.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t27051CD62EE75E3300C770D5 /* AutomationEventsHistoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationEventsHistoryTest.swift; sourceTree = \"<group>\"; };\n\t\t27077E4B2EE7531C0027A282 /* AutomationEventsHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationEventsHistory.swift; sourceTree = \"<group>\"; };\n\t\t271B38642DB2866200495D9F /* TagActionMutation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagActionMutation.swift; sourceTree = \"<group>\"; };\n\t\t27264FB22E81B064000B6FA3 /* AirshipSceneController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSceneController.swift; sourceTree = \"<group>\"; };\n\t\t2726505A2E81B80E000B6FA3 /* PagerControllerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerControllerTest.swift; sourceTree = \"<group>\"; };\n\t\t2753F6412F6C5BB50073882C /* MessageCenterMessageError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterMessageError.swift; sourceTree = \"<group>\"; };\n\t\t275D32AA2EF955AD00B75760 /* AirshipSimpleLayoutView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSimpleLayoutView.swift; sourceTree = \"<group>\"; };\n\t\t275D32AB2EF955AD00B75761 /* AirshipSimpleLayoutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSimpleLayoutViewModel.swift; sourceTree = \"<group>\"; };\n\t\t2797B4182F47687800A7F848 /* NativeLayoutPersistentDataStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeLayoutPersistentDataStore.swift; sourceTree = \"<group>\"; };\n\t\t27AFE70E2E733F4400767044 /* ModifyTagsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifyTagsAction.swift; sourceTree = \"<group>\"; };\n\t\t27AFE7102E73477200767044 /* ModifyTagsActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifyTagsActionTest.swift; sourceTree = \"<group>\"; };\n\t\t27CCF77C2F1656150018058F /* MessageViewAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t27CCF77E2F16DA500018058F /* MessageViewAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageViewAnalyticsTest.swift; sourceTree = \"<group>\"; };\n\t\t27CCF8D22F2382750018058F /* ThomasStateStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasStateStorage.swift; sourceTree = \"<group>\"; };\n\t\t27E419482EF484DB00D5C1A6 /* UAInbox 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAInbox 4.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t27E419492EF59F9800D5C1A6 /* MessageCenterThomasView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterThomasView.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F32F0E910B00E317DB /* ThomasLayoutButtonTapEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutButtonTapEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F42F0E910B00E317DB /* ThomasLayoutDisplayEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutDisplayEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F52F0E910B00E317DB /* ThomasLayoutEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F62F0E910B00E317DB /* ThomasLayoutFormDisplayEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutFormDisplayEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F72F0E910B00E317DB /* ThomasLayoutFormResultEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutFormResultEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F82F0E910B00E317DB /* ThomasLayoutGestureEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutGestureEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1F92F0E910B00E317DB /* ThomasLayoutPageActionEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPageActionEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1FA2F0E910B00E317DB /* ThomasLayoutPagerCompletedEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPagerCompletedEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1FB2F0E910B00E317DB /* ThomasLayoutPagerSummaryEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPagerSummaryEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1FC2F0E910B00E317DB /* ThomasLayoutPageSwipeEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPageSwipeEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1FD2F0E910B00E317DB /* ThomasLayoutPageViewEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPageViewEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1FE2F0E910B00E317DB /* ThomasLayoutPermissionResultEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPermissionResultEvent.swift; sourceTree = \"<group>\"; };\n\t\t27F1E1FF2F0E910B00E317DB /* ThomasLayoutResolutionEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutResolutionEvent.swift; sourceTree = \"<group>\"; };\n\t\t320AD3A529E7FA2000D66106 /* PagerGestureMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerGestureMap.swift; sourceTree = \"<group>\"; };\n\t\t3215CA9C2739349700B7D97E /* ModalView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModalView.swift; sourceTree = \"<group>\"; };\n\t\t322AAB1C2B5A869000652DAC /* ContactManagementView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactManagementView.swift; sourceTree = \"<group>\"; };\n\t\t322AAB202B5ACB2800652DAC /* ChannelListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListView.swift; sourceTree = \"<group>\"; };\n\t\t3231126929D5E4F600CF0D86 /* AirshipAutomationResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAutomationResources.swift; sourceTree = \"<group>\"; };\n\t\t3231127B29D5E67200CF0D86 /* FrequencyLimitStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrequencyLimitStore.swift; sourceTree = \"<group>\"; };\n\t\t3231127C29D5E67200CF0D86 /* FrequencyLimitManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrequencyLimitManager.swift; sourceTree = \"<group>\"; };\n\t\t3231127D29D5E67200CF0D86 /* Occurrence.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Occurrence.swift; sourceTree = \"<group>\"; };\n\t\t3231128029D5E67200CF0D86 /* FrequencyConstraint.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrequencyConstraint.swift; sourceTree = \"<group>\"; };\n\t\t3231128129D5E67200CF0D86 /* FrequencyChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrequencyChecker.swift; sourceTree = \"<group>\"; };\n\t\t3231128B29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UAFrequencyLimits.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t3231128F29D5E6C600CF0D86 /* FrequencyLimitManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FrequencyLimitManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t3237D5F12B865D990055932B /* JSONValueTransformer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = JSONValueTransformer.swift; sourceTree = \"<group>\"; };\n\t\t3243EC612D93109C00B43B25 /* AirshipCheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipCheckboxToggleStyle.swift; sourceTree = \"<group>\"; };\n\t\t3243EC622D93109C00B43B25 /* AirshipSwitchToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSwitchToggleStyle.swift; sourceTree = \"<group>\"; };\n\t\t324D3BFE273E6B4500058EE4 /* BannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BannerView.swift; sourceTree = \"<group>\"; };\n\t\t325108C72B7A596F0028F508 /* UAInbox 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAInbox 3.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t325108C82B7A5A220028F508 /* UARemoteData 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UARemoteData 4.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t32515866272AFB2E00DF8B44 /* Media.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Media.swift; sourceTree = \"<group>\"; };\n\t\t32515868272AFB2E00DF8B44 /* VideoMediaWebView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VideoMediaWebView.swift; sourceTree = \"<group>\"; };\n\t\t325D53D9295C7979003421B4 /* ActionRegistryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRegistryTest.swift; sourceTree = \"<group>\"; };\n\t\t3261A7F4243CD73100ADBF6B /* CoreTelephony.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreTelephony.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/CoreTelephony.framework; sourceTree = DEVELOPER_DIR; };\n\t\t3299EF162948CBC100251E70 /* RemoteDataAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t3299EF212949EC3E00251E70 /* AirshipBaseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipBaseTest.swift; sourceTree = \"<group>\"; };\n\t\t3299EF25294B222F00251E70 /* RemoteDataStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataStoreTest.swift; sourceTree = \"<group>\"; };\n\t\t329DFCCA2B7E4DA10039C8C0 /* UARemoteDataMappingV3toV4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = UARemoteDataMappingV3toV4.xcmappingmodel; sourceTree = \"<group>\"; };\n\t\t329DFCCE2B7E4DDA0039C8C0 /* UARemoteDataMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UARemoteDataMapping.swift; sourceTree = \"<group>\"; };\n\t\t329DFCD42B7E59700039C8C0 /* UAInboxDataMapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UAInboxDataMapping.swift; sourceTree = \"<group>\"; };\n\t\t32B513552B9F53A500BBE780 /* MessageCenterPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterPredicate.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE2728F8A7D600F2254B /* MessageCenterListItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterListItemView.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE2828F8A7D600F2254B /* MessageCenterView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterView.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE2928F8A7D600F2254B /* MessageCenterMessageView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterMessageView.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE2A28F8A7D600F2254B /* MessageCenterListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterListView.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE2B28F8A7D600F2254B /* MessageCenterListItemViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterListItemViewModel.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE3A28F8A7EB00F2254B /* MessageCenterController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterController.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE4828F8B66500F2254B /* MessageCenterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterViewController.swift; sourceTree = \"<group>\"; };\n\t\t32B632862906CA17000D3E34 /* MessageCenterThemeLoader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterThemeLoader.swift; sourceTree = \"<group>\"; };\n\t\t32B632872906CA17000D3E34 /* MessageCenterTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterTheme.swift; sourceTree = \"<group>\"; };\n\t\t32BBFB3F2B274C8600C6A998 /* ContactChannelsAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactChannelsAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t32C68D0429424449006BBB29 /* RemoteDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataTest.swift; sourceTree = \"<group>\"; };\n\t\t32CF81E1275627F4003009D1 /* AirshipAsyncImage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAsyncImage.swift; sourceTree = \"<group>\"; };\n\t\t32D6E87A2727F7060077C784 /* Image.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Image.swift; sourceTree = \"<group>\"; };\n\t\t32DDC0562AF1055300D23EBE /* AddChannelPromptView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddChannelPromptView.swift; sourceTree = \"<group>\"; };\n\t\t32E339E22A334A2000CD3BE5 /* AddCustomEventActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddCustomEventActionTest.swift; sourceTree = \"<group>\"; };\n\t\t32F293D4295AFD94004A7D9C /* ActionArgumentsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionArgumentsTest.swift; sourceTree = \"<group>\"; };\n\t\t32F615A628F708980015696D /* MessageCenterListTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterListTests.swift; sourceTree = \"<group>\"; };\n\t\t32F68CDA28F02A6B00F7F52A /* MessageCenterStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterStoreTest.swift; sourceTree = \"<group>\"; };\n\t\t32F68CE828F07C2B00F7F52A /* AirshipMessageCenterResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipMessageCenterResources.swift; sourceTree = \"<group>\"; };\n\t\t32F68CE928F07C2C00F7F52A /* MessageCenterSDKModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterSDKModule.swift; sourceTree = \"<group>\"; };\n\t\t32F68CED28F07C2C00F7F52A /* MessageCenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenter.swift; sourceTree = \"<group>\"; };\n\t\t32F68CF428F07C4800F7F52A /* MessageCenterList.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterList.swift; sourceTree = \"<group>\"; };\n\t\t32F97AC029E5986B00FED65F /* StoryIndicator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoryIndicator.swift; sourceTree = \"<group>\"; };\n\t\t32FD4C772D8079910056D141 /* BasicToggleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasicToggleLayout.swift; sourceTree = \"<group>\"; };\n\t\t3C63555F26CDD4F8006E9916 /* AirshipPush.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipPush.swift; sourceTree = \"<group>\"; };\n\t\t3CA0E223237CCBA600EE76CF /* AirshipEventData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AirshipEventData.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t3CA0E233237CCBA600EE76CF /* AirshipDebugManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipDebugManager.swift; sourceTree = \"<group>\"; };\n\t\t3CA0E241237CCBA600EE76CF /* AirshipEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipEvent.swift; sourceTree = \"<group>\"; };\n\t\t3CA0E24D237CCBA600EE76CF /* EventDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventDataManager.swift; sourceTree = \"<group>\"; };\n\t\t3CA0E24F237CCBA600EE76CF /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t3CA0E2AC237CCE2600EE76CF /* AirshipDebug.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipDebug.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t3CA0E305237E396100EE76CF /* UAInbox.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UAInbox.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t3CA0E423237E4A7B00EE76CF /* AirshipMessageCenter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipMessageCenter.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t3CA0E47F237E505200EE76CF /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t3CA84AA626DE255200A59685 /* EventStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventStore.swift; sourceTree = \"<group>\"; };\n\t\t3CA84AB626DE257200A59685 /* DefaultAirshipAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAirshipAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t3CA84AB726DE257200A59685 /* AirshipAnalytics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t3CB37A1D251151A400E60392 /* AirshipDebugResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugResources.swift; sourceTree = \"<group>\"; };\n\t\t3CC8AA0526BB3C7900405614 /* DefaultAirshipPush.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAirshipPush.swift; sourceTree = \"<group>\"; };\n\t\t3CC95B1E268E785900FE2ACD /* NotificationCategories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCategories.swift; sourceTree = \"<group>\"; };\n\t\t3CC95B2A2696549B00FE2ACD /* AirshipPushableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipPushableComponent.swift; sourceTree = \"<group>\"; };\n\t\t459D4049208FE64D00C40E2D /* Valid-UAInAppMessageFullScreenStyle.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"Valid-UAInAppMessageFullScreenStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t459D404B208FE6BA00C40E2D /* Invalid-UAInAppMessageFullScreenStyle.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"Invalid-UAInAppMessageFullScreenStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t459D40542092474300C40E2D /* Valid-UAInAppMessageModalStyle.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"Valid-UAInAppMessageModalStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t459D40562092474A00C40E2D /* Valid-UAInAppMessageBannerStyle.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"Valid-UAInAppMessageBannerStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t459D40582092475500C40E2D /* Invalid-UAInAppMessageBannerStyle.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"Invalid-UAInAppMessageBannerStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t459D405A2092475C00C40E2D /* Invalid-UAInAppMessageModalStyle.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"Invalid-UAInAppMessageModalStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t45A8ADD123133E51004AD8CA /* testMCColorsCatalog.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = testMCColorsCatalog.xcassets; sourceTree = \"<group>\"; };\n\t\t494DD9571B0EB677009C134E /* AirshipCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t494DD95B1B0EB677009C134E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6014AD662C1B5F540072DCF0 /* ChallengeResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeResolver.swift; sourceTree = \"<group>\"; };\n\t\t6014AD6A2C2032360072DCF0 /* ChallengeResolverTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChallengeResolverTest.swift; sourceTree = \"<group>\"; };\n\t\t6014AD742C20410A0072DCF0 /* airship.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = airship.der; sourceTree = \"<group>\"; };\n\t\t6018AF562B29C20A008E528B /* SearchEventTemplateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchEventTemplateTest.swift; sourceTree = \"<group>\"; };\n\t\t602AD0D42D7242B300C7D566 /* ThomasSmsLocale.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasSmsLocale.swift; sourceTree = \"<group>\"; };\n\t\t603269522BF4B141007F7F75 /* AdditionalAudienceCheckerApiClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalAudienceCheckerApiClient.swift; sourceTree = \"<group>\"; };\n\t\t603269542BF4B8D5007F7F75 /* AdditionalAudienceCheckerResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalAudienceCheckerResolver.swift; sourceTree = \"<group>\"; };\n\t\t603269572BF7550E007F7F75 /* AdditionalAudienceCheckerResolverTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdditionalAudienceCheckerResolverTest.swift; sourceTree = \"<group>\"; };\n\t\t6032695A2BF75D69007F7F75 /* AirshipHTTPResponseTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipHTTPResponseTest.swift; sourceTree = \"<group>\"; };\n\t\t605073822B2CD38200209B51 /* ActiveTimerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveTimerTest.swift; sourceTree = \"<group>\"; };\n\t\t605073892B32F85100209B51 /* ThomasViewModelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasViewModelTest.swift; sourceTree = \"<group>\"; };\n\t\t6050738F2B347B6400209B51 /* ThomasPresentationModelCodingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPresentationModelCodingTest.swift; sourceTree = \"<group>\"; };\n\t\t6058771C2AC73C7E0021628E /* AirshipMeteredUsageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipMeteredUsageTest.swift; sourceTree = \"<group>\"; };\n\t\t6058771E2ACAC86A0021628E /* MeteredUsageApiClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeteredUsageApiClientTest.swift; sourceTree = \"<group>\"; };\n\t\t60653FC02CBD2CD4009CD9A7 /* PushData+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"PushData+CoreDataClass.swift\"; sourceTree = \"<group>\"; };\n\t\t60653FC12CBD2CD4009CD9A7 /* PushData+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"PushData+CoreDataProperties.swift\"; sourceTree = \"<group>\"; };\n\t\t6068E0052B2A190300349E82 /* CustomEventTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6068E0072B2A2A6700349E82 /* AccountEventTemplateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountEventTemplateTest.swift; sourceTree = \"<group>\"; };\n\t\t6068E0312B2B785A00349E82 /* MediaEventTemplateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaEventTemplateTest.swift; sourceTree = \"<group>\"; };\n\t\t6068E0332B2B7CA100349E82 /* RetailEventTemplateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetailEventTemplateTest.swift; sourceTree = \"<group>\"; };\n\t\t6068E03A2B2CBCF200349E82 /* ActiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveTimer.swift; sourceTree = \"<group>\"; };\n\t\t6079511F2A1CD19F0086578F /* ExperimentManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExperimentManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6087DB872B278F7600449BA8 /* JsonValueMatcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonValueMatcherTest.swift; sourceTree = \"<group>\"; };\n\t\t608B16E52C2C1137005298FA /* SubscriptionListProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionListProvider.swift; sourceTree = \"<group>\"; };\n\t\t608B16E72C2C6DDB005298FA /* ChannelSubscriptionListProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelSubscriptionListProvider.swift; sourceTree = \"<group>\"; };\n\t\t608B16F02C2D5F0D005298FA /* BaseCachingRemoteDataProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseCachingRemoteDataProvider.swift; sourceTree = \"<group>\"; };\n\t\t609843552D6F518900690371 /* SmsLocalePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SmsLocalePicker.swift; sourceTree = \"<group>\"; };\n\t\t60A364EC2C3479BF00B05E26 /* ExecutionWindowTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionWindowTest.swift; sourceTree = \"<group>\"; };\n\t\t60A5CC072B28DC500017EDB2 /* NotificationCategoriesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationCategoriesTest.swift; sourceTree = \"<group>\"; };\n\t\t60A5CC0B2B29AE890017EDB2 /* ProximityRegionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProximityRegionTest.swift; sourceTree = \"<group>\"; };\n\t\t60A5CC0D2B29B1B80017EDB2 /* CircularRegionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularRegionTest.swift; sourceTree = \"<group>\"; };\n\t\t60A5CC0F2B29B4100017EDB2 /* RegionEventTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RegionEventTest.swift; sourceTree = \"<group>\"; };\n\t\t60C1DB0B2A8B743B00A1D3DA /* AirshipEmbeddedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipEmbeddedView.swift; sourceTree = \"<group>\"; };\n\t\t60C1DB0C2A8B743B00A1D3DA /* AirshipEmbeddedViewManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipEmbeddedViewManager.swift; sourceTree = \"<group>\"; };\n\t\t60C1DB0D2A8B743B00A1D3DA /* AirshipEmbeddedObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipEmbeddedObserver.swift; sourceTree = \"<group>\"; };\n\t\t60C1DB0E2A8B743C00A1D3DA /* EmbeddedView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmbeddedView.swift; sourceTree = \"<group>\"; };\n\t\t60CE9BDD2D0B6A0900A8B625 /* ThomasPagerControllerBranching.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPagerControllerBranching.swift; sourceTree = \"<group>\"; };\n\t\t60D1D9B72B68FB6400EBE0A4 /* PreparedTrigger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedTrigger.swift; sourceTree = \"<group>\"; };\n\t\t60D1D9BA2B6A53F000EBE0A4 /* PreparedTriggerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedTriggerTest.swift; sourceTree = \"<group>\"; };\n\t\t60D1D9BC2B6AB2D100EBE0A4 /* AutomationTriggerProcessorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationTriggerProcessorTest.swift; sourceTree = \"<group>\"; };\n\t\t60D2B3342D9F0FCF00B0752D /* PagerDisableSwipeSelectorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerDisableSwipeSelectorTest.swift; sourceTree = \"<group>\"; };\n\t\t60D3BCC32A1529D800E07524 /* ExperimentDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentDataProvider.swift; sourceTree = \"<group>\"; };\n\t\t60D3BCC52A152A0D00E07524 /* ExperimentManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentManager.swift; sourceTree = \"<group>\"; };\n\t\t60D3BCCB2A153C0700E07524 /* Experiment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Experiment.swift; sourceTree = \"<group>\"; };\n\t\t60D3BCCD2A15471C00E07524 /* AudienceHashSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudienceHashSelector.swift; sourceTree = \"<group>\"; };\n\t\t60D3BCCF2A154D9400E07524 /* MessageCriteria.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCriteria.swift; sourceTree = \"<group>\"; };\n\t\t60E09FDA2B2780DB005A16EA /* JsonMatcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JsonMatcherTest.swift; sourceTree = \"<group>\"; };\n\t\t60EACF532B7BF2EA00CAFDBB /* AirshipApptimizeIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipApptimizeIntegration.swift; sourceTree = \"<group>\"; };\n\t\t60F8E75B2B8F3D4B00460EDF /* CancelSchedulesAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelSchedulesAction.swift; sourceTree = \"<group>\"; };\n\t\t60F8E75D2B8FA12800460EDF /* CancelSchedulesActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancelSchedulesActionTest.swift; sourceTree = \"<group>\"; };\n\t\t60F8E75F2B8FAF5400460EDF /* ScheduleAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleAction.swift; sourceTree = \"<group>\"; };\n\t\t60F8E7612B8FB2CC00460EDF /* ScheduleActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleActionTest.swift; sourceTree = \"<group>\"; };\n\t\t60FCA3042B4F1110005C9232 /* LegacyInAppMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInAppMessaging.swift; sourceTree = \"<group>\"; };\n\t\t60FCA3062B4F1C73005C9232 /* LegacyInAppMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInAppMessage.swift; sourceTree = \"<group>\"; };\n\t\t60FCA3092B51364A005C9232 /* LegacyInAppMessageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInAppMessageTest.swift; sourceTree = \"<group>\"; };\n\t\t60FCA30B2B51492A005C9232 /* LegacyInAppMessagingTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInAppMessagingTest.swift; sourceTree = \"<group>\"; };\n\t\t60FCA3242B5EF3A8005C9232 /* AutomationEventFeedTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationEventFeedTest.swift; sourceTree = \"<group>\"; };\n\t\t6329102D2DD8103200B13C6C /* NativeVideoPlayer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeVideoPlayer.swift; sourceTree = \"<group>\"; };\n\t\t632913F92DE547A500B13C6C /* VideoMediaNativeView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoMediaNativeView.swift; sourceTree = \"<group>\"; };\n\t\t6E0031AA2D08CC8A0004F53E /* AirshipAuthorizedNotificationSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAuthorizedNotificationSettings.swift; sourceTree = \"<group>\"; };\n\t\t6E0104FE2DDF9B26009D651F /* IconView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconView.swift; sourceTree = \"<group>\"; };\n\t\t6E0105002DDFA5E6009D651F /* ScoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoreController.swift; sourceTree = \"<group>\"; };\n\t\t6E0105022DDFA719009D651F /* ScoreToggleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoreToggleLayout.swift; sourceTree = \"<group>\"; };\n\t\t6E0105042DDFA735009D651F /* ScoreState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScoreState.swift; sourceTree = \"<group>\"; };\n\t\t6E032A4F2B210E6000404630 /* RemoteConfigTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfigTest.swift; sourceTree = \"<group>\"; };\n\t\t6E062D02271656DE001A74A1 /* Container.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Container.swift; sourceTree = \"<group>\"; };\n\t\t6E062D04271656F8001A74A1 /* LinearLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinearLayout.swift; sourceTree = \"<group>\"; };\n\t\t6E062D0627165709001A74A1 /* Label.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Label.swift; sourceTree = \"<group>\"; };\n\t\t6E062D082716571F001A74A1 /* LabelButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabelButton.swift; sourceTree = \"<group>\"; };\n\t\t6E062D0C2718B505001A74A1 /* ViewConstraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewConstraints.swift; sourceTree = \"<group>\"; };\n\t\t6E07688729F9D28A0014E2A9 /* AirshipNotificationCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipNotificationCenter.swift; sourceTree = \"<group>\"; };\n\t\t6E07688B29F9F0830014E2A9 /* AirshipLocaleManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLocaleManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E07689129FB39440014E2A9 /* AirshipUnsafeSendableWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipUnsafeSendableWrapper.swift; sourceTree = \"<group>\"; };\n\t\t6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPrivacyManager.swift; sourceTree = \"<group>\"; };\n\t\t6E0B872A294A9C120064B7BD /* AirshipAutomation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipAutomation.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6E0B8731294A9C130064B7BD /* AirshipAutomationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirshipAutomationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6E0B8749294A9CCA0064B7BD /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6E0B875F294CE0BF0064B7BD /* FarmHashFingerprint64Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FarmHashFingerprint64Test.swift; sourceTree = \"<group>\"; };\n\t\t6E0B8761294CE0DC0064B7BD /* FarmHashFingerprint64.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FarmHashFingerprint64.swift; sourceTree = \"<group>\"; };\n\t\t6E0F4BE12B32190400673CA4 /* AutomationSchedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationSchedule.swift; sourceTree = \"<group>\"; };\n\t\t6E0F4BE42B32645600673CA4 /* AutomationTrigger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationTrigger.swift; sourceTree = \"<group>\"; };\n\t\t6E0F4BE62B32646000673CA4 /* AutomationDelay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationDelay.swift; sourceTree = \"<group>\"; };\n\t\t6E0F4BE82B3264A400673CA4 /* DeferredAutomationData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredAutomationData.swift; sourceTree = \"<group>\"; };\n\t\t6E0F557E2AE03BB900E7CB8C /* ThomasAsyncImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasAsyncImage.swift; sourceTree = \"<group>\"; };\n\t\t6E10A1472C2B825200ED9556 /* DefaultTaskSleeperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultTaskSleeperTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1185C52C3328A10071334E /* ExecutionWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionWindow.swift; sourceTree = \"<group>\"; };\n\t\t6E12539029A81ACE0009EE58 /* AirshipCoreDataPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipCoreDataPredicate.swift; sourceTree = \"<group>\"; };\n\t\t6E146C4F2F5214D900320A36 /* AirshipDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDevice.swift; sourceTree = \"<group>\"; };\n\t\t6E146D672F523DB700320A36 /* AirshipFont.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipFont.swift; sourceTree = \"<group>\"; };\n\t\t6E146D692F5241BA00320A36 /* AirshipColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipColor.swift; sourceTree = \"<group>\"; };\n\t\t6E146EDC2F52536C00320A36 /* AishipFontTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AishipFontTests.swift; sourceTree = \"<group>\"; };\n\t\t6E146FF22F525E7300320A36 /* AirshipPasteboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipPasteboard.swift; sourceTree = \"<group>\"; };\n\t\t6E1472D42F526DC600320A36 /* AirshipNativePlatform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipNativePlatform.swift; sourceTree = \"<group>\"; };\n\t\t6E1476CB2F56439A00320A36 /* MessageCenterNavigationAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterNavigationAppearance.swift; sourceTree = \"<group>\"; };\n\t\t6E14C9A028B5E4AF00A55E65 /* PushNotification.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotification.swift; sourceTree = \"<group>\"; };\n\t\t6E1528162B4DC3C000DF1377 /* ActionAutomationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionAutomationExecutor.swift; sourceTree = \"<group>\"; };\n\t\t6E1528182B4DC3D000DF1377 /* InAppMessageAutomationPreparer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageAutomationPreparer.swift; sourceTree = \"<group>\"; };\n\t\t6E15281A2B4DC3DF00DF1377 /* InAppMessageAutomationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageAutomationExecutor.swift; sourceTree = \"<group>\"; };\n\t\t6E15281C2B4DC43100DF1377 /* ActionAutomationPreparer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionAutomationPreparer.swift; sourceTree = \"<group>\"; };\n\t\t6E15281F2B4DC59C00DF1377 /* InAppMessageSceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageSceneDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E1528212B4DC5C000DF1377 /* InAppMessageDisplayDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageDisplayDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E1528232B4DC60200DF1377 /* DisplayCoordinatorManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayCoordinatorManager.swift; sourceTree = \"<group>\"; };\n\t\t6E1528252B4DC64B00DF1377 /* DisplayAdapterFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayAdapterFactory.swift; sourceTree = \"<group>\"; };\n\t\t6E1528272B4DCFCB00DF1377 /* AirshipActorValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipActorValue.swift; sourceTree = \"<group>\"; };\n\t\t6E15282B2B4DE81E00DF1377 /* AutomationSDKModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationSDKModule.swift; sourceTree = \"<group>\"; };\n\t\t6E15282E2B4DED7A00DF1377 /* InAppMessageAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t6E1528302B4DED8900DF1377 /* InAppMessageAnalyticsFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageAnalyticsFactory.swift; sourceTree = \"<group>\"; };\n\t\t6E1528322B4DF2E600DF1377 /* ScheduleConditionsChangedNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleConditionsChangedNotifier.swift; sourceTree = \"<group>\"; };\n\t\t6E1528342B4E11DB00DF1377 /* CustomDisplayAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisplayAdapter.swift; sourceTree = \"<group>\"; };\n\t\t6E1528362B4E11E800DF1377 /* CustomDisplayAdapterWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisplayAdapterWrapper.swift; sourceTree = \"<group>\"; };\n\t\t6E1528382B4E13D400DF1377 /* AirshipLayoutDisplayAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLayoutDisplayAdapter.swift; sourceTree = \"<group>\"; };\n\t\t6E15283D2B4F0B8200DF1377 /* ActionAutomationPreparerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionAutomationPreparerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E15283F2B4F153900DF1377 /* TestDisplayAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDisplayAdapter.swift; sourceTree = \"<group>\"; };\n\t\t6E1528412B4F156200DF1377 /* TestDisplayCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDisplayCoordinator.swift; sourceTree = \"<group>\"; };\n\t\t6E152BC92743235800788402 /* Icons.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Icons.swift; sourceTree = \"<group>\"; };\n\t\t6E15894F2AFEF19F00954A04 /* SessionTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTracker.swift; sourceTree = \"<group>\"; };\n\t\t6E1589532AFF021D00954A04 /* SessionState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionState.swift; sourceTree = \"<group>\"; };\n\t\t6E1589572AFF023400954A04 /* SessionEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEvent.swift; sourceTree = \"<group>\"; };\n\t\t6E15B6D826CC749F0099C92D /* RemoteConfigManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConfigManager.swift; sourceTree = \"<group>\"; };\n\t\t6E15B6F326CD85C40099C92D /* RuntimeConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeConfig.swift; sourceTree = \"<group>\"; };\n\t\t6E15B6F926CDCA6A0099C92D /* RuntimeConfigTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeConfigTest.swift; sourceTree = \"<group>\"; };\n\t\t6E15B70226CDE40E0099C92D /* RemoteConfigManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteConfigManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E15B70426CE07180099C92D /* TestRemoteData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRemoteData.swift; sourceTree = \"<group>\"; };\n\t\t6E15B70A26CEB4190099C92D /* RemoteDataStorePayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteDataStorePayload.swift; sourceTree = \"<group>\"; };\n\t\t6E15B70C26CEB4190099C92D /* RemoteDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteDataStore.swift; sourceTree = \"<group>\"; };\n\t\t6E15B72026CEC7030099C92D /* RemoteDataPayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteDataPayload.swift; sourceTree = \"<group>\"; };\n\t\t6E15B72926CEDBA50099C92D /* RemoteData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteData.swift; sourceTree = \"<group>\"; };\n\t\t6E15B72C26CF13BC0099C92D /* RemoteDataProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataProviderDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E15B72F26CF4F6B0099C92D /* TestRemoteDataAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRemoteDataAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E1620892B311219009240B2 /* DisplayCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayCoordinator.swift; sourceTree = \"<group>\"; };\n\t\t6E16208C2B3116AE009240B2 /* ImmediateDisplayCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmediateDisplayCoordinator.swift; sourceTree = \"<group>\"; };\n\t\t6E16208E2B3116BA009240B2 /* DefaultDisplayCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultDisplayCoordinator.swift; sourceTree = \"<group>\"; };\n\t\t6E1620912B3118D5009240B2 /* ImmediateDisplayCoordinatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmediateDisplayCoordinatorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1620942B311D8A009240B2 /* DefaultDisplayCoordinatorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultDisplayCoordinatorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1767F329B923D100D65F60 /* ChannelAuthTokenProviderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAuthTokenProviderTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1767F429B923D100D65F60 /* ChannelAuthTokenAPIClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelAuthTokenAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1767F529B923D100D65F60 /* TestChannelAuthTokenAPIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestChannelAuthTokenAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E1767F929B92F1700D65F60 /* AirshipUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipUtilsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1802F82C5C2DEC00198D0D /* AirshipAnalyticFeedTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAnalyticFeedTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1892C7268D15C300417887 /* PreferenceCenterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1892C9268D16E200417887 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6E1892D4268E3D8500417887 /* PreferenceCenterDecoder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterDecoder.swift; sourceTree = \"<group>\"; };\n\t\t6E1892D6268E3F1800417887 /* PreferenceCenterConfigTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = PreferenceCenterConfigTest.swift; path = AirshipPreferenceCenter/Tests/data/PreferenceCenterConfigTest.swift; sourceTree = SOURCE_ROOT; };\n\t\t6E1892DD2694F1C100417887 /* ChannelRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRegistrar.swift; sourceTree = \"<group>\"; };\n\t\t6E1A15052D6EA3A50056418B /* ThomasFormState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormState.swift; sourceTree = \"<group>\"; };\n\t\t6E1A19212D6F87550056418B /* ThomasFormValidationMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormValidationMode.swift; sourceTree = \"<group>\"; };\n\t\t6E1A19232D6F8BD50056418B /* AirshipInputValidationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipInputValidationTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1A1BB22D6F9D090056418B /* ThomasFormFieldProcessorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormFieldProcessorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1A1D842D70F36D0056418B /* ThomasState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasState.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BAA2B5AE38A00A6489B /* InAppMessageDisplayListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageDisplayListener.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BAF2B5B0C4C00A6489B /* AutomationActionRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationActionRunner.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BB12B5B172F00A6489B /* TestActionRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestActionRunner.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BB62B5B1D9E00A6489B /* TestActiveTimer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestActiveTimer.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BB82B5B20A500A6489B /* InAppMessageDisplayListenerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageDisplayListenerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BBA2B5B20D700A6489B /* TestInAppMessageAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestInAppMessageAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BBC2B5B290200A6489B /* ThomasDisplayListenerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasDisplayListenerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BBE2B5EE19000A6489B /* PreparedSchedule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedSchedule.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BC02B5EE1CF00A6489B /* SchedulePrepareResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SchedulePrepareResult.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BC22B5EE1DE00A6489B /* ScheduleReadyResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleReadyResult.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BC42B5EE1EE00A6489B /* ScheduleExecuteResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScheduleExecuteResult.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BC62B5EE32E00A6489B /* AutomationTriggerProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationTriggerProcessor.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BC82B5EE34600A6489B /* AutomationEventFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationEventFeed.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BD02B5EE84600A6489B /* AutomationScheduleData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationScheduleData.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BD22B5EE8A400A6489B /* AutomationScheduleState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationScheduleState.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BD42B5EE97000A6489B /* TriggeringInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggeringInfo.swift; sourceTree = \"<group>\"; };\n\t\t6E1A9BF62B606CF200A6489B /* AutomationDelayProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationDelayProcessor.swift; sourceTree = \"<group>\"; };\n\t\t6E1B7B122B714FFC00695561 /* LandingPageAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingPageAction.swift; sourceTree = \"<group>\"; };\n\t\t6E1B7B152B715FFE00695561 /* LandingPageActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LandingPageActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1BACDA2719ED7D0038399E /* ScrollLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScrollLayout.swift; sourceTree = \"<group>\"; };\n\t\t6E1BACDC2719FC0A0038399E /* ViewFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewFactory.swift; sourceTree = \"<group>\"; };\n\t\t6E1C9C39271E90EB009EF9EF /* LayoutModelsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutModelsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1C9C4A271F7878009EF9EF /* BackgroundColorViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundColorViewModifier.swift; sourceTree = \"<group>\"; };\n\t\t6E1CBD802BA3A30300519D9C /* AirshipEmbeddedInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipEmbeddedInfo.swift; sourceTree = \"<group>\"; };\n\t\t6E1CBDB52BA4CF0C00519D9C /* MessageDisplayHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDisplayHistory.swift; sourceTree = \"<group>\"; };\n\t\t6E1CBDE22BA51ED100519D9C /* InAppDisplayImpressionRuleProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppDisplayImpressionRuleProvider.swift; sourceTree = \"<group>\"; };\n\t\t6E1CBDFE2BAA1DF200519D9C /* DefaultInAppDisplayImpressionRuleProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultInAppDisplayImpressionRuleProviderTest.swift; sourceTree = \"<group>\"; };\n\t\t6E1CBE2B2BAA2AEA00519D9C /* AirshipAutomation 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"AirshipAutomation 2.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E1CBE2C2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AirshipAutomation.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t6E1D8AB226CC5D490049DACB /* RemoteConfig.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConfig.swift; sourceTree = \"<group>\"; };\n\t\t6E1D8AD726CC66BE0049DACB /* RemoteConfigCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteConfigCache.swift; sourceTree = \"<group>\"; };\n\t\t6E1D90012B2D1AB4004BA130 /* RetryingQueueTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetryingQueueTests.swift; sourceTree = \"<group>\"; };\n\t\t6E1EEE8F2BD81AF300B45A87 /* ContactChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactChannel.swift; sourceTree = \"<group>\"; };\n\t\t6E1F6E832BE6835400CFC7A7 /* UARemoteDataMappingV2toV4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = UARemoteDataMappingV2toV4.xcmappingmodel; sourceTree = \"<group>\"; };\n\t\t6E1F6E872BE683E600CFC7A7 /* UARemoteDataMappingV1toV4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = UARemoteDataMappingV1toV4.xcmappingmodel; sourceTree = \"<group>\"; };\n\t\t6E213B172BC60AF100BF24AE /* AirshipWeakValueHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipWeakValueHolder.swift; sourceTree = \"<group>\"; };\n\t\t6E213B1D2BC7054500BF24AE /* InAppActionRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppActionRunner.swift; sourceTree = \"<group>\"; };\n\t\t6E21852A237D32B30084933A /* EventData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventData.swift; sourceTree = \"<group>\"; };\n\t\t6E2486DE28945D3900657CE4 /* PreferenceCenterState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterState.swift; sourceTree = \"<group>\"; };\n\t\t6E2486EB2894901E00657CE4 /* ConditionsViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConditionsViewModifier.swift; sourceTree = \"<group>\"; };\n\t\t6E2486F02898341400657CE4 /* ConditionsMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConditionsMonitor.swift; sourceTree = \"<group>\"; };\n\t\t6E2486F628984D0D00657CE4 /* PreferenceCenterTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterTheme.swift; sourceTree = \"<group>\"; };\n\t\t6E2486FC2899C06100657CE4 /* PreferenceCenterContentLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterContentLoader.swift; sourceTree = \"<group>\"; };\n\t\t6E28116B2BE40E860040D928 /* FeatureFlagVariablesTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagVariablesTest.swift; sourceTree = \"<group>\"; };\n\t\t6E29474C2AD5DA3B009EC6DD /* LiveActivityRegistrationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityRegistrationStatus.swift; sourceTree = \"<group>\"; };\n\t\t6E2947502AD5DB5A009EC6DD /* LiveActivityRegistrationStatusUpdates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityRegistrationStatusUpdates.swift; sourceTree = \"<group>\"; };\n\t\t6E299FD428D13D00001305A7 /* DefaultAirshipRequestSessionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAirshipRequestSessionTest.swift; sourceTree = \"<group>\"; };\n\t\t6E299FD628D13E54001305A7 /* AirshipRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipRequest.swift; sourceTree = \"<group>\"; };\n\t\t6E299FDA28D14208001305A7 /* AirshipResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipResponse.swift; sourceTree = \"<group>\"; };\n\t\t6E299FDE28D14258001305A7 /* AirshipRequestSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipRequestSession.swift; sourceTree = \"<group>\"; };\n\t\t6E2D6AED26B083DB00B7C226 /* ChannelAudienceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAudienceManager.swift; sourceTree = \"<group>\"; };\n\t\t6E2D6AF126B0B64E00B7C226 /* SubscriptionListAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionListAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E2D6AF326B0C3C500B7C226 /* ChannelAudienceManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAudienceManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E2D6AF526B0C6CA00B7C226 /* TestSubscriptionListAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSubscriptionListAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E2E3CA12B32723C00B8515B /* InAppMessageNativeBridgeExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppMessageNativeBridgeExtension.swift; sourceTree = \"<group>\"; };\n\t\t6E2E3CA42B32726400B8515B /* InAppMessageNativeBridgeExtensionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppMessageNativeBridgeExtensionTest.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5A732A60833700CABD3D /* FeatureFlagManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagManager.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5A752A60871E00CABD3D /* FeatureFlagPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagPayload.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5A852A65F00200CABD3D /* RemoteDataSourceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataSourceStatus.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5A892A66088100CABD3D /* AudienceDeviceInfoProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudienceDeviceInfoProvider.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5A8D2A66FE8900CABD3D /* AirshipTimeCriteria.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipTimeCriteria.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5AB02A67434B00CABD3D /* FeatureFlag.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlag.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5AB62A675ADC00CABD3D /* TestAudienceChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAudienceChecker.swift; sourceTree = \"<group>\"; };\n\t\t6E2F5AB92A675D3600CABD3D /* FeatureFlagsRemoteDataAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagsRemoteDataAccess.swift; sourceTree = \"<group>\"; };\n\t\t6E2FA2882D515189005893E2 /* ThomasEmailRegistrationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasEmailRegistrationOptions.swift; sourceTree = \"<group>\"; };\n\t\t6E2FA28B2D515C5A005893E2 /* ThomasEmailRegistrationOptionsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasEmailRegistrationOptionsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E34C4B02C7D4B6400B00506 /* ExecutionWindowProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionWindowProcessor.swift; sourceTree = \"<group>\"; };\n\t\t6E34C4B22C7D4C6600B00506 /* ExecutionWindowProcessorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExecutionWindowProcessorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E382C20276D3E990091A351 /* ThomasValidationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasValidationTests.swift; sourceTree = \"<group>\"; };\n\t\t6E3B230E28A318CD0005D46E /* PreferenceCenterThemeLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterThemeLoader.swift; sourceTree = \"<group>\"; };\n\t\t6E3B231228A32EC30005D46E /* PreferenceCenterViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterViewExtensions.swift; sourceTree = \"<group>\"; };\n\t\t6E3B32CB27559D8B00B89C7B /* FormInputViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormInputViewModifier.swift; sourceTree = \"<group>\"; };\n\t\t6E3B32CE2755D8C700B89C7B /* LayoutState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutState.swift; sourceTree = \"<group>\"; };\n\t\t6E3CA5402ECB9B7400210C32 /* AirshipDisplayTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDisplayTarget.swift; sourceTree = \"<group>\"; };\n\t\t6E4007132A153AB20013C2DE /* AppRemoteDataProviderDelegateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRemoteDataProviderDelegateTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4007152A153ABE0013C2DE /* ContactRemoteDataProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRemoteDataProviderTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4007172A153AFE0013C2DE /* RemoteDataURLFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataURLFactoryTest.swift; sourceTree = \"<group>\"; };\n\t\t6E40868B2B8931C900435E2C /* AirshipViewSizeReader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipViewSizeReader.swift; sourceTree = \"<group>\"; };\n\t\t6E40868D2B8D036600435E2C /* AirshipEmbeddedSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipEmbeddedSize.swift; sourceTree = \"<group>\"; };\n\t\t6E411B6C2538C4E500FEE4E8 /* UANativeBridge */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = UANativeBridge; sourceTree = \"<group>\"; };\n\t\t6E411B6D2538C4E600FEE4E8 /* UANotificationCategories.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = UANotificationCategories.plist; sourceTree = \"<group>\"; };\n\t\t6E411C752538C60900FEE4E8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C862538C62B00FEE4E8 /* vi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = vi; path = vi.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C872538C62B00FEE4E8 /* ru */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ru; path = ru.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C882538C62B00FEE4E8 /* de */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = de; path = de.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C892538C62B00FEE4E8 /* es-419 */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"es-419\"; path = \"es-419.lproj/UrbanAirship.strings\"; sourceTree = \"<group>\"; };\n\t\t6E411C8A2538C62B00FEE4E8 /* ro */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ro; path = ro.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C8B2538C62B00FEE4E8 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C8C2538C62B00FEE4E8 /* zh-Hant */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hant\"; path = \"zh-Hant.lproj/UrbanAirship.strings\"; sourceTree = \"<group>\"; };\n\t\t6E411C8D2538C62B00FEE4E8 /* ja */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ja; path = ja.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C8E2538C62C00FEE4E8 /* pt-PT */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"pt-PT\"; path = \"pt-PT.lproj/UrbanAirship.strings\"; sourceTree = \"<group>\"; };\n\t\t6E411C8F2538C62C00FEE4E8 /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C902538C62C00FEE4E8 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = \"zh-Hans\"; path = \"zh-Hans.lproj/UrbanAirship.strings\"; sourceTree = \"<group>\"; };\n\t\t6E411C912538C62C00FEE4E8 /* sv */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sv; path = sv.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C922538C62C00FEE4E8 /* it */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = it; path = it.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C932538C62C00FEE4E8 /* pl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pl; path = pl.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C942538C62C00FEE4E8 /* iw */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = iw; path = iw.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C952538C62C00FEE4E8 /* fi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fi; path = fi.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C962538C62C00FEE4E8 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C972538C62D00FEE4E8 /* cs */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = cs; path = cs.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C982538C64700FEE4E8 /* tr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = tr; path = tr.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C992538C64700FEE4E8 /* pt */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = pt; path = pt.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C9A2538C64700FEE4E8 /* no */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = no; path = no.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C9B2538C64700FEE4E8 /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C9C2538C64800FEE4E8 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C9D2538C65100FEE4E8 /* ar */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ar; path = ar.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C9E2538C65400FEE4E8 /* hi */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hi; path = hi.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411C9F2538C65700FEE4E8 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411CA02538C65900FEE4E8 /* id */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = id; path = id.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411CA12538C65C00FEE4E8 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411CA22538C65F00FEE4E8 /* ms */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ms; path = ms.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411CA32538C66100FEE4E8 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/UrbanAirship.strings; sourceTree = \"<group>\"; };\n\t\t6E411CA52538C6A500FEE4E8 /* UARemoteData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UARemoteData.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t6E411CA62538C6A500FEE4E8 /* UARemoteData 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UARemoteData 2.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E411CA82538C6A500FEE4E8 /* UAEvents.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UAEvents.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t6E43204826EA814F009228AB /* AirshipBasement.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipBasement.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6E43218F26EA89B6009228AB /* NativeBridgeDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeBridgeDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E4325C22B7A9D9A00A9B000 /* AirshipPrivacyManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipPrivacyManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4325C42B7AC3F700A9B000 /* TestPush.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPush.swift; sourceTree = \"<group>\"; };\n\t\t6E4325CD2B7AD5A200A9B000 /* AirshipComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipComponent.swift; sourceTree = \"<group>\"; };\n\t\t6E4325D22B7AD96800A9B000 /* AirshipTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4325E82B7AEB1F00A9B000 /* AirshipEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipEvent.swift; sourceTree = \"<group>\"; };\n\t\t6E4325F12B7B1EDA00A9B000 /* SessionEventFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionEventFactory.swift; sourceTree = \"<group>\"; };\n\t\t6E4325F52B7B2F5800A9B000 /* FeatureFlagAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t6E4325F72B7C08A600A9B000 /* AirshipAnalyticsFeed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAnalyticsFeed.swift; sourceTree = \"<group>\"; };\n\t\t6E4326002B7C327C00A9B000 /* AirshipEvents.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipEvents.swift; sourceTree = \"<group>\"; };\n\t\t6E4326042B7C361F00A9B000 /* AssociatedIdentifiersTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssociatedIdentifiersTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4339EE2DFA039B000A7741 /* JSONValueMatcherPredicates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONValueMatcherPredicates.swift; sourceTree = \"<group>\"; };\n\t\t6E4339F02DFA099C000A7741 /* AirshipIvyVersionMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipIvyVersionMatcher.swift; sourceTree = \"<group>\"; };\n\t\t6E44626629E6813A00CB2B56 /* AsyncSerialQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncSerialQueue.swift; sourceTree = \"<group>\"; };\n\t\t6E46A272272B19760089CDE3 /* ViewExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewExtensions.swift; sourceTree = \"<group>\"; };\n\t\t6E46A27B272B63680089CDE3 /* ThomasEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasEnvironment.swift; sourceTree = \"<group>\"; };\n\t\t6E46A27E272B68660089CDE3 /* ThomasDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E475BFD2F5A3709003D8E42 /* VideoGroupState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoGroupState.swift; sourceTree = \"<group>\"; };\n\t\t6E475CB92F5B3E45003D8E42 /* VideoMediaWebViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoMediaWebViewTests.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7A628401D2D00C7BB9D /* Permission.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Permission.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7A728401D2D00C7BB9D /* PermissionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7A828401D2D00C7BB9D /* NotificationRegistrar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationRegistrar.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7A928401D2D00C7BB9D /* Atomic.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Atomic.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7AA28401D2D00C7BB9D /* NotificationPermissionDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationPermissionDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7AB28401D2D00C7BB9D /* PermissionsManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionsManager.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7AC28401D2D00C7BB9D /* RegistrationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegistrationDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7AD28401D2D00C7BB9D /* UNNotificationRegistrar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UNNotificationRegistrar.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7AE28401D2D00C7BB9D /* PermissionStatus.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PermissionStatus.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7AF28401D2E00C7BB9D /* PushNotificationDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushNotificationDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7B028401D2E00C7BB9D /* Badger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Badger.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7B128401D2E00C7BB9D /* APNSRegistrar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = APNSRegistrar.swift; sourceTree = \"<group>\"; };\n\t\t6E49D7CC284028C600C7BB9D /* PermissionsManagerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionsManagerTests.swift; sourceTree = \"<group>\"; };\n\t\t6E4A466028EF447C00A25617 /* MessageCenterUser.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterUser.swift; sourceTree = \"<group>\"; };\n\t\t6E4A466228EF448600A25617 /* MessageCenterStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterStore.swift; sourceTree = \"<group>\"; };\n\t\t6E4A466328EF448600A25617 /* MessageCenterAPIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E4A466428EF448600A25617 /* MessageCenterMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterMessage.swift; sourceTree = \"<group>\"; };\n\t\t6E4A466E28EF44F600A25617 /* AirshipMessageCenterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirshipMessageCenterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6E4A467828EF453400A25617 /* MessageCenterAPIClientTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageCenterAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAirshipRequestSession.swift; sourceTree = \"<group>\"; };\n\t\t6E4A469E28F4A7DF00A25617 /* MessageCenterAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterAction.swift; sourceTree = \"<group>\"; };\n\t\t6E4A46A028F4AEDF00A25617 /* MessageCenterNativeBridgeExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterNativeBridgeExtension.swift; sourceTree = \"<group>\"; };\n\t\t6E4A4FD92A30358F0049FEFC /* TagsActionArgs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsActionArgs.swift; sourceTree = \"<group>\"; };\n\t\t6E4A4FDD2A3132850049FEFC /* AirshipSDKModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSDKModule.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEE222B6B2E09008AEAC1 /* alternate-airship.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = \"alternate-airship.jpg\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE232B6B2E09008AEAC1 /* DefaultAssetFileManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAssetFileManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEE242B6B2E0A008AEAC1 /* airship.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; path = airship.jpg; sourceTree = \"<group>\"; };\n\t\t6E4AEE252B6B2E0A008AEAC1 /* AssetCacheManagerTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssetCacheManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEE262B6B2E0A008AEAC1 /* DefaultAssetDownloaderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAssetDownloaderTest.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEE4A2B6B4358008AEAC1 /* UAAutomation 7.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 7.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE4B2B6B4358008AEAC1 /* UAAutomation 12.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 12.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE4C2B6B4358008AEAC1 /* UAAutomation 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 2.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE4D2B6B4358008AEAC1 /* UAAutomation 11.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 11.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE4E2B6B4358008AEAC1 /* UAAutomation 8.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 8.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE4F2B6B4358008AEAC1 /* UAAutomation 4.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 4.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE502B6B4358008AEAC1 /* UAAutomation 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 3.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE512B6B4358008AEAC1 /* UAAutomation 13.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 13.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE522B6B4358008AEAC1 /* UAAutomation.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UAAutomation.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t6E4AEE532B6B4358008AEAC1 /* UAAutomation 6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 6.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE542B6B4358008AEAC1 /* UAAutomation 5.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 5.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE552B6B4358008AEAC1 /* UAAutomation 9.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 9.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE562B6B4358008AEAC1 /* UAAutomation 10.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAAutomation 10.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6E4AEE622B6B44EA008AEAC1 /* LegacyAutomationStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyAutomationStore.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEE632B6B44EA008AEAC1 /* AutomationStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutomationStore.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEEBB2B6D6380008AEAC1 /* TriggerData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TriggerData.swift; sourceTree = \"<group>\"; };\n\t\t6E4D20712E6B760C00A8D641 /* MessageCenterListViewWithNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterListViewWithNavigation.swift; sourceTree = \"<group>\"; };\n\t\t6E4D22492E6F813700A8D641 /* MessageCenterMessageViewWithNavigation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterMessageViewWithNavigation.swift; sourceTree = \"<group>\"; };\n\t\t6E4D224B2E6F968000A8D641 /* MessageCenterNavigationStack.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterNavigationStack.swift; sourceTree = \"<group>\"; };\n\t\t6E4D224D2E6F96B100A8D641 /* MessageCenterSplitNavigationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterSplitNavigationView.swift; sourceTree = \"<group>\"; };\n\t\t6E4D224F2E6F9CD700A8D641 /* MessageCenterContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterContent.swift; sourceTree = \"<group>\"; };\n\t\t6E4D22512E6FA2F300A8D641 /* MessageCenterBackButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterBackButton.swift; sourceTree = \"<group>\"; };\n\t\t6E4D22532E6FA5EA00A8D641 /* MessageCenterWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterWebView.swift; sourceTree = \"<group>\"; };\n\t\t6E4D225C2E70ADDB00A8D641 /* MessageCenterMessageViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterMessageViewModel.swift; sourceTree = \"<group>\"; };\n\t\t6E4D225E2E70AFE300A8D641 /* MessageCenterListViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterListViewModel.swift; sourceTree = \"<group>\"; };\n\t\t6E4E2E2729CEB222002E7682 /* ContactManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactManagerProtocol.swift; sourceTree = \"<group>\"; };\n\t\t6E4E5B3926E7F91600198175 /* AirshipLocalizationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipLocalizationUtils.swift; sourceTree = \"<group>\"; };\n\t\t6E4E5B3A26E7F91600198175 /* Attributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Attributes.swift; sourceTree = \"<group>\"; };\n\t\t6E5213E22DCA7A3800CF64B9 /* ThomasEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasEvent.swift; sourceTree = \"<group>\"; };\n\t\t6E5214662DCAB03600CF64B9 /* ThomasFormResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormResult.swift; sourceTree = \"<group>\"; };\n\t\t6E5214682DCABFCA00CF64B9 /* ThomasLayoutContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutContext.swift; sourceTree = \"<group>\"; };\n\t\t6E52146A2DCBF9BA00CF64B9 /* AirshipTimerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipTimerProtocol.swift; sourceTree = \"<group>\"; };\n\t\t6E52146C2DCBFAB900CF64B9 /* ThomasPagerTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPagerTracker.swift; sourceTree = \"<group>\"; };\n\t\t6E52146E2DCC075100CF64B9 /* ThomasPagerTrackerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPagerTrackerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E5215212DCEA10F00CF64B9 /* ThomasViewedPageInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasViewedPageInfo.swift; sourceTree = \"<group>\"; };\n\t\t6E524C722C126F5F002CA094 /* AirshipEventType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipEventType.swift; sourceTree = \"<group>\"; };\n\t\t6E524CC22C180A39002CA094 /* AirshipLogPrivacyLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipLogPrivacyLevel.swift; sourceTree = \"<group>\"; };\n\t\t6E524D012C1A2CAE002CA094 /* InAppMessageThemeManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeManager.swift; sourceTree = \"<group>\"; };\n\t\t6E524D032C1A454E002CA094 /* InAppMessageThemeShadow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeShadow.swift; sourceTree = \"<group>\"; };\n\t\t6E55A4D62E1DB4CB00B07DF8 /* ThomasAssociatedLabelResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAssociatedLabelResolver.swift; sourceTree = \"<group>\"; };\n\t\t6E57CE3128DB8BDA00287601 /* LiveActivityUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityUpdate.swift; sourceTree = \"<group>\"; };\n\t\t6E57CE3628DBBD9A00287601 /* LiveActivityRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityRegistry.swift; sourceTree = \"<group>\"; };\n\t\t6E590E6D29A94CA90036DFAB /* AppStateTrackerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppStateTrackerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E5A64C32AAB7D5C00574085 /* AirshipMeteredUsage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipMeteredUsage.swift; sourceTree = \"<group>\"; };\n\t\t6E5A64C72AABBE7100574085 /* MeteredUsageAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeteredUsageAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E5A64CF2AABBEAF00574085 /* AirshipMeteredUsageEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipMeteredUsageEvent.swift; sourceTree = \"<group>\"; };\n\t\t6E5A64D32AABBED600574085 /* MeteredUsageStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MeteredUsageStore.swift; sourceTree = \"<group>\"; };\n\t\t6E5A64D82AABC5A400574085 /* UAMeteredUsage.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UAMeteredUsage.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t6E5ADF812D7682A200A03799 /* StateSubscriptionsModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StateSubscriptionsModifier.swift; sourceTree = \"<group>\"; };\n\t\t6E5ADF832D7682D300A03799 /* ThomasStateTrigger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasStateTrigger.swift; sourceTree = \"<group>\"; };\n\t\t6E5B1A042AFF090B0019CA61 /* SessionTrackerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionTrackerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E60EF6529DF4BB5003F7A8D /* ApplicationMetricsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ApplicationMetricsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E60EF6929DF542B003F7A8D /* AnonContactData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnonContactData.swift; sourceTree = \"<group>\"; };\n\t\t6E6363E129DCD0CF009C358A /* ContactSubscriptionListClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSubscriptionListClient.swift; sourceTree = \"<group>\"; };\n\t\t6E6363E529DCE9A2009C358A /* ContactSubscriptionListAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSubscriptionListAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6363E729DCEB84009C358A /* ContactManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6363E929DCECA1009C358A /* TestContactSubscriptionListAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestContactSubscriptionListAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E6363EB29DDF84B009C358A /* SerialQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SerialQueue.swift; sourceTree = \"<group>\"; };\n\t\t6E64C87F27331ABA000EB887 /* PreferenceDataStoreTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceDataStoreTest.swift; sourceTree = \"<group>\"; };\n\t\t6E65244B2A4FD4270019F353 /* DeviceTagSelectorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTagSelectorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E65244D2A4FD69F0019F353 /* DeviceAudienceSelectorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAudienceSelectorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E65244F2A4FD8D30019F353 /* JSONPredicateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONPredicateTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6541DF2758976D009676CA /* AirshipProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipProgressView.swift; sourceTree = \"<group>\"; };\n\t\t6E65FB5F2C753CB400D9F341 /* EmbeddedViewSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedViewSelector.swift; sourceTree = \"<group>\"; };\n\t\t6E664BA026C43F5400A2C8E5 /* ActivityViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityViewController.swift; sourceTree = \"<group>\"; };\n\t\t6E664BA426C4417400A2C8E5 /* ShareAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ShareAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BC926C4852B00A2C8E5 /* AddCustomEventAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AddCustomEventAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BCF26C4916600A2C8E5 /* AddTagsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagsAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BD226C4917000A2C8E5 /* RemoveTagsAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveTagsAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BD526C4CD8700A2C8E5 /* PasteboardAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PasteboardAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BD626C4CD8700A2C8E5 /* EnableFeatureAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableFeatureAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BD726C4CD8700A2C8E5 /* OpenExternalURLAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OpenExternalURLAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BD826C4CD8700A2C8E5 /* FetchDeviceInfoAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchDeviceInfoAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BE426C5817B00A2C8E5 /* TestContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestContact.swift; sourceTree = \"<group>\"; };\n\t\t6E664BE626C5B21600A2C8E5 /* ModifyAttributesAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ModifyAttributesAction.swift; sourceTree = \"<group>\"; };\n\t\t6E664BE926C6DB7500A2C8E5 /* AirshipUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipUtils.swift; sourceTree = \"<group>\"; };\n\t\t6E66BA7E2D14B61A0083A9FD /* WrappingLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WrappingLayout.swift; sourceTree = \"<group>\"; };\n\t\t6E66DDA52E95A67C00D44555 /* WorkRateLimiterTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WorkRateLimiterTests.swift; sourceTree = \"<group>\"; };\n\t\t6E68028D2B852F6A00F4591F /* AutomationScheduleDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationScheduleDataTest.swift; sourceTree = \"<group>\"; };\n\t\t6E68028F2B8671E700F4591F /* InAppAutomationComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppAutomationComponent.swift; sourceTree = \"<group>\"; };\n\t\t6E6802912B86732200F4591F /* PreferenceCenterComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterComponent.swift; sourceTree = \"<group>\"; };\n\t\t6E6802932B8673F900F4591F /* FeatureFlagComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagComponent.swift; sourceTree = \"<group>\"; };\n\t\t6E6802952B86749900F4591F /* MessageCenterComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterComponent.swift; sourceTree = \"<group>\"; };\n\t\t6E6802972B8675A200F4591F /* DebugComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugComponent.swift; sourceTree = \"<group>\"; };\n\t\t6E68203128EDE3E200A4F90B /* LiveActivityRestorer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityRestorer.swift; sourceTree = \"<group>\"; };\n\t\t6E692AFC29E0CB2F00D96CCC /* JavaScriptCommand.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptCommand.swift; sourceTree = \"<group>\"; };\n\t\t6E692AFE29E0CB4100D96CCC /* JavaScriptCommandDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptCommandDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E692B0229E0CBB500D96CCC /* NativeBridgeExtensionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridgeExtensionDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E698DE826790AC300654DB2 /* PreferenceDataStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PreferenceDataStore.swift; sourceTree = \"<group>\"; };\n\t\t6E698DEA26790AC300654DB2 /* AirshipLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipLogger.swift; sourceTree = \"<group>\"; };\n\t\t6E698DEB26790AC300654DB2 /* AirshipPrivacyManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipPrivacyManager.swift; sourceTree = \"<group>\"; };\n\t\t6E698E02267A799500654DB2 /* AirshipErrors.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipErrors.swift; sourceTree = \"<group>\"; };\n\t\t6E698E08267A7DD900654DB2 /* RemoteDataAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E698E0B267A88D600654DB2 /* EventAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E698E11267A98AB00654DB2 /* ChannelAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E698E3A267BEDC300654DB2 /* DefaultAirshipContact.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultAirshipContact.swift; sourceTree = \"<group>\"; };\n\t\t6E698E3B267BEDC300654DB2 /* AttributesEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AttributesEditor.swift; sourceTree = \"<group>\"; };\n\t\t6E698E3C267BEDC300654DB2 /* TagGroupsEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TagGroupsEditor.swift; sourceTree = \"<group>\"; };\n\t\t6E698E52267BF63A00654DB2 /* AppStateTrackerAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStateTrackerAdapter.swift; sourceTree = \"<group>\"; };\n\t\t6E698E53267BF63A00654DB2 /* AppStateTracker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppStateTracker.swift; sourceTree = \"<group>\"; };\n\t\t6E698E56267BF63B00654DB2 /* ApplicationState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationState.swift; sourceTree = \"<group>\"; };\n\t\t6E698E61267C03C700654DB2 /* TestAppStateTracker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = TestAppStateTracker.swift; path = Support/TestAppStateTracker.swift; sourceTree = \"<group>\"; };\n\t\t6E6A848C2B6854FC006FFB35 /* AutomationDelayProcessorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationDelayProcessorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6A84912B68A571006FFB35 /* AutomationStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationStoreTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6B2DBD2B33B768008BF788 /* AutomationScheduleTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationScheduleTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2412AE995DA00B9DFC9 /* DeferredResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredResolver.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2452AEAFE7E00B9DFC9 /* DeferredAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2492AEAFEB700B9DFC9 /* AirsihpTriggerContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirsihpTriggerContext.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD24D2AEAFEC500B9DFC9 /* AirshipStateOverrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipStateOverrides.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2572AEC598C00B9DFC9 /* DeferredAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2592AEC626B00B9DFC9 /* DeferredResolverTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredResolverTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD26C2AF1AC5700B9DFC9 /* AirshipCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipCache.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2712AF1B05500B9DFC9 /* UAAirshipCache.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = UAAirshipCache.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t6E6BD2752AF1C1D800B9DFC9 /* DeferredFlagResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredFlagResolver.swift; sourceTree = \"<group>\"; };\n\t\t6E6BD2772AF2B97300B9DFC9 /* AirshipTaskSleeper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipTaskSleeper.swift; sourceTree = \"<group>\"; };\n\t\t6E6C3F7E27A20C3C007F55C7 /* ChannelScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelScope.swift; sourceTree = \"<group>\"; };\n\t\t6E6C3F8927A266C0007F55C7 /* CachedValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedValue.swift; sourceTree = \"<group>\"; };\n\t\t6E6C3F8C27A26992007F55C7 /* CachedValueTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedValueTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6C3F9927A47DB4007F55C7 /* PreferenceCenterConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterConfig.swift; sourceTree = \"<group>\"; };\n\t\t6E6C3F9D27A4C3D4007F55C7 /* AirshipJSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipJSON.swift; sourceTree = \"<group>\"; };\n\t\t6E6C84452A5C8CFD00DD83A2 /* AirshipConfigTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipConfigTest.swift; sourceTree = \"<group>\"; };\n\t\t6E6CC38023A3F9B4003D583C /* PushDataManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PushDataManager.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1352683A58D00A2CBD0 /* Dispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Dispatcher.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED13F2683A9F200A2CBD0 /* AirshipDate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipDate.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1422683B8FA00A2CBD0 /* TestDate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDate.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1442683BC7F00A2CBD0 /* TestDispatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDispatcher.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1482683D8E200A2CBD0 /* LocaleManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocaleManager.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED14D2683DBC200A2CBD0 /* AirshipBase64.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipBase64.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED14F2683DBC200A2CBD0 /* AirshipCoreResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipCoreResources.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1502683DBC300A2CBD0 /* AirshipVersion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipVersion.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1512683DBC300A2CBD0 /* ApplicationMetrics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationMetrics.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1532683DBC300A2CBD0 /* UACoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UACoreData.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED1542683DBC300A2CBD0 /* AirshipNetworkChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipNetworkChecker.swift; sourceTree = \"<group>\"; };\n\t\t6E6ED171268448EC00A2CBD0 /* TestNetworkMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNetworkMonitor.swift; sourceTree = \"<group>\"; };\n\t\t6E6EF9E6270625C400D30C35 /* AirshipEventsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipEventsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E7112962880DACB004942E4 /* EventHandlerViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EventHandlerViewModifier.swift; sourceTree = \"<group>\"; };\n\t\t6E7112982880DACB004942E4 /* EnableBehaviorModifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EnableBehaviorModifiers.swift; sourceTree = \"<group>\"; };\n\t\t6E7112992880DACB004942E4 /* VisibilityViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = VisibilityViewModifier.swift; sourceTree = \"<group>\"; };\n\t\t6E71129A2880DACB004942E4 /* StateController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StateController.swift; sourceTree = \"<group>\"; };\n\t\t6E739D6526B9BDC100BC6F6D /* ChannelBulkUpdateAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelBulkUpdateAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E739D6A26B9DFFB00BC6F6D /* AttributePendingMutations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributePendingMutations.swift; sourceTree = \"<group>\"; };\n\t\t6E739D6D26B9F58700BC6F6D /* TagGroupMutations.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagGroupMutations.swift; sourceTree = \"<group>\"; };\n\t\t6E739D7E26BAFCB800BC6F6D /* TestChannelBulkUpdateAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestChannelBulkUpdateAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E739D8126BB33A200BC6F6D /* ChannelBulkUpdateAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelBulkUpdateAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E75F50429C4EAF600E3585A /* AudienceOverridesProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudienceOverridesProvider.swift; sourceTree = \"<group>\"; };\n\t\t6E77CD472D8A225E0057A52C /* AirshipInputValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipInputValidator.swift; sourceTree = \"<group>\"; };\n\t\t6E77CD492D8A225E0057A52C /* SMSValidatorAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMSValidatorAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E77CE462D8A28B10057A52C /* CachingSMSValidatorAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachingSMSValidatorAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E78848E29B9643C00ACAE45 /* AirshipContact.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipContact.swift; sourceTree = \"<group>\"; };\n\t\t6E7DB38228ECDC41002725F6 /* LiveActivity.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivity.swift; sourceTree = \"<group>\"; };\n\t\t6E7DB38A28ECDDD9002725F6 /* LiveActivityRegistryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityRegistryTest.swift; sourceTree = \"<group>\"; };\n\t\t6E7DB38D28ECFCED002725F6 /* AirshipJSONTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipJSONTest.swift; sourceTree = \"<group>\"; };\n\t\t6E7E770C2DDFD0D80042086D /* AirshipAsyncSemaphore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAsyncSemaphore.swift; sourceTree = \"<group>\"; };\n\t\t6E7E770E2DDFD1040042086D /* AirshipAsyncSemaphoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAsyncSemaphoreTest.swift; sourceTree = \"<group>\"; };\n\t\t6E7EACD02AF4192400DA286B /* AirshipCacheTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipCacheTest.swift; sourceTree = \"<group>\"; };\n\t\t6E7EACD22AF4220E00DA286B /* FeatureFlagDeferredResolverTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagDeferredResolverTest.swift; sourceTree = \"<group>\"; };\n\t\t6E82482129A6D9DF00136EA0 /* CancellableValueHolder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CancellableValueHolder.swift; sourceTree = \"<group>\"; };\n\t\t6E82483729A6E1BE00136EA0 /* AirshipCancellable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipCancellable.swift; sourceTree = \"<group>\"; };\n\t\t6E8746482D8A3C64002469D7 /* TestSMSValidatorAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestSMSValidatorAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD6326D594870005D20D /* ChannelRegistrationPayload.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelRegistrationPayload.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD6626D6A39A0005D20D /* AirshipConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipConfig.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD8226D757CA0005D20D /* CloudSite.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloudSite.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD8C26D815780005D20D /* AppIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntegration.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD9126D963B60005D20D /* DefaultAppIntegrationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAppIntegrationDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD9C26DD78CC0005D20D /* DefaultAppIntegrationDelegateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAppIntegrationDelegateTest.swift; sourceTree = \"<group>\"; };\n\t\t6E87BD9E26DDDB250005D20D /* AppIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppIntegrationTests.swift; sourceTree = \"<group>\"; };\n\t\t6E87BDBC26E01FF40005D20D /* ModuleLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModuleLoader.swift; sourceTree = \"<group>\"; };\n\t\t6E87BDFD26E283840005D20D /* Airship.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Airship.swift; sourceTree = \"<group>\"; };\n\t\t6E87BDFE26E283840005D20D /* LogLevel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogLevel.swift; sourceTree = \"<group>\"; };\n\t\t6E87BE0026E283850005D20D /* DeepLinkDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeepLinkDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6E87BE1226E28F570005D20D /* AirshipInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipInstance.swift; sourceTree = \"<group>\"; };\n\t\t6E87BE1526E29BC90005D20D /* TestAirshipInstance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAirshipInstance.swift; sourceTree = \"<group>\"; };\n\t\t6E87BE1726E2C5940005D20D /* TestAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t6E8873992763D8AB00AC248A /* AirshipImageProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipImageProvider.swift; sourceTree = \"<group>\"; };\n\t\t6E887CD0272C5E8400E83363 /* CheckboxState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxState.swift; sourceTree = \"<group>\"; };\n\t\t6E887CD2272C5F5000E83363 /* Checkbox.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Checkbox.swift; sourceTree = \"<group>\"; };\n\t\t6E887CD4272C5F5A00E83363 /* CheckboxController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxController.swift; sourceTree = \"<group>\"; };\n\t\t6E892F2D2E7A193200FB0EC4 /* PreferenceCenterContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterContent.swift; sourceTree = \"<group>\"; };\n\t\t6E8932972E7B665200FB0EC4 /* APNSRegistrationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNSRegistrationResult.swift; sourceTree = \"<group>\"; };\n\t\t6E8932992E7B66B600FB0EC4 /* NotificationRegistrationResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationRegistrationResult.swift; sourceTree = \"<group>\"; };\n\t\t6E8B4BEF2888606400AA336E /* ChannelTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChannelTest.swift; sourceTree = \"<group>\"; };\n\t\t6E8BDA162B62EC9F00711DB8 /* AutomationRemoteDataSubscriber.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutomationRemoteDataSubscriber.swift; sourceTree = \"<group>\"; };\n\t\t6E8BDEFC2A67937E00F816D9 /* FeatureFlagInfoTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagInfoTest.swift; sourceTree = \"<group>\"; };\n\t\t6E8BDEFF2A679CD100F816D9 /* FeatureFlagRemoteDataAccessTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagRemoteDataAccessTest.swift; sourceTree = \"<group>\"; };\n\t\t6E8CE761284137D600CF4B11 /* AirshipPushTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipPushTest.swift; sourceTree = \"<group>\"; };\n\t\t6E8E1C9A26447B3800B11791 /* AirshipLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLock.swift; sourceTree = \"<group>\"; };\n\t\t6E916C562DB30D9E00C676FA /* AirshipWindowFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipWindowFactory.swift; sourceTree = \"<group>\"; };\n\t\t6E91B43B26868A6300DDB1A8 /* CircularRegion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CircularRegion.swift; sourceTree = \"<group>\"; };\n\t\t6E91B43E26868C3400DDB1A8 /* RegionEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RegionEvent.swift; sourceTree = \"<group>\"; };\n\t\t6E91B43F26868C3400DDB1A8 /* ProximityRegion.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ProximityRegion.swift; sourceTree = \"<group>\"; };\n\t\t6E91B4442686911B00DDB1A8 /* EventUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventUtils.swift; sourceTree = \"<group>\"; };\n\t\t6E91B4662689327D00DDB1A8 /* CustomEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomEvent.swift; sourceTree = \"<group>\"; };\n\t\t6E91E42F28EF423300B6F25E /* WorkBackgroundTasks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkBackgroundTasks.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43028EF423300B6F25E /* WorkConditionsMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkConditionsMonitor.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43128EF423300B6F25E /* Worker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Worker.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43228EF423300B6F25E /* WorkRateLimiterActor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WorkRateLimiterActor.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43328EF423300B6F25E /* AirshipWorkManagerProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipWorkManagerProtocol.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43528EF423300B6F25E /* AirshipWorkRequest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipWorkRequest.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43728EF423300B6F25E /* AirshipWorkManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipWorkManager.swift; sourceTree = \"<group>\"; };\n\t\t6E91E43928EF423400B6F25E /* AirshipWorkResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipWorkResult.swift; sourceTree = \"<group>\"; };\n\t\t6E92EC89284933750038802D /* PromptPermissionAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptPermissionAction.swift; sourceTree = \"<group>\"; };\n\t\t6E92EC8C2849378E0038802D /* PermissionPrompter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PermissionPrompter.swift; sourceTree = \"<group>\"; };\n\t\t6E92EC8F284954B10038802D /* ButtonState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonState.swift; sourceTree = \"<group>\"; };\n\t\t6E92ECA0284A79AB0038802D /* PromptPermissionActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PromptPermissionActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6E92ECA2284A7A2A0038802D /* TestPermissionPrompter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestPermissionPrompter.swift; sourceTree = \"<group>\"; };\n\t\t6E92ECA6284AC1120038802D /* EnableFeatureActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnableFeatureActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6E92ECAB284EA7DA0038802D /* AnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E92ECB0284ECE590038802D /* CachedList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedList.swift; sourceTree = \"<group>\"; };\n\t\t6E92ECB3284ED6F10038802D /* CachedListTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachedListTest.swift; sourceTree = \"<group>\"; };\n\t\t6E938DBB2AC39A0500F691D9 /* FeatureFlagAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagAnalyticsTest.swift; sourceTree = \"<group>\"; };\n\t\t6E94760E29BA8FA30025F364 /* ContactManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactManager.swift; sourceTree = \"<group>\"; };\n\t\t6E94761429BBC0230025F364 /* AirshipButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipButton.swift; sourceTree = \"<group>\"; };\n\t\t6E95291F268A6C1500398B54 /* SearchEventTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SearchEventTemplate.swift; sourceTree = \"<group>\"; };\n\t\t6E952922268B812000398B54 /* AccountEventTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AccountEventTemplate.swift; sourceTree = \"<group>\"; };\n\t\t6E952925268B8F6500398B54 /* AssociatedIdentifiers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociatedIdentifiers.swift; sourceTree = \"<group>\"; };\n\t\t6E95292B268B98A200398B54 /* MediaEventTemplate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MediaEventTemplate.swift; sourceTree = \"<group>\"; };\n\t\t6E95292E268BBD7D00398B54 /* RetailEventTemplate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RetailEventTemplate.swift; sourceTree = \"<group>\"; };\n\t\t6E96ECF1293EB7900053CC91 /* AirshipEventData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipEventData.swift; sourceTree = \"<group>\"; };\n\t\t6E96ECF5293FCE080053CC91 /* EventUploadScheduler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventUploadScheduler.swift; sourceTree = \"<group>\"; };\n\t\t6E96ECF9293FDDD90053CC91 /* AirshipSDKExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSDKExtension.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED01294115210053CC91 /* AsyncStream.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncStream.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED09294135500053CC91 /* EventManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventManager.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED0D29416E820053CC91 /* EventManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED0F29416E8F0053CC91 /* EventAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED1129416E990053CC91 /* EventSchedulerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventSchedulerTest.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED1329417A600053CC91 /* EventStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventStoreTest.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED15294197D90053CC91 /* EventUploadTuningInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventUploadTuningInfo.swift; sourceTree = \"<group>\"; };\n\t\t6E96ED192941A0EC0053CC91 /* EventTestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EventTestUtils.swift; sourceTree = \"<group>\"; };\n\t\t6E9752552A5F79E200E67B1A /* ExperimentTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentTest.swift; sourceTree = \"<group>\"; };\n\t\t6E97D6AC2D84B1610001CF7F /* ThomasFormStateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormStateTest.swift; sourceTree = \"<group>\"; };\n\t\t6E97D6AE2D84B1780001CF7F /* ThomasFormDataCollectorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormDataCollectorTest.swift; sourceTree = \"<group>\"; };\n\t\t6E97D6B02D84B1890001CF7F /* ThomasFormDataCollector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormDataCollector.swift; sourceTree = \"<group>\"; };\n\t\t6E97D6B32D84B1D10001CF7F /* ThomasStateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasStateTest.swift; sourceTree = \"<group>\"; };\n\t\t6E97D6B52D84B2330001CF7F /* ThomasFormFieldTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormFieldTest.swift; sourceTree = \"<group>\"; };\n\t\t6E986EE32B448D3C00FBE6A0 /* AutomationEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationEngine.swift; sourceTree = \"<group>\"; };\n\t\t6E986EF82B44D41E00FBE6A0 /* InAppAutomation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppAutomation.swift; sourceTree = \"<group>\"; };\n\t\t6E986EFA2B44D48C00FBE6A0 /* InAppMessaging.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessaging.swift; sourceTree = \"<group>\"; };\n\t\t6E986F052B47319E00FBE6A0 /* DeferredScheduleResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeferredScheduleResult.swift; sourceTree = \"<group>\"; };\n\t\t6E986F0D2B473EC700FBE6A0 /* AutomationRemoteDataAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationRemoteDataAccess.swift; sourceTree = \"<group>\"; };\n\t\t6E9B4873288F0CE000C905B1 /* RateAppAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateAppAction.swift; sourceTree = \"<group>\"; };\n\t\t6E9B4877288F360C00C905B1 /* RateAppActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateAppActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6E9B488A2891962000C905B1 /* PreferenceCenterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterView.swift; sourceTree = \"<group>\"; };\n\t\t6E9B488C2891B43F00C905B1 /* LabeledSectionBreakView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabeledSectionBreakView.swift; sourceTree = \"<group>\"; };\n\t\t6E9B488E2891B57300C905B1 /* CommonSectionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommonSectionView.swift; sourceTree = \"<group>\"; };\n\t\t6E9B48902891B68B00C905B1 /* PreferenceCenterAlertView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterAlertView.swift; sourceTree = \"<group>\"; };\n\t\t6E9B48922891B6A700C905B1 /* ChannelSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelSubscriptionView.swift; sourceTree = \"<group>\"; };\n\t\t6E9B48942891B6B400C905B1 /* ContactSubscriptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSubscriptionView.swift; sourceTree = \"<group>\"; };\n\t\t6E9B48962891B6BF00C905B1 /* ContactSubscriptionGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSubscriptionGroupView.swift; sourceTree = \"<group>\"; };\n\t\t6E9C2B7C2D014426000089A9 /* APNSEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNSEnvironment.swift; sourceTree = \"<group>\"; };\n\t\t6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RuntimeConfig.swift; sourceTree = \"<group>\"; };\n\t\t6E9C2BD92D027B5A000089A9 /* AirshipAppCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAppCredentials.swift; sourceTree = \"<group>\"; };\n\t\t6E9C2BDB2D028030000089A9 /* APNSEnvironmentTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APNSEnvironmentTest.swift; sourceTree = \"<group>\"; };\n\t\t6E9D529726C195F7004EA16B /* ActionRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionRunner.swift; sourceTree = \"<group>\"; };\n\t\t6E9D529A26C1A77C004EA16B /* ActionRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionRegistry.swift; sourceTree = \"<group>\"; };\n\t\t6EA5202227D1364E003011CA /* AirshipDateFormatter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDateFormatter.swift; sourceTree = \"<group>\"; };\n\t\t6EAA61482D5297A2006602F7 /* SubjectExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubjectExtension.swift; sourceTree = \"<group>\"; };\n\t\t6EAC295927580063006DFA63 /* ChannelRegistrarTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRegistrarTest.swift; sourceTree = \"<group>\"; };\n\t\t6EAD3AF72F45305B00FF274E /* AirshipSwizzler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSwizzler.swift; sourceTree = \"<group>\"; };\n\t\t6EAD3AF92F4530AD00FF274E /* AutoIntegration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutoIntegration.swift; sourceTree = \"<group>\"; };\n\t\t6EAD3AFB2F4530E400FF274E /* UAAppIntegrationDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UAAppIntegrationDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6EAD7CE426B216DB00B88EA7 /* DeepLinkAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkAction.swift; sourceTree = \"<group>\"; };\n\t\t6EB11C862697ACBF00DC698F /* ContactOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactOperation.swift; sourceTree = \"<group>\"; };\n\t\t6EB11C882697AF5600DC698F /* TagGroupUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagGroupUpdate.swift; sourceTree = \"<group>\"; };\n\t\t6EB11C8A2697AFC700DC698F /* AttributeUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeUpdate.swift; sourceTree = \"<group>\"; };\n\t\t6EB11C8C2698C50F00DC698F /* AudienceUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudienceUtils.swift; sourceTree = \"<group>\"; };\n\t\t6EB214D12E7DBA5E001A5660 /* FeatureFlagManagerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagManagerProtocol.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A342E81BB6E001A5660 /* AirshipDebugAddEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAddEventView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A352E81BB6E001A5660 /* AirshipDebugAnalyticIdentifierEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAnalyticIdentifierEditorView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A362E81BB6E001A5660 /* AirshipDebugAnalyticsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAnalyticsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A372E81BB6E001A5660 /* AirshipDebugEventDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugEventDetailsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A382E81BB6E001A5660 /* AirshipDebugEventsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugEventsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A3A2E81BB6E001A5660 /* AirshipDebugAppInfoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAppInfoView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A3C2E81BB6E001A5660 /* AirshipDebugExperimentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugExperimentsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A3D2E81BB6E001A5660 /* AirshipDebugInAppExperiencesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugInAppExperiencesView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A3E2E81BB6E001A5660 /* AirshipDebugAutomationsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAutomationsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A402E81BB6E001A5660 /* AirshipDebugChannelSubscriptionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugChannelSubscriptionsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A412E81BB6E001A5660 /* AirshipDebugChannelTagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugChannelTagView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A422E81BB6E001A5660 /* AirshipDebugChannelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugChannelView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A442E81BB6E001A5660 /* AirshipDebugAttributesEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAttributesEditorView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A452E81BB6E001A5660 /* AirshipDebugExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugExtensions.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A462E81BB6E001A5660 /* AirshipDebugTagGroupsEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugTagGroupsEditorView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A472E81BB6E001A5660 /* AirshipJSONDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipJSONDetailsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A482E81BB6E001A5660 /* AirshipJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipJSONView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A492E81BB6E001A5660 /* AirshipToast.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipToast.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A4A2E81BB6E001A5660 /* Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Extensions.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A4C2E81BB6E001A5660 /* AirshipDebugAddEmailChannelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAddEmailChannelView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A4D2E81BB6E001A5660 /* AirshipDebugAddOpenChannelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAddOpenChannelView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A4E2E81BB6E001A5660 /* AirshipDebugAddSMSChannelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAddSMSChannelView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A4F2E81BB6E001A5660 /* AirshipDebugContactSubscriptionEditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugContactSubscriptionEditorView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A502E81BB6E001A5660 /* AirshipDebugContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugContactView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A512E81BB6E001A5660 /* AirshipDebugNamedUserView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugNamedUserView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A532E81BB6E001A5660 /* AirshipDebugFeatureFlagDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugFeatureFlagDetailsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A552E81BB6E001A5660 /* AirshipDebugFeatureFlagView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugFeatureFlagView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A572E81BB6E001A5660 /* AirshipDebugPreferencCenterItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugPreferencCenterItemView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A582E81BB6E001A5660 /* AirshipDebugPreferenceCenterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugPreferenceCenterView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A5B2E81BB6E001A5660 /* AirshipDebugPrivacyManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugPrivacyManagerView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A5D2E81BB6E001A5660 /* AirshipDebugPushDetailsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugPushDetailsView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A5E2E81BB6E001A5660 /* AirshipDebugPushView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugPushView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A5F2E81BB6E001A5660 /* AirshipDebugReceivedPushView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugReceivedPushView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A612E81BB6E001A5660 /* TVDatePicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVDatePicker.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A622E81BB6E001A5660 /* TVSlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TVSlider.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A642E81BB6E001A5660 /* AirshipDebugContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugContentView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A652E81BB6E001A5660 /* AirshipDebugRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugRoute.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A662E81BB6E001A5660 /* AirshipDebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A902E81BFB9001A5660 /* AirshipDebugAddPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAddPropertyView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21A922E81C7A2001A5660 /* AirshipDebugAddStringPropertyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAddStringPropertyView.swift; sourceTree = \"<group>\"; };\n\t\t6EB21AFB2E82169F001A5660 /* AirshipoDebugTriggers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipoDebugTriggers.swift; sourceTree = \"<group>\"; };\n\t\t6EB21B5E2E82FE98001A5660 /* AirshipDebugAudienceSubject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDebugAudienceSubject.swift; sourceTree = \"<group>\"; };\n\t\t6EB3FCEE2ABCFA680018594E /* RemoteDataProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteDataProtocol.swift; sourceTree = \"<group>\"; };\n\t\t6EB4E4A32549F95200E3FFD0 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.15.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };\n\t\t6EB4E4BE2549F9B900E3FFD0 /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/AppleTVOS.platform/Developer/SDKs/AppleTVOS14.0.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };\n\t\t6EB5156D28A42B5800870C5A /* AirshipPreferenceCenterResources.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipPreferenceCenterResources.swift; sourceTree = \"<group>\"; };\n\t\t6EB5157028A4608C00870C5A /* PreferenceCenterViewControllerFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterViewControllerFactory.swift; sourceTree = \"<group>\"; };\n\t\t6EB5158028A47BD700870C5A /* SubscriptionListEdit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionListEdit.swift; sourceTree = \"<group>\"; };\n\t\t6EB5158228A47C7100870C5A /* ScopedSubscriptionListEdit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopedSubscriptionListEdit.swift; sourceTree = \"<group>\"; };\n\t\t6EB5158E28A5B15C00870C5A /* PreferenceThemeLoaderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceThemeLoaderTest.swift; sourceTree = \"<group>\"; };\n\t\t6EB5159128A5B1B400870C5A /* TestLegacyTheme.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = TestLegacyTheme.plist; sourceTree = \"<group>\"; };\n\t\t6EB5159328A5B8E900870C5A /* TestTheme.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TestTheme.plist; sourceTree = \"<group>\"; };\n\t\t6EB5159628A5C54400870C5A /* TestThemeEmpty.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TestThemeEmpty.plist; sourceTree = \"<group>\"; };\n\t\t6EB5159828A5C61D00870C5A /* TestThemeInvalid.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = TestThemeInvalid.plist; sourceTree = \"<group>\"; };\n\t\t6EB515A228A5F1C600870C5A /* PreferenceCenterStateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterStateTest.swift; sourceTree = \"<group>\"; };\n\t\t6EB839452BC83B96006611C4 /* DefaultInAppActionRunnerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultInAppActionRunnerTest.swift; sourceTree = \"<group>\"; };\n\t\t6EB839482BC8898E006611C4 /* AirshipAsyncChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAsyncChannel.swift; sourceTree = \"<group>\"; };\n\t\t6EB8394D2BC8B1F4006611C4 /* AirshipAsyncChannelTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAsyncChannelTest.swift; sourceTree = \"<group>\"; };\n\t\t6EBD12042DA73FD300F678AB /* ValidatableHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ValidatableHelper.swift; sourceTree = \"<group>\"; };\n\t\t6EBFA9AC2D15DA70002BA3E9 /* HashChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashChecker.swift; sourceTree = \"<group>\"; };\n\t\t6EBFA9AE2D15E04B002BA3E9 /* AirshipDeviceAudienceResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDeviceAudienceResult.swift; sourceTree = \"<group>\"; };\n\t\t6EBFA9B02D15F491002BA3E9 /* HashCheckerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HashCheckerTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA4E2B48987700333A87 /* AutomationRemoteDataAccessTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationRemoteDataAccessTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA522B48A2C300333A87 /* AutomationAudience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationAudience.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA542B48B05000333A87 /* ActionAutomationExecutorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionAutomationExecutorTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA5B2B48C2F500333A87 /* AutomationPreparer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationPreparer.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA672B49287100333A87 /* AutomationExecutor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AutomationExecutor.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA6A2B4B698000333A87 /* AutomationExecutorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationExecutorTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA6C2B4B879800333A87 /* AutomationPreparerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationPreparerTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA6E2B4B893500333A87 /* TestDeferredResolver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestDeferredResolver.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA712B4B897B00333A87 /* TestExperimentDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestExperimentDataProvider.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA752B4B8A3A00333A87 /* TestRemoteDataAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestRemoteDataAccess.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA772B4B8A4700333A87 /* TestFrequencyLimitsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestFrequencyLimitsManager.swift; sourceTree = \"<group>\"; };\n\t\t6EC0CA802B4C812A00333A87 /* DisplayAdapter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayAdapter.swift; sourceTree = \"<group>\"; };\n\t\t6EC755982A4E115400851ABB /* DeviceAudienceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAudienceSelector.swift; sourceTree = \"<group>\"; };\n\t\t6EC7559A2A4E129000851ABB /* DeviceTagSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceTagSelector.swift; sourceTree = \"<group>\"; };\n\t\t6EC7559E2A4E5AB200851ABB /* DeviceAudienceChecker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceAudienceChecker.swift; sourceTree = \"<group>\"; };\n\t\t6EC755AE2A4FCD8800851ABB /* AudienceHashSelectorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudienceHashSelectorTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E46D269E2A4C0038CFDD /* AttributeEditorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeEditorTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E46F269E33290038CFDD /* TagGroupsEditorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagGroupsEditorTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E471269E51030038CFDD /* AttributeUpdateTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeUpdateTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E473269E52600038CFDD /* ContactOperationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactOperationTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E47526A5EE910038CFDD /* AudienceUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AudienceUtilsTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E47726A604080038CFDD /* AirshipContactTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipContactTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E48126A60C060038CFDD /* AirshipChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipChannel.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E48426A60CDF0038CFDD /* TestChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestChannel.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E48626A60DD60038CFDD /* TestContactAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestContactAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6EC7E48C26A738C70038CFDD /* ContactConflictEvent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConflictEvent.swift; sourceTree = \"<group>\"; };\n\t\t6EC815AE2F2BBFD100E1C0C6 /* BundleExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BundleExtensions.swift; sourceTree = \"<group>\"; };\n\t\t6EC81D022F2D445500E1C0C6 /* UAInboxDataMappingV1toV4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = UAInboxDataMappingV1toV4.xcmappingmodel; sourceTree = \"<group>\"; };\n\t\t6EC81D042F2D448B00E1C0C6 /* UAInboxDataMappingV2toV4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = UAInboxDataMappingV2toV4.xcmappingmodel; sourceTree = \"<group>\"; };\n\t\t6EC81D072F2D44D700E1C0C6 /* UAInboxDataMappingV3toV4.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = UAInboxDataMappingV3toV4.xcmappingmodel; sourceTree = \"<group>\"; };\n\t\t6EC8249F2F33A4DD00E1C0C6 /* MessageDisplayHistory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageDisplayHistory.swift; sourceTree = \"<group>\"; };\n\t\t6EC824A12F33A5EC00E1C0C6 /* MessageCenterThemeLoaderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterThemeLoaderTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC824A32F33A5F600E1C0C6 /* MessageCenterMessageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterMessageTest.swift; sourceTree = \"<group>\"; };\n\t\t6EC9214D2D82144A000A3A59 /* ThomasFormField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormField.swift; sourceTree = \"<group>\"; };\n\t\t6EC922E02D832BAF000A3A59 /* ThomasFormFieldProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormFieldProcessor.swift; sourceTree = \"<group>\"; };\n\t\t6EC922E22D838DFA000A3A59 /* ThomasFormPayloadGenerator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormPayloadGenerator.swift; sourceTree = \"<group>\"; };\n\t\t6ECB627B2A369F5B0095C85C /* OpenExternalURLActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenExternalURLActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6ECB627D2A36A0770095C85C /* ExternalURLProcessor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExternalURLProcessor.swift; sourceTree = \"<group>\"; };\n\t\t6ECB62812A36A45F0095C85C /* TestURLOpener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestURLOpener.swift; sourceTree = \"<group>\"; };\n\t\t6ECB62832A36A7510095C85C /* DeepLinkActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6ECB62852A36C1EE0095C85C /* NativeBridgeActionHandlerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridgeActionHandlerTest.swift; sourceTree = \"<group>\"; };\n\t\t6ECD4F6C2DD7A7060060EE72 /* RadioInputToggleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioInputToggleLayout.swift; sourceTree = \"<group>\"; };\n\t\t6ECD4F6E2DD7A7090060EE72 /* CheckboxToggleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleLayout.swift; sourceTree = \"<group>\"; };\n\t\t6ECD4F702DD7A7C90060EE72 /* ToggleLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToggleLayout.swift; sourceTree = \"<group>\"; };\n\t\t6ECDDE6B29B7EEE9009D79DB /* AuthToken.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthToken.swift; sourceTree = \"<group>\"; };\n\t\t6ECDDE7329B80462009D79DB /* ChannelAuthTokenProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAuthTokenProvider.swift; sourceTree = \"<group>\"; };\n\t\t6ECDDE7829B804FB009D79DB /* ChannelAuthTokenAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAuthTokenAPIClient.swift; sourceTree = \"<group>\"; };\n\t\t6ED040EA278B5D7C00FCF773 /* ThomasFormPayloadGeneratorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormPayloadGeneratorTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F5242B7EE648000AFC80 /* AirshipBase64Test.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipBase64Test.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F5262B7EE82B000AFC80 /* AirshipJSONUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipJSONUtilsTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F5282B7FC59F000AFC80 /* AirshipColorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipColorTests.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F52A2B7FC5C8000AFC80 /* AirshipIvyVersionMatcherTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipIvyVersionMatcherTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F52C2B7FD403000AFC80 /* JavaScriptCommandTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptCommandTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F52E2B7FD49B000AFC80 /* AirshipURLAllowListTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipURLAllowListTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F5302B7FF819000AFC80 /* AirshipViewUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipViewUtils.swift; sourceTree = \"<group>\"; };\n\t\t6ED2F5342B7FFCD7000AFC80 /* AirshipDateFormatterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDateFormatterTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED5629F2EA9434900C20B55 /* StackImageButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackImageButton.swift; sourceTree = \"<group>\"; };\n\t\t6ED6ECA326ADCA6F00973364 /* BlockAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BlockAction.swift; sourceTree = \"<group>\"; };\n\t\t6ED6ECA626AE05B700973364 /* EmptyAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyAction.swift; sourceTree = \"<group>\"; };\n\t\t6ED735D926C73DC5003B0A7D /* DefaultAirshipChannel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAirshipChannel.swift; sourceTree = \"<group>\"; };\n\t\t6ED735DC26C7401D003B0A7D /* TagEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagEditor.swift; sourceTree = \"<group>\"; };\n\t\t6ED735DF26C74321003B0A7D /* TagEditorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagEditorTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED735E126CAE2D7003B0A7D /* TestChannelRegistrar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestChannelRegistrar.swift; sourceTree = \"<group>\"; };\n\t\t6ED735E326CAE8AA003B0A7D /* TestChannelAudienceManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestChannelAudienceManager.swift; sourceTree = \"<group>\"; };\n\t\t6ED735E526CAEABC003B0A7D /* TestLocaleManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestLocaleManager.swift; sourceTree = \"<group>\"; };\n\t\t6ED7BE5E2D13D9B600B6A124 /* AirshipAutomation 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"AirshipAutomation 3.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6ED7BE5F2D13D9E300B6A124 /* TestCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCache.swift; sourceTree = \"<group>\"; };\n\t\t6ED7BE622D13D9FE00B6A124 /* FeatureFlagResultCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagResultCache.swift; sourceTree = \"<group>\"; };\n\t\t6ED7BE642D13DA0400B6A124 /* FeatureFlagResultCacheTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagResultCacheTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED80792273CA0C800D1F455 /* EnvironmentValues.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EnvironmentValues.swift; sourceTree = \"<group>\"; };\n\t\t6ED80799273DA56000D1F455 /* ThomasViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasViewController.swift; sourceTree = \"<group>\"; };\n\t\t6ED838AA2D0CE9D6009CBB0C /* CompoundDeviceAudienceSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundDeviceAudienceSelector.swift; sourceTree = \"<group>\"; };\n\t\t6ED838AC2D0CEF4A009CBB0C /* CompoundDeviceAudienceSelectorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CompoundDeviceAudienceSelectorTest.swift; sourceTree = \"<group>\"; };\n\t\t6ED838CF2D0D118B009CBB0C /* AutomationCompoundAudience.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationCompoundAudience.swift; sourceTree = \"<group>\"; };\n\t\t6EDAFB252CB463C1000BD4AA /* ButtonLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonLayout.swift; sourceTree = \"<group>\"; };\n\t\t6EDE293E2A9802BF00235738 /* NativeBridgeActionRunner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridgeActionRunner.swift; sourceTree = \"<group>\"; };\n\t\t6EDE5F182B9BD7E700E33D04 /* InboxMessageData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InboxMessageData.swift; sourceTree = \"<group>\"; };\n\t\t6EDE5F4E2BA248FF00E33D04 /* TouchViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TouchViewModifier.swift; sourceTree = \"<group>\"; };\n\t\t6EDE5FC12BADDD96003ADF55 /* PreparedScheduleInfoTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreparedScheduleInfoTest.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1D922B292FB000E23BC4 /* InAppMessageTextInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageTextInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1D952B2A25B400E23BC4 /* InAppMessageButtonInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageButtonInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1D972B2A25C800E23BC4 /* InAppMessageMediaInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageMediaInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1D9B2B2A287A00E23BC4 /* InAppMessageButtonLayoutType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageButtonLayoutType.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1D9D2B2A2A5900E23BC4 /* InAppMessageDisplayContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageDisplayContent.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1DA32B2A2C6F00E23BC4 /* InAppMessageColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageColor.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1DA52B2A300100E23BC4 /* InAppMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessage.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1DAA2B2A6D9900E23BC4 /* InAppMessageTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageTest.swift; sourceTree = \"<group>\"; };\n\t\t6EDF1DB72B2BB2B800E23BC4 /* RetryingQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RetryingQueue.swift; sourceTree = \"<group>\"; };\n\t\t6EDFBBC22F5780BA0043D9EF /* BasementImport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasementImport.swift; sourceTree = \"<group>\"; };\n\t\t6EE49BDC2A09AD3600AB1CF4 /* AirshipNotificationStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipNotificationStatus.swift; sourceTree = \"<group>\"; };\n\t\t6EE49BE02A0AADC900AB1CF4 /* AppRemoteDataProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRemoteDataProviderDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C072A0BE9F600AB1CF4 /* RemoteDataURLFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataURLFactory.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C0B2A0C141800AB1CF4 /* RemoteDataSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataSource.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C0F2A0C142F00AB1CF4 /* RemoteDataInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C172A0C3CC600AB1CF4 /* ContactRemoteDataProviderDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRemoteDataProviderDelegate.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C1B2A0D544B00AB1CF4 /* UARemoteData 3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UARemoteData 3.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\t6EE49C1C2A0D9D8000AB1CF4 /* RemoteDataProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataProvider.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C212A13E32B00AB1CF4 /* RemoteDataProviderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataProviderProtocol.swift; sourceTree = \"<group>\"; };\n\t\t6EE49C252A1446B100AB1CF4 /* RemoteDataTestUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataTestUtils.swift; sourceTree = \"<group>\"; };\n\t\t6EE6529222A7E3B800F7D54D /* Valid-UAInAppMessageHTMLStyle.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = \"Valid-UAInAppMessageHTMLStyle.plist\"; sourceTree = \"<group>\"; };\n\t\t6EE6AA112B4F3003002FEA75 /* InAppMessageAutomationPreparerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageAutomationPreparerTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA142B4F302A002FEA75 /* InAppMessageAutomationExecutorTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageAutomationExecutorTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA182B4F304B002FEA75 /* DisplayAdapterFactoryTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayAdapterFactoryTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA1A2B4F3062002FEA75 /* DisplayCoordinatorManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayCoordinatorManagerTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA1D2B4F31B1002FEA75 /* TestCachedAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestCachedAssets.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA1F2B4F5246002FEA75 /* InAppMessageSceneManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageSceneManager.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA272B50C91E002FEA75 /* AutomationRemoteDataSubscriberTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationRemoteDataSubscriberTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA292B50C976002FEA75 /* TestAutomationEngine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestAutomationEngine.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA2B2B51DB1E002FEA75 /* AutomationSourceInfoStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationSourceInfoStore.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AA372B572897002FEA75 /* AutomationSourceInfoStoreTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationSourceInfoStoreTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAB62B58A945002FEA75 /* ThomasLayoutEventMessageID.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventMessageID.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AABA2B58A946002FEA75 /* ThomasLayoutEventRecorder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventRecorder.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAD42B58A977002FEA75 /* TestThomasLayoutEvent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestThomasLayoutEvent.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAD62B58A9D1002FEA75 /* ThomasLayoutDisplayEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutDisplayEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAD72B58A9D1002FEA75 /* ThomasLayoutEventRecorderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventRecorderTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAD82B58A9D1002FEA75 /* ThomasLayoutPagerCompletedEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPagerCompletedEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAD92B58A9D1002FEA75 /* ThomasLayoutPermissionResultEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPermissionResultEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AADA2B58A9D1002FEA75 /* ThomasLayoutEventTestUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventTestUtils.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AADB2B58A9D1002FEA75 /* ThomasLayoutResolutionEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutResolutionEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AADC2B58A9D2002FEA75 /* ThomasLayoutPageActionEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPageActionEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AADD2B58A9D2002FEA75 /* ThomasLayoutEventMessageIDTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventMessageIDTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AADE2B58A9D2002FEA75 /* ThomasLayoutButtonTapEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutButtonTapEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AADF2B58A9D2002FEA75 /* ThomasLayoutEventContextTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventContextTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE02B58A9D2002FEA75 /* ThomasLayoutFormResultEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutFormResultEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE12B58A9D2002FEA75 /* ThomasLayoutPagerSummaryEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPagerSummaryEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE22B58A9D2002FEA75 /* ThomasLayoutPageViewEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPageViewEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE32B58A9D3002FEA75 /* ThomasLayoutFormDisplayEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutFormDisplayEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE42B58A9D3002FEA75 /* ThomasLayoutGestureEventTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutGestureEventTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE52B58A9D3002FEA75 /* InAppMessageAnalyticsTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InAppMessageAnalyticsTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAE72B58A9D4002FEA75 /* ThomasLayoutPageSwipeEventAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutPageSwipeEventAction.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAFA2B58AA4F002FEA75 /* ThomasLayoutEventSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventSource.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAFB2B58AA4F002FEA75 /* ThomasLayoutEventContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ThomasLayoutEventContext.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AAFE2B58AB66002FEA75 /* LegacyInAppAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInAppAnalytics.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AB002B58B5AE002FEA75 /* LegacyInAppAnalyticsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LegacyInAppAnalyticsTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AB032B59C21A002FEA75 /* CustomDisplayAdapterWrapperTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomDisplayAdapterWrapperTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AB052B59C231002FEA75 /* AirshipLayoutDisplayAdapterTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLayoutDisplayAdapterTest.swift; sourceTree = \"<group>\"; };\n\t\t6EE6AB172B59E80E002FEA75 /* ThomasDisplayListener.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasDisplayListener.swift; sourceTree = \"<group>\"; };\n\t\t6EED67632CDEE75D0087CDCB /* ThomasViewInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasViewInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED676C2CDEE7E50087CDCB /* ThomasPresentationInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPresentationInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED67702CDEE8370087CDCB /* ThomasOrientation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasOrientation.swift; sourceTree = \"<group>\"; };\n\t\t6EED67742CDEE8460087CDCB /* ThomasWindowSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasWindowSize.swift; sourceTree = \"<group>\"; };\n\t\t6EED67782CDEE8790087CDCB /* ThomasShadow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasShadow.swift; sourceTree = \"<group>\"; };\n\t\t6EED677C2CDEE8FE0087CDCB /* ThomasSerializable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasSerializable.swift; sourceTree = \"<group>\"; };\n\t\t6EED67952CDEEA2B0087CDCB /* ThomasShapeInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasShapeInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED67992CDEEA380087CDCB /* ThomasToggleStyleInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasToggleStyleInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED679D2CDEEAA90087CDCB /* AirshipLayout.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLayout.swift; sourceTree = \"<group>\"; };\n\t\t6EED67A12CE1A4780087CDCB /* ThomasColor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasColor.swift; sourceTree = \"<group>\"; };\n\t\t6EED67A52CE1A4FC0087CDCB /* ThomasBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasBorder.swift; sourceTree = \"<group>\"; };\n\t\t6EED67A92CE1A5CC0087CDCB /* ThomasMarkdownOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasMarkdownOptions.swift; sourceTree = \"<group>\"; };\n\t\t6EED67AD2CE1A6B70087CDCB /* ThomasPosition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPosition.swift; sourceTree = \"<group>\"; };\n\t\t6EED67B12CE1A8300087CDCB /* ThomasEventHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasEventHandler.swift; sourceTree = \"<group>\"; };\n\t\t6EED67B52CE1B43C0087CDCB /* ThomasTextAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasTextAppearance.swift; sourceTree = \"<group>\"; };\n\t\t6EED67B92CE1B4E60087CDCB /* ThomasPlatform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPlatform.swift; sourceTree = \"<group>\"; };\n\t\t6EED67BD2CE1B5120087CDCB /* ThomasActionsPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasActionsPayload.swift; sourceTree = \"<group>\"; };\n\t\t6EED67C12CE1B5850087CDCB /* ThomasIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasIcon.swift; sourceTree = \"<group>\"; };\n\t\t6EED67C52CE1B5FF0087CDCB /* ThomasMediaFit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasMediaFit.swift; sourceTree = \"<group>\"; };\n\t\t6EED67E22CE268630087CDCB /* ThomasButtonTapEffect.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasButtonTapEffect.swift; sourceTree = \"<group>\"; };\n\t\t6EED67E62CE268BB0087CDCB /* ThomasButtonClickBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasButtonClickBehavior.swift; sourceTree = \"<group>\"; };\n\t\t6EED67EA2CE269930087CDCB /* ThomasDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasDirection.swift; sourceTree = \"<group>\"; };\n\t\t6EED67EF2CE26CA10087CDCB /* ThomasSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasSize.swift; sourceTree = \"<group>\"; };\n\t\t6EED67F32CE26CB50087CDCB /* ThomasConstrainedSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasConstrainedSize.swift; sourceTree = \"<group>\"; };\n\t\t6EED67F72CE26CE20087CDCB /* ThomasSizeConstraint.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasSizeConstraint.swift; sourceTree = \"<group>\"; };\n\t\t6EED67FB2CE26DAF0087CDCB /* ThomasAttributeName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAttributeName.swift; sourceTree = \"<group>\"; };\n\t\t6EED67FF2CE26DCA0087CDCB /* ThomasAttributeValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAttributeValue.swift; sourceTree = \"<group>\"; };\n\t\t6EED68032CE26E180087CDCB /* ThomasMargin.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasMargin.swift; sourceTree = \"<group>\"; };\n\t\t6EED68072CE26E510087CDCB /* ThomasAutomatedAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAutomatedAction.swift; sourceTree = \"<group>\"; };\n\t\t6EED680C2CE2707E0087CDCB /* ThomasStateAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasStateAction.swift; sourceTree = \"<group>\"; };\n\t\t6EED68102CE271E10087CDCB /* ThomasEnableBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasEnableBehavior.swift; sourceTree = \"<group>\"; };\n\t\t6EED68142CE271F30087CDCB /* ThomasFormSubmitBehavior.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasFormSubmitBehavior.swift; sourceTree = \"<group>\"; };\n\t\t6EED68182CE272710087CDCB /* ThomasAutomatedAccessibilityAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAutomatedAccessibilityAction.swift; sourceTree = \"<group>\"; };\n\t\t6EED681C2CE274290087CDCB /* ThomasAccessibilityAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAccessibilityAction.swift; sourceTree = \"<group>\"; };\n\t\t6EED68202CE2806B0087CDCB /* ThomasAccessibleInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasAccessibleInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED68282CE28C960087CDCB /* ThomasVisibilityInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasVisibilityInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED682C2CE28CBF0087CDCB /* ThomasValidationInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasValidationInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EED68302CE28FA70087CDCB /* ThomasConstants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasConstants.swift; sourceTree = \"<group>\"; };\n\t\t6EED68E42CE3ECC50087CDCB /* ThomasPropertyOverride.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasPropertyOverride.swift; sourceTree = \"<group>\"; };\n\t\t6EEE8BA1290B3EDE00230528 /* AirshipKeychainAccess.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipKeychainAccess.swift; sourceTree = \"<group>\"; };\n\t\t6EF02DEF2714EB500008B6C9 /* Thomas.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Thomas.swift; sourceTree = \"<group>\"; };\n\t\t6EF1401A2A2671ED009A125D /* AirshipDeviceID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDeviceID.swift; sourceTree = \"<group>\"; };\n\t\t6EF1401E2A268CE6009A125D /* TestKeychainAccess.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestKeychainAccess.swift; sourceTree = \"<group>\"; };\n\t\t6EF140202A269074009A125D /* AirshipDeviceIDTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipDeviceIDTest.swift; sourceTree = \"<group>\"; };\n\t\t6EF1933B2838062B005F192A /* AirshipLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLogHandler.swift; sourceTree = \"<group>\"; };\n\t\t6EF1933D28380644005F192A /* DefaultLogHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultLogHandler.swift; sourceTree = \"<group>\"; };\n\t\t6EF1E9252CD005E2005EAA07 /* PagerUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerUtils.swift; sourceTree = \"<group>\"; };\n\t\t6EF1E9292CD00698005EAA07 /* PagerSwipeDirection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerSwipeDirection.swift; sourceTree = \"<group>\"; };\n\t\t6EF27DD827306C9100548DA3 /* AirshipToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipToggle.swift; sourceTree = \"<group>\"; };\n\t\t6EF27DE22730E6F900548DA3 /* RadioInputController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioInputController.swift; sourceTree = \"<group>\"; };\n\t\t6EF27DE52730E77300548DA3 /* RadioInputState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioInputState.swift; sourceTree = \"<group>\"; };\n\t\t6EF27DE82730E85700548DA3 /* RadioInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioInput.swift; sourceTree = \"<group>\"; };\n\t\t6EF553E22B7EE40B00901A22 /* AirshipLocalizationUtilsTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipLocalizationUtilsTest.swift; sourceTree = \"<group>\"; };\n\t\t6EF66D8C276461DA00ABCB76 /* UrlInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UrlInfo.swift; sourceTree = \"<group>\"; };\n\t\t6EF66D902769B69C00ABCB76 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB77295525C3008AD187 /* ChannelAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB79295525CD008AD187 /* ChannelCaptureTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelCaptureTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB7B295525DF008AD187 /* ChannelRegistrationPayloadTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelRegistrationPayloadTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB8129555174008AD187 /* FetchDeviceInfoActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FetchDeviceInfoActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB8329561F23008AD187 /* ModifyAttributesActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModifyAttributesActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB8929562474008AD187 /* AddTagsActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddTagsActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFAFB8B29562866008AD187 /* RemoveTagsActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveTagsActionTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFB7B322A14A0EC00133115 /* RemoteDataProviderTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoteDataProviderTest.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D4A27272333005B26F1 /* EmptyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptyView.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D5B27273257005B26F1 /* Shapes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shapes.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D6D27290C0B005B26F1 /* FormController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormController.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D7027290C16005B26F1 /* TextInput.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextInput.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D81272A53AE005B26F1 /* PagerState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PagerState.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D84272A53FA005B26F1 /* Pager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Pager.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D85272A53FA005B26F1 /* PagerIndicator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagerIndicator.swift; sourceTree = \"<group>\"; };\n\t\t6EFD6D86272A53FB005B26F1 /* PagerController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PagerController.swift; sourceTree = \"<group>\"; };\n\t\t6EFE7E3E2A97ED600064AC31 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = \"<group>\"; };\n\t\t83A674F723AA7AA4005C0C8F /* AirshipDebugPushData.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = AirshipDebugPushData.xcdatamodel; sourceTree = \"<group>\"; };\n\t\t8401769326C5671100373AF7 /* JSONMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONMatcher.swift; sourceTree = \"<group>\"; };\n\t\t8401769726C5722400373AF7 /* JSONPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONPredicate.swift; sourceTree = \"<group>\"; };\n\t\t8401769926C5725800373AF7 /* AirshipJSONUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipJSONUtils.swift; sourceTree = \"<group>\"; };\n\t\t8401769B26C5729E00373AF7 /* JSONValueMatcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSONValueMatcher.swift; sourceTree = \"<group>\"; };\n\t\t841E7D11268617C800EA0317 /* PreferenceCenterResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterResponse.swift; sourceTree = \"<group>\"; };\n\t\t84483A67267CF0C000D0DA7D /* PreferenceCenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenter.swift; sourceTree = \"<group>\"; };\n\t\t847B0012267CE558007CD249 /* PreferenceCenterSDKModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterSDKModule.swift; sourceTree = \"<group>\"; };\n\t\t847BFFF4267CD739007CD249 /* AirshipPreferenceCenter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipPreferenceCenter.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t847BFFF7267CD73A007CD249 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t847BFFFC267CD73A007CD249 /* AirshipPreferenceCenterTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirshipPreferenceCenterTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t9908E60D2B000DBA00DB3E2E /* CustomView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomView.swift; sourceTree = \"<group>\"; };\n\t\t9908E6112B0189F800DB3E2E /* ArishipCustomViewManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ArishipCustomViewManager.swift; sourceTree = \"<group>\"; };\n\t\t990A09582B5C677C00244D90 /* InAppMessageWebView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageWebView.swift; sourceTree = \"<group>\"; };\n\t\t990A09932B5CA5B700244D90 /* InAppMessageExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageExtensions.swift; sourceTree = \"<group>\"; };\n\t\t990A09AE2B5DBD0400244D90 /* InAppMessageViewUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageViewUtils.swift; sourceTree = \"<group>\"; };\n\t\t990EB3B02BF59A1500315EAC /* ContactChannelsProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactChannelsProvider.swift; sourceTree = \"<group>\"; };\n\t\t99104DF22BA6689A0040C0FD /* PreferenceCloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCloseButton.swift; sourceTree = \"<group>\"; };\n\t\t99303B052BD97F89002174CA /* ChannelListViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelListViewCell.swift; sourceTree = \"<group>\"; };\n\t\t993AFDFD2C1B2D9A00AA875B /* PreferenceCenterConfig+ContactManagement.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = \"PreferenceCenterConfig+ContactManagement.swift\"; sourceTree = \"<group>\"; };\n\t\t993F91FA2CA37874001B1C2E /* FooterView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FooterView.swift; sourceTree = \"<group>\"; };\n\t\t99560C1D2BAE2FFA00F28BDC /* ChannelTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelTextField.swift; sourceTree = \"<group>\"; };\n\t\t99560C272BB3843600F28BDC /* PreferenceCenterUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterUtils.swift; sourceTree = \"<group>\"; };\n\t\t99560C2A2BB384A700F28BDC /* BackgroundShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundShape.swift; sourceTree = \"<group>\"; };\n\t\t99560C2C2BB3855800F28BDC /* EmptySectionLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySectionLabel.swift; sourceTree = \"<group>\"; };\n\t\t99560C362BB38A5F00F28BDC /* ErrorLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorLabel.swift; sourceTree = \"<group>\"; };\n\t\t9971A8842C125C0200092ED1 /* ContactChannelsProviderTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContactChannelsProviderTest.swift; sourceTree = \"<group>\"; };\n\t\t998572BE2B3CF95D0091E9C9 /* DefaultAssetDownloader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAssetDownloader.swift; sourceTree = \"<group>\"; };\n\t\t998572C02B3CF97B0091E9C9 /* DefaultAssetFileManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultAssetFileManager.swift; sourceTree = \"<group>\"; };\n\t\t999DC85D2B5B721D0048C6AF /* HTMLView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTMLView.swift; sourceTree = \"<group>\"; };\n\t\t99C3CC772BCF3DF700B5BED5 /* SMSValidatorAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMSValidatorAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t99C3CC7C2BCF401B00B5BED5 /* CachingSMSValidatorAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CachingSMSValidatorAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\t99CC0D942BC87868001D93D0 /* AddChannelPromptViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddChannelPromptViewModel.swift; sourceTree = \"<group>\"; };\n\t\t99CF46172B3217C300B6FD9B /* AirshipCachedAssets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipCachedAssets.swift; sourceTree = \"<group>\"; };\n\t\t99CF46192B3217DE00B6FD9B /* AssetCacheManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssetCacheManager.swift; sourceTree = \"<group>\"; };\n\t\t99D1B3272B44F08900447840 /* AirshipSceneManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipSceneManager.swift; sourceTree = \"<group>\"; };\n\t\t99E0BD0C2B4DD4AB00465B37 /* FullscreenView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullscreenView.swift; sourceTree = \"<group>\"; };\n\t\t99E0BD0E2B4DD71A00465B37 /* InAppMessageHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageHostingController.swift; sourceTree = \"<group>\"; };\n\t\t99E433972C9A044C006436B9 /* AirshipResources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AirshipResources.swift; sourceTree = \"<group>\"; };\n\t\t99E6EF692B8E36BA0006326A /* InAppMessageValidation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageValidation.swift; sourceTree = \"<group>\"; };\n\t\t99E6EF6B2B8E3AF60006326A /* InAppMessageContentValidationTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageContentValidationTest.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7962B4F17260099B6F3 /* CloseButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CloseButton.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7982B4F19BA0099B6F3 /* TextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextView.swift; sourceTree = \"<group>\"; };\n\t\t99E8D79A2B4F2FCE0099B6F3 /* InAppMessageTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageTheme.swift; sourceTree = \"<group>\"; };\n\t\t99E8D79C2B4F9E830099B6F3 /* InAppMessageThemeTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeTest.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7BA2B50A7C20099B6F3 /* InAppMessageViewDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageViewDelegate.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7BC2B50AA060099B6F3 /* InAppMessageEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageEnvironment.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7BE2B50C2C10099B6F3 /* ButtonGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonGroup.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7C02B50E5F40099B6F3 /* InAppMessageRootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageRootView.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7C42B5192D40099B6F3 /* MediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaView.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7C82B54A5CB0099B6F3 /* InAppMessageThemeModal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeModal.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7CA2B54A6340099B6F3 /* InAppMessageThemeFullscreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeFullscreen.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7CD2B54A66E0099B6F3 /* InAppMessageThemeBanner.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeBanner.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7CF2B54A68F0099B6F3 /* InAppMessageThemeHTML.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeHTML.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7D42B55B0300099B6F3 /* InAppMessageThemeAdditionalPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeAdditionalPadding.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7D72B55B0440099B6F3 /* InAppMessageThemeButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeButton.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7D92B55B05D0099B6F3 /* InAppMessageThemeText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeText.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7DB2B55C4C20099B6F3 /* InAppMessageThemeMedia.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageThemeMedia.swift; sourceTree = \"<group>\"; };\n\t\t99E8D7DD2B55C73B0099B6F3 /* ThemeExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThemeExtensions.swift; sourceTree = \"<group>\"; };\n\t\t99F4FE5A2BC36A6700754F0F /* PreferenceCenterContentStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreferenceCenterContentStyle.swift; sourceTree = \"<group>\"; };\n\t\t99F662AF2B5DDC2900696098 /* BeveledLoadingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BeveledLoadingView.swift; sourceTree = \"<group>\"; };\n\t\t99F662B12B60425E00696098 /* InAppMessageModalView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageModalView.swift; sourceTree = \"<group>\"; };\n\t\t99F662D12B63047300696098 /* InAppMessageBannerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppMessageBannerView.swift; sourceTree = \"<group>\"; };\n\t\t99FD20A42DEFC35900242551 /* MessageCenterUIKitAppearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageCenterUIKitAppearance.swift; sourceTree = \"<group>\"; };\n\t\tA1B2C3D4E5F60001VIDEOST /* VideoState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoState.swift; sourceTree = \"<group>\"; };\n\t\tA1B2C3D4E5F60003VIDEOCR /* VideoController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoController.swift; sourceTree = \"<group>\"; };\n\t\tA61517B126A9C4C3008A41C4 /* SubscriptionListEditor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = SubscriptionListEditor.swift; path = AirshipCore/Source/SubscriptionListEditor.swift; sourceTree = SOURCE_ROOT; };\n\t\tA61517B426AEEAAB008A41C4 /* SubscriptionListUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SubscriptionListUpdate.swift; path = AirshipCore/Source/SubscriptionListUpdate.swift; sourceTree = SOURCE_ROOT; };\n\t\tA61517C026B009D6008A41C4 /* SubscriptionListAPIClient.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SubscriptionListAPIClient.swift; sourceTree = \"<group>\"; };\n\t\tA61F3A732A5D9D6800EE94CC /* FeatureFlagManagerTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagManagerTest.swift; sourceTree = \"<group>\"; };\n\t\tA62058692A5841330041FBF9 /* AirshipFeatureFlags.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipFeatureFlags.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tA62058702A5841330041FBF9 /* AirshipFeatureFlagsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirshipFeatureFlagsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tA62058802A5842200041FBF9 /* AirshipFeatureFlagsSDKModule.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipFeatureFlagsSDKModule.swift; sourceTree = \"<group>\"; };\n\t\tA629F7D9295B514C00671647 /* PasteboardActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PasteboardActionTest.swift; sourceTree = \"<group>\"; };\n\t\tA62C3353299FD509004DB0DA /* ShareActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareActionTest.swift; sourceTree = \"<group>\"; };\n\t\tA63A567528F449D8004B8951 /* TestWorkManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestWorkManager.swift; sourceTree = \"<group>\"; };\n\t\tA63A567728F457FE004B8951 /* TestWorkRateLimiterActor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestWorkRateLimiterActor.swift; sourceTree = \"<group>\"; };\n\t\tA641E1472BDBBDB400DE6FAA /* AirshipObjectiveC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipObjectiveC.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tA649F50A252F4F39005453CB /* UAInbox 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = \"UAInbox 2.xcdatamodel\"; sourceTree = \"<group>\"; };\n\t\tA658DE0A2727020100007672 /* ImageButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageButton.swift; sourceTree = \"<group>\"; };\n\t\tA658DE182728498900007672 /* AirshipWebview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipWebview.swift; sourceTree = \"<group>\"; };\n\t\tA658DE2A272AFB0400007672 /* AirshipImageLoader.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipImageLoader.swift; sourceTree = \"<group>\"; };\n\t\tA67229B928199D430033F54D /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = \"sourcecode.text-based-dylib-definition\"; name = libz.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS8.3.sdk/usr/lib/libz.tbd; sourceTree = DEVELOPER_DIR; };\n\t\tA67229BB28199D590033F54D /* libsqlite3.tbd */ = {isa = PBXFileReference; lastKnownFileType = \"sourcecode.text-based-dylib-definition\"; name = libsqlite3.tbd; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS8.3.sdk/usr/lib/libsqlite3.tbd; sourceTree = DEVELOPER_DIR; };\n\t\tA67229BD28199D6A0033F54D /* Network.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Network.framework; path = Platforms/WatchOS.platform/Developer/SDKs/WatchOS8.3.sdk/System/Library/Frameworks/Network.framework; sourceTree = DEVELOPER_DIR; };\n\t\tA67EC248279B1A40009089E1 /* ScopedSubscriptionListEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopedSubscriptionListEditor.swift; sourceTree = \"<group>\"; };\n\t\tA67EC24A279B1C34009089E1 /* ScopedSubscriptionListUpdate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScopedSubscriptionListUpdate.swift; sourceTree = \"<group>\"; };\n\t\tA67F87D1268DECCE00EF5F43 /* ContactAPIClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAPIClient.swift; sourceTree = \"<group>\"; };\n\t\tA6849386273290520021675E /* Score.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Score.swift; sourceTree = \"<group>\"; };\n\t\tA684939C273436370021675E /* FontViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontViewModifier.swift; sourceTree = \"<group>\"; };\n\t\tA69C987E27E247B20063A101 /* SubscriptionListAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionListAction.swift; sourceTree = \"<group>\"; };\n\t\tA6A5530926D548AF002B20F6 /* NativeBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridge.swift; sourceTree = \"<group>\"; };\n\t\tA6A5530F26D548D6002B20F6 /* JavaScriptEnvironment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JavaScriptEnvironment.swift; sourceTree = \"<group>\"; };\n\t\tA6A5531226D548FF002B20F6 /* NativeBridgeActionHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeBridgeActionHandler.swift; sourceTree = \"<group>\"; };\n\t\tA6AC44822B923ACB00769ED2 /* TestInAppMessageAutomationExecutor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestInAppMessageAutomationExecutor.swift; sourceTree = \"<group>\"; };\n\t\tA6AF8D2C27E8D4910068C7EE /* SubscriptionListActionTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionListActionTest.swift; sourceTree = \"<group>\"; };\n\t\tA6CDD8CF269491BE0040A673 /* ContactAPIClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactAPIClientTest.swift; sourceTree = \"<group>\"; };\n\t\tA6D6D48E2A0253AA0072A5CA /* ActionArguments.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionArguments.swift; sourceTree = \"<group>\"; };\n\t\tA6D6D49C2A0260780072A5CA /* AirshipAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipAction.swift; sourceTree = \"<group>\"; };\n\t\tA6D6D49E2A02608C0072A5CA /* ActionResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionResult.swift; sourceTree = \"<group>\"; };\n\t\tA6E9AD972D4D12C60091BBAF /* FeatureFlagUpdateStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FeatureFlagUpdateStatus.swift; sourceTree = \"<group>\"; };\n\t\tA6E9ADEC2D4D20300091BBAF /* InAppAutomationUpdateStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InAppAutomationUpdateStatus.swift; sourceTree = \"<group>\"; };\n\t\tA6F0B18F2B837E36002D10A4 /* AutomationEngineTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AutomationEngineTest.swift; sourceTree = \"<group>\"; };\n\t\tC00ED4CE26C729390040C5D0 /* URLAllowList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = URLAllowList.swift; sourceTree = \"<group>\"; };\n\t\tC02D0B6526C1A3E200F673E6 /* ChannelCapture.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelCapture.swift; sourceTree = \"<group>\"; };\n\t\tC088383526E0244C00D40838 /* TestURLAllowList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestURLAllowList.swift; sourceTree = \"<group>\"; };\n\t\tCC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = AirshipConfig.xcconfig; sourceTree = \"<group>\"; };\n\t\tCC64F0541D8B77E3009CEF27 /* AirshipTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirshipTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\tCC64F0581D8B77E3009CEF27 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\tCC64F0611D8B781C009CEF27 /* CustomNotificationCategories.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = CustomNotificationCategories.plist; path = ../CustomNotificationCategories.plist; sourceTree = \"<group>\"; };\n\t\tCC64F0621D8B781C009CEF27 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = Info.plist; path = ../Info.plist; sourceTree = \"<group>\"; };\n\t\tCC64F1451D8B7954009CEF27 /* AirshipConfig-Valid-Legacy.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = \"AirshipConfig-Valid-Legacy.plist\"; sourceTree = \"<group>\"; };\n\t\tCC64F1471D8B7954009CEF27 /* AirshipConfig-Valid.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = \"AirshipConfig-Valid.plist\"; sourceTree = \"<group>\"; };\n\t\tCC64F1491D8B7954009CEF27 /* development-embedded.mobileprovision */ = {isa = PBXFileReference; lastKnownFileType = file; path = \"development-embedded.mobileprovision\"; sourceTree = \"<group>\"; };\n\t\tCC64F14A1D8B7954009CEF27 /* production-embedded.mobileprovision */ = {isa = PBXFileReference; lastKnownFileType = file; path = \"production-embedded.mobileprovision\"; sourceTree = \"<group>\"; };\n\t\tDFD2464D2473404C000FD565 /* DebugSDKModule.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DebugSDKModule.swift; sourceTree = \"<group>\"; };\n\t\tE976486E27A46CC50024518D /* ChannelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelType.swift; sourceTree = \"<group>\"; };\n\t\tE99605A027A071EA00365AE4 /* EmailRegistrationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmailRegistrationOptions.swift; sourceTree = \"<group>\"; };\n\t\tE99605A327A075B800365AE4 /* SMSRegistrationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SMSRegistrationOptions.swift; sourceTree = \"<group>\"; };\n\t\tE99605A627A075C600365AE4 /* OpenRegistrationOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenRegistrationOptions.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t6E128BA02D305F2E00733024 /* Source */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Source; sourceTree = \"<group>\"; };\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t3CA0E29F237CCE2600EE76CF /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E4AEE0C2B6B24D7008AEAC1 /* AirshipAutomation.framework in Frameworks */,\n\t\t\t\t6E29474B2AD47E15009EC6DD /* AirshipPreferenceCenter.framework in Frameworks */,\n\t\t\t\t3CA0E2A0237CCE2600EE76CF /* AirshipCore.framework in Frameworks */,\n\t\t\t\t3C39D3092384C8BE003C50D4 /* AirshipMessageCenter.framework in Frameworks */,\n\t\t\t\t6E2F5A932A67316C00CABD3D /* AirshipFeatureFlags.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t3CA0E3A0237E4A7B00EE76CF /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EE7725B238F179300E79944 /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t494DD9531B0EB677009C134E /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t3261A7F6243CD7F900ADBF6B /* CoreTelephony.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B8727294A9C120064B7BD /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E0B8744294A9C950064B7BD /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B872E294A9C130064B7BD /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E0B8732294A9C130064B7BD /* AirshipAutomation.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E43202026EA814F009228AB /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E43202226EA814F009228AB /* CoreTelephony.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E4A466B28EF44F600A25617 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E4A467228EF44F600A25617 /* AirshipMessageCenter.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFF1267CD739007CD249 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t847B000B267CD85E007CD249 /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFF9267CD73A007CD249 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t847BFFFD267CD73A007CD249 /* AirshipPreferenceCenter.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA62058662A5841330041FBF9 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA61F3A782A5DBA1800EE94CC /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA620586D2A5841330041FBF9 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA62058712A5841330041FBF9 /* AirshipFeatureFlags.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA641E1442BDBBDB400DE6FAA /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tCC64F0511D8B77E3009CEF27 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tCC64F0591D8B77E3009CEF27 /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t0A550D587E2599A4BD33CDF4 /* Pods */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t494DD9581B0EB677009C134E /* Products */,\n\t\t\t);\n\t\t\tname = Pods;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1B05132624AE100C00F5051F /* Locale */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E07688B29F9F0830014E2A9 /* AirshipLocaleManagerTest.swift */,\n\t\t\t);\n\t\t\tname = Locale;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t1B8DCF452507B9380006E595 /* Recovered References */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = \"Recovered References\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t2797B4172F47685900A7F848 /* StateStore */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2797B4182F47687800A7F848 /* NativeLayoutPersistentDataStore.swift */,\n\t\t\t);\n\t\t\tpath = StateStore;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t27F1E18B2F0E825100E317DB /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EE6AADF2B58A9D2002FEA75 /* ThomasLayoutEventContextTest.swift */,\n\t\t\t\t6EE6AADD2B58A9D2002FEA75 /* ThomasLayoutEventMessageIDTest.swift */,\n\t\t\t\t6EE6AAD72B58A9D1002FEA75 /* ThomasLayoutEventRecorderTest.swift */,\n\t\t\t\t6EE6AAF92B58A9DD002FEA75 /* Events */,\n\t\t\t\t6E1A9BBC2B5B290200A6489B /* ThomasDisplayListenerTest.swift */,\n\t\t\t);\n\t\t\tpath = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t27F1E20E2F0E911600E317DB /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t27F1E20F2F0E916F00E317DB /* Events */,\n\t\t\t\t6EE6AAFB2B58AA4F002FEA75 /* ThomasLayoutEventContext.swift */,\n\t\t\t\t6EE6AABA2B58A946002FEA75 /* ThomasLayoutEventRecorder.swift */,\n\t\t\t\t6EE6AAB62B58A945002FEA75 /* ThomasLayoutEventMessageID.swift */,\n\t\t\t\t6EE6AAFA2B58AA4F002FEA75 /* ThomasLayoutEventSource.swift */,\n\t\t\t\t6EE6AB172B59E80E002FEA75 /* ThomasDisplayListener.swift */,\n\t\t\t);\n\t\t\tname = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t27F1E20F2F0E916F00E317DB /* Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t27F1E1F32F0E910B00E317DB /* ThomasLayoutButtonTapEvent.swift */,\n\t\t\t\t27F1E1F42F0E910B00E317DB /* ThomasLayoutDisplayEvent.swift */,\n\t\t\t\t27F1E1F52F0E910B00E317DB /* ThomasLayoutEvent.swift */,\n\t\t\t\t27F1E1F62F0E910B00E317DB /* ThomasLayoutFormDisplayEvent.swift */,\n\t\t\t\t27F1E1F72F0E910B00E317DB /* ThomasLayoutFormResultEvent.swift */,\n\t\t\t\t27F1E1F82F0E910B00E317DB /* ThomasLayoutGestureEvent.swift */,\n\t\t\t\t27F1E1F92F0E910B00E317DB /* ThomasLayoutPageActionEvent.swift */,\n\t\t\t\t27F1E1FA2F0E910B00E317DB /* ThomasLayoutPagerCompletedEvent.swift */,\n\t\t\t\t27F1E1FB2F0E910B00E317DB /* ThomasLayoutPagerSummaryEvent.swift */,\n\t\t\t\t27F1E1FC2F0E910B00E317DB /* ThomasLayoutPageSwipeEvent.swift */,\n\t\t\t\t27F1E1FD2F0E910B00E317DB /* ThomasLayoutPageViewEvent.swift */,\n\t\t\t\t27F1E1FE2F0E910B00E317DB /* ThomasLayoutPermissionResultEvent.swift */,\n\t\t\t\t27F1E1FF2F0E910B00E317DB /* ThomasLayoutResolutionEvent.swift */,\n\t\t\t);\n\t\t\tname = Events;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t322AAB1F2B5ACA3400652DAC /* Contact management */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t99CC0D942BC87868001D93D0 /* AddChannelPromptViewModel.swift */,\n\t\t\t\t32DDC0562AF1055300D23EBE /* AddChannelPromptView.swift */,\n\t\t\t\t993F91FA2CA37874001B1C2E /* FooterView.swift */,\n\t\t\t\t322AAB1C2B5A869000652DAC /* ContactManagementView.swift */,\n\t\t\t\t322AAB202B5ACB2800652DAC /* ChannelListView.swift */,\n\t\t\t\t99303B052BD97F89002174CA /* ChannelListViewCell.swift */,\n\t\t\t\t99560C292BB3848A00F28BDC /* Component Views */,\n\t\t\t);\n\t\t\tpath = \"Contact management\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3231127A29D5E67200CF0D86 /* Limits */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3231127B29D5E67200CF0D86 /* FrequencyLimitStore.swift */,\n\t\t\t\t3231127C29D5E67200CF0D86 /* FrequencyLimitManager.swift */,\n\t\t\t\t3231127D29D5E67200CF0D86 /* Occurrence.swift */,\n\t\t\t\t3231128029D5E67200CF0D86 /* FrequencyConstraint.swift */,\n\t\t\t\t3231128129D5E67200CF0D86 /* FrequencyChecker.swift */,\n\t\t\t);\n\t\t\tpath = Limits;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3231128929D5E69400CF0D86 /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1CBE2A2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodeld */,\n\t\t\t\t6E4AEE492B6B4358008AEAC1 /* UAAutomation.xcdatamodeld */,\n\t\t\t\t3231128A29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodeld */,\n\t\t\t);\n\t\t\tpath = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3231128D29D5E6C600CF0D86 /* Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1B7B142B715FE400695561 /* Actions */,\n\t\t\t\t459D405B2092476100C40E2D /* InAppMessaging */,\n\t\t\t\t6E15283C2B4F0B6600DF1377 /* Action Automation */,\n\t\t\t\t60FCA3082B513634005C9232 /* Legacy */,\n\t\t\t\t6EC0CA742B4B8A2800333A87 /* Test Utils */,\n\t\t\t\t6EC0CA4D2B48985B00333A87 /* RemoteData */,\n\t\t\t\t6E0F4BEA2B326F6B00673CA4 /* Automation */,\n\t\t\t\t6E1D8FFF2B2D1A94004BA130 /* Utils */,\n\t\t\t\t6EDF1DA92B2A6D7B00E23BC4 /* InAppMessage */,\n\t\t\t\t3231128E29D5E6C600CF0D86 /* Limits */,\n\t\t\t\t60A364EC2C3479BF00B05E26 /* ExecutionWindowTest.swift */,\n\t\t\t);\n\t\t\tpath = Tests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3231128E29D5E6C600CF0D86 /* Limits */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3231128F29D5E6C600CF0D86 /* FrequencyLimitManagerTest.swift */,\n\t\t\t);\n\t\t\tpath = Limits;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t32B5BE2D28F8A7D600F2254B /* Views */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4D225B2E70AD2200A8D641 /* Shared */,\n\t\t\t\t6E4D225A2E70ACD200A8D641 /* MessageList */,\n\t\t\t\t6E4D22592E70ACA200A8D641 /* MessageView */,\n\t\t\t\t6E4D20732E6B7A2700A8D641 /* MessageCenter */,\n\t\t\t\t32B5BE4828F8B66500F2254B /* MessageCenterViewController.swift */,\n\t\t\t);\n\t\t\tpath = Views;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t32B632852906CA17000D3E34 /* Theme */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1476CB2F56439A00320A36 /* MessageCenterNavigationAppearance.swift */,\n\t\t\t\t32B632872906CA17000D3E34 /* MessageCenterTheme.swift */,\n\t\t\t\t32B632862906CA17000D3E34 /* MessageCenterThemeLoader.swift */,\n\t\t\t);\n\t\t\tpath = Theme;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t32BACFAA2901827D009DA5C8 /* ViewModel */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32B5BE2B28F8A7D600F2254B /* MessageCenterListItemViewModel.swift */,\n\t\t\t);\n\t\t\tpath = ViewModel;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t32BACFAB290182B7009DA5C8 /* Model */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EDE5F182B9BD7E700E33D04 /* InboxMessageData.swift */,\n\t\t\t\t6E4A466028EF447C00A25617 /* MessageCenterUser.swift */,\n\t\t\t\t6E4A466428EF448600A25617 /* MessageCenterMessage.swift */,\n\t\t\t\t329DFCD42B7E59700039C8C0 /* UAInboxDataMapping.swift */,\n\t\t\t);\n\t\t\tpath = Model;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3C927F8223A42609003C5FC8 /* App State */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E590E6D29A94CA90036DFAB /* AppStateTrackerTest.swift */,\n\t\t\t);\n\t\t\tname = \"App State\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E21A237CCBA600EE76CF /* AirshipDebug */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3CA0E21B237CCBA600EE76CF /* Resources */,\n\t\t\t\t3CA0E227237CCBA600EE76CF /* Source */,\n\t\t\t\t3CA0E24F237CCBA600EE76CF /* Info.plist */,\n\t\t\t);\n\t\t\tpath = AirshipDebug;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E21B237CCBA600EE76CF /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3CA0E222237CCBA600EE76CF /* AirshipDebugEventData.xcdatamodeld */,\n\t\t\t\t83A674F623AA7AA4005C0C8F /* AirshipDebugPushData.xcdatamodeld */,\n\t\t\t);\n\t\t\tpath = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E227237CCBA600EE76CF /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A672E81BB6E001A5660 /* View */,\n\t\t\t\t6E14C9B528B6D54200A55E65 /* Push */,\n\t\t\t\t3CA0E240237CCBA600EE76CF /* Events */,\n\t\t\t\t3CA0E233237CCBA600EE76CF /* AirshipDebugManager.swift */,\n\t\t\t\tDFD2464D2473404C000FD565 /* DebugSDKModule.swift */,\n\t\t\t\t3CB37A1D251151A400E60392 /* AirshipDebugResources.swift */,\n\t\t\t\t6E6802972B8675A200F4591F /* DebugComponent.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E240237CCBA600EE76CF /* Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E21852A237D32B30084933A /* EventData.swift */,\n\t\t\t\t3CA0E241237CCBA600EE76CF /* AirshipEvent.swift */,\n\t\t\t\t3CA0E24D237CCBA600EE76CF /* EventDataManager.swift */,\n\t\t\t);\n\t\t\tpath = Events;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E302237E396100EE76CF /* AirshipMessageCenter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4A466F28EF44F600A25617 /* Tests */,\n\t\t\t\t3CA0E47F237E505200EE76CF /* Info.plist */,\n\t\t\t\t3CA0E303237E396100EE76CF /* Resources */,\n\t\t\t\t3CA0E30B237E396100EE76CF /* Source */,\n\t\t\t);\n\t\t\tpath = AirshipMessageCenter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E303237E396100EE76CF /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t3CA0E304237E396100EE76CF /* UAInbox.xcdatamodeld */,\n\t\t\t\t6EC81D022F2D445500E1C0C6 /* UAInboxDataMappingV1toV4.xcmappingmodel */,\n\t\t\t\t6EC81D042F2D448B00E1C0C6 /* UAInboxDataMappingV2toV4.xcmappingmodel */,\n\t\t\t\t6EC81D072F2D44D700E1C0C6 /* UAInboxDataMappingV3toV4.xcmappingmodel */,\n\t\t\t);\n\t\t\tpath = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t3CA0E30B237E396100EE76CF /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2797B4172F47685900A7F848 /* StateStore */,\n\t\t\t\t32B632852906CA17000D3E34 /* Theme */,\n\t\t\t\t32BACFAB290182B7009DA5C8 /* Model */,\n\t\t\t\t32BACFAA2901827D009DA5C8 /* ViewModel */,\n\t\t\t\t32B5BE2D28F8A7D600F2254B /* Views */,\n\t\t\t\t6E4A466328EF448600A25617 /* MessageCenterAPIClient.swift */,\n\t\t\t\t6E4A466228EF448600A25617 /* MessageCenterStore.swift */,\n\t\t\t\t32F68CED28F07C2C00F7F52A /* MessageCenter.swift */,\n\t\t\t\t32F68CE828F07C2B00F7F52A /* AirshipMessageCenterResources.swift */,\n\t\t\t\t32F68CE928F07C2C00F7F52A /* MessageCenterSDKModule.swift */,\n\t\t\t\t32F68CF428F07C4800F7F52A /* MessageCenterList.swift */,\n\t\t\t\t6E4A469E28F4A7DF00A25617 /* MessageCenterAction.swift */,\n\t\t\t\t6E4A46A028F4AEDF00A25617 /* MessageCenterNativeBridgeExtension.swift */,\n\t\t\t\t32B513552B9F53A500BBE780 /* MessageCenterPredicate.swift */,\n\t\t\t\t6E6802952B86749900F4591F /* MessageCenterComponent.swift */,\n\t\t\t\t27CCF77C2F1656150018058F /* MessageViewAnalytics.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t459D405B2092476100C40E2D /* InAppMessaging */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t459D40562092474A00C40E2D /* Valid-UAInAppMessageBannerStyle.plist */,\n\t\t\t\t459D40542092474300C40E2D /* Valid-UAInAppMessageModalStyle.plist */,\n\t\t\t\t459D4049208FE64D00C40E2D /* Valid-UAInAppMessageFullScreenStyle.plist */,\n\t\t\t\t6EE6529222A7E3B800F7D54D /* Valid-UAInAppMessageHTMLStyle.plist */,\n\t\t\t\t459D40582092475500C40E2D /* Invalid-UAInAppMessageBannerStyle.plist */,\n\t\t\t\t459D405A2092475C00C40E2D /* Invalid-UAInAppMessageModalStyle.plist */,\n\t\t\t\t459D404B208FE6BA00C40E2D /* Invalid-UAInAppMessageFullScreenStyle.plist */,\n\t\t\t);\n\t\t\tpath = InAppMessaging;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t494DD94D1B0EB677009C134E = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E43212826EA844E009228AB /* AirshipBasement */,\n\t\t\t\t6E93769E23761FFA00AA9C2A /* AirshipCore */,\n\t\t\t\t847BFFF5267CD73A007CD249 /* AirshipPreferenceCenter */,\n\t\t\t\t3CA0E21A237CCBA600EE76CF /* AirshipDebug */,\n\t\t\t\t6E0B872B294A9C130064B7BD /* AirshipAutomation */,\n\t\t\t\t3CA0E302237E396100EE76CF /* AirshipMessageCenter */,\n\t\t\t\tCC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */,\n\t\t\t\t0A550D587E2599A4BD33CDF4 /* Pods */,\n\t\t\t\tA620586A2A5841330041FBF9 /* AirshipFeatureFlags */,\n\t\t\t\tA641E1482BDBBDB400DE6FAA /* AirshipObjectiveC */,\n\t\t\t\tE3E85E514DBE69D4C8BF51CE /* Frameworks */,\n\t\t\t\t1B8DCF452507B9380006E595 /* Recovered References */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t494DD9581B0EB677009C134E /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t494DD9571B0EB677009C134E /* AirshipCore.framework */,\n\t\t\t\tCC64F0541D8B77E3009CEF27 /* AirshipTests.xctest */,\n\t\t\t\t3CA0E2AC237CCE2600EE76CF /* AirshipDebug.framework */,\n\t\t\t\t3CA0E423237E4A7B00EE76CF /* AirshipMessageCenter.framework */,\n\t\t\t\t847BFFF4267CD739007CD249 /* AirshipPreferenceCenter.framework */,\n\t\t\t\t847BFFFC267CD73A007CD249 /* AirshipPreferenceCenterTests.xctest */,\n\t\t\t\t6E43204826EA814F009228AB /* AirshipBasement.framework */,\n\t\t\t\t6E4A466E28EF44F600A25617 /* AirshipMessageCenterTests.xctest */,\n\t\t\t\t6E0B872A294A9C120064B7BD /* AirshipAutomation.framework */,\n\t\t\t\t6E0B8731294A9C130064B7BD /* AirshipAutomationTests.xctest */,\n\t\t\t\tA62058692A5841330041FBF9 /* AirshipFeatureFlags.framework */,\n\t\t\t\tA62058702A5841330041FBF9 /* AirshipFeatureFlagsTests.xctest */,\n\t\t\t\tA641E1472BDBBDB400DE6FAA /* AirshipObjectiveC.framework */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t603269512BF4B12B007F7F75 /* AudienceCheck */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t603269522BF4B141007F7F75 /* AdditionalAudienceCheckerApiClient.swift */,\n\t\t\t\t603269542BF4B8D5007F7F75 /* AdditionalAudienceCheckerResolver.swift */,\n\t\t\t);\n\t\t\tpath = AudienceCheck;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t603269562BF754FE007F7F75 /* AudienceCheck */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t603269572BF7550E007F7F75 /* AdditionalAudienceCheckerResolverTest.swift */,\n\t\t\t);\n\t\t\tpath = AudienceCheck;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6058771B2AC73C550021628E /* MeteredUsage */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6058771C2AC73C7E0021628E /* AirshipMeteredUsageTest.swift */,\n\t\t\t\t6058771E2ACAC86A0021628E /* MeteredUsageApiClientTest.swift */,\n\t\t\t);\n\t\t\tname = MeteredUsage;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6079511E2A1CD1880086578F /* Experiments */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6079511F2A1CD19F0086578F /* ExperimentManagerTest.swift */,\n\t\t\t\t6E9752552A5F79E200E67B1A /* ExperimentTest.swift */,\n\t\t\t);\n\t\t\tname = Experiments;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t60C1DB0A2A8B741C00A1D3DA /* Embedded */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60C1DB0B2A8B743B00A1D3DA /* AirshipEmbeddedView.swift */,\n\t\t\t\t60C1DB0C2A8B743B00A1D3DA /* AirshipEmbeddedViewManager.swift */,\n\t\t\t\t60C1DB0D2A8B743B00A1D3DA /* AirshipEmbeddedObserver.swift */,\n\t\t\t\t60C1DB0E2A8B743C00A1D3DA /* EmbeddedView.swift */,\n\t\t\t\t6E40868B2B8931C900435E2C /* AirshipViewSizeReader.swift */,\n\t\t\t\t6E40868D2B8D036600435E2C /* AirshipEmbeddedSize.swift */,\n\t\t\t\t6E1CBD802BA3A30300519D9C /* AirshipEmbeddedInfo.swift */,\n\t\t\t\t6E65FB5F2C753CB400D9F341 /* EmbeddedViewSelector.swift */,\n\t\t\t);\n\t\t\tname = Embedded;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t60D1D9B62B68FB4E00EBE0A4 /* TriggerProcessor */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1A9BC62B5EE32E00A6489B /* AutomationTriggerProcessor.swift */,\n\t\t\t\t60D1D9B72B68FB6400EBE0A4 /* PreparedTrigger.swift */,\n\t\t\t\t6E4AEEBB2B6D6380008AEAC1 /* TriggerData.swift */,\n\t\t\t);\n\t\t\tpath = TriggerProcessor;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t60D3BCC22A1528F100E07524 /* Experimentation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60D3BCC32A1529D800E07524 /* ExperimentDataProvider.swift */,\n\t\t\t\t60D3BCC52A152A0D00E07524 /* ExperimentManager.swift */,\n\t\t\t\t60D3BCCB2A153C0700E07524 /* Experiment.swift */,\n\t\t\t\t60D3BCCF2A154D9400E07524 /* MessageCriteria.swift */,\n\t\t\t);\n\t\t\tname = Experimentation;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t60FCA3032B4F10D9005C9232 /* Legacy */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60FCA3042B4F1110005C9232 /* LegacyInAppMessaging.swift */,\n\t\t\t\t60FCA3062B4F1C73005C9232 /* LegacyInAppMessage.swift */,\n\t\t\t\t6EE6AAFE2B58AB66002FEA75 /* LegacyInAppAnalytics.swift */,\n\t\t\t);\n\t\t\tpath = Legacy;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t60FCA3082B513634005C9232 /* Legacy */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60FCA3092B51364A005C9232 /* LegacyInAppMessageTest.swift */,\n\t\t\t\t60FCA30B2B51492A005C9232 /* LegacyInAppMessagingTest.swift */,\n\t\t\t\t6EE6AB002B58B5AE002FEA75 /* LegacyInAppAnalyticsTest.swift */,\n\t\t\t);\n\t\t\tpath = Legacy;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E062CFF27165642001A74A1 /* Thomas */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E475BFD2F5A3709003D8E42 /* VideoGroupState.swift */,\n\t\t\t\t6EC8249F2F33A4DD00E1C0C6 /* MessageDisplayHistory.swift */,\n\t\t\t\t27F1E20E2F0E911600E317DB /* Analytics */,\n\t\t\t\t6E5213E22DCA7A3800CF64B9 /* ThomasEvent.swift */,\n\t\t\t\t6EED68302CE28FA70087CDCB /* ThomasConstants.swift */,\n\t\t\t\t6E46A27E272B68660089CDE3 /* ThomasDelegate.swift */,\n\t\t\t\t6EF02DEF2714EB500008B6C9 /* Thomas.swift */,\n\t\t\t\t6EED67642CDEE75D0087CDCB /* Types */,\n\t\t\t\t6ED80799273DA56000D1F455 /* ThomasViewController.swift */,\n\t\t\t\t6E3B32CE2755D8C700B89C7B /* LayoutState.swift */,\n\t\t\t\t6EFD6D732729D84D005B26F1 /* Environment */,\n\t\t\t\t6E1C9C43271F74D9009EF9EF /* ViewModifiers */,\n\t\t\t\t6E1C9C42271F744E009EF9EF /* Views */,\n\t\t\t\t6EF66D8C276461DA00ABCB76 /* UrlInfo.swift */,\n\t\t\t);\n\t\t\tname = Thomas;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E0B872B294A9C130064B7BD /* AirshipAutomation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E0B8749294A9CCA0064B7BD /* Info.plist */,\n\t\t\t\t3231128D29D5E6C600CF0D86 /* Tests */,\n\t\t\t\t3231128929D5E69400CF0D86 /* Resources */,\n\t\t\t\t6E0B8740294A9C500064B7BD /* Source */,\n\t\t\t);\n\t\t\tpath = AirshipAutomation;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E0B8740294A9C500064B7BD /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t603269512BF4B12B007F7F75 /* AudienceCheck */,\n\t\t\t\t6E1B7B112B714FED00695561 /* Actions */,\n\t\t\t\t6E986EF82B44D41E00FBE6A0 /* InAppAutomation.swift */,\n\t\t\t\tA6E9ADEC2D4D20300091BBAF /* InAppAutomationUpdateStatus.swift */,\n\t\t\t\t6E986F002B44E86900FBE6A0 /* RemoteData */,\n\t\t\t\t6E0F4BE32B32644200673CA4 /* Automation */,\n\t\t\t\t6EDF1D912B292F8600E23BC4 /* InAppMessage */,\n\t\t\t\t6E15281E2B4DC55800DF1377 /* ActionAutomation */,\n\t\t\t\t3231127A29D5E67200CF0D86 /* Limits */,\n\t\t\t\t6E1D90002B2D1A9B004BA130 /* Utils */,\n\t\t\t\t3231126929D5E4F600CF0D86 /* AirshipAutomationResources.swift */,\n\t\t\t\t6E15282B2B4DE81E00DF1377 /* AutomationSDKModule.swift */,\n\t\t\t\t6E68028F2B8671E700F4591F /* InAppAutomationComponent.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E0F4BE32B32644200673CA4 /* Automation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ED838CF2D0D118B009CBB0C /* AutomationCompoundAudience.swift */,\n\t\t\t\t6E6ED1512683DBC300A2CBD0 /* ApplicationMetrics.swift */,\n\t\t\t\t6EC0CA5F2B491CC800333A87 /* Engine */,\n\t\t\t\t6E0F4BE12B32190400673CA4 /* AutomationSchedule.swift */,\n\t\t\t\t6E0F4BE42B32645600673CA4 /* AutomationTrigger.swift */,\n\t\t\t\t6E0F4BE62B32646000673CA4 /* AutomationDelay.swift */,\n\t\t\t\t6E0F4BE82B3264A400673CA4 /* DeferredAutomationData.swift */,\n\t\t\t\t6EC0CA522B48A2C300333A87 /* AutomationAudience.swift */,\n\t\t\t\t6E1185C52C3328A10071334E /* ExecutionWindow.swift */,\n\t\t\t);\n\t\t\tpath = Automation;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E0F4BEA2B326F6B00673CA4 /* Automation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t603269562BF754FE007F7F75 /* AudienceCheck */,\n\t\t\t\t6EC0CA692B4B696500333A87 /* Engine */,\n\t\t\t\t6E6B2DBD2B33B768008BF788 /* AutomationScheduleTest.swift */,\n\t\t\t\t6E60EF6529DF4BB5003F7A8D /* ApplicationMetricsTest.swift */,\n\t\t\t\t6E68028D2B852F6A00F4591F /* AutomationScheduleDataTest.swift */,\n\t\t\t);\n\t\t\tpath = Automation;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E14C9B528B6D54200A55E65 /* Push */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60653FC02CBD2CD4009CD9A7 /* PushData+CoreDataClass.swift */,\n\t\t\t\t60653FC12CBD2CD4009CD9A7 /* PushData+CoreDataProperties.swift */,\n\t\t\t\t6E6CC38023A3F9B4003D583C /* PushDataManager.swift */,\n\t\t\t\t6E14C9A028B5E4AF00A55E65 /* PushNotification.swift */,\n\t\t\t);\n\t\t\tpath = Push;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E15281E2B4DC55800DF1377 /* ActionAutomation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1528162B4DC3C000DF1377 /* ActionAutomationExecutor.swift */,\n\t\t\t\t6E15281C2B4DC43100DF1377 /* ActionAutomationPreparer.swift */,\n\t\t\t);\n\t\t\tpath = ActionAutomation;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E15282D2B4DED4F00DF1377 /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E15282E2B4DED7A00DF1377 /* InAppMessageAnalytics.swift */,\n\t\t\t\t6E1528302B4DED8900DF1377 /* InAppMessageAnalyticsFactory.swift */,\n\t\t\t\t6E1CBDE22BA51ED100519D9C /* InAppDisplayImpressionRuleProvider.swift */,\n\t\t\t\t6E1CBDB52BA4CF0C00519D9C /* MessageDisplayHistory.swift */,\n\t\t\t);\n\t\t\tpath = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E15283C2B4F0B6600DF1377 /* Action Automation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EC0CA542B48B05000333A87 /* ActionAutomationExecutorTest.swift */,\n\t\t\t\t6E15283D2B4F0B8200DF1377 /* ActionAutomationPreparerTest.swift */,\n\t\t\t);\n\t\t\tpath = \"Action Automation\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E15894E2AFEF18900954A04 /* Session */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E15894F2AFEF19F00954A04 /* SessionTracker.swift */,\n\t\t\t\t6E1589532AFF021D00954A04 /* SessionState.swift */,\n\t\t\t\t6E1589572AFF023400954A04 /* SessionEvent.swift */,\n\t\t\t\t6E4325F12B7B1EDA00A9B000 /* SessionEventFactory.swift */,\n\t\t\t);\n\t\t\tname = Session;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E16208B2B31169F009240B2 /* Display Coordinators */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1528232B4DC60200DF1377 /* DisplayCoordinatorManager.swift */,\n\t\t\t\t6E1620892B311219009240B2 /* DisplayCoordinator.swift */,\n\t\t\t\t6E16208C2B3116AE009240B2 /* ImmediateDisplayCoordinator.swift */,\n\t\t\t\t6E16208E2B3116BA009240B2 /* DefaultDisplayCoordinator.swift */,\n\t\t\t);\n\t\t\tpath = \"Display Coordinators\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1620902B3118BF009240B2 /* Display Coordinators */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1620912B3118D5009240B2 /* ImmediateDisplayCoordinatorTest.swift */,\n\t\t\t\t6E1620942B311D8A009240B2 /* DefaultDisplayCoordinatorTest.swift */,\n\t\t\t\t6EE6AA1A2B4F3062002FEA75 /* DisplayCoordinatorManagerTest.swift */,\n\t\t\t);\n\t\t\tpath = \"Display Coordinators\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1892C5268D159900417887 /* Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB515A128A5F1B600870C5A /* view */,\n\t\t\t\t6EB5159E28A5C87100870C5A /* data */,\n\t\t\t\t6EB5158928A5AEFD00870C5A /* theme */,\n\t\t\t\t6EB5159028A5B19400870C5A /* test data */,\n\t\t\t\t6E1892C9268D16E200417887 /* Info.plist */,\n\t\t\t\t6E1892C7268D15C300417887 /* PreferenceCenterTest.swift */,\n\t\t\t);\n\t\t\tpath = Tests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1A1BB12D6F9D020056418B /* Environment */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E52146E2DCC075100CF64B9 /* ThomasPagerTrackerTest.swift */,\n\t\t\t\t6E97D6B32D84B1D10001CF7F /* ThomasStateTest.swift */,\n\t\t\t\t6E97D6B52D84B2330001CF7F /* ThomasFormFieldTest.swift */,\n\t\t\t\t6E97D6AE2D84B1780001CF7F /* ThomasFormDataCollectorTest.swift */,\n\t\t\t\t6E97D6AC2D84B1610001CF7F /* ThomasFormStateTest.swift */,\n\t\t\t\t6E1A1BB22D6F9D090056418B /* ThomasFormFieldProcessorTest.swift */,\n\t\t\t\t6ED040EA278B5D7C00FCF773 /* ThomasFormPayloadGeneratorTest.swift */,\n\t\t\t);\n\t\t\tpath = Environment;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1B7B112B714FED00695561 /* Actions */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1B7B122B714FFC00695561 /* LandingPageAction.swift */,\n\t\t\t\t60F8E75B2B8F3D4B00460EDF /* CancelSchedulesAction.swift */,\n\t\t\t\t60F8E75F2B8FAF5400460EDF /* ScheduleAction.swift */,\n\t\t\t);\n\t\t\tpath = Actions;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1B7B142B715FE400695561 /* Actions */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1B7B152B715FFE00695561 /* LandingPageActionTest.swift */,\n\t\t\t\t60F8E75D2B8FA12800460EDF /* CancelSchedulesActionTest.swift */,\n\t\t\t\t60F8E7612B8FB2CC00460EDF /* ScheduleActionTest.swift */,\n\t\t\t);\n\t\t\tpath = Actions;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1C9C38271E90D1009EF9EF /* Thomas */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E475CB92F5B3E45003D8E42 /* VideoMediaWebViewTests.swift */,\n\t\t\t\t27F1E18B2F0E825100E317DB /* Analytics */,\n\t\t\t\t6E1A1BB12D6F9D020056418B /* Environment */,\n\t\t\t\t6E2FA28A2D515C56005893E2 /* Types */,\n\t\t\t\t6E1C9C39271E90EB009EF9EF /* LayoutModelsTest.swift */,\n\t\t\t\t6E382C20276D3E990091A351 /* ThomasValidationTests.swift */,\n\t\t\t\t605073892B32F85100209B51 /* ThomasViewModelTest.swift */,\n\t\t\t\t6050738F2B347B6400209B51 /* ThomasPresentationModelCodingTest.swift */,\n\t\t\t\t2726505A2E81B80E000B6FA3 /* PagerControllerTest.swift */,\n\t\t\t);\n\t\t\tname = Thomas;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1C9C42271F744E009EF9EF /* Views */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ED5629F2EA9434900C20B55 /* StackImageButton.swift */,\n\t\t\t\t6E0104FE2DDF9B26009D651F /* IconView.swift */,\n\t\t\t\t6E66BA7E2D14B61A0083A9FD /* WrappingLayout.swift */,\n\t\t\t\t6EF1E9292CD00698005EAA07 /* PagerSwipeDirection.swift */,\n\t\t\t\t6EF1E9252CD005E2005EAA07 /* PagerUtils.swift */,\n\t\t\t\t6EFD6D86272A53FB005B26F1 /* PagerController.swift */,\n\t\t\t\t6EFD6D85272A53FA005B26F1 /* PagerIndicator.swift */,\n\t\t\t\t32F97AC029E5986B00FED65F /* StoryIndicator.swift */,\n\t\t\t\t6EDAFB252CB463C1000BD4AA /* ButtonLayout.swift */,\n\t\t\t\t60C1DB0A2A8B741C00A1D3DA /* Embedded */,\n\t\t\t\t6E0F557E2AE03BB900E7CB8C /* ThomasAsyncImage.swift */,\n\t\t\t\t6E94761429BBC0230025F364 /* AirshipButton.swift */,\n\t\t\t\t6E71129A2880DACB004942E4 /* StateController.swift */,\n\t\t\t\t6E46A272272B19760089CDE3 /* ViewExtensions.swift */,\n\t\t\t\t6EF27DD627306C6900548DA3 /* Forms */,\n\t\t\t\t6EFD6D5B27273257005B26F1 /* Shapes.swift */,\n\t\t\t\tA658DE0A2727020100007672 /* ImageButton.swift */,\n\t\t\t\t6E062D02271656DE001A74A1 /* Container.swift */,\n\t\t\t\t6E062D04271656F8001A74A1 /* LinearLayout.swift */,\n\t\t\t\t6E062D0627165709001A74A1 /* Label.swift */,\n\t\t\t\t6E062D082716571F001A74A1 /* LabelButton.swift */,\n\t\t\t\t6E062D0C2718B505001A74A1 /* ViewConstraints.swift */,\n\t\t\t\t6E1BACDA2719ED7D0038399E /* ScrollLayout.swift */,\n\t\t\t\t6E1BACDC2719FC0A0038399E /* ViewFactory.swift */,\n\t\t\t\t32515866272AFB2E00DF8B44 /* Media.swift */,\n\t\t\t\t6329102D2DD8103200B13C6C /* NativeVideoPlayer.swift */,\n\t\t\t\t632913F92DE547A500B13C6C /* VideoMediaNativeView.swift */,\n\t\t\t\t32515868272AFB2E00DF8B44 /* VideoMediaWebView.swift */,\n\t\t\t\t6EFD6D4A27272333005B26F1 /* EmptyView.swift */,\n\t\t\t\t3215CA9C2739349700B7D97E /* ModalView.swift */,\n\t\t\t\t324D3BFE273E6B4500058EE4 /* BannerView.swift */,\n\t\t\t\tA658DE182728498900007672 /* AirshipWebview.swift */,\n\t\t\t\t9908E6102B01841A00DB3E2E /* Custom */,\n\t\t\t\t6E152BC92743235800788402 /* Icons.swift */,\n\t\t\t\t6E6541DF2758976D009676CA /* AirshipProgressView.swift */,\n\t\t\t\t6EF66D902769B69C00ABCB76 /* RootView.swift */,\n\t\t\t\t320AD3A529E7FA2000D66106 /* PagerGestureMap.swift */,\n\t\t\t\t6EFD6D84272A53FA005B26F1 /* Pager.swift */,\n\t\t\t\t275D32AA2EF955AD00B75760 /* AirshipSimpleLayoutView.swift */,\n\t\t\t\t275D32AB2EF955AD00B75761 /* AirshipSimpleLayoutViewModel.swift */,\n\t\t\t);\n\t\t\tname = Views;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1C9C43271F74D9009EF9EF /* ViewModifiers */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E7112982880DACB004942E4 /* EnableBehaviorModifiers.swift */,\n\t\t\t\t6E5ADF812D7682A200A03799 /* StateSubscriptionsModifier.swift */,\n\t\t\t\t6E7112962880DACB004942E4 /* EventHandlerViewModifier.swift */,\n\t\t\t\t6E7112992880DACB004942E4 /* VisibilityViewModifier.swift */,\n\t\t\t\t6E3B32CB27559D8B00B89C7B /* FormInputViewModifier.swift */,\n\t\t\t\tA684939C273436370021675E /* FontViewModifier.swift */,\n\t\t\t\t6E1C9C4A271F7878009EF9EF /* BackgroundColorViewModifier.swift */,\n\t\t\t\t6EDE5F4E2BA248FF00E33D04 /* TouchViewModifier.swift */,\n\t\t\t);\n\t\t\tname = ViewModifiers;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1D8FFF2B2D1A94004BA130 /* Utils */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E7E770E2DDFD1040042086D /* AirshipAsyncSemaphoreTest.swift */,\n\t\t\t\t605073822B2CD38200209B51 /* ActiveTimerTest.swift */,\n\t\t\t\t6E1D90012B2D1AB4004BA130 /* RetryingQueueTests.swift */,\n\t\t\t);\n\t\t\tpath = Utils;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E1D90002B2D1A9B004BA130 /* Utils */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E7E770C2DDFD0D80042086D /* AirshipAsyncSemaphore.swift */,\n\t\t\t\t6EDF1DB72B2BB2B800E23BC4 /* RetryingQueue.swift */,\n\t\t\t\t6E1528322B4DF2E600DF1377 /* ScheduleConditionsChangedNotifier.swift */,\n\t\t\t\t6068E03A2B2CBCF200349E82 /* ActiveTimer.swift */,\n\t\t\t\t6E1A9BAF2B5B0C4C00A6489B /* AutomationActionRunner.swift */,\n\t\t\t);\n\t\t\tpath = Utils;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E2486FA2899BB0E00657CE4 /* view */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t99560C272BB3843600F28BDC /* PreferenceCenterUtils.swift */,\n\t\t\t\t322AAB1F2B5ACA3400652DAC /* Contact management */,\n\t\t\t\t6E2486F02898341400657CE4 /* ConditionsMonitor.swift */,\n\t\t\t\t6E2486EB2894901E00657CE4 /* ConditionsViewModifier.swift */,\n\t\t\t\t6E9B488E2891B57300C905B1 /* CommonSectionView.swift */,\n\t\t\t\t6E9B488C2891B43F00C905B1 /* LabeledSectionBreakView.swift */,\n\t\t\t\t6E9B48902891B68B00C905B1 /* PreferenceCenterAlertView.swift */,\n\t\t\t\t6E9B48962891B6BF00C905B1 /* ContactSubscriptionGroupView.swift */,\n\t\t\t\t6E9B48922891B6A700C905B1 /* ChannelSubscriptionView.swift */,\n\t\t\t\t6E9B48942891B6B400C905B1 /* ContactSubscriptionView.swift */,\n\t\t\t\t99F4FE5A2BC36A6700754F0F /* PreferenceCenterContentStyle.swift */,\n\t\t\t\t6E9B488A2891962000C905B1 /* PreferenceCenterView.swift */,\n\t\t\t\t6E2486FC2899C06100657CE4 /* PreferenceCenterContentLoader.swift */,\n\t\t\t\t6E892F2D2E7A193200FB0EC4 /* PreferenceCenterContent.swift */,\n\t\t\t\t6E2486DE28945D3900657CE4 /* PreferenceCenterState.swift */,\n\t\t\t\t6E3B231228A32EC30005D46E /* PreferenceCenterViewExtensions.swift */,\n\t\t\t\t6EB5157028A4608C00870C5A /* PreferenceCenterViewControllerFactory.swift */,\n\t\t\t);\n\t\t\tpath = view;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E2D6AF026B0B62900B7C226 /* Subscription Lists */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E2D6AF126B0B64E00B7C226 /* SubscriptionListAPIClientTest.swift */,\n\t\t\t);\n\t\t\tname = \"Subscription Lists\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E2E3CA02B3271BC00B8515B /* View */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t99E0BD0C2B4DD4AB00465B37 /* FullscreenView.swift */,\n\t\t\t\t99F662B12B60425E00696098 /* InAppMessageModalView.swift */,\n\t\t\t\t999DC85D2B5B721D0048C6AF /* HTMLView.swift */,\n\t\t\t\t99F662D12B63047300696098 /* InAppMessageBannerView.swift */,\n\t\t\t\t99E8D7962B4F17260099B6F3 /* CloseButton.swift */,\n\t\t\t\t990A09932B5CA5B700244D90 /* InAppMessageExtensions.swift */,\n\t\t\t\t99E8D7BE2B50C2C10099B6F3 /* ButtonGroup.swift */,\n\t\t\t\t990A09582B5C677C00244D90 /* InAppMessageWebView.swift */,\n\t\t\t\t990A09AE2B5DBD0400244D90 /* InAppMessageViewUtils.swift */,\n\t\t\t\t99E8D7982B4F19BA0099B6F3 /* TextView.swift */,\n\t\t\t\t99E8D7C02B50E5F40099B6F3 /* InAppMessageRootView.swift */,\n\t\t\t\t99E8D7C42B5192D40099B6F3 /* MediaView.swift */,\n\t\t\t\t99F662AF2B5DDC2900696098 /* BeveledLoadingView.swift */,\n\t\t\t\t99E8D7CC2B54A6470099B6F3 /* Theme */,\n\t\t\t\t6E2E3CA12B32723C00B8515B /* InAppMessageNativeBridgeExtension.swift */,\n\t\t\t\t99E0BD0E2B4DD71A00465B37 /* InAppMessageHostingController.swift */,\n\t\t\t);\n\t\t\tpath = View;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E2E3CA32B32725900B8515B /* View */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E2E3CA42B32726400B8515B /* InAppMessageNativeBridgeExtensionTest.swift */,\n\t\t\t);\n\t\t\tpath = View;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E2FA28A2D515C56005893E2 /* Types */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E2FA28B2D515C5A005893E2 /* ThomasEmailRegistrationOptionsTest.swift */,\n\t\t\t\t60D2B3342D9F0FCF00B0752D /* PagerDisableSwipeSelectorTest.swift */,\n\t\t\t);\n\t\t\tpath = Types;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411CBD2538CA4100FEE4E8 /* Actions */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E9D529A26C1A77C004EA16B /* ActionRegistry.swift */,\n\t\t\t\t6E9D529726C195F7004EA16B /* ActionRunner.swift */,\n\t\t\t\t6E664BA026C43F5400A2C8E5 /* ActivityViewController.swift */,\n\t\t\t\t6E664BC926C4852B00A2C8E5 /* AddCustomEventAction.swift */,\n\t\t\t\t6E664BCF26C4916600A2C8E5 /* AddTagsAction.swift */,\n\t\t\t\t6ED6ECA326ADCA6F00973364 /* BlockAction.swift */,\n\t\t\t\t6EAD7CE426B216DB00B88EA7 /* DeepLinkAction.swift */,\n\t\t\t\t6ED6ECA626AE05B700973364 /* EmptyAction.swift */,\n\t\t\t\t6E664BD626C4CD8700A2C8E5 /* EnableFeatureAction.swift */,\n\t\t\t\t6E664BD826C4CD8700A2C8E5 /* FetchDeviceInfoAction.swift */,\n\t\t\t\t6E664BE626C5B21600A2C8E5 /* ModifyAttributesAction.swift */,\n\t\t\t\t6E664BD726C4CD8700A2C8E5 /* OpenExternalURLAction.swift */,\n\t\t\t\t6E664BD526C4CD8700A2C8E5 /* PasteboardAction.swift */,\n\t\t\t\t6E664BD226C4917000A2C8E5 /* RemoveTagsAction.swift */,\n\t\t\t\t6E664BA426C4417400A2C8E5 /* ShareAction.swift */,\n\t\t\t\tA69C987E27E247B20063A101 /* SubscriptionListAction.swift */,\n\t\t\t\t6E92EC89284933750038802D /* PromptPermissionAction.swift */,\n\t\t\t\t6E92EC8C2849378E0038802D /* PermissionPrompter.swift */,\n\t\t\t\tA6D6D48E2A0253AA0072A5CA /* ActionArguments.swift */,\n\t\t\t\tA6D6D49C2A0260780072A5CA /* AirshipAction.swift */,\n\t\t\t\tA6D6D49E2A02608C0072A5CA /* ActionResult.swift */,\n\t\t\t\t6E4A4FD92A30358F0049FEFC /* TagsActionArgs.swift */,\n\t\t\t\t271B38642DB2866200495D9F /* TagActionMutation.swift */,\n\t\t\t\t27AFE70E2E733F4400767044 /* ModifyTagsAction.swift */,\n\t\t\t);\n\t\t\tname = Actions;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411CE12538CBA800FEE4E8 /* Locale */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E6ED1482683D8E200A2CBD0 /* LocaleManager.swift */,\n\t\t\t);\n\t\t\tname = Locale;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411CE22538CBB200FEE4E8 /* JSON */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t8401769326C5671100373AF7 /* JSONMatcher.swift */,\n\t\t\t\t8401769726C5722400373AF7 /* JSONPredicate.swift */,\n\t\t\t\t8401769926C5725800373AF7 /* AirshipJSONUtils.swift */,\n\t\t\t\t8401769B26C5729E00373AF7 /* JSONValueMatcher.swift */,\n\t\t\t\t6E4339EE2DFA039B000A7741 /* JSONValueMatcherPredicates.swift */,\n\t\t\t\t6E6C3F9D27A4C3D4007F55C7 /* AirshipJSON.swift */,\n\t\t\t);\n\t\t\tname = JSON;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411CEF2538CC2900FEE4E8 /* Airship */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E87BDFD26E283840005D20D /* Airship.swift */,\n\t\t\t\t6E87BE0026E283850005D20D /* DeepLinkDelegate.swift */,\n\t\t\t\t6E6ED14F2683DBC200A2CBD0 /* AirshipCoreResources.swift */,\n\t\t\t\t6E6ED1502683DBC300A2CBD0 /* AirshipVersion.swift */,\n\t\t\t\t6E87BDBC26E01FF40005D20D /* ModuleLoader.swift */,\n\t\t\t\t6E87BE1226E28F570005D20D /* AirshipInstance.swift */,\n\t\t\t\t3CC95B2A2696549B00FE2ACD /* AirshipPushableComponent.swift */,\n\t\t\t\t6E4325CD2B7AD5A200A9B000 /* AirshipComponent.swift */,\n\t\t\t);\n\t\t\tname = Airship;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411CFC2538CC7A00FEE4E8 /* Integration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E87BD8C26D815780005D20D /* AppIntegration.swift */,\n\t\t\t\t6E87BD9126D963B60005D20D /* DefaultAppIntegrationDelegate.swift */,\n\t\t\t);\n\t\t\tname = Integration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D092538CCDA00FEE4E8 /* Config */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E9C2BD92D027B5A000089A9 /* AirshipAppCredentials.swift */,\n\t\t\t\tC00ED4CE26C729390040C5D0 /* URLAllowList.swift */,\n\t\t\t\t6E15B6F326CD85C40099C92D /* RuntimeConfig.swift */,\n\t\t\t\t6E1D8AD726CC66BE0049DACB /* RemoteConfigCache.swift */,\n\t\t\t\t6E87BD6626D6A39A0005D20D /* AirshipConfig.swift */,\n\t\t\t\t6E87BD8226D757CA0005D20D /* CloudSite.swift */,\n\t\t\t\t6E9C2B7C2D014426000089A9 /* APNSEnvironment.swift */,\n\t\t\t);\n\t\t\tname = Config;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D0A2538CD1900FEE4E8 /* App State */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E698E56267BF63B00654DB2 /* ApplicationState.swift */,\n\t\t\t\t6E698E53267BF63A00654DB2 /* AppStateTracker.swift */,\n\t\t\t\t6E698E52267BF63A00654DB2 /* AppStateTrackerAdapter.swift */,\n\t\t\t);\n\t\t\tname = \"App State\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D0B2538CD4B00FEE4E8 /* Remote Config */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E15B6D826CC749F0099C92D /* RemoteConfigManager.swift */,\n\t\t\t\t6E1D8AB226CC5D490049DACB /* RemoteConfig.swift */,\n\t\t\t);\n\t\t\tname = \"Remote Config\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D0C2538CD6400FEE4E8 /* Remote Data */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB3FCEE2ABCFA680018594E /* RemoteDataProtocol.swift */,\n\t\t\t\t6E15B72926CEDBA50099C92D /* RemoteData.swift */,\n\t\t\t\t6E698E08267A7DD900654DB2 /* RemoteDataAPIClient.swift */,\n\t\t\t\t6EE49C0F2A0C142F00AB1CF4 /* RemoteDataInfo.swift */,\n\t\t\t\t6E15B72026CEC7030099C92D /* RemoteDataPayload.swift */,\n\t\t\t\t6EE49C1C2A0D9D8000AB1CF4 /* RemoteDataProvider.swift */,\n\t\t\t\t6EE49C212A13E32B00AB1CF4 /* RemoteDataProviderProtocol.swift */,\n\t\t\t\t6E15B72C26CF13BC0099C92D /* RemoteDataProviderDelegate.swift */,\n\t\t\t\t6EE49BE02A0AADC900AB1CF4 /* AppRemoteDataProviderDelegate.swift */,\n\t\t\t\t6EE49C172A0C3CC600AB1CF4 /* ContactRemoteDataProviderDelegate.swift */,\n\t\t\t\t6EE49C0B2A0C141800AB1CF4 /* RemoteDataSource.swift */,\n\t\t\t\t6E15B70C26CEB4190099C92D /* RemoteDataStore.swift */,\n\t\t\t\t6E15B70A26CEB4190099C92D /* RemoteDataStorePayload.swift */,\n\t\t\t\t6EE49C072A0BE9F600AB1CF4 /* RemoteDataURLFactory.swift */,\n\t\t\t\t6E2F5A852A65F00200CABD3D /* RemoteDataSourceStatus.swift */,\n\t\t\t\t6E2F5A8D2A66FE8900CABD3D /* AirshipTimeCriteria.swift */,\n\t\t\t\t329DFCCE2B7E4DDA0039C8C0 /* UARemoteDataMapping.swift */,\n\t\t\t);\n\t\t\tname = \"Remote Data\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D0E2538CDE200FEE4E8 /* Channel */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ECDDE7729B804BF009D79DB /* Auth */,\n\t\t\t\t6E57CE3528DBBD8C00287601 /* LiveActivities */,\n\t\t\t\t6E87BD6326D594870005D20D /* ChannelRegistrationPayload.swift */,\n\t\t\t\t6E2D6AED26B083DB00B7C226 /* ChannelAudienceManager.swift */,\n\t\t\t\tC02D0B6526C1A3E200F673E6 /* ChannelCapture.swift */,\n\t\t\t\t6E698E11267A98AB00654DB2 /* ChannelAPIClient.swift */,\n\t\t\t\t6E1892DD2694F1C100417887 /* ChannelRegistrar.swift */,\n\t\t\t\t6EC7E48126A60C060038CFDD /* AirshipChannel.swift */,\n\t\t\t\t6E739D6526B9BDC100BC6F6D /* ChannelBulkUpdateAPIClient.swift */,\n\t\t\t\t6ED735D926C73DC5003B0A7D /* DefaultAirshipChannel.swift */,\n\t\t\t\t6ED735DC26C7401D003B0A7D /* TagEditor.swift */,\n\t\t\t\tE976486E27A46CC50024518D /* ChannelType.swift */,\n\t\t\t\t608B16E72C2C6DDB005298FA /* ChannelSubscriptionListProvider.swift */,\n\t\t\t);\n\t\t\tname = Channel;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D1B2538CE6200FEE4E8 /* Application Metrics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = \"Application Metrics\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D282538CF0500FEE4E8 /* Contacts */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t608B16F02C2D5F0D005298FA /* BaseCachingRemoteDataProvider.swift */,\n\t\t\t\tA67F87D1268DECCE00EF5F43 /* ContactAPIClient.swift */,\n\t\t\t\t6EB11C862697ACBF00DC698F /* ContactOperation.swift */,\n\t\t\t\t6EC7E48C26A738C70038CFDD /* ContactConflictEvent.swift */,\n\t\t\t\t6E698E3A267BEDC300654DB2 /* DefaultAirshipContact.swift */,\n\t\t\t\tE99605A027A071EA00365AE4 /* EmailRegistrationOptions.swift */,\n\t\t\t\tE99605A327A075B800365AE4 /* SMSRegistrationOptions.swift */,\n\t\t\t\tE99605A627A075C600365AE4 /* OpenRegistrationOptions.swift */,\n\t\t\t\t6E78848E29B9643C00ACAE45 /* AirshipContact.swift */,\n\t\t\t\t6E94760E29BA8FA30025F364 /* ContactManager.swift */,\n\t\t\t\t6E4E2E2729CEB222002E7682 /* ContactManagerProtocol.swift */,\n\t\t\t\t6E6363E129DCD0CF009C358A /* ContactSubscriptionListClient.swift */,\n\t\t\t\t32BBFB3F2B274C8600C6A998 /* ContactChannelsAPIClient.swift */,\n\t\t\t\t6E60EF6929DF542B003F7A8D /* AnonContactData.swift */,\n\t\t\t\t6E1EEE8F2BD81AF300B45A87 /* ContactChannel.swift */,\n\t\t\t\t990EB3B02BF59A1500315EAC /* ContactChannelsProvider.swift */,\n\t\t\t\t608B16E52C2C1137005298FA /* SubscriptionListProvider.swift */,\n\t\t\t);\n\t\t\tname = Contacts;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D292538CF1000FEE4E8 /* Native Bridge */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E43218F26EA89B6009228AB /* NativeBridgeDelegate.swift */,\n\t\t\t\tA6A5530926D548AF002B20F6 /* NativeBridge.swift */,\n\t\t\t\tA6A5530F26D548D6002B20F6 /* JavaScriptEnvironment.swift */,\n\t\t\t\tA6A5531226D548FF002B20F6 /* NativeBridgeActionHandler.swift */,\n\t\t\t\t6E692AFC29E0CB2F00D96CCC /* JavaScriptCommand.swift */,\n\t\t\t\t6E692AFE29E0CB4100D96CCC /* JavaScriptCommandDelegate.swift */,\n\t\t\t\t6E692B0229E0CBB500D96CCC /* NativeBridgeExtensionDelegate.swift */,\n\t\t\t\t6EDE293E2A9802BF00235738 /* NativeBridgeActionRunner.swift */,\n\t\t\t);\n\t\t\tname = \"Native Bridge\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D362538CF3900FEE4E8 /* Push */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E8932992E7B66B600FB0EC4 /* NotificationRegistrationResult.swift */,\n\t\t\t\t6E8932972E7B665200FB0EC4 /* APNSRegistrationResult.swift */,\n\t\t\t\t6E0031AA2D08CC8A0004F53E /* AirshipAuthorizedNotificationSettings.swift */,\n\t\t\t\t6E8CE760284136BF00CF4B11 /* Registration */,\n\t\t\t\t6E49D7AF28401D2E00C7BB9D /* PushNotificationDelegate.swift */,\n\t\t\t\t6E49D7AC28401D2D00C7BB9D /* RegistrationDelegate.swift */,\n\t\t\t\t3CC8AA0526BB3C7900405614 /* DefaultAirshipPush.swift */,\n\t\t\t\t3C63555F26CDD4F8006E9916 /* AirshipPush.swift */,\n\t\t\t\t6EE49BDC2A09AD3600AB1CF4 /* AirshipNotificationStatus.swift */,\n\t\t\t);\n\t\t\tname = Push;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D372538CF5300FEE4E8 /* Tag Groups */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB11C882697AF5600DC698F /* TagGroupUpdate.swift */,\n\t\t\t\t6E698E3C267BEDC300654DB2 /* TagGroupsEditor.swift */,\n\t\t\t\t6E739D6D26B9F58700BC6F6D /* TagGroupMutations.swift */,\n\t\t\t);\n\t\t\tname = \"Tag Groups\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D502538CFA600FEE4E8 /* HTTP */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ECDDE6B29B7EEE9009D79DB /* AuthToken.swift */,\n\t\t\t\t6E299FD628D13E54001305A7 /* AirshipRequest.swift */,\n\t\t\t\t6E299FDA28D14208001305A7 /* AirshipResponse.swift */,\n\t\t\t\t6E299FDE28D14258001305A7 /* AirshipRequestSession.swift */,\n\t\t\t\t6014AD662C1B5F540072DCF0 /* ChallengeResolver.swift */,\n\t\t\t);\n\t\t\tname = HTTP;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D512538D00300FEE4E8 /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4325EF2B7AEC7100A9B000 /* Events */,\n\t\t\t\t6E15894E2AFEF18900954A04 /* Session */,\n\t\t\t\t6E4325F72B7C08A600A9B000 /* AirshipAnalyticsFeed.swift */,\n\t\t\t\t3CA84AB626DE257200A59685 /* DefaultAirshipAnalytics.swift */,\n\t\t\t\t3CA84AB726DE257200A59685 /* AirshipAnalytics.swift */,\n\t\t\t\t3CA84AA626DE255200A59685 /* EventStore.swift */,\n\t\t\t\t6E952925268B8F6500398B54 /* AssociatedIdentifiers.swift */,\n\t\t\t\t6E698E0B267A88D600654DB2 /* EventAPIClient.swift */,\n\t\t\t\t6E91B4442686911B00DDB1A8 /* EventUtils.swift */,\n\t\t\t\t6E96ECF5293FCE080053CC91 /* EventUploadScheduler.swift */,\n\t\t\t\t6E96ECF9293FDDD90053CC91 /* AirshipSDKExtension.swift */,\n\t\t\t\t6E96ED09294135500053CC91 /* EventManager.swift */,\n\t\t\t\t6E96ED15294197D90053CC91 /* EventUploadTuningInfo.swift */,\n\t\t\t);\n\t\t\tname = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D5E2538D15300FEE4E8 /* Attributes */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB11C8A2697AFC700DC698F /* AttributeUpdate.swift */,\n\t\t\t\t6E698E3B267BEDC300654DB2 /* AttributesEditor.swift */,\n\t\t\t\t6E739D6A26B9DFFB00BC6F6D /* AttributePendingMutations.swift */,\n\t\t\t\t6E4E5B3A26E7F91600198175 /* Attributes.swift */,\n\t\t\t);\n\t\t\tname = Attributes;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E411D5F2538D26200FEE4E8 /* Utils */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1472D42F526DC600320A36 /* AirshipNativePlatform.swift */,\n\t\t\t\t6E146FF22F525E7300320A36 /* AirshipPasteboard.swift */,\n\t\t\t\t6E146D692F5241BA00320A36 /* AirshipColor.swift */,\n\t\t\t\t6E146D672F523DB700320A36 /* AirshipFont.swift */,\n\t\t\t\t6E146C4F2F5214D900320A36 /* AirshipDevice.swift */,\n\t\t\t\t6EC815AE2F2BBFD100E1C0C6 /* BundleExtensions.swift */,\n\t\t\t\t6E3CA5402ECB9B7400210C32 /* AirshipDisplayTarget.swift */,\n\t\t\t\t6E4339F02DFA099C000A7741 /* AirshipIvyVersionMatcher.swift */,\n\t\t\t\t6E52146A2DCBF9BA00CF64B9 /* AirshipTimerProtocol.swift */,\n\t\t\t\t6E916C562DB30D9E00C676FA /* AirshipWindowFactory.swift */,\n\t\t\t\t6EAA61482D5297A2006602F7 /* SubjectExtension.swift */,\n\t\t\t\t99E433972C9A044C006436B9 /* AirshipResources.swift */,\n\t\t\t\t3237D5F12B865D990055932B /* JSONValueTransformer.swift */,\n\t\t\t\t99D1B3272B44F08900447840 /* AirshipSceneManager.swift */,\n\t\t\t\t6E8E1C9A26447B3800B11791 /* AirshipLock.swift */,\n\t\t\t\t6E44626629E6813A00CB2B56 /* AsyncSerialQueue.swift */,\n\t\t\t\t6E96ED01294115210053CC91 /* AsyncStream.swift */,\n\t\t\t\t6EEE8BA1290B3EDE00230528 /* AirshipKeychainAccess.swift */,\n\t\t\t\t6E49D7A928401D2D00C7BB9D /* Atomic.swift */,\n\t\t\t\t6E4E5B3926E7F91600198175 /* AirshipLocalizationUtils.swift */,\n\t\t\t\t6E664BE926C6DB7500A2C8E5 /* AirshipUtils.swift */,\n\t\t\t\t6EB11C8C2698C50F00DC698F /* AudienceUtils.swift */,\n\t\t\t\t6E6ED14D2683DBC200A2CBD0 /* AirshipBase64.swift */,\n\t\t\t\t6E6ED1532683DBC300A2CBD0 /* UACoreData.swift */,\n\t\t\t\t6E6ED1542683DBC300A2CBD0 /* AirshipNetworkChecker.swift */,\n\t\t\t\t6E6ED13F2683A9F200A2CBD0 /* AirshipDate.swift */,\n\t\t\t\t6E6ED1352683A58D00A2CBD0 /* Dispatcher.swift */,\n\t\t\t\t32D6E87A2727F7060077C784 /* Image.swift */,\n\t\t\t\t6EA5202227D1364E003011CA /* AirshipDateFormatter.swift */,\n\t\t\t\t6E6C3F8927A266C0007F55C7 /* CachedValue.swift */,\n\t\t\t\t6E92ECB0284ECE590038802D /* CachedList.swift */,\n\t\t\t\t6E0B8761294CE0DC0064B7BD /* FarmHashFingerprint64.swift */,\n\t\t\t\t6E82482129A6D9DF00136EA0 /* CancellableValueHolder.swift */,\n\t\t\t\t6E82483729A6E1BE00136EA0 /* AirshipCancellable.swift */,\n\t\t\t\t6E12539029A81ACE0009EE58 /* AirshipCoreDataPredicate.swift */,\n\t\t\t\t6E6363EB29DDF84B009C358A /* SerialQueue.swift */,\n\t\t\t\t6E07688729F9D28A0014E2A9 /* AirshipNotificationCenter.swift */,\n\t\t\t\t6E07689129FB39440014E2A9 /* AirshipUnsafeSendableWrapper.swift */,\n\t\t\t\t6ECB627D2A36A0770095C85C /* ExternalURLProcessor.swift */,\n\t\t\t\t6E6BD2772AF2B97300B9DFC9 /* AirshipTaskSleeper.swift */,\n\t\t\t\t6E1528272B4DCFCB00DF1377 /* AirshipActorValue.swift */,\n\t\t\t\t6ED2F5302B7FF819000AFC80 /* AirshipViewUtils.swift */,\n\t\t\t\t6E213B172BC60AF100BF24AE /* AirshipWeakValueHolder.swift */,\n\t\t\t\t6EB839482BC8898E006611C4 /* AirshipAsyncChannel.swift */,\n\t\t\t);\n\t\t\tname = Utils;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E43212826EA844E009228AB /* AirshipBasement */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E43212926EA847D009228AB /* Source */,\n\t\t\t);\n\t\t\tpath = AirshipBasement;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E43212926EA847D009228AB /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EF1933828380086005F192A /* Logger */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4325C12B7A9D6E00A9B000 /* Privacy Manager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4325C22B7A9D9A00A9B000 /* AirshipPrivacyManagerTest.swift */,\n\t\t\t);\n\t\t\tname = \"Privacy Manager\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4325D12B7AD94800A9B000 /* Airship */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4325D22B7AD96800A9B000 /* AirshipTest.swift */,\n\t\t\t);\n\t\t\tname = Airship;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4325EC2B7AEC2E00A9B000 /* Custom Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E91B4662689327D00DDB1A8 /* CustomEvent.swift */,\n\t\t\t\t6E4325ED2B7AEC3B00A9B000 /* Templates */,\n\t\t\t);\n\t\t\tname = \"Custom Events\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4325ED2B7AEC3B00A9B000 /* Templates */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E95292B268B98A200398B54 /* MediaEventTemplate.swift */,\n\t\t\t\t6E952922268B812000398B54 /* AccountEventTemplate.swift */,\n\t\t\t\t6E95291F268A6C1500398B54 /* SearchEventTemplate.swift */,\n\t\t\t\t6E95292E268BBD7D00398B54 /* RetailEventTemplate.swift */,\n\t\t\t);\n\t\t\tname = Templates;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4325EF2B7AEC7100A9B000 /* Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4325E82B7AEB1F00A9B000 /* AirshipEvent.swift */,\n\t\t\t\t6E96ECF1293EB7900053CC91 /* AirshipEventData.swift */,\n\t\t\t\t6E4326002B7C327C00A9B000 /* AirshipEvents.swift */,\n\t\t\t\t6E524C722C126F5F002CA094 /* AirshipEventType.swift */,\n\t\t\t\t6E4325F02B7AEC9700A9B000 /* Region */,\n\t\t\t\t6E4325EC2B7AEC2E00A9B000 /* Custom Events */,\n\t\t\t);\n\t\t\tname = Events;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4325F02B7AEC9700A9B000 /* Region */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E91B43B26868A6300DDB1A8 /* CircularRegion.swift */,\n\t\t\t\t6E91B43F26868C3400DDB1A8 /* ProximityRegion.swift */,\n\t\t\t\t6E91B43E26868C3400DDB1A8 /* RegionEvent.swift */,\n\t\t\t);\n\t\t\tname = Region;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E49D7CA2840257000C7BB9D /* PermissionsManager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E49D7A628401D2D00C7BB9D /* Permission.swift */,\n\t\t\t\t6E49D7A728401D2D00C7BB9D /* PermissionDelegate.swift */,\n\t\t\t\t6E49D7AB28401D2D00C7BB9D /* PermissionsManager.swift */,\n\t\t\t\t6E49D7AE28401D2D00C7BB9D /* PermissionStatus.swift */,\n\t\t\t);\n\t\t\tname = PermissionsManager;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E49D7CB284028A900C7BB9D /* PermissionsManager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E49D7CC284028C600C7BB9D /* PermissionsManagerTests.swift */,\n\t\t\t);\n\t\t\tname = PermissionsManager;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4A466F28EF44F600A25617 /* Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EC824A32F33A5F600E1C0C6 /* MessageCenterMessageTest.swift */,\n\t\t\t\t6EC824A12F33A5EC00E1C0C6 /* MessageCenterThemeLoaderTest.swift */,\n\t\t\t\t6E4A467828EF453400A25617 /* MessageCenterAPIClientTest.swift */,\n\t\t\t\t32F68CDA28F02A6B00F7F52A /* MessageCenterStoreTest.swift */,\n\t\t\t\t32F615A628F708980015696D /* MessageCenterListTests.swift */,\n\t\t\t\t27CCF77E2F16DA500018058F /* MessageViewAnalyticsTest.swift */,\n\t\t\t);\n\t\t\tpath = Tests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4AEE212B6B2DFD008AEAC1 /* Assets */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4AEE242B6B2E0A008AEAC1 /* airship.jpg */,\n\t\t\t\t6E4AEE222B6B2E09008AEAC1 /* alternate-airship.jpg */,\n\t\t\t\t6E4AEE252B6B2E0A008AEAC1 /* AssetCacheManagerTest.swift */,\n\t\t\t\t6E4AEE262B6B2E0A008AEAC1 /* DefaultAssetDownloaderTest.swift */,\n\t\t\t\t6E4AEE232B6B2E09008AEAC1 /* DefaultAssetFileManagerTest.swift */,\n\t\t\t);\n\t\t\tpath = Assets;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4D20732E6B7A2700A8D641 /* MessageCenter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32B5BE3A28F8A7EB00F2254B /* MessageCenterController.swift */,\n\t\t\t\t6E4D224F2E6F9CD700A8D641 /* MessageCenterContent.swift */,\n\t\t\t\t6E4D224D2E6F96B100A8D641 /* MessageCenterSplitNavigationView.swift */,\n\t\t\t\t6E4D224B2E6F968000A8D641 /* MessageCenterNavigationStack.swift */,\n\t\t\t\t99FD20A42DEFC35900242551 /* MessageCenterUIKitAppearance.swift */,\n\t\t\t\t32B5BE2828F8A7D600F2254B /* MessageCenterView.swift */,\n\t\t\t);\n\t\t\tpath = MessageCenter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4D22592E70ACA200A8D641 /* MessageView */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t2753F6412F6C5BB50073882C /* MessageCenterMessageError.swift */,\n\t\t\t\t6E4D225C2E70ADDB00A8D641 /* MessageCenterMessageViewModel.swift */,\n\t\t\t\t32B5BE2928F8A7D600F2254B /* MessageCenterMessageView.swift */,\n\t\t\t\t6E4D22492E6F813700A8D641 /* MessageCenterMessageViewWithNavigation.swift */,\n\t\t\t\t6E4D22532E6FA5EA00A8D641 /* MessageCenterWebView.swift */,\n\t\t\t\t27E419492EF59F9800D5C1A6 /* MessageCenterThomasView.swift */,\n\t\t\t);\n\t\t\tpath = MessageView;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4D225A2E70ACD200A8D641 /* MessageList */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4D225E2E70AFE300A8D641 /* MessageCenterListViewModel.swift */,\n\t\t\t\t32B5BE2A28F8A7D600F2254B /* MessageCenterListView.swift */,\n\t\t\t\t6E4D20712E6B760C00A8D641 /* MessageCenterListViewWithNavigation.swift */,\n\t\t\t\t32B5BE2728F8A7D600F2254B /* MessageCenterListItemView.swift */,\n\t\t\t);\n\t\t\tpath = MessageList;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E4D225B2E70AD2200A8D641 /* Shared */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4D22512E6FA2F300A8D641 /* MessageCenterBackButton.swift */,\n\t\t\t);\n\t\t\tpath = Shared;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E57CE3528DBBD8C00287601 /* LiveActivities */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E57CE3628DBBD9A00287601 /* LiveActivityRegistry.swift */,\n\t\t\t\t6E57CE3128DB8BDA00287601 /* LiveActivityUpdate.swift */,\n\t\t\t\t6E7DB38228ECDC41002725F6 /* LiveActivity.swift */,\n\t\t\t\t6E68203128EDE3E200A4F90B /* LiveActivityRestorer.swift */,\n\t\t\t\t6E29474C2AD5DA3B009EC6DD /* LiveActivityRegistrationStatus.swift */,\n\t\t\t\t6E2947502AD5DB5A009EC6DD /* LiveActivityRegistrationStatusUpdates.swift */,\n\t\t\t);\n\t\t\tname = LiveActivities;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E5A64C22AAB7D3B00574085 /* MeteredUsage */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E5A64C32AAB7D5C00574085 /* AirshipMeteredUsage.swift */,\n\t\t\t\t6E5A64C72AABBE7100574085 /* MeteredUsageAPIClient.swift */,\n\t\t\t\t6E5A64CF2AABBEAF00574085 /* AirshipMeteredUsageEvent.swift */,\n\t\t\t\t6E5A64D32AABBED600574085 /* MeteredUsageStore.swift */,\n\t\t\t);\n\t\t\tname = MeteredUsage;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E5B1A012AFF08F00019CA61 /* Session */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E5B1A042AFF090B0019CA61 /* SessionTrackerTest.swift */,\n\t\t\t);\n\t\t\tname = Session;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E66DDA42E95A67700D44555 /* WorkManager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E66DDA52E95A67C00D44555 /* WorkRateLimiterTests.swift */,\n\t\t\t);\n\t\t\tpath = WorkManager;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E698DE726790A9C00654DB2 /* Privacy Manager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E698DEB26790AC300654DB2 /* AirshipPrivacyManager.swift */,\n\t\t\t);\n\t\t\tname = \"Privacy Manager\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E6BD2402AE995C800B9DFC9 /* Deferred */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E6BD2412AE995DA00B9DFC9 /* DeferredResolver.swift */,\n\t\t\t\t6E6BD2452AEAFE7E00B9DFC9 /* DeferredAPIClient.swift */,\n\t\t\t\t6E6BD2492AEAFEB700B9DFC9 /* AirsihpTriggerContext.swift */,\n\t\t\t\t6E6BD24D2AEAFEC500B9DFC9 /* AirshipStateOverrides.swift */,\n\t\t\t);\n\t\t\tname = Deferred;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E6BD2562AEC596D00B9DFC9 /* Deferred */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E6BD2572AEC598C00B9DFC9 /* DeferredAPIClientTest.swift */,\n\t\t\t\t6E6BD2592AEC626B00B9DFC9 /* DeferredResolverTest.swift */,\n\t\t\t);\n\t\t\tname = Deferred;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E6C3F9227A47D78007F55C7 /* data */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t841E7D11268617C800EA0317 /* PreferenceCenterResponse.swift */,\n\t\t\t\t6E1892D4268E3D8500417887 /* PreferenceCenterDecoder.swift */,\n\t\t\t\t6E6C3F9927A47DB4007F55C7 /* PreferenceCenterConfig.swift */,\n\t\t\t\t993AFDFD2C1B2D9A00AA875B /* PreferenceCenterConfig+ContactManagement.swift */,\n\t\t\t);\n\t\t\tpath = data;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E77CD462D8A22320057A52C /* Input Validation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E77CD472D8A225E0057A52C /* AirshipInputValidator.swift */,\n\t\t\t\t6E77CD492D8A225E0057A52C /* SMSValidatorAPIClient.swift */,\n\t\t\t\t6E77CE462D8A28B10057A52C /* CachingSMSValidatorAPIClient.swift */,\n\t\t\t);\n\t\t\tname = \"Input Validation\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E77CE482D8A2B9E0057A52C /* Input Validation */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E8746482D8A3C64002469D7 /* TestSMSValidatorAPIClient.swift */,\n\t\t\t\t99C3CC772BCF3DF700B5BED5 /* SMSValidatorAPIClientTest.swift */,\n\t\t\t\t99C3CC7C2BCF401B00B5BED5 /* CachingSMSValidatorAPIClientTest.swift */,\n\t\t\t\t6E1A19232D6F8BD50056418B /* AirshipInputValidationTest.swift */,\n\t\t\t);\n\t\t\tpath = \"Input Validation\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E7DB38128ECDB4C002725F6 /* LiveActivities */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E7DB38A28ECDDD9002725F6 /* LiveActivityRegistryTest.swift */,\n\t\t\t);\n\t\t\tname = LiveActivities;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E7DB38C28ECFCDB002725F6 /* JSON */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E7DB38D28ECFCED002725F6 /* AirshipJSONTest.swift */,\n\t\t\t\t6E65244F2A4FD8D30019F353 /* JSONPredicateTest.swift */,\n\t\t\t\t60E09FDA2B2780DB005A16EA /* JsonMatcherTest.swift */,\n\t\t\t\t6087DB872B278F7600449BA8 /* JsonValueMatcherTest.swift */,\n\t\t\t);\n\t\t\tname = JSON;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E87BD9B26DD78B40005D20D /* Integration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E87BD9C26DD78CC0005D20D /* DefaultAppIntegrationDelegateTest.swift */,\n\t\t\t\t6E87BD9E26DDDB250005D20D /* AppIntegrationTests.swift */,\n\t\t\t);\n\t\t\tname = Integration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E8873982763D80400AC248A /* Image Loading */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32CF81E1275627F4003009D1 /* AirshipAsyncImage.swift */,\n\t\t\t\tA658DE2A272AFB0400007672 /* AirshipImageLoader.swift */,\n\t\t\t\t6E8873992763D8AB00AC248A /* AirshipImageProvider.swift */,\n\t\t\t);\n\t\t\tname = \"Image Loading\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E8CE760284136BF00CF4B11 /* Registration */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E49D7AD28401D2D00C7BB9D /* UNNotificationRegistrar.swift */,\n\t\t\t\t3CC95B1E268E785900FE2ACD /* NotificationCategories.swift */,\n\t\t\t\t6E49D7B128401D2E00C7BB9D /* APNSRegistrar.swift */,\n\t\t\t\t6E49D7B028401D2E00C7BB9D /* Badger.swift */,\n\t\t\t\t6E49D7AA28401D2D00C7BB9D /* NotificationPermissionDelegate.swift */,\n\t\t\t\t6E49D7A828401D2D00C7BB9D /* NotificationRegistrar.swift */,\n\t\t\t);\n\t\t\tname = Registration;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E90F0ED228F550900E1FCB0 /* Config */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E9C2BDB2D028030000089A9 /* APNSEnvironmentTest.swift */,\n\t\t\t\t6E6C84452A5C8CFD00DD83A2 /* AirshipConfigTest.swift */,\n\t\t\t\t6E15B6F926CDCA6A0099C92D /* RuntimeConfigTest.swift */,\n\t\t\t);\n\t\t\tname = Config;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E91E42E28EF420700B6F25E /* WorkManager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E91E43728EF423300B6F25E /* AirshipWorkManager.swift */,\n\t\t\t\t6E91E43328EF423300B6F25E /* AirshipWorkManagerProtocol.swift */,\n\t\t\t\t6E91E43528EF423300B6F25E /* AirshipWorkRequest.swift */,\n\t\t\t\t6E91E43928EF423400B6F25E /* AirshipWorkResult.swift */,\n\t\t\t\t6E91E42F28EF423300B6F25E /* WorkBackgroundTasks.swift */,\n\t\t\t\t6E91E43028EF423300B6F25E /* WorkConditionsMonitor.swift */,\n\t\t\t\t6E91E43128EF423300B6F25E /* Worker.swift */,\n\t\t\t\t6E91E43228EF423300B6F25E /* WorkRateLimiterActor.swift */,\n\t\t\t);\n\t\t\tname = WorkManager;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E937300237615B400AA9C2A /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EDFBBC22F5780BA0043D9EF /* BasementImport.swift */,\n\t\t\t\t6EAD3AFB2F4530E400FF274E /* UAAppIntegrationDelegate.swift */,\n\t\t\t\t6EAD3AF92F4530AD00FF274E /* AutoIntegration.swift */,\n\t\t\t\t6EAD3AF72F45305B00FF274E /* AirshipSwizzler.swift */,\n\t\t\t\t6E77CD462D8A22320057A52C /* Input Validation */,\n\t\t\t\t6E6BD2402AE995C800B9DFC9 /* Deferred */,\n\t\t\t\t6E5A64C22AAB7D3B00574085 /* MeteredUsage */,\n\t\t\t\t6E8873982763D80400AC248A /* Image Loading */,\n\t\t\t\t6EC755972A4E114700851ABB /* Audience Checks */,\n\t\t\t\t6E91E42E28EF420700B6F25E /* WorkManager */,\n\t\t\t\t6E49D7CA2840257000C7BB9D /* PermissionsManager */,\n\t\t\t\t6E062CFF27165642001A74A1 /* Thomas */,\n\t\t\t\tA61517AD26A97419008A41C4 /* Subscription Lists */,\n\t\t\t\t6E698E02267A799500654DB2 /* AirshipErrors.swift */,\n\t\t\t\t6E698DE826790AC300654DB2 /* PreferenceDataStore.swift */,\n\t\t\t\t6E698DE726790A9C00654DB2 /* Privacy Manager */,\n\t\t\t\t6E411CBD2538CA4100FEE4E8 /* Actions */,\n\t\t\t\t6E411CEF2538CC2900FEE4E8 /* Airship */,\n\t\t\t\t6E411D512538D00300FEE4E8 /* Analytics */,\n\t\t\t\t6E411D0A2538CD1900FEE4E8 /* App State */,\n\t\t\t\t6E411D1B2538CE6200FEE4E8 /* Application Metrics */,\n\t\t\t\t6E411D5E2538D15300FEE4E8 /* Attributes */,\n\t\t\t\t6E411D0E2538CDE200FEE4E8 /* Channel */,\n\t\t\t\t6E411D092538CCDA00FEE4E8 /* Config */,\n\t\t\t\t6E411D502538CFA600FEE4E8 /* HTTP */,\n\t\t\t\t6E411CFC2538CC7A00FEE4E8 /* Integration */,\n\t\t\t\t6E411CE22538CBB200FEE4E8 /* JSON */,\n\t\t\t\t6E411CE12538CBA800FEE4E8 /* Locale */,\n\t\t\t\t6E411D282538CF0500FEE4E8 /* Contacts */,\n\t\t\t\t6E411D292538CF1000FEE4E8 /* Native Bridge */,\n\t\t\t\t6E411D362538CF3900FEE4E8 /* Push */,\n\t\t\t\t6E411D0B2538CD4B00FEE4E8 /* Remote Config */,\n\t\t\t\t6E411D0C2538CD6400FEE4E8 /* Remote Data */,\n\t\t\t\t60D3BCC22A1528F100E07524 /* Experimentation */,\n\t\t\t\t6E411D372538CF5300FEE4E8 /* Tag Groups */,\n\t\t\t\t6E411D5F2538D26200FEE4E8 /* Utils */,\n\t\t\t\t6E9B4873288F0CE000C905B1 /* RateAppAction.swift */,\n\t\t\t\t6E75F50429C4EAF600E3585A /* AudienceOverridesProvider.swift */,\n\t\t\t\t6E4A4FDD2A3132850049FEFC /* AirshipSDKModule.swift */,\n\t\t\t\t6EF1401A2A2671ED009A125D /* AirshipDeviceID.swift */,\n\t\t\t\t6E6BD26C2AF1AC5700B9DFC9 /* AirshipCache.swift */,\n\t\t\t\t60EACF532B7BF2EA00CAFDBB /* AirshipApptimizeIntegration.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E93769E23761FFA00AA9C2A /* AirshipCore */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t494DD95B1B0EB677009C134E /* Info.plist */,\n\t\t\t\tCC89997D1D8B642D00A0CECC /* Resources */,\n\t\t\t\t6E937300237615B400AA9C2A /* Source */,\n\t\t\t\tCC64F0551D8B77E3009CEF27 /* Tests */,\n\t\t\t);\n\t\t\tpath = AirshipCore;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E986F002B44E86900FBE6A0 /* RemoteData */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E8BDA162B62EC9F00711DB8 /* AutomationRemoteDataSubscriber.swift */,\n\t\t\t\t6E986F052B47319E00FBE6A0 /* DeferredScheduleResult.swift */,\n\t\t\t\t6E986F0D2B473EC700FBE6A0 /* AutomationRemoteDataAccess.swift */,\n\t\t\t\t6EE6AA2B2B51DB1E002FEA75 /* AutomationSourceInfoStore.swift */,\n\t\t\t);\n\t\t\tpath = RemoteData;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A392E81BB6E001A5660 /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A342E81BB6E001A5660 /* AirshipDebugAddEventView.swift */,\n\t\t\t\t6EB21A352E81BB6E001A5660 /* AirshipDebugAnalyticIdentifierEditorView.swift */,\n\t\t\t\t6EB21A362E81BB6E001A5660 /* AirshipDebugAnalyticsView.swift */,\n\t\t\t\t6EB21A372E81BB6E001A5660 /* AirshipDebugEventDetailsView.swift */,\n\t\t\t\t6EB21A382E81BB6E001A5660 /* AirshipDebugEventsView.swift */,\n\t\t\t);\n\t\t\tpath = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A3B2E81BB6E001A5660 /* AppInfo */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A3A2E81BB6E001A5660 /* AirshipDebugAppInfoView.swift */,\n\t\t\t);\n\t\t\tpath = AppInfo;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A3F2E81BB6E001A5660 /* Automations */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A3C2E81BB6E001A5660 /* AirshipDebugExperimentsView.swift */,\n\t\t\t\t6EB21A3D2E81BB6E001A5660 /* AirshipDebugInAppExperiencesView.swift */,\n\t\t\t\t6EB21A3E2E81BB6E001A5660 /* AirshipDebugAutomationsView.swift */,\n\t\t\t);\n\t\t\tpath = Automations;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A432E81BB6E001A5660 /* Channel */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A402E81BB6E001A5660 /* AirshipDebugChannelSubscriptionsView.swift */,\n\t\t\t\t6EB21A412E81BB6E001A5660 /* AirshipDebugChannelTagView.swift */,\n\t\t\t\t6EB21A422E81BB6E001A5660 /* AirshipDebugChannelView.swift */,\n\t\t\t);\n\t\t\tpath = Channel;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A4B2E81BB6E001A5660 /* Common */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A922E81C7A2001A5660 /* AirshipDebugAddStringPropertyView.swift */,\n\t\t\t\t6EB21A902E81BFB9001A5660 /* AirshipDebugAddPropertyView.swift */,\n\t\t\t\t6EB21A442E81BB6E001A5660 /* AirshipDebugAttributesEditorView.swift */,\n\t\t\t\t6EB21A452E81BB6E001A5660 /* AirshipDebugExtensions.swift */,\n\t\t\t\t6EB21A462E81BB6E001A5660 /* AirshipDebugTagGroupsEditorView.swift */,\n\t\t\t\t6EB21A472E81BB6E001A5660 /* AirshipJSONDetailsView.swift */,\n\t\t\t\t6EB21A482E81BB6E001A5660 /* AirshipJSONView.swift */,\n\t\t\t\t6EB21A492E81BB6E001A5660 /* AirshipToast.swift */,\n\t\t\t\t6EB21A4A2E81BB6E001A5660 /* Extensions.swift */,\n\t\t\t\t6EB21B5E2E82FE98001A5660 /* AirshipDebugAudienceSubject.swift */,\n\t\t\t);\n\t\t\tpath = Common;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A522E81BB6E001A5660 /* Contact */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A4C2E81BB6E001A5660 /* AirshipDebugAddEmailChannelView.swift */,\n\t\t\t\t6EB21A4D2E81BB6E001A5660 /* AirshipDebugAddOpenChannelView.swift */,\n\t\t\t\t6EB21A4E2E81BB6E001A5660 /* AirshipDebugAddSMSChannelView.swift */,\n\t\t\t\t6EB21A4F2E81BB6E001A5660 /* AirshipDebugContactSubscriptionEditorView.swift */,\n\t\t\t\t6EB21A502E81BB6E001A5660 /* AirshipDebugContactView.swift */,\n\t\t\t\t6EB21A512E81BB6E001A5660 /* AirshipDebugNamedUserView.swift */,\n\t\t\t);\n\t\t\tpath = Contact;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A562E81BB6E001A5660 /* FeatureFlags */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A532E81BB6E001A5660 /* AirshipDebugFeatureFlagDetailsView.swift */,\n\t\t\t\t6EB21A552E81BB6E001A5660 /* AirshipDebugFeatureFlagView.swift */,\n\t\t\t);\n\t\t\tpath = FeatureFlags;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A5A2E81BB6E001A5660 /* PreferenceCenter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A572E81BB6E001A5660 /* AirshipDebugPreferencCenterItemView.swift */,\n\t\t\t\t6EB21A582E81BB6E001A5660 /* AirshipDebugPreferenceCenterView.swift */,\n\t\t\t);\n\t\t\tpath = PreferenceCenter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A5C2E81BB6E001A5660 /* PrivacyManager */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A5B2E81BB6E001A5660 /* AirshipDebugPrivacyManagerView.swift */,\n\t\t\t);\n\t\t\tpath = PrivacyManager;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A602E81BB6E001A5660 /* Push */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A5D2E81BB6E001A5660 /* AirshipDebugPushDetailsView.swift */,\n\t\t\t\t6EB21A5E2E81BB6E001A5660 /* AirshipDebugPushView.swift */,\n\t\t\t\t6EB21A5F2E81BB6E001A5660 /* AirshipDebugReceivedPushView.swift */,\n\t\t\t);\n\t\t\tpath = Push;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A632E81BB6E001A5660 /* TvOSComponents */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21A612E81BB6E001A5660 /* TVDatePicker.swift */,\n\t\t\t\t6EB21A622E81BB6E001A5660 /* TVSlider.swift */,\n\t\t\t);\n\t\t\tpath = TvOSComponents;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21A672E81BB6E001A5660 /* View */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21AFB2E82169F001A5660 /* AirshipoDebugTriggers.swift */,\n\t\t\t\t6EB21A392E81BB6E001A5660 /* Analytics */,\n\t\t\t\t6EB21A3B2E81BB6E001A5660 /* AppInfo */,\n\t\t\t\t6EB21A3F2E81BB6E001A5660 /* Automations */,\n\t\t\t\t6EB21A432E81BB6E001A5660 /* Channel */,\n\t\t\t\t6EB21A4B2E81BB6E001A5660 /* Common */,\n\t\t\t\t6EB21A522E81BB6E001A5660 /* Contact */,\n\t\t\t\t6EB21A562E81BB6E001A5660 /* FeatureFlags */,\n\t\t\t\t6EB21A5A2E81BB6E001A5660 /* PreferenceCenter */,\n\t\t\t\t6EB21A5C2E81BB6E001A5660 /* PrivacyManager */,\n\t\t\t\t6EB21A602E81BB6E001A5660 /* Push */,\n\t\t\t\t6EB21A632E81BB6E001A5660 /* TvOSComponents */,\n\t\t\t\t6EB21A642E81BB6E001A5660 /* AirshipDebugContentView.swift */,\n\t\t\t\t6EB21A652E81BB6E001A5660 /* AirshipDebugRoute.swift */,\n\t\t\t\t6EB21A662E81BB6E001A5660 /* AirshipDebugView.swift */,\n\t\t\t);\n\t\t\tpath = View;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB5156F28A42F9C00870C5A /* theme */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E2486F628984D0D00657CE4 /* PreferenceCenterTheme.swift */,\n\t\t\t\t6E3B230E28A318CD0005D46E /* PreferenceCenterThemeLoader.swift */,\n\t\t\t);\n\t\t\tpath = theme;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB5158928A5AEFD00870C5A /* theme */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB5158E28A5B15C00870C5A /* PreferenceThemeLoaderTest.swift */,\n\t\t\t);\n\t\t\tpath = theme;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB5159028A5B19400870C5A /* test data */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB5159828A5C61D00870C5A /* TestThemeInvalid.plist */,\n\t\t\t\t6EB5159628A5C54400870C5A /* TestThemeEmpty.plist */,\n\t\t\t\t6EB5159328A5B8E900870C5A /* TestTheme.plist */,\n\t\t\t\t6EB5159128A5B1B400870C5A /* TestLegacyTheme.plist */,\n\t\t\t);\n\t\t\tpath = \"test data\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB5159E28A5C87100870C5A /* data */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1892D6268E3F1800417887 /* PreferenceCenterConfigTest.swift */,\n\t\t\t);\n\t\t\tpath = data;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB515A128A5F1B600870C5A /* view */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB515A228A5F1C600870C5A /* PreferenceCenterStateTest.swift */,\n\t\t\t);\n\t\t\tpath = view;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC0CA4D2B48985B00333A87 /* RemoteData */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EC0CA4E2B48987700333A87 /* AutomationRemoteDataAccessTest.swift */,\n\t\t\t\t6EE6AA272B50C91E002FEA75 /* AutomationRemoteDataSubscriberTest.swift */,\n\t\t\t\t6EE6AA372B572897002FEA75 /* AutomationSourceInfoStoreTest.swift */,\n\t\t\t);\n\t\t\tpath = RemoteData;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC0CA5F2B491CC800333A87 /* Engine */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60D1D9B62B68FB4E00EBE0A4 /* TriggerProcessor */,\n\t\t\t\t6E4AEE632B6B44EA008AEAC1 /* AutomationStore.swift */,\n\t\t\t\t6E4AEE622B6B44EA008AEAC1 /* LegacyAutomationStore.swift */,\n\t\t\t\t6E986EE32B448D3C00FBE6A0 /* AutomationEngine.swift */,\n\t\t\t\t6EC0CA672B49287100333A87 /* AutomationExecutor.swift */,\n\t\t\t\t6EC0CA5B2B48C2F500333A87 /* AutomationPreparer.swift */,\n\t\t\t\t6E1A9BBE2B5EE19000A6489B /* PreparedSchedule.swift */,\n\t\t\t\t6E1A9BC22B5EE1DE00A6489B /* ScheduleReadyResult.swift */,\n\t\t\t\t6E1A9BC02B5EE1CF00A6489B /* SchedulePrepareResult.swift */,\n\t\t\t\t6E1A9BC42B5EE1EE00A6489B /* ScheduleExecuteResult.swift */,\n\t\t\t\t6E1A9BC82B5EE34600A6489B /* AutomationEventFeed.swift */,\n\t\t\t\t6E1A9BD02B5EE84600A6489B /* AutomationScheduleData.swift */,\n\t\t\t\t6E1A9BD22B5EE8A400A6489B /* AutomationScheduleState.swift */,\n\t\t\t\t6E1A9BD42B5EE97000A6489B /* TriggeringInfo.swift */,\n\t\t\t\t6E1A9BF62B606CF200A6489B /* AutomationDelayProcessor.swift */,\n\t\t\t\t6E34C4B02C7D4B6400B00506 /* ExecutionWindowProcessor.swift */,\n\t\t\t\t27077E4B2EE7531C0027A282 /* AutomationEventsHistory.swift */,\n\t\t\t);\n\t\t\tpath = Engine;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC0CA692B4B696500333A87 /* Engine */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EC0CA6A2B4B698000333A87 /* AutomationExecutorTest.swift */,\n\t\t\t\t6EC0CA6C2B4B879800333A87 /* AutomationPreparerTest.swift */,\n\t\t\t\t60FCA3242B5EF3A8005C9232 /* AutomationEventFeedTest.swift */,\n\t\t\t\t6E6A848C2B6854FC006FFB35 /* AutomationDelayProcessorTest.swift */,\n\t\t\t\t6E6A84912B68A571006FFB35 /* AutomationStoreTest.swift */,\n\t\t\t\t60D1D9BA2B6A53F000EBE0A4 /* PreparedTriggerTest.swift */,\n\t\t\t\t60D1D9BC2B6AB2D100EBE0A4 /* AutomationTriggerProcessorTest.swift */,\n\t\t\t\tA6F0B18F2B837E36002D10A4 /* AutomationEngineTest.swift */,\n\t\t\t\t6EDE5FC12BADDD96003ADF55 /* PreparedScheduleInfoTest.swift */,\n\t\t\t\t6E34C4B22C7D4C6600B00506 /* ExecutionWindowProcessorTest.swift */,\n\t\t\t\t27051CD62EE75E3300C770D5 /* AutomationEventsHistoryTest.swift */,\n\t\t\t);\n\t\t\tpath = Engine;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC0CA742B4B8A2800333A87 /* Test Utils */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EC0CA752B4B8A3A00333A87 /* TestRemoteDataAccess.swift */,\n\t\t\t\t6EC0CA772B4B8A4700333A87 /* TestFrequencyLimitsManager.swift */,\n\t\t\t\t6E15283F2B4F153900DF1377 /* TestDisplayAdapter.swift */,\n\t\t\t\t6E1528412B4F156200DF1377 /* TestDisplayCoordinator.swift */,\n\t\t\t\t6EE6AA1D2B4F31B1002FEA75 /* TestCachedAssets.swift */,\n\t\t\t\t6EE6AA292B50C976002FEA75 /* TestAutomationEngine.swift */,\n\t\t\t\t6E1A9BB12B5B172F00A6489B /* TestActionRunner.swift */,\n\t\t\t\t6E1A9BB62B5B1D9E00A6489B /* TestActiveTimer.swift */,\n\t\t\t\t6E1A9BBA2B5B20D700A6489B /* TestInAppMessageAnalytics.swift */,\n\t\t\t\tA6AC44822B923ACB00769ED2 /* TestInAppMessageAutomationExecutor.swift */,\n\t\t\t);\n\t\t\tpath = \"Test Utils\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC0CA7F2B4C811A00333A87 /* Display Adapter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1528382B4E13D400DF1377 /* AirshipLayoutDisplayAdapter.swift */,\n\t\t\t\t6E1A9BAA2B5AE38A00A6489B /* InAppMessageDisplayListener.swift */,\n\t\t\t\t6EC0CA802B4C812A00333A87 /* DisplayAdapter.swift */,\n\t\t\t\t6E1528252B4DC64B00DF1377 /* DisplayAdapterFactory.swift */,\n\t\t\t\t6E1528342B4E11DB00DF1377 /* CustomDisplayAdapter.swift */,\n\t\t\t\t6E1528362B4E11E800DF1377 /* CustomDisplayAdapterWrapper.swift */,\n\t\t\t);\n\t\t\tpath = \"Display Adapter\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC755972A4E114700851ABB /* Audience Checks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EBFA9AE2D15E04B002BA3E9 /* AirshipDeviceAudienceResult.swift */,\n\t\t\t\t6EBFA9AC2D15DA70002BA3E9 /* HashChecker.swift */,\n\t\t\t\t6ED838AA2D0CE9D6009CBB0C /* CompoundDeviceAudienceSelector.swift */,\n\t\t\t\t60D3BCCD2A15471C00E07524 /* AudienceHashSelector.swift */,\n\t\t\t\t6EC755982A4E115400851ABB /* DeviceAudienceSelector.swift */,\n\t\t\t\t6EC7559A2A4E129000851ABB /* DeviceTagSelector.swift */,\n\t\t\t\t6EC7559E2A4E5AB200851ABB /* DeviceAudienceChecker.swift */,\n\t\t\t\t6E2F5A892A66088100CABD3D /* AudienceDeviceInfoProvider.swift */,\n\t\t\t);\n\t\t\tname = \"Audience Checks\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EC755AD2A4FCD7000851ABB /* Audience Checks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EBFA9B02D15F491002BA3E9 /* HashCheckerTest.swift */,\n\t\t\t\t6ED838AC2D0CEF4A009CBB0C /* CompoundDeviceAudienceSelectorTest.swift */,\n\t\t\t\t6EC755AE2A4FCD8800851ABB /* AudienceHashSelectorTest.swift */,\n\t\t\t\t6E65244B2A4FD4270019F353 /* DeviceTagSelectorTest.swift */,\n\t\t\t\t6E65244D2A4FD69F0019F353 /* DeviceAudienceSelectorTest.swift */,\n\t\t\t);\n\t\t\tname = \"Audience Checks\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6ECDDE7729B804BF009D79DB /* Auth */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ECDDE7329B80462009D79DB /* ChannelAuthTokenProvider.swift */,\n\t\t\t\t6ECDDE7829B804FB009D79DB /* ChannelAuthTokenAPIClient.swift */,\n\t\t\t);\n\t\t\tname = Auth;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EDF1D912B292F8600E23BC4 /* InAppMessage */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E213B1D2BC7054500BF24AE /* InAppActionRunner.swift */,\n\t\t\t\t60FCA3032B4F10D9005C9232 /* Legacy */,\n\t\t\t\t6E986EFA2B44D48C00FBE6A0 /* InAppMessaging.swift */,\n\t\t\t\t6E1528182B4DC3D000DF1377 /* InAppMessageAutomationPreparer.swift */,\n\t\t\t\t6E15281A2B4DC3DF00DF1377 /* InAppMessageAutomationExecutor.swift */,\n\t\t\t\t6E15281F2B4DC59C00DF1377 /* InAppMessageSceneDelegate.swift */,\n\t\t\t\t6EE6AA1F2B4F5246002FEA75 /* InAppMessageSceneManager.swift */,\n\t\t\t\t6E1528212B4DC5C000DF1377 /* InAppMessageDisplayDelegate.swift */,\n\t\t\t\t6E15282D2B4DED4F00DF1377 /* Analytics */,\n\t\t\t\t6EC0CA7F2B4C811A00333A87 /* Display Adapter */,\n\t\t\t\t6E2E3CA02B3271BC00B8515B /* View */,\n\t\t\t\t6E16208B2B31169F009240B2 /* Display Coordinators */,\n\t\t\t\t6EDF1DA52B2A300100E23BC4 /* InAppMessage.swift */,\n\t\t\t\t99E6EF692B8E36BA0006326A /* InAppMessageValidation.swift */,\n\t\t\t\t99E8D7BC2B50AA060099B6F3 /* InAppMessageEnvironment.swift */,\n\t\t\t\t99E8D7BA2B50A7C20099B6F3 /* InAppMessageViewDelegate.swift */,\n\t\t\t\t99198E402B2FB453001F3054 /* Assets */,\n\t\t\t\t6EDF1DA32B2A2C6F00E23BC4 /* InAppMessageColor.swift */,\n\t\t\t\t6EDF1D9D2B2A2A5900E23BC4 /* InAppMessageDisplayContent.swift */,\n\t\t\t\t6EDF1D942B2A25A000E23BC4 /* Info */,\n\t\t\t);\n\t\t\tpath = InAppMessage;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EDF1D942B2A25A000E23BC4 /* Info */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EDF1D922B292FB000E23BC4 /* InAppMessageTextInfo.swift */,\n\t\t\t\t6EDF1D952B2A25B400E23BC4 /* InAppMessageButtonInfo.swift */,\n\t\t\t\t6EDF1D972B2A25C800E23BC4 /* InAppMessageMediaInfo.swift */,\n\t\t\t\t6EDF1D9B2B2A287A00E23BC4 /* InAppMessageButtonLayoutType.swift */,\n\t\t\t);\n\t\t\tpath = Info;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EDF1DA92B2A6D7B00E23BC4 /* InAppMessage */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4AEE212B6B2DFD008AEAC1 /* Assets */,\n\t\t\t\t6EE6AAE62B58A9D4002FEA75 /* Analytics */,\n\t\t\t\t6EE6AA172B4F3038002FEA75 /* Display Adapter */,\n\t\t\t\t6E2E3CA32B32725900B8515B /* View */,\n\t\t\t\t6E1620902B3118BF009240B2 /* Display Coordinators */,\n\t\t\t\t6EDF1DAA2B2A6D9900E23BC4 /* InAppMessageTest.swift */,\n\t\t\t\t6EE6AA112B4F3003002FEA75 /* InAppMessageAutomationPreparerTest.swift */,\n\t\t\t\t99E6EF6B2B8E3AF60006326A /* InAppMessageContentValidationTest.swift */,\n\t\t\t\t6EE6AA142B4F302A002FEA75 /* InAppMessageAutomationExecutorTest.swift */,\n\t\t\t\t99E8D79C2B4F9E830099B6F3 /* InAppMessageThemeTest.swift */,\n\t\t\t\t6EB839452BC83B96006611C4 /* DefaultInAppActionRunnerTest.swift */,\n\t\t\t);\n\t\t\tpath = InAppMessage;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EE6AA172B4F3038002FEA75 /* Display Adapter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EE6AA182B4F304B002FEA75 /* DisplayAdapterFactoryTest.swift */,\n\t\t\t\t6EE6AB032B59C21A002FEA75 /* CustomDisplayAdapterWrapperTest.swift */,\n\t\t\t\t6EE6AB052B59C231002FEA75 /* AirshipLayoutDisplayAdapterTest.swift */,\n\t\t\t\t6E1A9BB82B5B20A500A6489B /* InAppMessageDisplayListenerTest.swift */,\n\t\t\t);\n\t\t\tpath = \"Display Adapter\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EE6AAE62B58A9D4002FEA75 /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1CBDFE2BAA1DF200519D9C /* DefaultInAppDisplayImpressionRuleProviderTest.swift */,\n\t\t\t\t6EE6AAE52B58A9D3002FEA75 /* InAppMessageAnalyticsTest.swift */,\n\t\t\t);\n\t\t\tpath = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EE6AAF92B58A9DD002FEA75 /* Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EE6AADE2B58A9D2002FEA75 /* ThomasLayoutButtonTapEventTest.swift */,\n\t\t\t\t6EE6AAD62B58A9D1002FEA75 /* ThomasLayoutDisplayEventTest.swift */,\n\t\t\t\t6EE6AADA2B58A9D1002FEA75 /* ThomasLayoutEventTestUtils.swift */,\n\t\t\t\t6EE6AAE32B58A9D3002FEA75 /* ThomasLayoutFormDisplayEventTest.swift */,\n\t\t\t\t6EE6AAE02B58A9D2002FEA75 /* ThomasLayoutFormResultEventTest.swift */,\n\t\t\t\t6EE6AAE42B58A9D3002FEA75 /* ThomasLayoutGestureEventTest.swift */,\n\t\t\t\t6EE6AADC2B58A9D2002FEA75 /* ThomasLayoutPageActionEventTest.swift */,\n\t\t\t\t6EE6AAD82B58A9D1002FEA75 /* ThomasLayoutPagerCompletedEventTest.swift */,\n\t\t\t\t6EE6AAE12B58A9D2002FEA75 /* ThomasLayoutPagerSummaryEventTest.swift */,\n\t\t\t\t6EE6AAE72B58A9D4002FEA75 /* ThomasLayoutPageSwipeEventAction.swift */,\n\t\t\t\t6EE6AAE22B58A9D2002FEA75 /* ThomasLayoutPageViewEventTest.swift */,\n\t\t\t\t6EE6AAD92B58A9D1002FEA75 /* ThomasLayoutPermissionResultEventTest.swift */,\n\t\t\t\t6EE6AADB2B58A9D1002FEA75 /* ThomasLayoutResolutionEventTest.swift */,\n\t\t\t);\n\t\t\tpath = Events;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EED67642CDEE75D0087CDCB /* Types */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E5ADF832D7682D300A03799 /* ThomasStateTrigger.swift */,\n\t\t\t\t6E1A19212D6F87550056418B /* ThomasFormValidationMode.swift */,\n\t\t\t\t6E2FA2882D515189005893E2 /* ThomasEmailRegistrationOptions.swift */,\n\t\t\t\t6EED68E42CE3ECC50087CDCB /* ThomasPropertyOverride.swift */,\n\t\t\t\t6EED679D2CDEEAA90087CDCB /* AirshipLayout.swift */,\n\t\t\t\t6EED681C2CE274290087CDCB /* ThomasAccessibilityAction.swift */,\n\t\t\t\t6EED68202CE2806B0087CDCB /* ThomasAccessibleInfo.swift */,\n\t\t\t\t6EED67BD2CE1B5120087CDCB /* ThomasActionsPayload.swift */,\n\t\t\t\t6EED67FB2CE26DAF0087CDCB /* ThomasAttributeName.swift */,\n\t\t\t\t6EED67FF2CE26DCA0087CDCB /* ThomasAttributeValue.swift */,\n\t\t\t\t6EED68182CE272710087CDCB /* ThomasAutomatedAccessibilityAction.swift */,\n\t\t\t\t6EED68072CE26E510087CDCB /* ThomasAutomatedAction.swift */,\n\t\t\t\t6EED67A52CE1A4FC0087CDCB /* ThomasBorder.swift */,\n\t\t\t\t6EED67E62CE268BB0087CDCB /* ThomasButtonClickBehavior.swift */,\n\t\t\t\t6EED67E22CE268630087CDCB /* ThomasButtonTapEffect.swift */,\n\t\t\t\t6EED67A12CE1A4780087CDCB /* ThomasColor.swift */,\n\t\t\t\t6EED67F32CE26CB50087CDCB /* ThomasConstrainedSize.swift */,\n\t\t\t\t6EED67EA2CE269930087CDCB /* ThomasDirection.swift */,\n\t\t\t\t6EED68102CE271E10087CDCB /* ThomasEnableBehavior.swift */,\n\t\t\t\t6EED67B12CE1A8300087CDCB /* ThomasEventHandler.swift */,\n\t\t\t\t6EED68142CE271F30087CDCB /* ThomasFormSubmitBehavior.swift */,\n\t\t\t\t6EED67C12CE1B5850087CDCB /* ThomasIcon.swift */,\n\t\t\t\t6EED68032CE26E180087CDCB /* ThomasMargin.swift */,\n\t\t\t\t6EED67A92CE1A5CC0087CDCB /* ThomasMarkdownOptions.swift */,\n\t\t\t\t6EED67C52CE1B5FF0087CDCB /* ThomasMediaFit.swift */,\n\t\t\t\t6EED67702CDEE8370087CDCB /* ThomasOrientation.swift */,\n\t\t\t\t6EED67B92CE1B4E60087CDCB /* ThomasPlatform.swift */,\n\t\t\t\t6EED67AD2CE1A6B70087CDCB /* ThomasPosition.swift */,\n\t\t\t\t6EED676C2CDEE7E50087CDCB /* ThomasPresentationInfo.swift */,\n\t\t\t\t6EED677C2CDEE8FE0087CDCB /* ThomasSerializable.swift */,\n\t\t\t\t6EED67782CDEE8790087CDCB /* ThomasShadow.swift */,\n\t\t\t\t6EED67952CDEEA2B0087CDCB /* ThomasShapeInfo.swift */,\n\t\t\t\t6EED67EF2CE26CA10087CDCB /* ThomasSize.swift */,\n\t\t\t\t6EED67F72CE26CE20087CDCB /* ThomasSizeConstraint.swift */,\n\t\t\t\t6EED680C2CE2707E0087CDCB /* ThomasStateAction.swift */,\n\t\t\t\t6EED67B52CE1B43C0087CDCB /* ThomasTextAppearance.swift */,\n\t\t\t\t6EED67992CDEEA380087CDCB /* ThomasToggleStyleInfo.swift */,\n\t\t\t\t6EED682C2CE28CBF0087CDCB /* ThomasValidationInfo.swift */,\n\t\t\t\t6EED67632CDEE75D0087CDCB /* ThomasViewInfo.swift */,\n\t\t\t\t6EED68282CE28C960087CDCB /* ThomasVisibilityInfo.swift */,\n\t\t\t\t6EED67742CDEE8460087CDCB /* ThomasWindowSize.swift */,\n\t\t\t\t60CE9BDD2D0B6A0900A8B625 /* ThomasPagerControllerBranching.swift */,\n\t\t\t\t602AD0D42D7242B300C7D566 /* ThomasSmsLocale.swift */,\n\t\t\t);\n\t\t\tname = Types;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EF1933828380086005F192A /* Logger */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E524CC22C180A39002CA094 /* AirshipLogPrivacyLevel.swift */,\n\t\t\t\t6E87BDFE26E283840005D20D /* LogLevel.swift */,\n\t\t\t\t6E698DEA26790AC300654DB2 /* AirshipLogger.swift */,\n\t\t\t\t6EF1933B2838062B005F192A /* AirshipLogHandler.swift */,\n\t\t\t\t6EF1933D28380644005F192A /* DefaultLogHandler.swift */,\n\t\t\t);\n\t\t\tname = Logger;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EF27DD627306C6900548DA3 /* Forms */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E0105002DDFA5E6009D651F /* ScoreController.swift */,\n\t\t\t\t6E0105022DDFA719009D651F /* ScoreToggleLayout.swift */,\n\t\t\t\t3243EC612D93109C00B43B25 /* AirshipCheckboxToggleStyle.swift */,\n\t\t\t\t3243EC622D93109C00B43B25 /* AirshipSwitchToggleStyle.swift */,\n\t\t\t\t6EFD6D7027290C16005B26F1 /* TextInput.swift */,\n\t\t\t\t6E887CD2272C5F5000E83363 /* Checkbox.swift */,\n\t\t\t\t6E887CD4272C5F5A00E83363 /* CheckboxController.swift */,\n\t\t\t\t6EF27DD827306C9100548DA3 /* AirshipToggle.swift */,\n\t\t\t\t6EFD6D6D27290C0B005B26F1 /* FormController.swift */,\n\t\t\t\t6EF27DE22730E6F900548DA3 /* RadioInputController.swift */,\n\t\t\t\t6EF27DE82730E85700548DA3 /* RadioInput.swift */,\n\t\t\t\tA6849386273290520021675E /* Score.swift */,\n\t\t\t\t32FD4C772D8079910056D141 /* BasicToggleLayout.swift */,\n\t\t\t\t6ECD4F6C2DD7A7060060EE72 /* RadioInputToggleLayout.swift */,\n\t\t\t\t6ECD4F6E2DD7A7090060EE72 /* CheckboxToggleLayout.swift */,\n\t\t\t\t6ECD4F702DD7A7C90060EE72 /* ToggleLayout.swift */,\n\t\t\t\t609843552D6F518900690371 /* SmsLocalePicker.swift */,\n\t\t\t);\n\t\t\tname = Forms;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EFD6D732729D84D005B26F1 /* Environment */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E55A4D62E1DB4CB00B07DF8 /* ThomasAssociatedLabelResolver.swift */,\n\t\t\t\t6E5215212DCEA10F00CF64B9 /* ThomasViewedPageInfo.swift */,\n\t\t\t\t6E5214662DCAB03600CF64B9 /* ThomasFormResult.swift */,\n\t\t\t\t6EBD12042DA73FD300F678AB /* ValidatableHelper.swift */,\n\t\t\t\t6E97D6B02D84B1890001CF7F /* ThomasFormDataCollector.swift */,\n\t\t\t\t6EC9214D2D82144A000A3A59 /* ThomasFormField.swift */,\n\t\t\t\t6EC922E22D838DFA000A3A59 /* ThomasFormPayloadGenerator.swift */,\n\t\t\t\t6EC922E02D832BAF000A3A59 /* ThomasFormFieldProcessor.swift */,\n\t\t\t\t6E1A15052D6EA3A50056418B /* ThomasFormState.swift */,\n\t\t\t\t6E1A1D842D70F36D0056418B /* ThomasState.swift */,\n\t\t\t\t6E46A27B272B63680089CDE3 /* ThomasEnvironment.swift */,\n\t\t\t\t6EFD6D81272A53AE005B26F1 /* PagerState.swift */,\n\t\t\t\tA1B2C3D4E5F60001VIDEOST /* VideoState.swift */,\n\t\t\t\tA1B2C3D4E5F60003VIDEOCR /* VideoController.swift */,\n\t\t\t\t6E887CD0272C5E8400E83363 /* CheckboxState.swift */,\n\t\t\t\t6EF27DE52730E77300548DA3 /* RadioInputState.swift */,\n\t\t\t\t6E0105042DDFA735009D651F /* ScoreState.swift */,\n\t\t\t\t6ED80792273CA0C800D1F455 /* EnvironmentValues.swift */,\n\t\t\t\t6E92EC8F284954B10038802D /* ButtonState.swift */,\n\t\t\t\t6E52146C2DCBFAB900CF64B9 /* ThomasPagerTracker.swift */,\n\t\t\t\t6E5214682DCABFCA00CF64B9 /* ThomasLayoutContext.swift */,\n\t\t\t\t27CCF8D22F2382750018058F /* ThomasStateStorage.swift */,\n\t\t\t);\n\t\t\tname = Environment;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t847B0011267CD925007CD249 /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB5156F28A42F9C00870C5A /* theme */,\n\t\t\t\t6E2486FA2899BB0E00657CE4 /* view */,\n\t\t\t\t6E6C3F9227A47D78007F55C7 /* data */,\n\t\t\t\t6EB5156D28A42B5800870C5A /* AirshipPreferenceCenterResources.swift */,\n\t\t\t\t847B0012267CE558007CD249 /* PreferenceCenterSDKModule.swift */,\n\t\t\t\t84483A67267CF0C000D0DA7D /* PreferenceCenter.swift */,\n\t\t\t\t6E6802912B86732200F4591F /* PreferenceCenterComponent.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t847BFFF5267CD73A007CD249 /* AirshipPreferenceCenter */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1892C5268D159900417887 /* Tests */,\n\t\t\t\t847B0011267CD925007CD249 /* Source */,\n\t\t\t\t847BFFF7267CD73A007CD249 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = AirshipPreferenceCenter;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t9908E6102B01841A00DB3E2E /* Custom */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9908E60D2B000DBA00DB3E2E /* CustomView.swift */,\n\t\t\t\t9908E6112B0189F800DB3E2E /* ArishipCustomViewManager.swift */,\n\t\t\t\t27264FB22E81B064000B6FA3 /* AirshipSceneController.swift */,\n\t\t\t);\n\t\t\tname = Custom;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t99198E402B2FB453001F3054 /* Assets */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t99CF46172B3217C300B6FD9B /* AirshipCachedAssets.swift */,\n\t\t\t\t99CF46192B3217DE00B6FD9B /* AssetCacheManager.swift */,\n\t\t\t\t998572BE2B3CF95D0091E9C9 /* DefaultAssetDownloader.swift */,\n\t\t\t\t998572C02B3CF97B0091E9C9 /* DefaultAssetFileManager.swift */,\n\t\t\t);\n\t\t\tpath = Assets;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t99560C292BB3848A00F28BDC /* Component Views */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t99560C2C2BB3855800F28BDC /* EmptySectionLabel.swift */,\n\t\t\t\t99560C362BB38A5F00F28BDC /* ErrorLabel.swift */,\n\t\t\t\t99560C1D2BAE2FFA00F28BDC /* ChannelTextField.swift */,\n\t\t\t\t99104DF22BA6689A0040C0FD /* PreferenceCloseButton.swift */,\n\t\t\t\t99560C2A2BB384A700F28BDC /* BackgroundShape.swift */,\n\t\t\t);\n\t\t\tpath = \"Component Views\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t99E8D7CC2B54A6470099B6F3 /* Theme */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t99E8D7D42B55B0300099B6F3 /* InAppMessageThemeAdditionalPadding.swift */,\n\t\t\t\t6E524D012C1A2CAE002CA094 /* InAppMessageThemeManager.swift */,\n\t\t\t\t99E8D7DD2B55C73B0099B6F3 /* ThemeExtensions.swift */,\n\t\t\t\t99E8D79A2B4F2FCE0099B6F3 /* InAppMessageTheme.swift */,\n\t\t\t\t6E524D032C1A454E002CA094 /* InAppMessageThemeShadow.swift */,\n\t\t\t\t99E8D7D72B55B0440099B6F3 /* InAppMessageThemeButton.swift */,\n\t\t\t\t99E8D7DB2B55C4C20099B6F3 /* InAppMessageThemeMedia.swift */,\n\t\t\t\t99E8D7D92B55B05D0099B6F3 /* InAppMessageThemeText.swift */,\n\t\t\t\t99E8D7CD2B54A66E0099B6F3 /* InAppMessageThemeBanner.swift */,\n\t\t\t\t99E8D7C82B54A5CB0099B6F3 /* InAppMessageThemeModal.swift */,\n\t\t\t\t99E8D7CA2B54A6340099B6F3 /* InAppMessageThemeFullscreen.swift */,\n\t\t\t\t99E8D7CF2B54A68F0099B6F3 /* InAppMessageThemeHTML.swift */,\n\t\t\t);\n\t\t\tpath = Theme;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA61517AD26A97419008A41C4 /* Subscription Lists */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA61517C026B009D6008A41C4 /* SubscriptionListAPIClient.swift */,\n\t\t\t\tA61517B126A9C4C3008A41C4 /* SubscriptionListEditor.swift */,\n\t\t\t\tA61517B426AEEAAB008A41C4 /* SubscriptionListUpdate.swift */,\n\t\t\t\tA67EC24A279B1C34009089E1 /* ScopedSubscriptionListUpdate.swift */,\n\t\t\t\tA67EC248279B1A40009089E1 /* ScopedSubscriptionListEditor.swift */,\n\t\t\t\t6EB5158228A47C7100870C5A /* ScopedSubscriptionListEdit.swift */,\n\t\t\t\t6EB5158028A47BD700870C5A /* SubscriptionListEdit.swift */,\n\t\t\t\t6E6C3F7E27A20C3C007F55C7 /* ChannelScope.swift */,\n\t\t\t);\n\t\t\tname = \"Subscription Lists\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA61F3A722A5D9D2400EE94CC /* Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ED7BE642D13DA0400B6A124 /* FeatureFlagResultCacheTest.swift */,\n\t\t\t\tA61F3A732A5D9D6800EE94CC /* FeatureFlagManagerTest.swift */,\n\t\t\t\t6E8BDEFC2A67937E00F816D9 /* FeatureFlagInfoTest.swift */,\n\t\t\t\t6E8BDEFF2A679CD100F816D9 /* FeatureFlagRemoteDataAccessTest.swift */,\n\t\t\t\t6E938DBB2AC39A0500F691D9 /* FeatureFlagAnalyticsTest.swift */,\n\t\t\t\t6E7EACD22AF4220E00DA286B /* FeatureFlagDeferredResolverTest.swift */,\n\t\t\t\t6E28116B2BE40E860040D928 /* FeatureFlagVariablesTest.swift */,\n\t\t\t);\n\t\t\tpath = Tests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA620586A2A5841330041FBF9 /* AirshipFeatureFlags */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA61F3A722A5D9D2400EE94CC /* Tests */,\n\t\t\t\tA620587F2A5841FA0041FBF9 /* Source */,\n\t\t\t);\n\t\t\tpath = AirshipFeatureFlags;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA620587F2A5841FA0041FBF9 /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB214D12E7DBA5E001A5660 /* FeatureFlagManagerProtocol.swift */,\n\t\t\t\t6ED7BE622D13D9FE00B6A124 /* FeatureFlagResultCache.swift */,\n\t\t\t\tA62058802A5842200041FBF9 /* AirshipFeatureFlagsSDKModule.swift */,\n\t\t\t\t6E2F5A732A60833700CABD3D /* FeatureFlagManager.swift */,\n\t\t\t\t6E2F5A752A60871E00CABD3D /* FeatureFlagPayload.swift */,\n\t\t\t\t6E2F5AB02A67434B00CABD3D /* FeatureFlag.swift */,\n\t\t\t\t6E2F5AB92A675D3600CABD3D /* FeatureFlagsRemoteDataAccess.swift */,\n\t\t\t\t6E6BD2752AF1C1D800B9DFC9 /* DeferredFlagResolver.swift */,\n\t\t\t\t6E4325F52B7B2F5800A9B000 /* FeatureFlagAnalytics.swift */,\n\t\t\t\t6E6802932B8673F900F4591F /* FeatureFlagComponent.swift */,\n\t\t\t\tA6E9AD972D4D12C60091BBAF /* FeatureFlagUpdateStatus.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA641E1482BDBBDB400DE6FAA /* AirshipObjectiveC */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E128BA02D305F2E00733024 /* Source */,\n\t\t\t);\n\t\t\tpath = AirshipObjectiveC;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tA6CDD8CE269491850040A673 /* Contacts */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t9971A8842C125C0200092ED1 /* ContactChannelsProviderTest.swift */,\n\t\t\t\tA6CDD8CF269491BE0040A673 /* ContactAPIClientTest.swift */,\n\t\t\t\t6EC7E46D269E2A4C0038CFDD /* AttributeEditorTest.swift */,\n\t\t\t\t6EC7E46F269E33290038CFDD /* TagGroupsEditorTest.swift */,\n\t\t\t\t6EC7E471269E51030038CFDD /* AttributeUpdateTest.swift */,\n\t\t\t\t6EC7E473269E52600038CFDD /* ContactOperationTest.swift */,\n\t\t\t\t6EC7E47526A5EE910038CFDD /* AudienceUtilsTest.swift */,\n\t\t\t\t6EC7E47726A604080038CFDD /* AirshipContactTest.swift */,\n\t\t\t\t6E6363E529DCE9A2009C358A /* ContactSubscriptionListAPIClientTest.swift */,\n\t\t\t\t6E6363E729DCEB84009C358A /* ContactManagerTest.swift */,\n\t\t\t);\n\t\t\tname = Contacts;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2001DCAB28300B4842D /* TestUtils */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ED7BE5F2D13D9E300B6A124 /* TestCache.swift */,\n\t\t\t\t6E9C2BCF2D023216000089A9 /* RuntimeConfig.swift */,\n\t\t\t\t6E4A467F28EF4FAF00A25617 /* TestAirshipRequestSession.swift */,\n\t\t\t\t6E6ED1422683B8FA00A2CBD0 /* TestDate.swift */,\n\t\t\t\t3299EF212949EC3E00251E70 /* AirshipBaseTest.swift */,\n\t\t\t\tC088383526E0244C00D40838 /* TestURLAllowList.swift */,\n\t\t\t\t6E698E61267C03C700654DB2 /* TestAppStateTracker.swift */,\n\t\t\t\t6E6ED1442683BC7F00A2CBD0 /* TestDispatcher.swift */,\n\t\t\t\t6E6ED171268448EC00A2CBD0 /* TestNetworkMonitor.swift */,\n\t\t\t\t6EC7E48426A60CDF0038CFDD /* TestChannel.swift */,\n\t\t\t\t6EC7E48626A60DD60038CFDD /* TestContactAPIClient.swift */,\n\t\t\t\t6E2D6AF526B0C6CA00B7C226 /* TestSubscriptionListAPIClient.swift */,\n\t\t\t\t6E739D7E26BAFCB800BC6F6D /* TestChannelBulkUpdateAPIClient.swift */,\n\t\t\t\t6E664BE426C5817B00A2C8E5 /* TestContact.swift */,\n\t\t\t\t6ED735E126CAE2D7003B0A7D /* TestChannelRegistrar.swift */,\n\t\t\t\t6ED735E326CAE8AA003B0A7D /* TestChannelAudienceManager.swift */,\n\t\t\t\t6ED735E526CAEABC003B0A7D /* TestLocaleManager.swift */,\n\t\t\t\t6E15B70426CE07180099C92D /* TestRemoteData.swift */,\n\t\t\t\t6E15B72F26CF4F6B0099C92D /* TestRemoteDataAPIClient.swift */,\n\t\t\t\t6E87BE1526E29BC90005D20D /* TestAirshipInstance.swift */,\n\t\t\t\t6E87BE1726E2C5940005D20D /* TestAnalytics.swift */,\n\t\t\t\tA63A567528F449D8004B8951 /* TestWorkManager.swift */,\n\t\t\t\tA63A567728F457FE004B8951 /* TestWorkRateLimiterActor.swift */,\n\t\t\t\t6E6363E929DCECA1009C358A /* TestContactSubscriptionListAPIClient.swift */,\n\t\t\t\t6EE49C252A1446B100AB1CF4 /* RemoteDataTestUtils.swift */,\n\t\t\t\t6ECB62812A36A45F0095C85C /* TestURLOpener.swift */,\n\t\t\t\t6EF1401E2A268CE6009A125D /* TestKeychainAccess.swift */,\n\t\t\t\t6E2F5AB62A675ADC00CABD3D /* TestAudienceChecker.swift */,\n\t\t\t\t6EC0CA6E2B4B893500333A87 /* TestDeferredResolver.swift */,\n\t\t\t\t6EC0CA712B4B897B00333A87 /* TestExperimentDataProvider.swift */,\n\t\t\t\t6E4325C42B7AC3F700A9B000 /* TestPush.swift */,\n\t\t\t);\n\t\t\tname = TestUtils;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2061DCAB4C000B4842D /* Push */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tCC04F20B1DCAB52900B4842D /* Interactive */,\n\t\t\t\t6E8CE761284137D600CF4B11 /* AirshipPushTest.swift */,\n\t\t\t);\n\t\t\tname = Push;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2091DCAB4FD00B4842D /* Channel */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E1767F429B923D100D65F60 /* ChannelAuthTokenAPIClientTest.swift */,\n\t\t\t\t6E1767F329B923D100D65F60 /* ChannelAuthTokenProviderTest.swift */,\n\t\t\t\t6E1767F529B923D100D65F60 /* TestChannelAuthTokenAPIClient.swift */,\n\t\t\t\t6E7DB38128ECDB4C002725F6 /* LiveActivities */,\n\t\t\t\t6E8B4BEF2888606400AA336E /* ChannelTest.swift */,\n\t\t\t\t6E2D6AF326B0C3C500B7C226 /* ChannelAudienceManagerTest.swift */,\n\t\t\t\t6E739D8126BB33A200BC6F6D /* ChannelBulkUpdateAPIClientTest.swift */,\n\t\t\t\t6ED735DF26C74321003B0A7D /* TagEditorTest.swift */,\n\t\t\t\t6EAC295927580063006DFA63 /* ChannelRegistrarTest.swift */,\n\t\t\t\t6EFAFB77295525C3008AD187 /* ChannelAPIClientTest.swift */,\n\t\t\t\t6EFAFB79295525CD008AD187 /* ChannelCaptureTest.swift */,\n\t\t\t\t6EFAFB7B295525DF008AD187 /* ChannelRegistrationPayloadTest.swift */,\n\t\t\t);\n\t\t\tname = Channel;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F20B1DCAB52900B4842D /* Interactive */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t60A5CC072B28DC500017EDB2 /* NotificationCategoriesTest.swift */,\n\t\t\t);\n\t\t\tname = Interactive;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F20E1DCAB5C600B4842D /* ActionsFramework */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tCC04F20F1DCAB5D800B4842D /* Actions */,\n\t\t\t\t6E92ECA0284A79AB0038802D /* PromptPermissionActionTest.swift */,\n\t\t\t\t6E92ECA2284A7A2A0038802D /* TestPermissionPrompter.swift */,\n\t\t\t\t6E92ECA6284AC1120038802D /* EnableFeatureActionTest.swift */,\n\t\t\t\t6E9B4877288F360C00C905B1 /* RateAppActionTest.swift */,\n\t\t\t\t32F293D4295AFD94004A7D9C /* ActionArgumentsTest.swift */,\n\t\t\t\t325D53D9295C7979003421B4 /* ActionRegistryTest.swift */,\n\t\t\t);\n\t\t\tname = ActionsFramework;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F20F1DCAB5D800B4842D /* Actions */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA6AF8D2C27E8D4910068C7EE /* SubscriptionListActionTest.swift */,\n\t\t\t\t6EFAFB8129555174008AD187 /* FetchDeviceInfoActionTest.swift */,\n\t\t\t\t6EFAFB8329561F23008AD187 /* ModifyAttributesActionTest.swift */,\n\t\t\t\t6EFAFB8929562474008AD187 /* AddTagsActionTest.swift */,\n\t\t\t\t6EFAFB8B29562866008AD187 /* RemoveTagsActionTest.swift */,\n\t\t\t\tA629F7D9295B514C00671647 /* PasteboardActionTest.swift */,\n\t\t\t\tA62C3353299FD509004DB0DA /* ShareActionTest.swift */,\n\t\t\t\t6ECB627B2A369F5B0095C85C /* OpenExternalURLActionTest.swift */,\n\t\t\t\t6ECB62832A36A7510095C85C /* DeepLinkActionTest.swift */,\n\t\t\t\t27AFE7102E73477200767044 /* ModifyTagsActionTest.swift */,\n\t\t\t);\n\t\t\tname = Actions;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2151DCAB73000B4842D /* Analytics */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E5B1A012AFF08F00019CA61 /* Session */,\n\t\t\t\t32E339E22A334A2000CD3BE5 /* AddCustomEventActionTest.swift */,\n\t\t\t\tCC04F2171DCAB75E00B4842D /* Events */,\n\t\t\t\t6E92ECAB284EA7DA0038802D /* AnalyticsTest.swift */,\n\t\t\t\t6E96ED0D29416E820053CC91 /* EventManagerTest.swift */,\n\t\t\t\t6E96ED0F29416E8F0053CC91 /* EventAPIClientTest.swift */,\n\t\t\t\t6E96ED1129416E990053CC91 /* EventSchedulerTest.swift */,\n\t\t\t\t6E96ED1329417A600053CC91 /* EventStoreTest.swift */,\n\t\t\t\t6E96ED192941A0EC0053CC91 /* EventTestUtils.swift */,\n\t\t\t\t6E4326042B7C361F00A9B000 /* AssociatedIdentifiersTest.swift */,\n\t\t\t\t6E1802F82C5C2DEC00198D0D /* AirshipAnalyticFeedTest.swift */,\n\t\t\t);\n\t\t\tname = Analytics;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2171DCAB75E00B4842D /* Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E6EF9E6270625C400D30C35 /* AirshipEventsTest.swift */,\n\t\t\t\tCC04F2181DCAB78500B4842D /* Custom Events */,\n\t\t\t\t60A5CC0B2B29AE890017EDB2 /* ProximityRegionTest.swift */,\n\t\t\t\t60A5CC0D2B29B1B80017EDB2 /* CircularRegionTest.swift */,\n\t\t\t\t60A5CC0F2B29B4100017EDB2 /* RegionEventTest.swift */,\n\t\t\t);\n\t\t\tname = Events;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2181DCAB78500B4842D /* Custom Events */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6018AF562B29C20A008E528B /* SearchEventTemplateTest.swift */,\n\t\t\t\t6068E0052B2A190300349E82 /* CustomEventTest.swift */,\n\t\t\t\t6068E0072B2A2A6700349E82 /* AccountEventTemplateTest.swift */,\n\t\t\t\t6068E0312B2B785A00349E82 /* MediaEventTemplateTest.swift */,\n\t\t\t\t6068E0332B2B7CA100349E82 /* RetailEventTemplateTest.swift */,\n\t\t\t);\n\t\t\tname = \"Custom Events\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F2191DCAB7BE00B4842D /* Utils */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E146EDC2F52536C00320A36 /* AishipFontTests.swift */,\n\t\t\t\t6E07B5F72D925ED30087EC47 /* TestPrivacyManager.swift */,\n\t\t\t\t6E6C3F8C27A26992007F55C7 /* CachedValueTest.swift */,\n\t\t\t\t6E92ECB3284ED6F10038802D /* CachedListTest.swift */,\n\t\t\t\t6E0B875F294CE0BF0064B7BD /* FarmHashFingerprint64Test.swift */,\n\t\t\t\t6E1767F929B92F1700D65F60 /* AirshipUtilsTest.swift */,\n\t\t\t\t6EF553E22B7EE40B00901A22 /* AirshipLocalizationUtilsTest.swift */,\n\t\t\t\t6ED2F5242B7EE648000AFC80 /* AirshipBase64Test.swift */,\n\t\t\t\t6ED2F5262B7EE82B000AFC80 /* AirshipJSONUtilsTest.swift */,\n\t\t\t\t6ED2F5282B7FC59F000AFC80 /* AirshipColorTests.swift */,\n\t\t\t\t6ED2F52A2B7FC5C8000AFC80 /* AirshipIvyVersionMatcherTest.swift */,\n\t\t\t\t6ED2F5342B7FFCD7000AFC80 /* AirshipDateFormatterTest.swift */,\n\t\t\t\t6EB8394D2BC8B1F4006611C4 /* AirshipAsyncChannelTest.swift */,\n\t\t\t\t6032695A2BF75D69007F7F75 /* AirshipHTTPResponseTest.swift */,\n\t\t\t\t6E10A1472C2B825200ED9556 /* DefaultTaskSleeperTest.swift */,\n\t\t\t\t6EE6AAD42B58A977002FEA75 /* TestThomasLayoutEvent.swift */,\n\t\t\t);\n\t\t\tname = Utils;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC04F21B1DCAB82200B4842D /* NativeBridge */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6ECB62852A36C1EE0095C85C /* NativeBridgeActionHandlerTest.swift */,\n\t\t\t\t6ED2F52C2B7FD403000AFC80 /* JavaScriptCommandTest.swift */,\n\t\t\t);\n\t\t\tname = NativeBridge;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC64F0551D8B77E3009CEF27 /* Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E66DDA42E95A67700D44555 /* WorkManager */,\n\t\t\t\t6E77CE482D8A2B9E0057A52C /* Input Validation */,\n\t\t\t\t6E4325D12B7AD94800A9B000 /* Airship */,\n\t\t\t\t6E4325C12B7A9D6E00A9B000 /* Privacy Manager */,\n\t\t\t\t6E6BD2562AEC596D00B9DFC9 /* Deferred */,\n\t\t\t\t6058771B2AC73C550021628E /* MeteredUsage */,\n\t\t\t\t6EC755AD2A4FCD7000851ABB /* Audience Checks */,\n\t\t\t\t6079511E2A1CD1880086578F /* Experiments */,\n\t\t\t\t6E7DB38C28ECFCDB002725F6 /* JSON */,\n\t\t\t\t6E49D7CB284028A900C7BB9D /* PermissionsManager */,\n\t\t\t\t6E1C9C38271E90D1009EF9EF /* Thomas */,\n\t\t\t\t6E64C87F27331ABA000EB887 /* PreferenceDataStoreTest.swift */,\n\t\t\t\t6E87BD9B26DD78B40005D20D /* Integration */,\n\t\t\t\t6E2D6AF026B0B62900B7C226 /* Subscription Lists */,\n\t\t\t\tA6CDD8CE269491850040A673 /* Contacts */,\n\t\t\t\t1B05132624AE100C00F5051F /* Locale */,\n\t\t\t\t3C927F8223A42609003C5FC8 /* App State */,\n\t\t\t\t6E90F0ED228F550900E1FCB0 /* Config */,\n\t\t\t\tDFB5F12F1FC4EDB70085F784 /* RemoteConfig */,\n\t\t\t\tDF17A10E1F57614800DC39E0 /* RemoteData */,\n\t\t\t\tCC64F1421D8B7954009CEF27 /* Support */,\n\t\t\t\tCC04F2001DCAB28300B4842D /* TestUtils */,\n\t\t\t\tCC944EDA1DB6AEAF00C42269 /* HTTP */,\n\t\t\t\tCC04F2091DCAB4FD00B4842D /* Channel */,\n\t\t\t\tCC04F2061DCAB4C000B4842D /* Push */,\n\t\t\t\tCC04F20E1DCAB5C600B4842D /* ActionsFramework */,\n\t\t\t\tCC04F2151DCAB73000B4842D /* Analytics */,\n\t\t\t\tCC04F2191DCAB7BE00B4842D /* Utils */,\n\t\t\t\tCC04F21B1DCAB82200B4842D /* NativeBridge */,\n\t\t\t\tCC64F0581D8B77E3009CEF27 /* Info.plist */,\n\t\t\t\t6EF140202A269074009A125D /* AirshipDeviceIDTest.swift */,\n\t\t\t\t6E7EACD02AF4192400DA286B /* AirshipCacheTest.swift */,\n\t\t\t\t6ED2F52E2B7FD49B000AFC80 /* AirshipURLAllowListTest.swift */,\n\t\t\t);\n\t\t\tpath = Tests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC64F1421D8B7954009CEF27 /* Support */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6014AD742C20410A0072DCF0 /* airship.der */,\n\t\t\t\tCC64F0611D8B781C009CEF27 /* CustomNotificationCategories.plist */,\n\t\t\t\tCC64F0621D8B781C009CEF27 /* Info.plist */,\n\t\t\t\tCC64F1451D8B7954009CEF27 /* AirshipConfig-Valid-Legacy.plist */,\n\t\t\t\tCC64F1471D8B7954009CEF27 /* AirshipConfig-Valid.plist */,\n\t\t\t\t45A8ADD123133E51004AD8CA /* testMCColorsCatalog.xcassets */,\n\t\t\t\tCC64F1491D8B7954009CEF27 /* development-embedded.mobileprovision */,\n\t\t\t\tCC64F14A1D8B7954009CEF27 /* production-embedded.mobileprovision */,\n\t\t\t);\n\t\t\tpath = Support;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC89997D1D8B642D00A0CECC /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E411CA72538C6A500FEE4E8 /* UAEvents.xcdatamodeld */,\n\t\t\t\t6E411CA42538C6A500FEE4E8 /* UARemoteData.xcdatamodeld */,\n\t\t\t\t329DFCCA2B7E4DA10039C8C0 /* UARemoteDataMappingV3toV4.xcmappingmodel */,\n\t\t\t\t6E1F6E832BE6835400CFC7A7 /* UARemoteDataMappingV2toV4.xcmappingmodel */,\n\t\t\t\t6E1F6E872BE683E600CFC7A7 /* UARemoteDataMappingV1toV4.xcmappingmodel */,\n\t\t\t\t6E411C742538C60900FEE4E8 /* UrbanAirship.strings */,\n\t\t\t\t6E411B6C2538C4E500FEE4E8 /* UANativeBridge */,\n\t\t\t\t6E411B6D2538C4E600FEE4E8 /* UANotificationCategories.plist */,\n\t\t\t\t6EFE7E3E2A97ED600064AC31 /* PrivacyInfo.xcprivacy */,\n\t\t\t\t6E5A64D72AABC5A400574085 /* UAMeteredUsage.xcdatamodeld */,\n\t\t\t\t6E6BD2702AF1B05500B9DFC9 /* UAirshipCache.xcdatamodeld */,\n\t\t\t);\n\t\t\tpath = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCC944EDA1DB6AEAF00C42269 /* HTTP */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E299FD428D13D00001305A7 /* DefaultAirshipRequestSessionTest.swift */,\n\t\t\t\t6014AD6A2C2032360072DCF0 /* ChallengeResolverTest.swift */,\n\t\t\t);\n\t\t\tname = HTTP;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDF17A10E1F57614800DC39E0 /* RemoteData */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32C68D0429424449006BBB29 /* RemoteDataTest.swift */,\n\t\t\t\t3299EF162948CBC100251E70 /* RemoteDataAPIClientTest.swift */,\n\t\t\t\t3299EF25294B222F00251E70 /* RemoteDataStoreTest.swift */,\n\t\t\t\t6EFB7B322A14A0EC00133115 /* RemoteDataProviderTest.swift */,\n\t\t\t\t6E4007132A153AB20013C2DE /* AppRemoteDataProviderDelegateTest.swift */,\n\t\t\t\t6E4007152A153ABE0013C2DE /* ContactRemoteDataProviderTest.swift */,\n\t\t\t\t6E4007172A153AFE0013C2DE /* RemoteDataURLFactoryTest.swift */,\n\t\t\t);\n\t\t\tname = RemoteData;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDFB5F12F1FC4EDB70085F784 /* RemoteConfig */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E15B70226CDE40E0099C92D /* RemoteConfigManagerTest.swift */,\n\t\t\t\t6E032A4F2B210E6000404630 /* RemoteConfigTest.swift */,\n\t\t\t);\n\t\t\tname = RemoteConfig;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tE3E85E514DBE69D4C8BF51CE /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\tA67229BD28199D6A0033F54D /* Network.framework */,\n\t\t\t\tA67229BB28199D590033F54D /* libsqlite3.tbd */,\n\t\t\t\tA67229B928199D430033F54D /* libz.tbd */,\n\t\t\t\t6EB4E4A32549F95200E3FFD0 /* Network.framework */,\n\t\t\t\t6EB4E4BE2549F9B900E3FFD0 /* Network.framework */,\n\t\t\t\t3261A7F4243CD73100ADBF6B /* CoreTelephony.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\t3CA0E2A2237CCE2600EE76CF /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t3CA0E3A1237E4A7B00EE76CF /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t494DD9541B0EB677009C134E /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B8725294A9C120064B7BD /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E43202526EA814F009228AB /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFEF267CD739007CD249 /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA62058642A5841330041FBF9 /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA641E1422BDBBDB400DE6FAA /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\t3CA0E298237CCE2600EE76CF /* AirshipDebug */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 3CA0E2A9237CCE2600EE76CF /* Build configuration list for PBXNativeTarget \"AirshipDebug\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t3CA0E2A2237CCE2600EE76CF /* Headers */,\n\t\t\t\t3CA0E29F237CCE2600EE76CF /* Frameworks */,\n\t\t\t\t3CA0E29C237CCE2600EE76CF /* Sources */,\n\t\t\t\t3CA0E2A8237CCE2600EE76CF /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E4AEE0B2B6B24D1008AEAC1 /* PBXTargetDependency */,\n\t\t\t\t6E29474A2AD47E0C009EC6DD /* PBXTargetDependency */,\n\t\t\t\t6E2F5A922A67314A00CABD3D /* PBXTargetDependency */,\n\t\t\t\t3CA0E2DA237CD59100EE76CF /* PBXTargetDependency */,\n\t\t\t\t3C39D3082384C8B6003C50D4 /* PBXTargetDependency */,\n\t\t\t\t6E2F5A962A67316C00CABD3D /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipDebug;\n\t\t\tproductName = AirshipDebug;\n\t\t\tproductReference = 3CA0E2AC237CCE2600EE76CF /* AirshipDebug.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\t3CA0E346237E4A7B00EE76CF /* AirshipMessageCenter */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 3CA0E420237E4A7B00EE76CF /* Build configuration list for PBXNativeTarget \"AirshipMessageCenter\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t3CA0E3A0237E4A7B00EE76CF /* Frameworks */,\n\t\t\t\t3CA0E3A1237E4A7B00EE76CF /* Headers */,\n\t\t\t\t3CA0E34A237E4A7B00EE76CF /* Sources */,\n\t\t\t\t3CA0E417237E4A7B00EE76CF /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t3CA0E347237E4A7B00EE76CF /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipMessageCenter;\n\t\t\tproductName = AirshipCore;\n\t\t\tproductReference = 3CA0E423237E4A7B00EE76CF /* AirshipMessageCenter.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\t494DD9561B0EB677009C134E /* AirshipCore */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 494DD96D1B0EB677009C134E /* Build configuration list for PBXNativeTarget \"AirshipCore\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t494DD9541B0EB677009C134E /* Headers */,\n\t\t\t\t494DD9531B0EB677009C134E /* Frameworks */,\n\t\t\t\t494DD9521B0EB677009C134E /* Sources */,\n\t\t\t\t494DD9551B0EB677009C134E /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E43218B26EA891F009228AB /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipCore;\n\t\t\tproductName = AirshipCore;\n\t\t\tproductReference = 494DD9571B0EB677009C134E /* AirshipCore.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\t6E0B8729294A9C120064B7BD /* AirshipAutomation */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6E0B8739294A9C130064B7BD /* Build configuration list for PBXNativeTarget \"AirshipAutomation\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6E0B8725294A9C120064B7BD /* Headers */,\n\t\t\t\t6E0B8726294A9C120064B7BD /* Sources */,\n\t\t\t\t6E0B8727294A9C120064B7BD /* Frameworks */,\n\t\t\t\t6E0B8728294A9C120064B7BD /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E0B8743294A9C780064B7BD /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipAutomation;\n\t\t\tproductName = AirshipAutomationSwift;\n\t\t\tproductReference = 6E0B872A294A9C120064B7BD /* AirshipAutomation.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\t6E0B8730294A9C130064B7BD /* AirshipAutomationTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6E0B873C294A9C130064B7BD /* Build configuration list for PBXNativeTarget \"AirshipAutomationTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6E0B872D294A9C130064B7BD /* Sources */,\n\t\t\t\t6E0B872E294A9C130064B7BD /* Frameworks */,\n\t\t\t\t6E0B872F294A9C130064B7BD /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E107F042B30B887007AFC4D /* PBXTargetDependency */,\n\t\t\t\t6E0B8734294A9C130064B7BD /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipAutomationTests;\n\t\t\tproductName = AirshipAutomationSwiftTests;\n\t\t\tproductReference = 6E0B8731294A9C130064B7BD /* AirshipAutomationTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t6E431F6B26EA814F009228AB /* AirshipBasement */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6E43204526EA814F009228AB /* Build configuration list for PBXNativeTarget \"AirshipBasement\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6E431F6D26EA814F009228AB /* Sources */,\n\t\t\t\t6E43202026EA814F009228AB /* Frameworks */,\n\t\t\t\t6E43202526EA814F009228AB /* Headers */,\n\t\t\t\t6E43203F26EA814F009228AB /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = AirshipBasement;\n\t\t\tproductName = AirshipCore;\n\t\t\tproductReference = 6E43204826EA814F009228AB /* AirshipBasement.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\t6E4A466D28EF44F600A25617 /* AirshipMessageCenterTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6E4A467528EF44F600A25617 /* Build configuration list for PBXNativeTarget \"AirshipMessageCenterTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6E4A466A28EF44F600A25617 /* Sources */,\n\t\t\t\t6E4A466B28EF44F600A25617 /* Frameworks */,\n\t\t\t\t6E4A466C28EF44F600A25617 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E4A467428EF44F600A25617 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipMessageCenterTests;\n\t\t\tproductName = AirshipMessageCenterTests;\n\t\t\tproductReference = 6E4A466E28EF44F600A25617 /* AirshipMessageCenterTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\t847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 847B0005267CD73A007CD249 /* Build configuration list for PBXNativeTarget \"AirshipPreferenceCenter\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t847BFFEF267CD739007CD249 /* Headers */,\n\t\t\t\t847BFFF0267CD739007CD249 /* Sources */,\n\t\t\t\t847BFFF1267CD739007CD249 /* Frameworks */,\n\t\t\t\t847BFFF2267CD739007CD249 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t847B000E267CD85E007CD249 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipPreferenceCenter;\n\t\t\tproductName = AirshipPreferenceCenter;\n\t\t\tproductReference = 847BFFF4267CD739007CD249 /* AirshipPreferenceCenter.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\t847BFFFB267CD73A007CD249 /* AirshipPreferenceCenterTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 847B0008267CD73A007CD249 /* Build configuration list for PBXNativeTarget \"AirshipPreferenceCenterTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t847BFFF8267CD73A007CD249 /* Sources */,\n\t\t\t\t847BFFF9267CD73A007CD249 /* Frameworks */,\n\t\t\t\t847BFFFA267CD73A007CD249 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t847BFFFF267CD73A007CD249 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipPreferenceCenterTests;\n\t\t\tproductName = AirshipPreferenceCenterTests;\n\t\t\tproductReference = 847BFFFC267CD73A007CD249 /* AirshipPreferenceCenterTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\tA62058682A5841330041FBF9 /* AirshipFeatureFlags */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = A620587C2A5841340041FBF9 /* Build configuration list for PBXNativeTarget \"AirshipFeatureFlags\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tA62058642A5841330041FBF9 /* Headers */,\n\t\t\t\tA62058652A5841330041FBF9 /* Sources */,\n\t\t\t\tA62058662A5841330041FBF9 /* Frameworks */,\n\t\t\t\tA62058672A5841330041FBF9 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tA61F3A772A5DBA0E00EE94CC /* PBXTargetDependency */,\n\t\t\t\tA61F3A7B2A5DBA1800EE94CC /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipFeatureFlags;\n\t\t\tproductName = AirshipFeatureFlags;\n\t\t\tproductReference = A62058692A5841330041FBF9 /* AirshipFeatureFlags.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\tA620586F2A5841330041FBF9 /* AirshipFeatureFlagsTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = A620587D2A5841340041FBF9 /* Build configuration list for PBXNativeTarget \"AirshipFeatureFlagsTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tA620586C2A5841330041FBF9 /* Sources */,\n\t\t\t\tA620586D2A5841330041FBF9 /* Frameworks */,\n\t\t\t\tA620586E2A5841330041FBF9 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tA62058732A5841330041FBF9 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipFeatureFlagsTests;\n\t\t\tproductName = AirshipFeatureFlagsTests;\n\t\t\tproductReference = A62058702A5841330041FBF9 /* AirshipFeatureFlagsTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n\t\tA641E1462BDBBDB400DE6FAA /* AirshipObjectiveC */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = A641E14D2BDBBDB400DE6FAA /* Build configuration list for PBXNativeTarget \"AirshipObjectiveC\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tA641E1422BDBBDB400DE6FAA /* Headers */,\n\t\t\t\tA641E1432BDBBDB400DE6FAA /* Sources */,\n\t\t\t\tA641E1442BDBBDB400DE6FAA /* Frameworks */,\n\t\t\t\tA641E1452BDBBDB400DE6FAA /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tA60235362CCB9E3C00CF412B /* PBXTargetDependency */,\n\t\t\t\tA641E1572BDBF5FF00DE6FAA /* PBXTargetDependency */,\n\t\t\t\tA641E1592BDBF5FF00DE6FAA /* PBXTargetDependency */,\n\t\t\t\tA641E15B2BDBF5FF00DE6FAA /* PBXTargetDependency */,\n\t\t\t\tA641E15D2BDBF5FF00DE6FAA /* PBXTargetDependency */,\n\t\t\t\tA641E15F2BDBF5FF00DE6FAA /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t6E128BA02D305F2E00733024 /* Source */,\n\t\t\t);\n\t\t\tname = AirshipObjectiveC;\n\t\t\tproductName = AirshipObjectiveC;\n\t\t\tproductReference = A641E1472BDBBDB400DE6FAA /* AirshipObjectiveC.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\tCC64F0531D8B77E3009CEF27 /* AirshipTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = CC64F05E1D8B77E3009CEF27 /* Build configuration list for PBXNativeTarget \"AirshipTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tCC64F0501D8B77E3009CEF27 /* Sources */,\n\t\t\t\tCC64F0511D8B77E3009CEF27 /* Frameworks */,\n\t\t\t\tCC64F0521D8B77E3009CEF27 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tCC64F05B1D8B77E3009CEF27 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipTests;\n\t\t\tproductName = AirshipTests;\n\t\t\tproductReference = CC64F0541D8B77E3009CEF27 /* AirshipTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t494DD94E1B0EB677009C134E /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = YES;\n\t\t\t\tLastSwiftUpdateCheck = 1410;\n\t\t\t\tLastUpgradeCheck = 1600;\n\t\t\t\tORGANIZATIONNAME = \"Urban Airship\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t3CA0E298237CCE2600EE76CF = {\n\t\t\t\t\t\tLastSwiftMigration = 1140;\n\t\t\t\t\t};\n\t\t\t\t\t3CA0E346237E4A7B00EE76CF = {\n\t\t\t\t\t\tLastSwiftMigration = 1340;\n\t\t\t\t\t};\n\t\t\t\t\t494DD9561B0EB677009C134E = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 6.3.1;\n\t\t\t\t\t\tDevelopmentTeam = PGJV57GD94;\n\t\t\t\t\t\tDevelopmentTeamName = \"Urban Airship Inc.\";\n\t\t\t\t\t\tLastSwiftMigration = 1250;\n\t\t\t\t\t};\n\t\t\t\t\t6E0B8729294A9C120064B7BD = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.1;\n\t\t\t\t\t\tLastSwiftMigration = 1410;\n\t\t\t\t\t};\n\t\t\t\t\t6E0B8730294A9C130064B7BD = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.1;\n\t\t\t\t\t};\n\t\t\t\t\t6E4A466D28EF44F600A25617 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.4;\n\t\t\t\t\t\tLastSwiftMigration = 1340;\n\t\t\t\t\t};\n\t\t\t\t\t6EAAE85D28C2AD3A003CAE53 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.4;\n\t\t\t\t\t};\n\t\t\t\t\t847BFFF3267CD739007CD249 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 12.5;\n\t\t\t\t\t\tLastSwiftMigration = 1250;\n\t\t\t\t\t};\n\t\t\t\t\t847BFFFB267CD73A007CD249 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 12.5;\n\t\t\t\t\t\tLastSwiftMigration = 1250;\n\t\t\t\t\t};\n\t\t\t\t\tA62058682A5841330041FBF9 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.1;\n\t\t\t\t\t\tLastSwiftMigration = 1410;\n\t\t\t\t\t};\n\t\t\t\t\tA620586F2A5841330041FBF9 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.1;\n\t\t\t\t\t};\n\t\t\t\t\tA641E1462BDBBDB400DE6FAA = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 15.3;\n\t\t\t\t\t\tLastSwiftMigration = 1530;\n\t\t\t\t\t};\n\t\t\t\t\tCC64F0531D8B77E3009CEF27 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.0;\n\t\t\t\t\t\tDevelopmentTeam = PGJV57GD94;\n\t\t\t\t\t\tLastSwiftMigration = 1100;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 494DD9511B0EB677009C134E /* Build configuration list for PBXProject \"Airship\" */;\n\t\t\tcompatibilityVersion = \"Xcode 3.2\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tar,\n\t\t\t\tcs,\n\t\t\t\tda,\n\t\t\t\tde,\n\t\t\t\t\"es-419\",\n\t\t\t\tes,\n\t\t\t\tfi,\n\t\t\t\tfr,\n\t\t\t\thi,\n\t\t\t\thu,\n\t\t\t\tid,\n\t\t\t\tit,\n\t\t\t\tiw,\n\t\t\t\tja,\n\t\t\t\tko,\n\t\t\t\tms,\n\t\t\t\tnl,\n\t\t\t\tno,\n\t\t\t\tpl,\n\t\t\t\t\"pt-PT\",\n\t\t\t\tpt,\n\t\t\t\tro,\n\t\t\t\tru,\n\t\t\t\tsk,\n\t\t\t\tsv,\n\t\t\t\tth,\n\t\t\t\ttr,\n\t\t\t\tvi,\n\t\t\t\t\"zh-Hans\",\n\t\t\t\t\"zh-Hant\",\n\t\t\t\the,\n\t\t\t\tnb,\n\t\t\t\tBase,\n\t\t\t\taf,\n\t\t\t\tam,\n\t\t\t\tbg,\n\t\t\t\tca,\n\t\t\t\tel,\n\t\t\t\tet,\n\t\t\t\tfa,\n\t\t\t\t\"fr-CA\",\n\t\t\t\thr,\n\t\t\t\tlt,\n\t\t\t\tlv,\n\t\t\t\tsl,\n\t\t\t\tsr,\n\t\t\t\tsw,\n\t\t\t\tuk,\n\t\t\t\t\"zh-HK\",\n\t\t\t\tzu,\n\t\t\t);\n\t\t\tmainGroup = 494DD94D1B0EB677009C134E;\n\t\t\tproductRefGroup = 494DD9581B0EB677009C134E /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t6E431F6B26EA814F009228AB /* AirshipBasement */,\n\t\t\t\t494DD9561B0EB677009C134E /* AirshipCore */,\n\t\t\t\t6E0B8729294A9C120064B7BD /* AirshipAutomation */,\n\t\t\t\tA62058682A5841330041FBF9 /* AirshipFeatureFlags */,\n\t\t\t\t3CA0E346237E4A7B00EE76CF /* AirshipMessageCenter */,\n\t\t\t\t847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */,\n\t\t\t\tA641E1462BDBBDB400DE6FAA /* AirshipObjectiveC */,\n\t\t\t\t3CA0E298237CCE2600EE76CF /* AirshipDebug */,\n\t\t\t\tCC64F0531D8B77E3009CEF27 /* AirshipTests */,\n\t\t\t\t6E0B8730294A9C130064B7BD /* AirshipAutomationTests */,\n\t\t\t\tA620586F2A5841330041FBF9 /* AirshipFeatureFlagsTests */,\n\t\t\t\t6E4A466D28EF44F600A25617 /* AirshipMessageCenterTests */,\n\t\t\t\t847BFFFB267CD73A007CD249 /* AirshipPreferenceCenterTests */,\n\t\t\t\t6EAAE85D28C2AD3A003CAE53 /* AirshipRelease */,\n\t\t\t\t6ECCAD252CF55BC700423D86 /* AirshipRelease tvOS */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t3CA0E2A8237CCE2600EE76CF /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t3CA0E417237E4A7B00EE76CF /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t3CA0E479237E4E3000EE76CF /* UAInbox.xcdatamodeld in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t494DD9551B0EB677009C134E /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t329DFCFF2B7FB8810039C8C0 /* UARemoteDataMappingV3toV4.xcmappingmodel in Resources */,\n\t\t\t\t6E1F6E882BE683E600CFC7A7 /* UARemoteDataMappingV1toV4.xcmappingmodel in Resources */,\n\t\t\t\t6E1F6E842BE6835400CFC7A7 /* UARemoteDataMappingV2toV4.xcmappingmodel in Resources */,\n\t\t\t\t6E411C782538C60900FEE4E8 /* UrbanAirship.strings in Resources */,\n\t\t\t\t6E411B782538C4E600FEE4E8 /* UANativeBridge in Resources */,\n\t\t\t\t6EFE7E3F2A97ED660064AC31 /* PrivacyInfo.xcprivacy in Resources */,\n\t\t\t\t6E411B7C2538C4E600FEE4E8 /* UANotificationCategories.plist in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B8728294A9C120064B7BD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B872F294A9C130064B7BD /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E4AEE312B6B3A6A008AEAC1 /* Valid-UAInAppMessageModalStyle.plist in Resources */,\n\t\t\t\t6E4AEE292B6B2E0A008AEAC1 /* airship.jpg in Resources */,\n\t\t\t\t6E4AEE322B6B3A6A008AEAC1 /* Valid-UAInAppMessageBannerStyle.plist in Resources */,\n\t\t\t\t6E4AEE352B6B3A6A008AEAC1 /* Valid-UAInAppMessageHTMLStyle.plist in Resources */,\n\t\t\t\t6E4AEE332B6B3A6A008AEAC1 /* Valid-UAInAppMessageFullScreenStyle.plist in Resources */,\n\t\t\t\t6E4AEE362B6B3A6A008AEAC1 /* Invalid-UAInAppMessageModalStyle.plist in Resources */,\n\t\t\t\t6E4AEE272B6B2E0A008AEAC1 /* alternate-airship.jpg in Resources */,\n\t\t\t\t6E4AEE372B6B3A6A008AEAC1 /* Invalid-UAInAppMessageFullScreenStyle.plist in Resources */,\n\t\t\t\t6E4AEE342B6B3A6A008AEAC1 /* Invalid-UAInAppMessageBannerStyle.plist in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E43203F26EA814F009228AB /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E4A466C28EF44F600A25617 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFF2267CD739007CD249 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFFA267CD73A007CD249 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EB5159728A5C54400870C5A /* TestThemeEmpty.plist in Resources */,\n\t\t\t\t6EB5159428A5B8E900870C5A /* TestTheme.plist in Resources */,\n\t\t\t\t6EB5159928A5C61D00870C5A /* TestThemeInvalid.plist in Resources */,\n\t\t\t\t6EB5159228A5B1B400870C5A /* TestLegacyTheme.plist in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA62058672A5841330041FBF9 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA620586E2A5841330041FBF9 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA641E1452BDBBDB400DE6FAA /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tCC64F0521D8B77E3009CEF27 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tCC64F14F1D8B7954009CEF27 /* AirshipConfig-Valid.plist in Resources */,\n\t\t\t\tCC64F1511D8B7954009CEF27 /* development-embedded.mobileprovision in Resources */,\n\t\t\t\tCC64F14D1D8B7954009CEF27 /* AirshipConfig-Valid-Legacy.plist in Resources */,\n\t\t\t\tCC64F1521D8B7954009CEF27 /* production-embedded.mobileprovision in Resources */,\n\t\t\t\tCC64F0CE1D8B781C009CEF27 /* CustomNotificationCategories.plist in Resources */,\n\t\t\t\t45A8ADF023134B38004AD8CA /* testMCColorsCatalog.xcassets in Resources */,\n\t\t\t\t6014AD752C20410B0072DCF0 /* airship.der in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t3CA0E29C237CCE2600EE76CF /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t3C693E4F25141CAC00EBFB88 /* AirshipDebugPushData.xcdatamodeld in Sources */,\n\t\t\t\t3C693E4E25141CAC00EBFB88 /* AirshipDebugEventData.xcdatamodeld in Sources */,\n\t\t\t\t60653FC22CBD2CD4009CD9A7 /* PushData+CoreDataClass.swift in Sources */,\n\t\t\t\t60653FC32CBD2CD4009CD9A7 /* PushData+CoreDataProperties.swift in Sources */,\n\t\t\t\t6E6802982B8675A200F4591F /* DebugComponent.swift in Sources */,\n\t\t\t\tDFD2464E2473404C000FD565 /* DebugSDKModule.swift in Sources */,\n\t\t\t\t3CB37A1E251151A400E60392 /* AirshipDebugResources.swift in Sources */,\n\t\t\t\t6E21852B237D32B30084933A /* EventData.swift in Sources */,\n\t\t\t\t6E14C9A128B5E4AF00A55E65 /* PushNotification.swift in Sources */,\n\t\t\t\t3CA0E2BF237CD05F00EE76CF /* AirshipEvent.swift in Sources */,\n\t\t\t\t3CA0E2CA237CD05F00EE76CF /* EventDataManager.swift in Sources */,\n\t\t\t\t3CA0E2CD237CD05F00EE76CF /* AirshipDebugManager.swift in Sources */,\n\t\t\t\t6EB21A682E81BB6E001A5660 /* AirshipDebugAddEmailChannelView.swift in Sources */,\n\t\t\t\t6EB21A692E81BB6E001A5660 /* AirshipDebugChannelTagView.swift in Sources */,\n\t\t\t\t6EB21A6A2E81BB6E001A5660 /* AirshipDebugAutomationsView.swift in Sources */,\n\t\t\t\t6EB21A6B2E81BB6E001A5660 /* AirshipDebugFeatureFlagDetailsView.swift in Sources */,\n\t\t\t\t6EB21A6C2E81BB6E001A5660 /* AirshipDebugExperimentsView.swift in Sources */,\n\t\t\t\t6EB21A6D2E81BB6E001A5660 /* AirshipDebugExtensions.swift in Sources */,\n\t\t\t\t6EB21A6E2E81BB6E001A5660 /* AirshipDebugAddEventView.swift in Sources */,\n\t\t\t\t6EB21A6F2E81BB6E001A5660 /* AirshipDebugPushDetailsView.swift in Sources */,\n\t\t\t\t6EB21A702E81BB6E001A5660 /* AirshipDebugView.swift in Sources */,\n\t\t\t\t6EB21A712E81BB6E001A5660 /* AirshipDebugAddSMSChannelView.swift in Sources */,\n\t\t\t\t6EB21A722E81BB6E001A5660 /* AirshipJSONDetailsView.swift in Sources */,\n\t\t\t\t6EB21A732E81BB6E001A5660 /* AirshipDebugAnalyticIdentifierEditorView.swift in Sources */,\n\t\t\t\t6EB21A742E81BB6E001A5660 /* AirshipDebugContentView.swift in Sources */,\n\t\t\t\t6EB21A752E81BB6E001A5660 /* AirshipDebugRoute.swift in Sources */,\n\t\t\t\t6EB21A932E81C7AB001A5660 /* AirshipDebugAddStringPropertyView.swift in Sources */,\n\t\t\t\t6EB21A772E81BB6E001A5660 /* AirshipDebugPreferencCenterItemView.swift in Sources */,\n\t\t\t\t6EB21AFC2E8216A4001A5660 /* AirshipoDebugTriggers.swift in Sources */,\n\t\t\t\t6EB21A792E81BB6E001A5660 /* AirshipDebugAttributesEditorView.swift in Sources */,\n\t\t\t\t6EB21A7A2E81BB6E001A5660 /* AirshipToast.swift in Sources */,\n\t\t\t\t6EB21A7B2E81BB6E001A5660 /* AirshipDebugFeatureFlagView.swift in Sources */,\n\t\t\t\t6EB21A7C2E81BB6E001A5660 /* Extensions.swift in Sources */,\n\t\t\t\t6EB21A7D2E81BB6E001A5660 /* AirshipDebugTagGroupsEditorView.swift in Sources */,\n\t\t\t\t6EB21A7E2E81BB6E001A5660 /* AirshipDebugAppInfoView.swift in Sources */,\n\t\t\t\t6EB21A7F2E81BB6E001A5660 /* AirshipDebugEventDetailsView.swift in Sources */,\n\t\t\t\t6EB21A802E81BB6E001A5660 /* AirshipDebugPrivacyManagerView.swift in Sources */,\n\t\t\t\t6EB21A912E81BFC1001A5660 /* AirshipDebugAddPropertyView.swift in Sources */,\n\t\t\t\t6EB21A812E81BB6E001A5660 /* AirshipDebugEventsView.swift in Sources */,\n\t\t\t\t6EB21A822E81BB6E001A5660 /* AirshipDebugContactSubscriptionEditorView.swift in Sources */,\n\t\t\t\t6EB21A832E81BB6E001A5660 /* AirshipDebugPreferenceCenterView.swift in Sources */,\n\t\t\t\t6EB21A842E81BB6E001A5660 /* AirshipDebugAnalyticsView.swift in Sources */,\n\t\t\t\t6EB21A852E81BB6E001A5660 /* AirshipDebugReceivedPushView.swift in Sources */,\n\t\t\t\t6EB21A862E81BB6E001A5660 /* AirshipDebugPushView.swift in Sources */,\n\t\t\t\t6EB21A872E81BB6E001A5660 /* AirshipDebugNamedUserView.swift in Sources */,\n\t\t\t\t6EB21A882E81BB6E001A5660 /* AirshipJSONView.swift in Sources */,\n\t\t\t\t6EB21A892E81BB6E001A5660 /* AirshipDebugChannelSubscriptionsView.swift in Sources */,\n\t\t\t\t6EB21A8A2E81BB6E001A5660 /* AirshipDebugInAppExperiencesView.swift in Sources */,\n\t\t\t\t6EB21A8B2E81BB6E001A5660 /* TVSlider.swift in Sources */,\n\t\t\t\t6EB21A8C2E81BB6E001A5660 /* TVDatePicker.swift in Sources */,\n\t\t\t\t6EB21A8D2E81BB6E001A5660 /* AirshipDebugChannelView.swift in Sources */,\n\t\t\t\t6EB21A8E2E81BB6E001A5660 /* AirshipDebugContactView.swift in Sources */,\n\t\t\t\t6EB21B5F2E82FE9F001A5660 /* AirshipDebugAudienceSubject.swift in Sources */,\n\t\t\t\t6EB21A8F2E81BB6E001A5660 /* AirshipDebugAddOpenChannelView.swift in Sources */,\n\t\t\t\t6E6CC38623A3F9B4003D583C /* PushDataManager.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t3CA0E34A237E4A7B00EE76CF /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E4D22502E6F9CF200A8D641 /* MessageCenterContent.swift in Sources */,\n\t\t\t\t6E4A469F28F4A7DF00A25617 /* MessageCenterAction.swift in Sources */,\n\t\t\t\t6E4A466528EF448600A25617 /* MessageCenterStore.swift in Sources */,\n\t\t\t\t32B632892906CA17000D3E34 /* MessageCenterTheme.swift in Sources */,\n\t\t\t\t2797B4192F47687800A7F848 /* NativeLayoutPersistentDataStore.swift in Sources */,\n\t\t\t\t32F68CEF28F07C2C00F7F52A /* MessageCenterSDKModule.swift in Sources */,\n\t\t\t\t32B5BE3F28F8A8C200F2254B /* MessageCenterView.swift in Sources */,\n\t\t\t\t6E4D224C2E6F968A00A8D641 /* MessageCenterNavigationStack.swift in Sources */,\n\t\t\t\t32F68CEE28F07C2C00F7F52A /* AirshipMessageCenterResources.swift in Sources */,\n\t\t\t\t6E4A466628EF448600A25617 /* MessageCenterAPIClient.swift in Sources */,\n\t\t\t\t6E4A466128EF447C00A25617 /* MessageCenterUser.swift in Sources */,\n\t\t\t\t6E6802962B86749900F4591F /* MessageCenterComponent.swift in Sources */,\n\t\t\t\t6E4A46A128F4AEDF00A25617 /* MessageCenterNativeBridgeExtension.swift in Sources */,\n\t\t\t\t27CCF77D2F1656150018058F /* MessageViewAnalytics.swift in Sources */,\n\t\t\t\t27E4194A2EF59F9800D5C1A6 /* MessageCenterThomasView.swift in Sources */,\n\t\t\t\t32B632882906CA17000D3E34 /* MessageCenterThemeLoader.swift in Sources */,\n\t\t\t\t32B5BE4928F8B66500F2254B /* MessageCenterViewController.swift in Sources */,\n\t\t\t\t6E4A466728EF448600A25617 /* MessageCenterMessage.swift in Sources */,\n\t\t\t\t6EC81D052F2D448B00E1C0C6 /* UAInboxDataMappingV2toV4.xcmappingmodel in Sources */,\n\t\t\t\t6E4D224E2E6F96B800A8D641 /* MessageCenterSplitNavigationView.swift in Sources */,\n\t\t\t\t6E1476CC2F5643A100320A36 /* MessageCenterNavigationAppearance.swift in Sources */,\n\t\t\t\t32B5BE4128F8A8C700F2254B /* MessageCenterListView.swift in Sources */,\n\t\t\t\t6E4D22522E6FA2F700A8D641 /* MessageCenterBackButton.swift in Sources */,\n\t\t\t\t6E4D224A2E6F814000A8D641 /* MessageCenterMessageViewWithNavigation.swift in Sources */,\n\t\t\t\t6E4D225F2E70AFE800A8D641 /* MessageCenterListViewModel.swift in Sources */,\n\t\t\t\t329DFCD52B7E59700039C8C0 /* UAInboxDataMapping.swift in Sources */,\n\t\t\t\t6EDE5F192B9BD7E700E33D04 /* InboxMessageData.swift in Sources */,\n\t\t\t\t32B5BE3E28F8A8C000F2254B /* MessageCenterListItemView.swift in Sources */,\n\t\t\t\t32B513562B9F53A500BBE780 /* MessageCenterPredicate.swift in Sources */,\n\t\t\t\t32B5BE3B28F8A7EB00F2254B /* MessageCenterController.swift in Sources */,\n\t\t\t\t32F68CF328F07C2C00F7F52A /* MessageCenter.swift in Sources */,\n\t\t\t\t99FD20A52DEFC35900242551 /* MessageCenterUIKitAppearance.swift in Sources */,\n\t\t\t\t6EC81D032F2D445500E1C0C6 /* UAInboxDataMappingV1toV4.xcmappingmodel in Sources */,\n\t\t\t\t2753F6422F6C5BB50073882C /* MessageCenterMessageError.swift in Sources */,\n\t\t\t\t6EC81D082F2D44D700E1C0C6 /* UAInboxDataMappingV3toV4.xcmappingmodel in Sources */,\n\t\t\t\t6E4D22542E6FA5ED00A8D641 /* MessageCenterWebView.swift in Sources */,\n\t\t\t\t6E4D20722E6B761200A8D641 /* MessageCenterListViewWithNavigation.swift in Sources */,\n\t\t\t\t32F68CF528F07C4900F7F52A /* MessageCenterList.swift in Sources */,\n\t\t\t\t6E4D225D2E70ADE100A8D641 /* MessageCenterMessageViewModel.swift in Sources */,\n\t\t\t\t32B5BE4228F8A8CA00F2254B /* MessageCenterListItemViewModel.swift in Sources */,\n\t\t\t\t32B5BE4028F8A8C500F2254B /* MessageCenterMessageView.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t494DD9521B0EB677009C134E /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E52146D2DCBFABE00CF64B9 /* ThomasPagerTracker.swift in Sources */,\n\t\t\t\t6EB1B3F326EAA4D6000421B9 /* ChannelAPIClient.swift in Sources */,\n\t\t\t\t6EED67C02CE1B5160087CDCB /* ThomasActionsPayload.swift in Sources */,\n\t\t\t\t990EB3B12BF59A1500315EAC /* ContactChannelsProvider.swift in Sources */,\n\t\t\t\t6EED67722CDEE8390087CDCB /* ThomasOrientation.swift in Sources */,\n\t\t\t\t6E7112A12880DACB004942E4 /* EnableBehaviorModifiers.swift in Sources */,\n\t\t\t\t6E12539129A81ACE0009EE58 /* AirshipCoreDataPredicate.swift in Sources */,\n\t\t\t\t6E6C3F7F27A20C3C007F55C7 /* ChannelScope.swift in Sources */,\n\t\t\t\t6E4E2E2829CEB222002E7682 /* ContactManagerProtocol.swift in Sources */,\n\t\t\t\t6E6ED15F2683DBC300A2CBD0 /* AirshipCoreResources.swift in Sources */,\n\t\t\t\t6E87BE0126E283850005D20D /* Airship.swift in Sources */,\n\t\t\t\t6E6363E229DCD0CF009C358A /* ContactSubscriptionListClient.swift in Sources */,\n\t\t\t\t6E3B32CC27559D8B00B89C7B /* FormInputViewModifier.swift in Sources */,\n\t\t\t\t3243EC632D93109C00B43B25 /* AirshipSwitchToggleStyle.swift in Sources */,\n\t\t\t\t3243EC642D93109C00B43B25 /* AirshipCheckboxToggleStyle.swift in Sources */,\n\t\t\t\t6EED67B62CE1B4420087CDCB /* ThomasTextAppearance.swift in Sources */,\n\t\t\t\t6E46A27F272B68660089CDE3 /* ThomasDelegate.swift in Sources */,\n\t\t\t\t6E1CBD812BA3A30300519D9C /* AirshipEmbeddedInfo.swift in Sources */,\n\t\t\t\t6ECD4F6F2DD7A7090060EE72 /* CheckboxToggleLayout.swift in Sources */,\n\t\t\t\t6E4325CE2B7AD5A200A9B000 /* AirshipComponent.swift in Sources */,\n\t\t\t\t6EC7E48226A60C060038CFDD /* AirshipChannel.swift in Sources */,\n\t\t\t\t6E2F5A8A2A66088100CABD3D /* AudienceDeviceInfoProvider.swift in Sources */,\n\t\t\t\t6E664BDD26C4CD8700A2C8E5 /* OpenExternalURLAction.swift in Sources */,\n\t\t\t\t6E75F50529C4EAF600E3585A /* AudienceOverridesProvider.swift in Sources */,\n\t\t\t\t3237D5F22B865D990055932B /* JSONValueTransformer.swift in Sources */,\n\t\t\t\t6E4339EF2DFA03A3000A7741 /* JSONValueMatcherPredicates.swift in Sources */,\n\t\t\t\t6EED68162CE271F80087CDCB /* ThomasFormSubmitBehavior.swift in Sources */,\n\t\t\t\t6ECD4F6D2DD7A7060060EE72 /* RadioInputToggleLayout.swift in Sources */,\n\t\t\t\t6EB5158328A47C7100870C5A /* ScopedSubscriptionListEdit.swift in Sources */,\n\t\t\t\t60D3BCD02A154D9400E07524 /* MessageCriteria.swift in Sources */,\n\t\t\t\t6E1472D52F526DCD00320A36 /* AirshipNativePlatform.swift in Sources */,\n\t\t\t\t608B16E62C2C1138005298FA /* SubscriptionListProvider.swift in Sources */,\n\t\t\t\t6E94761529BBC0240025F364 /* AirshipButton.swift in Sources */,\n\t\t\t\t3251586B272AFB2E00DF8B44 /* VideoMediaWebView.swift in Sources */,\n\t\t\t\t6E96ED02294115210053CC91 /* AsyncStream.swift in Sources */,\n\t\t\t\t6EB11C8D2698C50F00DC698F /* AudienceUtils.swift in Sources */,\n\t\t\t\t6EED682D2CE28CC10087CDCB /* ThomasValidationInfo.swift in Sources */,\n\t\t\t\t6E4A4FDA2A30358F0049FEFC /* TagsActionArgs.swift in Sources */,\n\t\t\t\t6E57CE3728DBBD9A00287601 /* LiveActivityRegistry.swift in Sources */,\n\t\t\t\t6EBFA9AD2D15DA73002BA3E9 /* HashChecker.swift in Sources */,\n\t\t\t\t6E96ED0A294135500053CC91 /* EventManager.swift in Sources */,\n\t\t\t\t6E4325F22B7B1EDA00A9B000 /* SessionEventFactory.swift in Sources */,\n\t\t\t\t6EE49C102A0C142F00AB1CF4 /* RemoteDataInfo.swift in Sources */,\n\t\t\t\t6E5214692DCABFCE00CF64B9 /* ThomasLayoutContext.swift in Sources */,\n\t\t\t\t6E15B71826CEB4190099C92D /* RemoteDataStore.swift in Sources */,\n\t\t\t\t6E87BDBD26E01FF40005D20D /* ModuleLoader.swift in Sources */,\n\t\t\t\t271B38652DB2866200495D9F /* TagActionMutation.swift in Sources */,\n\t\t\t\t6E6541E02758976D009676CA /* AirshipProgressView.swift in Sources */,\n\t\t\t\tA67EC249279B1A40009089E1 /* ScopedSubscriptionListEditor.swift in Sources */,\n\t\t\t\t6E698DF226790AC300654DB2 /* AirshipPrivacyManager.swift in Sources */,\n\t\t\t\t6E96ED16294197D90053CC91 /* EventUploadTuningInfo.swift in Sources */,\n\t\t\t\t6E95292C268B98A200398B54 /* MediaEventTemplate.swift in Sources */,\n\t\t\t\t6E66BA7F2D14B61A0083A9FD /* WrappingLayout.swift in Sources */,\n\t\t\t\tA6A5530A26D548AF002B20F6 /* NativeBridge.swift in Sources */,\n\t\t\t\t6E91E45828EF423400B6F25E /* AirshipWorkResult.swift in Sources */,\n\t\t\t\t6EED68192CE272790087CDCB /* ThomasAutomatedAccessibilityAction.swift in Sources */,\n\t\t\t\t32CF81E2275627F4003009D1 /* AirshipAsyncImage.swift in Sources */,\n\t\t\t\t6E664BD026C4916600A2C8E5 /* AddTagsAction.swift in Sources */,\n\t\t\t\t6EED679C2CDEEA380087CDCB /* ThomasToggleStyleInfo.swift in Sources */,\n\t\t\t\t6E664BA726C4417400A2C8E5 /* ShareAction.swift in Sources */,\n\t\t\t\t6ED6ECA426ADCA6F00973364 /* BlockAction.swift in Sources */,\n\t\t\t\t6E475BFE2F5A3709003D8E42 /* VideoGroupState.swift in Sources */,\n\t\t\t\t6E87BD6426D594870005D20D /* ChannelRegistrationPayload.swift in Sources */,\n\t\t\t\t27F1E2012F0E910B00E317DB /* ThomasLayoutButtonTapEvent.swift in Sources */,\n\t\t\t\t27F1E2022F0E910B00E317DB /* ThomasLayoutDisplayEvent.swift in Sources */,\n\t\t\t\t27F1E2032F0E910B00E317DB /* ThomasLayoutEvent.swift in Sources */,\n\t\t\t\t27F1E2042F0E910B00E317DB /* ThomasLayoutFormDisplayEvent.swift in Sources */,\n\t\t\t\t27F1E2052F0E910B00E317DB /* ThomasLayoutFormResultEvent.swift in Sources */,\n\t\t\t\t27F1E2062F0E910B00E317DB /* ThomasLayoutGestureEvent.swift in Sources */,\n\t\t\t\t27F1E2072F0E910B00E317DB /* ThomasLayoutPageActionEvent.swift in Sources */,\n\t\t\t\t27F1E2082F0E910B00E317DB /* ThomasLayoutPagerCompletedEvent.swift in Sources */,\n\t\t\t\t27F1E2092F0E910B00E317DB /* ThomasLayoutPagerSummaryEvent.swift in Sources */,\n\t\t\t\t27F1E20A2F0E910B00E317DB /* ThomasLayoutPageSwipeEvent.swift in Sources */,\n\t\t\t\t27F1E20B2F0E910B00E317DB /* ThomasLayoutPageViewEvent.swift in Sources */,\n\t\t\t\t27F1E20C2F0E910B00E317DB /* ThomasLayoutPermissionResultEvent.swift in Sources */,\n\t\t\t\t27F1E20D2F0E910B00E317DB /* ThomasLayoutResolutionEvent.swift in Sources */,\n\t\t\t\t6E299FD728D13E54001305A7 /* AirshipRequest.swift in Sources */,\n\t\t\t\t8401769C26C5729E00373AF7 /* JSONValueMatcher.swift in Sources */,\n\t\t\t\t3CC95B2B2696549B00FE2ACD /* AirshipPushableComponent.swift in Sources */,\n\t\t\t\t32F97AC129E5986B00FED65F /* StoryIndicator.swift in Sources */,\n\t\t\t\t6EFD6D4B27272333005B26F1 /* EmptyView.swift in Sources */,\n\t\t\t\t324D3BFF273E6B4500058EE4 /* BannerView.swift in Sources */,\n\t\t\t\t6EAA61492D5297A7006602F7 /* SubjectExtension.swift in Sources */,\n\t\t\t\t6E6BD26D2AF1AC5700B9DFC9 /* AirshipCache.swift in Sources */,\n\t\t\t\t6EBFA9AF2D15E04D002BA3E9 /* AirshipDeviceAudienceResult.swift in Sources */,\n\t\t\t\t320AD3A629E7FA2000D66106 /* PagerGestureMap.swift in Sources */,\n\t\t\t\tA6A5531026D548D6002B20F6 /* JavaScriptEnvironment.swift in Sources */,\n\t\t\t\t6E664BE726C5B21600A2C8E5 /* ModifyAttributesAction.swift in Sources */,\n\t\t\t\t6EED67BA2CE1B4E90087CDCB /* ThomasPlatform.swift in Sources */,\n\t\t\t\t6E664BD926C4CD8700A2C8E5 /* PasteboardAction.swift in Sources */,\n\t\t\t\t6E15B71426CEB4190099C92D /* RemoteDataStorePayload.swift in Sources */,\n\t\t\t\tA6D6D49F2A02608C0072A5CA /* ActionResult.swift in Sources */,\n\t\t\t\t6E91B43C26868A6300DDB1A8 /* CircularRegion.swift in Sources */,\n\t\t\t\t6E1589582AFF023400954A04 /* SessionEvent.swift in Sources */,\n\t\t\t\t6014AD672C1B5F540072DCF0 /* ChallengeResolver.swift in Sources */,\n\t\t\t\t6E5ADF822D7682A300A03799 /* StateSubscriptionsModifier.swift in Sources */,\n\t\t\t\t6E6C3F9E27A4C3D4007F55C7 /* AirshipJSON.swift in Sources */,\n\t\t\t\t6E49D7C828401D2E00C7BB9D /* APNSRegistrar.swift in Sources */,\n\t\t\t\t6E49D7B228401D2E00C7BB9D /* Permission.swift in Sources */,\n\t\t\t\t6329102E2DD8103200B13C6C /* NativeVideoPlayer.swift in Sources */,\n\t\t\t\t27F1E1342F0E7C9C00E317DB /* ThomasLayoutEventRecorder.swift in Sources */,\n\t\t\t\t6E49D7BC28401D2E00C7BB9D /* PermissionsManager.swift in Sources */,\n\t\t\t\t6E062D0727165709001A74A1 /* Label.swift in Sources */,\n\t\t\t\t60C1DB0F2A8B743C00A1D3DA /* AirshipEmbeddedView.swift in Sources */,\n\t\t\t\t6E698DEC26790AC300654DB2 /* PreferenceDataStore.swift in Sources */,\n\t\t\t\tE99605A127A071EA00365AE4 /* EmailRegistrationOptions.swift in Sources */,\n\t\t\t\t6EFD6D6E27290C0B005B26F1 /* FormController.swift in Sources */,\n\t\t\t\t6E5ADF842D7682D600A03799 /* ThomasStateTrigger.swift in Sources */,\n\t\t\t\t6EED67EB2CE269970087CDCB /* ThomasDirection.swift in Sources */,\n\t\t\t\t6E91E44C28EF423400B6F25E /* AirshipWorkRequest.swift in Sources */,\n\t\t\t\t6EE49BDD2A09AD3600AB1CF4 /* AirshipNotificationStatus.swift in Sources */,\n\t\t\t\t6EED67A02CDEEAAB0087CDCB /* AirshipLayout.swift in Sources */,\n\t\t\t\t608B16F12C2D5F0D005298FA /* BaseCachingRemoteDataProvider.swift in Sources */,\n\t\t\t\t6EFD6D82272A53AE005B26F1 /* PagerState.swift in Sources */,\n\t\t\t\tA1B2C3D4E5F60002VIDEOST /* VideoState.swift in Sources */,\n\t\t\t\tA1B2C3D4E5F60004VIDEOCR /* VideoController.swift in Sources */,\n\t\t\t\t6E9D529B26C1A77C004EA16B /* ActionRegistry.swift in Sources */,\n\t\t\t\t6E87BE0726E283850005D20D /* DeepLinkDelegate.swift in Sources */,\n\t\t\t\t6E0105032DDFA719009D651F /* ScoreToggleLayout.swift in Sources */,\n\t\t\t\t6E8932982E7B666000FB0EC4 /* APNSRegistrationResult.swift in Sources */,\n\t\t\t\t6E60EF6A29DF542B003F7A8D /* AnonContactData.swift in Sources */,\n\t\t\t\t6E6363EC29DDF84B009C358A /* SerialQueue.swift in Sources */,\n\t\t\t\t6E40868E2B8D036600435E2C /* AirshipEmbeddedSize.swift in Sources */,\n\t\t\t\t6E92ECB1284ECE590038802D /* CachedList.swift in Sources */,\n\t\t\t\t6ED562A02EA9434B00C20B55 /* StackImageButton.swift in Sources */,\n\t\t\t\t6E952923268B812000398B54 /* AccountEventTemplate.swift in Sources */,\n\t\t\t\tA658DE2B272AFB0400007672 /* AirshipImageLoader.swift in Sources */,\n\t\t\t\t6EA5202327D1364E003011CA /* AirshipDateFormatter.swift in Sources */,\n\t\t\t\t6E7DB38328ECDC41002725F6 /* LiveActivity.swift in Sources */,\n\t\t\t\t6E4A4FDE2A3132850049FEFC /* AirshipSDKModule.swift in Sources */,\n\t\t\t\t6E062D0D2718B505001A74A1 /* ViewConstraints.swift in Sources */,\n\t\t\t\t6E698E3D267BEDC300654DB2 /* DefaultAirshipContact.swift in Sources */,\n\t\t\t\t6EED67972CDEEA2E0087CDCB /* ThomasShapeInfo.swift in Sources */,\n\t\t\t\t60D3BCCC2A153C0700E07524 /* Experiment.swift in Sources */,\n\t\t\t\t6ED838AB2D0CE9D6009CBB0C /* CompoundDeviceAudienceSelector.swift in Sources */,\n\t\t\t\t6E52146B2DCBF9BD00CF64B9 /* AirshipTimerProtocol.swift in Sources */,\n\t\t\t\t60C1DB122A8B743C00A1D3DA /* EmbeddedView.swift in Sources */,\n\t\t\t\t27F1E1362F0E7D7B00E317DB /* ThomasLayoutEventSource.swift in Sources */,\n\t\t\t\tA658DE192728498900007672 /* AirshipWebview.swift in Sources */,\n\t\t\t\t6EED67F02CE26CA40087CDCB /* ThomasSize.swift in Sources */,\n\t\t\t\t6E15B72D26CF13BC0099C92D /* RemoteDataProviderDelegate.swift in Sources */,\n\t\t\t\t6E07688829F9D28A0014E2A9 /* AirshipNotificationCenter.swift in Sources */,\n\t\t\t\t99E433932C9A0362006436B9 /* PagerIndicator.swift in Sources */,\n\t\t\t\t6E6ED1402683A9F200A2CBD0 /* AirshipDate.swift in Sources */,\n\t\t\t\t6EC922E12D832BB8000A3A59 /* ThomasFormFieldProcessor.swift in Sources */,\n\t\t\t\t6E411CAF2538C6A600FEE4E8 /* UAEvents.xcdatamodeld in Sources */,\n\t\t\t\t6EF66D912769B69C00ABCB76 /* RootView.swift in Sources */,\n\t\t\t\t6EED680D2CE2707F0087CDCB /* ThomasStateAction.swift in Sources */,\n\t\t\t\tA6849387273290520021675E /* Score.swift in Sources */,\n\t\t\t\t6E062D03271656DE001A74A1 /* Container.swift in Sources */,\n\t\t\t\tC02D0B6626C1A3E200F673E6 /* ChannelCapture.swift in Sources */,\n\t\t\t\t6EE49C222A13E32B00AB1CF4 /* RemoteDataProviderProtocol.swift in Sources */,\n\t\t\t\t6EF1401B2A2671ED009A125D /* AirshipDeviceID.swift in Sources */,\n\t\t\t\t99E433942C9A03D9006436B9 /* Pager.swift in Sources */,\n\t\t\t\t6E698E5F267BF63B00654DB2 /* ApplicationState.swift in Sources */,\n\t\t\t\t8401769826C5722400373AF7 /* JSONPredicate.swift in Sources */,\n\t\t\t\t602AD0D52D7242B300C7D566 /* ThomasSmsLocale.swift in Sources */,\n\t\t\t\t6E146D682F523DB900320A36 /* AirshipFont.swift in Sources */,\n\t\t\t\t6EFD6D8B272A53FB005B26F1 /* PagerController.swift in Sources */,\n\t\t\t\t6E664BD326C4917000A2C8E5 /* RemoveTagsAction.swift in Sources */,\n\t\t\t\t6EED67B32CE1A8330087CDCB /* ThomasEventHandler.swift in Sources */,\n\t\t\t\t6EF66D8D276461DA00ABCB76 /* UrlInfo.swift in Sources */,\n\t\t\t\t32BBFB402B274C8600C6A998 /* ContactChannelsAPIClient.swift in Sources */,\n\t\t\t\t6E1EEE902BD81AF300B45A87 /* ContactChannel.swift in Sources */,\n\t\t\t\t27F1E1352F0E7D2C00E317DB /* ThomasLayoutEventMessageID.swift in Sources */,\n\t\t\t\t6E6BD2422AE995DA00B9DFC9 /* DeferredResolver.swift in Sources */,\n\t\t\t\t6ECDDE6C29B7EEE9009D79DB /* AuthToken.swift in Sources */,\n\t\t\t\t6EAD7CE526B216DB00B88EA7 /* DeepLinkAction.swift in Sources */,\n\t\t\t\t6EED67AB2CE1A5D00087CDCB /* ThomasMarkdownOptions.swift in Sources */,\n\t\t\t\t6E299FDB28D14208001305A7 /* AirshipResponse.swift in Sources */,\n\t\t\t\t6EED67AE2CE1A6B90087CDCB /* ThomasPosition.swift in Sources */,\n\t\t\t\t6ED2F5312B7FF819000AFC80 /* AirshipViewUtils.swift in Sources */,\n\t\t\t\t6E87BD9226D963B60005D20D /* DefaultAppIntegrationDelegate.swift in Sources */,\n\t\t\t\t6E91B44026868C3400DDB1A8 /* RegionEvent.swift in Sources */,\n\t\t\t\t60D3BCC42A1529D800E07524 /* ExperimentDataProvider.swift in Sources */,\n\t\t\t\t6E87BD8D26D815780005D20D /* AppIntegration.swift in Sources */,\n\t\t\t\t6E5A64D92AABC5A400574085 /* UAMeteredUsage.xcdatamodeld in Sources */,\n\t\t\t\t6E4326012B7C327C00A9B000 /* AirshipEvents.swift in Sources */,\n\t\t\t\t6E1A1D852D70F3700056418B /* ThomasState.swift in Sources */,\n\t\t\t\t6EED67752CDEE8460087CDCB /* ThomasWindowSize.swift in Sources */,\n\t\t\t\t6E887CD3272C5F5000E83363 /* Checkbox.swift in Sources */,\n\t\t\t\t6EDAFB262CB463C5000BD4AA /* ButtonLayout.swift in Sources */,\n\t\t\t\t275D32AC2EF957F200B75760 /* AirshipSimpleLayoutView.swift in Sources */,\n\t\t\t\t275D32AD2EF957F200B75761 /* AirshipSimpleLayoutViewModel.swift in Sources */,\n\t\t\t\t6EB11C8B2697AFC700DC698F /* AttributeUpdate.swift in Sources */,\n\t\t\t\t6EE49C0C2A0C141800AB1CF4 /* RemoteDataSource.swift in Sources */,\n\t\t\t\t6E91B4692689327D00DDB1A8 /* CustomEvent.swift in Sources */,\n\t\t\t\t6E1589542AFF021D00954A04 /* SessionState.swift in Sources */,\n\t\t\t\t6EED680A2CE26E550087CDCB /* ThomasAutomatedAction.swift in Sources */,\n\t\t\t\t6EED68122CE271E50087CDCB /* ThomasEnableBehavior.swift in Sources */,\n\t\t\t\t6E49D7C628401D2E00C7BB9D /* Badger.swift in Sources */,\n\t\t\t\t6EAD3AF82F45305E00FF274E /* AirshipSwizzler.swift in Sources */,\n\t\t\t\t6E96ECF6293FCE080053CC91 /* EventUploadScheduler.swift in Sources */,\n\t\t\t\t6E71129D2880DACB004942E4 /* EventHandlerViewModifier.swift in Sources */,\n\t\t\t\t6EF27DE62730E77300548DA3 /* RadioInputState.swift in Sources */,\n\t\t\t\t6E91E44328EF423400B6F25E /* WorkRateLimiterActor.swift in Sources */,\n\t\t\t\t608B16E82C2C6DDB005298FA /* ChannelSubscriptionListProvider.swift in Sources */,\n\t\t\t\t6ECB627E2A36A0770095C85C /* ExternalURLProcessor.swift in Sources */,\n\t\t\t\t6E1BACDD2719FC0A0038399E /* ViewFactory.swift in Sources */,\n\t\t\t\t6E9B4874288F0CE000C905B1 /* RateAppAction.swift in Sources */,\n\t\t\t\t6EAD3AFA2F4530BE00FF274E /* AutoIntegration.swift in Sources */,\n\t\t\t\t6ED80793273CA0C800D1F455 /* EnvironmentValues.swift in Sources */,\n\t\t\t\t6E49D7BE28401D2E00C7BB9D /* RegistrationDelegate.swift in Sources */,\n\t\t\t\t6E6ED15B2683DBC300A2CBD0 /* AirshipBase64.swift in Sources */,\n\t\t\t\tA61517B226A9C4C3008A41C4 /* SubscriptionListEditor.swift in Sources */,\n\t\t\t\t6E146C502F5214D900320A36 /* AirshipDevice.swift in Sources */,\n\t\t\t\t6E1528282B4DCFCB00DF1377 /* AirshipActorValue.swift in Sources */,\n\t\t\t\t6E664BCA26C4852B00A2C8E5 /* AddCustomEventAction.swift in Sources */,\n\t\t\t\t6EC922E32D838DFF000A3A59 /* ThomasFormPayloadGenerator.swift in Sources */,\n\t\t\t\t6ECDDE7929B804FB009D79DB /* ChannelAuthTokenAPIClient.swift in Sources */,\n\t\t\t\t6E77CD4A2D8A225E0057A52C /* SMSValidatorAPIClient.swift in Sources */,\n\t\t\t\t6E77CD4B2D8A225E0057A52C /* AirshipInputValidator.swift in Sources */,\n\t\t\t\t6E952920268A6C1500398B54 /* SearchEventTemplate.swift in Sources */,\n\t\t\t\t6EAD3AFC2F4530F600FF274E /* UAAppIntegrationDelegate.swift in Sources */,\n\t\t\t\t6E739D6E26B9F58700BC6F6D /* TagGroupMutations.swift in Sources */,\n\t\t\t\t6EF02DF02714EB500008B6C9 /* Thomas.swift in Sources */,\n\t\t\t\t6E92EC90284954B10038802D /* ButtonState.swift in Sources */,\n\t\t\t\t6E5215222DCEA12A00CF64B9 /* ThomasViewedPageInfo.swift in Sources */,\n\t\t\t\t6E152BCA2743235800788402 /* Icons.swift in Sources */,\n\t\t\t\t27F1E2112F0FF5FE00E317DB /* ThomasDisplayListener.swift in Sources */,\n\t\t\t\t6E692AFD29E0CB2F00D96CCC /* JavaScriptCommand.swift in Sources */,\n\t\t\t\t6E698E59267BF63B00654DB2 /* AppStateTracker.swift in Sources */,\n\t\t\t\t6EF27DE32730E6F900548DA3 /* RadioInputController.swift in Sources */,\n\t\t\t\t6EC9214E2D82144A000A3A59 /* ThomasFormField.swift in Sources */,\n\t\t\t\t6EE49C1D2A0D9D8000AB1CF4 /* RemoteDataProvider.swift in Sources */,\n\t\t\t\t6EC815AF2F2BBFD500E1C0C6 /* BundleExtensions.swift in Sources */,\n\t\t\t\t6E698E09267A7DD900654DB2 /* RemoteDataAPIClient.swift in Sources */,\n\t\t\t\t6EED67FC2CE26DB60087CDCB /* ThomasAttributeName.swift in Sources */,\n\t\t\t\tA61517C426B009D6008A41C4 /* SubscriptionListAPIClient.swift in Sources */,\n\t\t\t\t8401769426C5671100373AF7 /* JSONMatcher.swift in Sources */,\n\t\t\t\t6EED68292CE28C9A0087CDCB /* ThomasVisibilityInfo.swift in Sources */,\n\t\t\t\t6E146D6A2F5241BC00320A36 /* AirshipColor.swift in Sources */,\n\t\t\t\t9908E60E2B000DBA00DB3E2E /* CustomView.swift in Sources */,\n\t\t\t\t6E739D6626B9BDC100BC6F6D /* ChannelBulkUpdateAPIClient.swift in Sources */,\n\t\t\t\t27F1E1332F0E7AA400E317DB /* ThomasLayoutEventContext.swift in Sources */,\n\t\t\t\t6E2F5A862A65F00200CABD3D /* RemoteDataSourceStatus.swift in Sources */,\n\t\t\t\t6E7112A32880DACB004942E4 /* VisibilityViewModifier.swift in Sources */,\n\t\t\t\t6E46A27C272B63680089CDE3 /* ThomasEnvironment.swift in Sources */,\n\t\t\t\t6E1D8AD126CC5D490049DACB /* RemoteConfig.swift in Sources */,\n\t\t\t\t6E15B72A26CEDBA50099C92D /* RemoteData.swift in Sources */,\n\t\t\t\t6ED6ECA726AE05B700973364 /* EmptyAction.swift in Sources */,\n\t\t\t\t6E92EC8A284933750038802D /* PromptPermissionAction.swift in Sources */,\n\t\t\t\t6EED67E82CE268BF0087CDCB /* ThomasButtonClickBehavior.swift in Sources */,\n\t\t\t\t6E95292F268BBD7D00398B54 /* RetailEventTemplate.swift in Sources */,\n\t\t\t\t6E4E5B3B26E7F91600198175 /* AirshipLocalizationUtils.swift in Sources */,\n\t\t\t\t6E1892B1268CE8FE00417887 /* AirshipLock.swift in Sources */,\n\t\t\t\t6E692B0329E0CBB500D96CCC /* NativeBridgeExtensionDelegate.swift in Sources */,\n\t\t\t\t6EDE293F2A9802BF00235738 /* NativeBridgeActionRunner.swift in Sources */,\n\t\t\t\t632913FA2DE547A500B13C6C /* VideoMediaNativeView.swift in Sources */,\n\t\t\t\t6E698E0C267A88D600654DB2 /* EventAPIClient.swift in Sources */,\n\t\t\t\t6EC7559F2A4E5AB200851ABB /* DeviceAudienceChecker.swift in Sources */,\n\t\t\t\t6E15B6F426CD85C40099C92D /* RuntimeConfig.swift in Sources */,\n\t\t\t\t6E0104FF2DDF9B26009D651F /* IconView.swift in Sources */,\n\t\t\t\t6ED8079A273DA56000D1F455 /* ThomasViewController.swift in Sources */,\n\t\t\t\t6E15B6DB26CC749F0099C92D /* RemoteConfigManager.swift in Sources */,\n\t\t\t\t6EED67F82CE26CE40087CDCB /* ThomasSizeConstraint.swift in Sources */,\n\t\t\t\tA6D6D48F2A0253AA0072A5CA /* ActionArguments.swift in Sources */,\n\t\t\t\t6E6BD24E2AEAFEC500B9DFC9 /* AirshipStateOverrides.swift in Sources */,\n\t\t\t\t6E49D7C228401D2E00C7BB9D /* PermissionStatus.swift in Sources */,\n\t\t\t\t6E6C3F8A27A266C0007F55C7 /* CachedValue.swift in Sources */,\n\t\t\t\t6E1D8AD826CC66BE0049DACB /* RemoteConfigCache.swift in Sources */,\n\t\t\t\tE99605A727A075C600365AE4 /* OpenRegistrationOptions.swift in Sources */,\n\t\t\t\t6E664BDB26C4CD8700A2C8E5 /* EnableFeatureAction.swift in Sources */,\n\t\t\t\t6EB839492BC8898E006611C4 /* AirshipAsyncChannel.swift in Sources */,\n\t\t\t\tA6D6D49D2A0260780072A5CA /* AirshipAction.swift in Sources */,\n\t\t\t\t6E87BD6726D6A39A0005D20D /* AirshipConfig.swift in Sources */,\n\t\t\t\t6E82483829A6E1BE00136EA0 /* AirshipCancellable.swift in Sources */,\n\t\t\t\t6E6BD2782AF2B97300B9DFC9 /* AirshipTaskSleeper.swift in Sources */,\n\t\t\t\t6E57CE3228DB8BDA00287601 /* LiveActivityUpdate.swift in Sources */,\n\t\t\t\t6EED67E32CE268680087CDCB /* ThomasButtonTapEffect.swift in Sources */,\n\t\t\t\t6E5214672DCAB03900CF64B9 /* ThomasFormResult.swift in Sources */,\n\t\t\t\t6EED68012CE26DCD0087CDCB /* ThomasAttributeValue.swift in Sources */,\n\t\t\t\t6E78848F29B9643C00ACAE45 /* AirshipContact.swift in Sources */,\n\t\t\t\t6EED67A22CE1A47C0087CDCB /* ThomasColor.swift in Sources */,\n\t\t\t\t6E916C572DB30DA200C676FA /* AirshipWindowFactory.swift in Sources */,\n\t\t\t\t6E299FDF28D14258001305A7 /* AirshipRequestSession.swift in Sources */,\n\t\t\t\t6E7112A52880DACB004942E4 /* StateController.swift in Sources */,\n\t\t\t\t6ECDDE7429B80462009D79DB /* ChannelAuthTokenProvider.swift in Sources */,\n\t\t\t\t6E88739A2763D8AB00AC248A /* AirshipImageProvider.swift in Sources */,\n\t\t\t\t6E65FB602C753CB400D9F341 /* EmbeddedViewSelector.swift in Sources */,\n\t\t\t\t6EF27DD927306C9100548DA3 /* AirshipToggle.swift in Sources */,\n\t\t\t\t6EB5158128A47BD700870C5A /* SubscriptionListEdit.swift in Sources */,\n\t\t\t\t6EED67652CDEE7900087CDCB /* ThomasViewInfo.swift in Sources */,\n\t\t\t\t6E91E43D28EF423400B6F25E /* WorkConditionsMonitor.swift in Sources */,\n\t\t\t\t6E698E03267A799500654DB2 /* AirshipErrors.swift in Sources */,\n\t\t\t\t6E2D6AEE26B083DB00B7C226 /* ChannelAudienceManager.swift in Sources */,\n\t\t\t\t6ECD4F712DD7A7CD0060EE72 /* ToggleLayout.swift in Sources */,\n\t\t\t\t6EED677D2CDEE9040087CDCB /* ThomasSerializable.swift in Sources */,\n\t\t\t\t6E698E3F267BEDC300654DB2 /* AttributesEditor.swift in Sources */,\n\t\t\t\t6EB11C892697AF5600DC698F /* TagGroupUpdate.swift in Sources */,\n\t\t\t\t6E3B32CF2755D8C700B89C7B /* LayoutState.swift in Sources */,\n\t\t\t\t6EED68E72CE3ECCB0087CDCB /* ThomasPropertyOverride.swift in Sources */,\n\t\t\t\t99E433982C9A044C006436B9 /* AirshipResources.swift in Sources */,\n\t\t\t\t6E87BE1326E28F570005D20D /* AirshipInstance.swift in Sources */,\n\t\t\t\t32515869272AFB2E00DF8B44 /* Media.swift in Sources */,\n\t\t\t\t6E91B4452686911B00DDB1A8 /* EventUtils.swift in Sources */,\n\t\t\t\t6E96ECFA293FDDD90053CC91 /* AirshipSDKExtension.swift in Sources */,\n\t\t\t\tE976486F27A46CC50024518D /* ChannelType.swift in Sources */,\n\t\t\t\t6E68203228EDE3E200A4F90B /* LiveActivityRestorer.swift in Sources */,\n\t\t\t\t6E92EC8D2849378E0038802D /* PermissionPrompter.swift in Sources */,\n\t\t\t\t6EDE5F4F2BA248FF00E33D04 /* TouchViewModifier.swift in Sources */,\n\t\t\t\t6E3CA5412ECB9B7900210C32 /* AirshipDisplayTarget.swift in Sources */,\n\t\t\t\t609843562D6F518900690371 /* SmsLocalePicker.swift in Sources */,\n\t\t\t\t6E1892DE2694F1C100417887 /* ChannelRegistrar.swift in Sources */,\n\t\t\t\t6E2F5A8E2A66FE8900CABD3D /* AirshipTimeCriteria.swift in Sources */,\n\t\t\t\t6E213B182BC60AF100BF24AE /* AirshipWeakValueHolder.swift in Sources */,\n\t\t\t\t6E91E44628EF423400B6F25E /* AirshipWorkManagerProtocol.swift in Sources */,\n\t\t\t\t6E91E45228EF423400B6F25E /* AirshipWorkManager.swift in Sources */,\n\t\t\t\t6EF27DE92730E85700548DA3 /* RadioInput.swift in Sources */,\n\t\t\t\t6E49D7B828401D2E00C7BB9D /* Atomic.swift in Sources */,\n\t\t\t\t6EED67F62CE26CB80087CDCB /* ThomasConstrainedSize.swift in Sources */,\n\t\t\t\t6E146FF32F525E7300320A36 /* AirshipPasteboard.swift in Sources */,\n\t\t\t\t6EED677A2CDEE87D0087CDCB /* ThomasShadow.swift in Sources */,\n\t\t\t\t6E4325E92B7AEB1F00A9B000 /* AirshipEvent.swift in Sources */,\n\t\t\t\t3CA84AB826DE257200A59685 /* DefaultAirshipAnalytics.swift in Sources */,\n\t\t\t\t6EED681D2CE274300087CDCB /* ThomasAccessibilityAction.swift in Sources */,\n\t\t\t\t6E94760F29BA8FA30025F364 /* ContactManager.swift in Sources */,\n\t\t\t\t6E55A4D72E1DB4F700B07DF8 /* ThomasAssociatedLabelResolver.swift in Sources */,\n\t\t\t\t6E49D7B428401D2E00C7BB9D /* PermissionDelegate.swift in Sources */,\n\t\t\t\t6E524C732C126F5F002CA094 /* AirshipEventType.swift in Sources */,\n\t\t\t\t6E91E44028EF423400B6F25E /* Worker.swift in Sources */,\n\t\t\t\t6E91B44226868C3400DDB1A8 /* ProximityRegion.swift in Sources */,\n\t\t\t\t6EDFBBC32F5780BC0043D9EF /* BasementImport.swift in Sources */,\n\t\t\t\t6E0105012DDFA5E9009D651F /* ScoreController.swift in Sources */,\n\t\t\t\t6E1A15062D6EA3A50056418B /* ThomasFormState.swift in Sources */,\n\t\t\t\tA69C987F27E247B20063A101 /* SubscriptionListAction.swift in Sources */,\n\t\t\t\t6E887CD5272C5F5A00E83363 /* CheckboxController.swift in Sources */,\n\t\t\t\t6E49D7C428401D2E00C7BB9D /* PushNotificationDelegate.swift in Sources */,\n\t\t\t\t6E97D6B12D84B18E0001CF7F /* ThomasFormDataCollector.swift in Sources */,\n\t\t\t\t27264FB32E81B064000B6FA3 /* AirshipSceneController.swift in Sources */,\n\t\t\t\t3CA84AAE26DE255200A59685 /* EventStore.swift in Sources */,\n\t\t\t\t6E5A64C42AAB7D5C00574085 /* AirshipMeteredUsage.swift in Sources */,\n\t\t\t\t6E6BD2462AEAFE7E00B9DFC9 /* DeferredAPIClient.swift in Sources */,\n\t\t\t\t3CC8AA0626BB3C7900405614 /* DefaultAirshipPush.swift in Sources */,\n\t\t\t\t60EACF542B7BF2EA00CAFDBB /* AirshipApptimizeIntegration.swift in Sources */,\n\t\t\t\t60D3BCCE2A15471C00E07524 /* AudienceHashSelector.swift in Sources */,\n\t\t\t\t6E40868C2B8931C900435E2C /* AirshipViewSizeReader.swift in Sources */,\n\t\t\t\t6E5213E32DCA7A3B00CF64B9 /* ThomasEvent.swift in Sources */,\n\t\t\t\t6E6ED1672683DBC300A2CBD0 /* UACoreData.swift in Sources */,\n\t\t\t\t6E87BD8326D757CA0005D20D /* CloudSite.swift in Sources */,\n\t\t\t\t6ED735DA26C73DC5003B0A7D /* DefaultAirshipChannel.swift in Sources */,\n\t\t\t\t6E1BACDB2719ED7D0038399E /* ScrollLayout.swift in Sources */,\n\t\t\t\t6EF1E9282CD005E4005EAA07 /* PagerUtils.swift in Sources */,\n\t\t\t\t6E89329A2E7B66C300FB0EC4 /* NotificationRegistrationResult.swift in Sources */,\n\t\t\t\t6EED67C32CE1B5890087CDCB /* ThomasIcon.swift in Sources */,\n\t\t\t\t6E49D7C028401D2E00C7BB9D /* UNNotificationRegistrar.swift in Sources */,\n\t\t\t\t6EE49BE12A0AADC900AB1CF4 /* AppRemoteDataProviderDelegate.swift in Sources */,\n\t\t\t\tC00ED4CF26C729390040C5D0 /* URLAllowList.swift in Sources */,\n\t\t\t\t6E6BD24A2AEAFEB700B9DFC9 /* AirsihpTriggerContext.swift in Sources */,\n\t\t\t\t60C1DB112A8B743C00A1D3DA /* AirshipEmbeddedObserver.swift in Sources */,\n\t\t\t\t6E5A64D42AABBED600574085 /* MeteredUsageStore.swift in Sources */,\n\t\t\t\t6E07689229FB39440014E2A9 /* AirshipUnsafeSendableWrapper.swift in Sources */,\n\t\t\t\t6E6ED1492683D8E200A2CBD0 /* LocaleManager.swift in Sources */,\n\t\t\t\t6E96ECF2293EB7900053CC91 /* AirshipEventData.swift in Sources */,\n\t\t\t\tE99605A427A075B800365AE4 /* SMSRegistrationOptions.swift in Sources */,\n\t\t\t\t6EFD6D7127290C16005B26F1 /* TextInput.swift in Sources */,\n\t\t\t\t6E5A64D02AABBEAF00574085 /* AirshipMeteredUsageEvent.swift in Sources */,\n\t\t\t\t6E5A64C82AABBE7100574085 /* MeteredUsageAPIClient.swift in Sources */,\n\t\t\t\t6EC755992A4E115400851ABB /* DeviceAudienceSelector.swift in Sources */,\n\t\t\t\t6E44626729E6813A00CB2B56 /* AsyncSerialQueue.swift in Sources */,\n\t\t\t\t6EF1E92A2CD0069B005EAA07 /* PagerSwipeDirection.swift in Sources */,\n\t\t\t\t6E6ED1692683DBC300A2CBD0 /* AirshipNetworkChecker.swift in Sources */,\n\t\t\t\t6E29474D2AD5DA3B009EC6DD /* LiveActivityRegistrationStatus.swift in Sources */,\n\t\t\t\t6E4E5B3D26E7F91600198175 /* Attributes.swift in Sources */,\n\t\t\t\t6EED68332CE28FAC0087CDCB /* ThomasConstants.swift in Sources */,\n\t\t\t\t6E77CE472D8A28B90057A52C /* CachingSMSValidatorAPIClient.swift in Sources */,\n\t\t\t\tA658DE0C2727020200007672 /* ImageButton.swift in Sources */,\n\t\t\t\t60CE9BDE2D0B6A0900A8B625 /* ThomasPagerControllerBranching.swift in Sources */,\n\t\t\t\t6E0F557F2AE03BB900E7CB8C /* ThomasAsyncImage.swift in Sources */,\n\t\t\t\t6E43219226EA89B6009228AB /* NativeBridgeDelegate.swift in Sources */,\n\t\t\t\t6E0B8762294CE0DC0064B7BD /* FarmHashFingerprint64.swift in Sources */,\n\t\t\t\t6EE49C082A0BE9F600AB1CF4 /* RemoteDataURLFactory.swift in Sources */,\n\t\t\t\t6E2947512AD5DB5A009EC6DD /* LiveActivityRegistrationStatusUpdates.swift in Sources */,\n\t\t\t\t6E411CAB2538C6A600FEE4E8 /* UARemoteData.xcdatamodeld in Sources */,\n\t\t\t\tA67EC24B279B1C34009089E1 /* ScopedSubscriptionListUpdate.swift in Sources */,\n\t\t\t\tA684939D273436370021675E /* FontViewModifier.swift in Sources */,\n\t\t\t\t6E062D092716571F001A74A1 /* LabelButton.swift in Sources */,\n\t\t\t\t6E0105052DDFA735009D651F /* ScoreState.swift in Sources */,\n\t\t\t\t6EB3FCEF2ABCFA680018594E /* RemoteDataProtocol.swift in Sources */,\n\t\t\t\t6E1589502AFEF19F00954A04 /* SessionTracker.swift in Sources */,\n\t\t\t\t6EC7E48D26A738C80038CFDD /* ContactConflictEvent.swift in Sources */,\n\t\t\t\t32FD4C782D8079910056D141 /* BasicToggleLayout.swift in Sources */,\n\t\t\t\t60C1DB102A8B743C00A1D3DA /* AirshipEmbeddedViewManager.swift in Sources */,\n\t\t\t\t9908E6122B0189F800DB3E2E /* ArishipCustomViewManager.swift in Sources */,\n\t\t\t\t6E664BA126C43F5400A2C8E5 /* ActivityViewController.swift in Sources */,\n\t\t\t\t6E82482229A6D9DF00136EA0 /* CancellableValueHolder.swift in Sources */,\n\t\t\t\t6E46A273272B19760089CDE3 /* ViewExtensions.swift in Sources */,\n\t\t\t\t6E664BEA26C6DB7600A2C8E5 /* AirshipUtils.swift in Sources */,\n\t\t\t\t6EED68222CE2806D0087CDCB /* ThomasAccessibleInfo.swift in Sources */,\n\t\t\t\t6E49D7BA28401D2E00C7BB9D /* NotificationPermissionDelegate.swift in Sources */,\n\t\t\t\t6EED67A72CE1A5000087CDCB /* ThomasBorder.swift in Sources */,\n\t\t\t\t6EC824A02F33A4DD00E1C0C6 /* MessageDisplayHistory.swift in Sources */,\n\t\t\t\t6E1A19222D6F875A0056418B /* ThomasFormValidationMode.swift in Sources */,\n\t\t\t\t6E6ED1612683DBC300A2CBD0 /* AirshipVersion.swift in Sources */,\n\t\t\t\t6E49D7B628401D2E00C7BB9D /* NotificationRegistrar.swift in Sources */,\n\t\t\t\t6EEE8BA2290B3EDE00230528 /* AirshipKeychainAccess.swift in Sources */,\n\t\t\t\t3215CA9D2739349800B7D97E /* ModalView.swift in Sources */,\n\t\t\t\t6E9C2B7D2D014438000089A9 /* APNSEnvironment.swift in Sources */,\n\t\t\t\t3CC95B1F268E785900FE2ACD /* NotificationCategories.swift in Sources */,\n\t\t\t\tA61517B526AEEAAB008A41C4 /* SubscriptionListUpdate.swift in Sources */,\n\t\t\t\t6E952926268B8F6600398B54 /* AssociatedIdentifiers.swift in Sources */,\n\t\t\t\t6EB11C872697ACBF00DC698F /* ContactOperation.swift in Sources */,\n\t\t\t\t27CCF8D32F2382750018058F /* ThomasStateStorage.swift in Sources */,\n\t\t\t\t27AFE70F2E733F4400767044 /* ModifyTagsAction.swift in Sources */,\n\t\t\t\t8401769A26C5725800373AF7 /* AirshipJSONUtils.swift in Sources */,\n\t\t\t\t6E0031AB2D08CC920004F53E /* AirshipAuthorizedNotificationSettings.swift in Sources */,\n\t\t\t\t6ED2F5392B7FFF68000AFC80 /* AirshipSceneManager.swift in Sources */,\n\t\t\t\t6E692AFF29E0CB4100D96CCC /* JavaScriptCommandDelegate.swift in Sources */,\n\t\t\t\t6EBD12052DA73FDA00F678AB /* ValidatableHelper.swift in Sources */,\n\t\t\t\t6E9C2BDA2D027B5F000089A9 /* AirshipAppCredentials.swift in Sources */,\n\t\t\t\t6E887CD1272C5E8400E83363 /* CheckboxState.swift in Sources */,\n\t\t\t\t6E4339F12DFA099F000A7741 /* AirshipIvyVersionMatcher.swift in Sources */,\n\t\t\t\t3CA84ABA26DE257200A59685 /* AirshipAnalytics.swift in Sources */,\n\t\t\t\t6ED735DD26C7401D003B0A7D /* TagEditor.swift in Sources */,\n\t\t\t\t6E698E41267BEDC300654DB2 /* TagGroupsEditor.swift in Sources */,\n\t\t\t\t6E739D6B26B9DFFB00BC6F6D /* AttributePendingMutations.swift in Sources */,\n\t\t\t\tA67F87D2268DECCE00EF5F43 /* ContactAPIClient.swift in Sources */,\n\t\t\t\t6EFD6D5C27273257005B26F1 /* Shapes.swift in Sources */,\n\t\t\t\t6E1C9C4B271F7878009EF9EF /* BackgroundColorViewModifier.swift in Sources */,\n\t\t\t\t6E6ED13B2683A58D00A2CBD0 /* Dispatcher.swift in Sources */,\n\t\t\t\t6EED67C82CE1B6020087CDCB /* ThomasMediaFit.swift in Sources */,\n\t\t\t\t6E6BD2722AF1B05500B9DFC9 /* UAirshipCache.xcdatamodeld in Sources */,\n\t\t\t\t6EE49C182A0C3CC600AB1CF4 /* ContactRemoteDataProviderDelegate.swift in Sources */,\n\t\t\t\t32D6E87B2727F7060077C784 /* Image.swift in Sources */,\n\t\t\t\t6E4325F82B7C08A600A9B000 /* AirshipAnalyticsFeed.swift in Sources */,\n\t\t\t\t6E062D05271656F8001A74A1 /* LinearLayout.swift in Sources */,\n\t\t\t\t60D3BCC62A152A0D00E07524 /* ExperimentManager.swift in Sources */,\n\t\t\t\t6E15B72326CEC7030099C92D /* RemoteDataPayload.swift in Sources */,\n\t\t\t\t6E91E43A28EF423400B6F25E /* WorkBackgroundTasks.swift in Sources */,\n\t\t\t\t6E2FA2892D51519B005893E2 /* ThomasEmailRegistrationOptions.swift in Sources */,\n\t\t\t\t6EED68042CE26E1A0087CDCB /* ThomasMargin.swift in Sources */,\n\t\t\t\t6E664BDF26C4CD8700A2C8E5 /* FetchDeviceInfoAction.swift in Sources */,\n\t\t\t\t3C63556026CDD4F8006E9916 /* AirshipPush.swift in Sources */,\n\t\t\t\tA6A5531326D548FF002B20F6 /* NativeBridgeActionHandler.swift in Sources */,\n\t\t\t\t6E698E57267BF63B00654DB2 /* AppStateTrackerAdapter.swift in Sources */,\n\t\t\t\t6EED676E2CDEE7EB0087CDCB /* ThomasPresentationInfo.swift in Sources */,\n\t\t\t\t6E9D529826C195F7004EA16B /* ActionRunner.swift in Sources */,\n\t\t\t\t6EC7559B2A4E129000851ABB /* DeviceTagSelector.swift in Sources */,\n\t\t\t\t329DFCCF2B7E4DDA0039C8C0 /* UARemoteDataMapping.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B8726294A9C120064B7BD /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t99E8D7BF2B50C2C10099B6F3 /* ButtonGroup.swift in Sources */,\n\t\t\t\t998572BF2B3CF95D0091E9C9 /* DefaultAssetDownloader.swift in Sources */,\n\t\t\t\t6E1B7B132B714FFC00695561 /* LandingPageAction.swift in Sources */,\n\t\t\t\t6068E03B2B2CBCF200349E82 /* ActiveTimer.swift in Sources */,\n\t\t\t\t99E8D7CE2B54A66E0099B6F3 /* InAppMessageThemeBanner.swift in Sources */,\n\t\t\t\t3231128329D5E67200CF0D86 /* FrequencyLimitManager.swift in Sources */,\n\t\t\t\t6E1528202B4DC59C00DF1377 /* InAppMessageSceneDelegate.swift in Sources */,\n\t\t\t\t99E8D7D52B55B0300099B6F3 /* InAppMessageThemeAdditionalPadding.swift in Sources */,\n\t\t\t\t99E8D79B2B4F2FCE0099B6F3 /* InAppMessageTheme.swift in Sources */,\n\t\t\t\t99E6EF6A2B8E36BA0006326A /* InAppMessageValidation.swift in Sources */,\n\t\t\t\t99E8D7DE2B55C73B0099B6F3 /* ThemeExtensions.swift in Sources */,\n\t\t\t\t6EDF1D982B2A25C800E23BC4 /* InAppMessageMediaInfo.swift in Sources */,\n\t\t\t\t99E8D7DA2B55B05D0099B6F3 /* InAppMessageThemeText.swift in Sources */,\n\t\t\t\t6EDF1DA62B2A300100E23BC4 /* InAppMessage.swift in Sources */,\n\t\t\t\t99E8D7D02B54A68F0099B6F3 /* InAppMessageThemeHTML.swift in Sources */,\n\t\t\t\t6EDF1D962B2A25B400E23BC4 /* InAppMessageButtonInfo.swift in Sources */,\n\t\t\t\t6EDF1DB82B2BB2B800E23BC4 /* RetryingQueue.swift in Sources */,\n\t\t\t\t6E1A9BC12B5EE1CF00A6489B /* SchedulePrepareResult.swift in Sources */,\n\t\t\t\t6E15281B2B4DC3DF00DF1377 /* InAppMessageAutomationExecutor.swift in Sources */,\n\t\t\t\t6E1A9BB02B5B0C4C00A6489B /* AutomationActionRunner.swift in Sources */,\n\t\t\t\t990A09592B5C677C00244D90 /* InAppMessageWebView.swift in Sources */,\n\t\t\t\t6E986F0E2B473EC700FBE6A0 /* AutomationRemoteDataAccess.swift in Sources */,\n\t\t\t\t6E1528352B4E11DB00DF1377 /* CustomDisplayAdapter.swift in Sources */,\n\t\t\t\t6EC0CA5C2B48C2F500333A87 /* AutomationPreparer.swift in Sources */,\n\t\t\t\t990A09AF2B5DBD0400244D90 /* InAppMessageViewUtils.swift in Sources */,\n\t\t\t\t3231128429D5E67200CF0D86 /* Occurrence.swift in Sources */,\n\t\t\t\t6E1CBE2D2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodeld in Sources */,\n\t\t\t\t99CF46182B3217C300B6FD9B /* AirshipCachedAssets.swift in Sources */,\n\t\t\t\t999DC85E2B5B721D0048C6AF /* HTMLView.swift in Sources */,\n\t\t\t\t6E1185C62C3328A10071334E /* ExecutionWindow.swift in Sources */,\n\t\t\t\t99CF461A2B3217DE00B6FD9B /* AssetCacheManager.swift in Sources */,\n\t\t\t\t6E1A9BF72B606CF200A6489B /* AutomationDelayProcessor.swift in Sources */,\n\t\t\t\t6E16208F2B3116BA009240B2 /* DefaultDisplayCoordinator.swift in Sources */,\n\t\t\t\t99E0BD0F2B4DD71A00465B37 /* InAppMessageHostingController.swift in Sources */,\n\t\t\t\t6E1CBDE32BA51ED100519D9C /* InAppDisplayImpressionRuleProvider.swift in Sources */,\n\t\t\t\t60D1D9B82B68FB6400EBE0A4 /* PreparedTrigger.swift in Sources */,\n\t\t\t\t6E2E3CA22B32723C00B8515B /* InAppMessageNativeBridgeExtension.swift in Sources */,\n\t\t\t\t99F662B22B60425E00696098 /* InAppMessageModalView.swift in Sources */,\n\t\t\t\t6E213B1E2BC7054500BF24AE /* InAppActionRunner.swift in Sources */,\n\t\t\t\t6E0F4BE92B3264A400673CA4 /* DeferredAutomationData.swift in Sources */,\n\t\t\t\t6E1528332B4DF2E600DF1377 /* ScheduleConditionsChangedNotifier.swift in Sources */,\n\t\t\t\t6E1A9BC52B5EE1EE00A6489B /* ScheduleExecuteResult.swift in Sources */,\n\t\t\t\t6E16208D2B3116AE009240B2 /* ImmediateDisplayCoordinator.swift in Sources */,\n\t\t\t\t990A09942B5CA5B700244D90 /* InAppMessageExtensions.swift in Sources */,\n\t\t\t\t99E8D7BB2B50A7C20099B6F3 /* InAppMessageViewDelegate.swift in Sources */,\n\t\t\t\t6EDF1DA42B2A2C6F00E23BC4 /* InAppMessageColor.swift in Sources */,\n\t\t\t\t3231128729D5E67200CF0D86 /* FrequencyConstraint.swift in Sources */,\n\t\t\t\t6E1528192B4DC3D000DF1377 /* InAppMessageAutomationPreparer.swift in Sources */,\n\t\t\t\t99F662B02B5DDC2900696098 /* BeveledLoadingView.swift in Sources */,\n\t\t\t\t6E1528372B4E11E800DF1377 /* CustomDisplayAdapterWrapper.swift in Sources */,\n\t\t\t\t6E4AEE572B6B4358008AEAC1 /* UAAutomation.xcdatamodeld in Sources */,\n\t\t\t\t6E4AEE642B6B44EA008AEAC1 /* LegacyAutomationStore.swift in Sources */,\n\t\t\t\t6EC0CA532B48A2C300333A87 /* AutomationAudience.swift in Sources */,\n\t\t\t\t6E1A9BC92B5EE34600A6489B /* AutomationEventFeed.swift in Sources */,\n\t\t\t\t99E8D7C12B50E5F40099B6F3 /* InAppMessageRootView.swift in Sources */,\n\t\t\t\t99E8D7CB2B54A6340099B6F3 /* InAppMessageThemeFullscreen.swift in Sources */,\n\t\t\t\t603269552BF4B8D5007F7F75 /* AdditionalAudienceCheckerResolver.swift in Sources */,\n\t\t\t\t6EDF1D9E2B2A2A5900E23BC4 /* InAppMessageDisplayContent.swift in Sources */,\n\t\t\t\t3231128829D5E67200CF0D86 /* FrequencyChecker.swift in Sources */,\n\t\t\t\t998572C12B3CF97B0091E9C9 /* DefaultAssetFileManager.swift in Sources */,\n\t\t\t\t6EE6AA2C2B51DB1E002FEA75 /* AutomationSourceInfoStore.swift in Sources */,\n\t\t\t\t99E8D7992B4F19BA0099B6F3 /* TextView.swift in Sources */,\n\t\t\t\t6EE6AAFF2B58AB66002FEA75 /* LegacyInAppAnalytics.swift in Sources */,\n\t\t\t\t6E524D022C1A2CAE002CA094 /* InAppMessageThemeManager.swift in Sources */,\n\t\t\t\t6E986F062B47319E00FBE6A0 /* DeferredScheduleResult.swift in Sources */,\n\t\t\t\t6E986EF92B44D41E00FBE6A0 /* InAppAutomation.swift in Sources */,\n\t\t\t\t6E1528392B4E13D400DF1377 /* AirshipLayoutDisplayAdapter.swift in Sources */,\n\t\t\t\t6E1A9BD32B5EE8A400A6489B /* AutomationScheduleState.swift in Sources */,\n\t\t\t\t6E1A9BBF2B5EE19000A6489B /* PreparedSchedule.swift in Sources */,\n\t\t\t\t6E1A9BD12B5EE84600A6489B /* AutomationScheduleData.swift in Sources */,\n\t\t\t\t6ED838D02D0D118B009CBB0C /* AutomationCompoundAudience.swift in Sources */,\n\t\t\t\t3231128C29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodeld in Sources */,\n\t\t\t\t6E8BDA172B62EC9F00711DB8 /* AutomationRemoteDataSubscriber.swift in Sources */,\n\t\t\t\t60FCA3052B4F1110005C9232 /* LegacyInAppMessaging.swift in Sources */,\n\t\t\t\t6EDF1D932B292FB100E23BC4 /* InAppMessageTextInfo.swift in Sources */,\n\t\t\t\t6E16208A2B311219009240B2 /* DisplayCoordinator.swift in Sources */,\n\t\t\t\t6E15281D2B4DC43100DF1377 /* ActionAutomationPreparer.swift in Sources */,\n\t\t\t\t6E986EFB2B44D48C00FBE6A0 /* InAppMessaging.swift in Sources */,\n\t\t\t\t99E8D7DC2B55C4C20099B6F3 /* InAppMessageThemeMedia.swift in Sources */,\n\t\t\t\t99E8D7C52B5192D40099B6F3 /* MediaView.swift in Sources */,\n\t\t\t\t6E524D042C1A454E002CA094 /* InAppMessageThemeShadow.swift in Sources */,\n\t\t\t\tA6E9ADED2D4D204B0091BBAF /* InAppAutomationUpdateStatus.swift in Sources */,\n\t\t\t\t603269532BF4B141007F7F75 /* AdditionalAudienceCheckerApiClient.swift in Sources */,\n\t\t\t\t6E4AEEBC2B6D6380008AEAC1 /* TriggerData.swift in Sources */,\n\t\t\t\t99E8D7D82B55B0440099B6F3 /* InAppMessageThemeButton.swift in Sources */,\n\t\t\t\t6E0F4BE52B32645600673CA4 /* AutomationTrigger.swift in Sources */,\n\t\t\t\t6E1A9BAB2B5AE38A00A6489B /* InAppMessageDisplayListener.swift in Sources */,\n\t\t\t\t6E34C4B12C7D4B6400B00506 /* ExecutionWindowProcessor.swift in Sources */,\n\t\t\t\t60FCA3072B4F1C73005C9232 /* LegacyInAppMessage.swift in Sources */,\n\t\t\t\t6EDF1D9C2B2A287A00E23BC4 /* InAppMessageButtonLayoutType.swift in Sources */,\n\t\t\t\t6E1A9BC72B5EE32E00A6489B /* AutomationTriggerProcessor.swift in Sources */,\n\t\t\t\t6EE6AA202B4F5246002FEA75 /* InAppMessageSceneManager.swift in Sources */,\n\t\t\t\t6E1A9BD52B5EE97000A6489B /* TriggeringInfo.swift in Sources */,\n\t\t\t\t6E68028B2B850DDE00F4591F /* ApplicationMetrics.swift in Sources */,\n\t\t\t\t6E15282C2B4DE81E00DF1377 /* AutomationSDKModule.swift in Sources */,\n\t\t\t\t6E7E770D2DDFD0D80042086D /* AirshipAsyncSemaphore.swift in Sources */,\n\t\t\t\t99E0BD0D2B4DD4AB00465B37 /* FullscreenView.swift in Sources */,\n\t\t\t\t6E6802902B8671E700F4591F /* InAppAutomationComponent.swift in Sources */,\n\t\t\t\t27077E4C2EE7531C0027A282 /* AutomationEventsHistory.swift in Sources */,\n\t\t\t\t6E1528262B4DC64B00DF1377 /* DisplayAdapterFactory.swift in Sources */,\n\t\t\t\t3231128229D5E67200CF0D86 /* FrequencyLimitStore.swift in Sources */,\n\t\t\t\t6E1528172B4DC3C000DF1377 /* ActionAutomationExecutor.swift in Sources */,\n\t\t\t\t99F662D22B63047300696098 /* InAppMessageBannerView.swift in Sources */,\n\t\t\t\t6E15282F2B4DED7A00DF1377 /* InAppMessageAnalytics.swift in Sources */,\n\t\t\t\t6EC0CA682B49287100333A87 /* AutomationExecutor.swift in Sources */,\n\t\t\t\t6E1528222B4DC5C000DF1377 /* InAppMessageDisplayDelegate.swift in Sources */,\n\t\t\t\t99E8D7BD2B50AA060099B6F3 /* InAppMessageEnvironment.swift in Sources */,\n\t\t\t\t6E1A9BC32B5EE1DE00A6489B /* ScheduleReadyResult.swift in Sources */,\n\t\t\t\t6E0F4BE22B32190400673CA4 /* AutomationSchedule.swift in Sources */,\n\t\t\t\t6E986EE42B448D3C00FBE6A0 /* AutomationEngine.swift in Sources */,\n\t\t\t\t6E0F4BE72B32646000673CA4 /* AutomationDelay.swift in Sources */,\n\t\t\t\t3231126A29D5E4F600CF0D86 /* AirshipAutomationResources.swift in Sources */,\n\t\t\t\t6EC0CA812B4C812A00333A87 /* DisplayAdapter.swift in Sources */,\n\t\t\t\t6E1528312B4DED8900DF1377 /* InAppMessageAnalyticsFactory.swift in Sources */,\n\t\t\t\t6E1528242B4DC60200DF1377 /* DisplayCoordinatorManager.swift in Sources */,\n\t\t\t\t99E8D7972B4F17260099B6F3 /* CloseButton.swift in Sources */,\n\t\t\t\t99E8D7C92B54A5CB0099B6F3 /* InAppMessageThemeModal.swift in Sources */,\n\t\t\t\t6E4AEE652B6B44EA008AEAC1 /* AutomationStore.swift in Sources */,\n\t\t\t\t60F8E7602B8FAF5400460EDF /* ScheduleAction.swift in Sources */,\n\t\t\t\t60F8E75C2B8F3D4B00460EDF /* CancelSchedulesAction.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E0B872D294A9C130064B7BD /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EC0CA502B4899CC00333A87 /* TestRemoteData.swift in Sources */,\n\t\t\t\t6EE6AB062B59C231002FEA75 /* AirshipLayoutDisplayAdapterTest.swift in Sources */,\n\t\t\t\t6EDE5FC22BADDD96003ADF55 /* PreparedScheduleInfoTest.swift in Sources */,\n\t\t\t\t325D53FA29648818003421B4 /* TestAirshipRequestSession.swift in Sources */,\n\t\t\t\t6E1A9BBB2B5B20D700A6489B /* TestInAppMessageAnalytics.swift in Sources */,\n\t\t\t\t6E6B2DBE2B33B768008BF788 /* AutomationScheduleTest.swift in Sources */,\n\t\t\t\t325D53F629646E53003421B4 /* TestChannel.swift in Sources */,\n\t\t\t\t60FCA30C2B51492A005C9232 /* LegacyInAppMessagingTest.swift in Sources */,\n\t\t\t\t6EC0CA782B4B8A4700333A87 /* TestFrequencyLimitsManager.swift in Sources */,\n\t\t\t\t6E4AEE302B6B3041008AEAC1 /* InAppMessageThemeTest.swift in Sources */,\n\t\t\t\t6E1620952B311D8A009240B2 /* DefaultDisplayCoordinatorTest.swift in Sources */,\n\t\t\t\t6E07B5F92D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */,\n\t\t\t\t60F8E7622B8FB2CC00460EDF /* ScheduleActionTest.swift in Sources */,\n\t\t\t\t6EE6AA132B4F3009002FEA75 /* InAppMessageAutomationPreparerTest.swift in Sources */,\n\t\t\t\t6EC0CA762B4B8A3A00333A87 /* TestRemoteDataAccess.swift in Sources */,\n\t\t\t\t6E34C4B32C7D4C6600B00506 /* ExecutionWindowProcessorTest.swift in Sources */,\n\t\t\t\t60D1D9BD2B6AB2D100EBE0A4 /* AutomationTriggerProcessorTest.swift in Sources */,\n\t\t\t\t6EC0CA6D2B4B879800333A87 /* AutomationPreparerTest.swift in Sources */,\n\t\t\t\t6E4AEE2C2B6B302D008AEAC1 /* ActionAutomationPreparerTest.swift in Sources */,\n\t\t\t\t6E1B7B162B715FFE00695561 /* LandingPageActionTest.swift in Sources */,\n\t\t\t\t603269582BF7550E007F7F75 /* AdditionalAudienceCheckerResolverTest.swift in Sources */,\n\t\t\t\t60FCA30E2B5535F4005C9232 /* TestAnalytics.swift in Sources */,\n\t\t\t\t6E68028C2B85149900F4591F /* ApplicationMetricsTest.swift in Sources */,\n\t\t\t\t6EC0CA792B4B8C2B00333A87 /* TestAudienceChecker.swift in Sources */,\n\t\t\t\t6EC0CA512B4899DB00333A87 /* TestNetworkMonitor.swift in Sources */,\n\t\t\t\t99E6EF6D2B8E3C250006326A /* InAppMessageContentValidationTest.swift in Sources */,\n\t\t\t\t6EB839472BC83B9D006611C4 /* DefaultInAppActionRunnerTest.swift in Sources */,\n\t\t\t\t6E6A84932B68A57E006FFB35 /* AutomationStoreTest.swift in Sources */,\n\t\t\t\t6EE6AA382B572897002FEA75 /* AutomationSourceInfoStoreTest.swift in Sources */,\n\t\t\t\t60FCA3252B5EF3A8005C9232 /* AutomationEventFeedTest.swift in Sources */,\n\t\t\t\t6E68028E2B852F6A00F4591F /* AutomationScheduleDataTest.swift in Sources */,\n\t\t\t\t60A364ED2C3479BF00B05E26 /* ExecutionWindowTest.swift in Sources */,\n\t\t\t\t6EC0CA732B4B897B00333A87 /* TestExperimentDataProvider.swift in Sources */,\n\t\t\t\t6EC0CA562B48B05600333A87 /* ActionAutomationExecutorTest.swift in Sources */,\n\t\t\t\t6E4325C62B7AC40D00A9B000 /* TestPush.swift in Sources */,\n\t\t\t\t6E1A9BB22B5B172F00A6489B /* TestActionRunner.swift in Sources */,\n\t\t\t\t60FCA30D2B5534DB005C9232 /* TestAirshipInstance.swift in Sources */,\n\t\t\t\t603269592BF75976007F7F75 /* AirshipCacheTest.swift in Sources */,\n\t\t\t\t6EE6AB092B59C236002FEA75 /* CustomDisplayAdapterWrapperTest.swift in Sources */,\n\t\t\t\t605073842B2CD46D00209B51 /* TestAppStateTracker.swift in Sources */,\n\t\t\t\t27F1E19D2F0E836000E317DB /* InAppMessageAnalyticsTest.swift in Sources */,\n\t\t\t\t60F8E75E2B8FA12800460EDF /* CancelSchedulesActionTest.swift in Sources */,\n\t\t\t\t6E2E3CA62B327A6C00B8515B /* InAppMessageNativeBridgeExtensionTest.swift in Sources */,\n\t\t\t\t6EE6AA1B2B4F3062002FEA75 /* DisplayCoordinatorManagerTest.swift in Sources */,\n\t\t\t\t6EE6AA2A2B50C976002FEA75 /* TestAutomationEngine.swift in Sources */,\n\t\t\t\tA6F0B1912B83CD9B002D10A4 /* AutomationEngineTest.swift in Sources */,\n\t\t\t\t6E6A848D2B6854FC006FFB35 /* AutomationDelayProcessorTest.swift in Sources */,\n\t\t\t\t605073832B2CD38200209B51 /* ActiveTimerTest.swift in Sources */,\n\t\t\t\t6EE6AA162B4F302D002FEA75 /* InAppMessageAutomationExecutorTest.swift in Sources */,\n\t\t\t\t6E7E770F2DDFD10A0042086D /* AirshipAsyncSemaphoreTest.swift in Sources */,\n\t\t\t\t6EDF1DAD2B2A73FC00E23BC4 /* InAppMessageTest.swift in Sources */,\n\t\t\t\t325D53F729648150003421B4 /* TestDate.swift in Sources */,\n\t\t\t\t6E4AEE2A2B6B2E0A008AEAC1 /* AssetCacheManagerTest.swift in Sources */,\n\t\t\t\t6E4AEE2B2B6B2E0A008AEAC1 /* DefaultAssetDownloaderTest.swift in Sources */,\n\t\t\t\t6EE6AA1C2B4F3066002FEA75 /* DisplayAdapterFactoryTest.swift in Sources */,\n\t\t\t\t60FCA30A2B51364A005C9232 /* LegacyInAppMessageTest.swift in Sources */,\n\t\t\t\t3231129129D5E6D900CF0D86 /* FrequencyLimitManagerTest.swift in Sources */,\n\t\t\t\t6EC0CA4F2B48987700333A87 /* AutomationRemoteDataAccessTest.swift in Sources */,\n\t\t\t\t6E1473EA2F527C4D00320A36 /* TestURLOpener.swift in Sources */,\n\t\t\t\t6E1A9BB72B5B1D9E00A6489B /* TestActiveTimer.swift in Sources */,\n\t\t\t\t6E1CBDFF2BAA1DF200519D9C /* DefaultInAppDisplayImpressionRuleProviderTest.swift in Sources */,\n\t\t\t\t6E1A9BB92B5B20A500A6489B /* InAppMessageDisplayListenerTest.swift in Sources */,\n\t\t\t\t6EE6AB022B58B6E9002FEA75 /* LegacyInAppAnalyticsTest.swift in Sources */,\n\t\t\t\t27F1E1A02F0E84C300E317DB /* ThomasLayoutEventTestUtils.swift in Sources */,\n\t\t\t\t60D1D9BB2B6A53F000EBE0A4 /* PreparedTriggerTest.swift in Sources */,\n\t\t\t\tA6AC44832B923ACB00769ED2 /* TestInAppMessageAutomationExecutor.swift in Sources */,\n\t\t\t\t6EC0CA6B2B4B698000333A87 /* AutomationExecutorTest.swift in Sources */,\n\t\t\t\t27F1E19F2F0E848B00E317DB /* TestThomasLayoutEvent.swift in Sources */,\n\t\t\t\t6E1620932B3118D9009240B2 /* ImmediateDisplayCoordinatorTest.swift in Sources */,\n\t\t\t\t6E1D90022B2D1AB4004BA130 /* RetryingQueueTests.swift in Sources */,\n\t\t\t\t6E1528402B4F153900DF1377 /* TestDisplayAdapter.swift in Sources */,\n\t\t\t\t6E4AEE282B6B2E0A008AEAC1 /* DefaultAssetFileManagerTest.swift in Sources */,\n\t\t\t\t6E9C2BD32D02683D000089A9 /* RuntimeConfig.swift in Sources */,\n\t\t\t\t6EE6AA1E2B4F31B1002FEA75 /* TestCachedAssets.swift in Sources */,\n\t\t\t\t6EC0CA702B4B895A00333A87 /* TestDeferredResolver.swift in Sources */,\n\t\t\t\t325D53FB2964885B003421B4 /* AirshipBaseTest.swift in Sources */,\n\t\t\t\t6032695C2BF75E39007F7F75 /* AirshipHTTPResponseTest.swift in Sources */,\n\t\t\t\t6E1528422B4F156200DF1377 /* TestDisplayCoordinator.swift in Sources */,\n\t\t\t\t27051CD72EE75E3300C770D5 /* AutomationEventsHistoryTest.swift in Sources */,\n\t\t\t\t6EE6AA282B50C91E002FEA75 /* AutomationRemoteDataSubscriberTest.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E431F6D26EA814F009228AB /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EDFBBC42F5780EA0043D9EF /* AirshipLogHandler.swift in Sources */,\n\t\t\t\t6EDFBBC52F5780EA0043D9EF /* AirshipLogger.swift in Sources */,\n\t\t\t\t6EDFBBC62F5780EA0043D9EF /* AirshipLogPrivacyLevel.swift in Sources */,\n\t\t\t\t6EDFBBC72F5780EA0043D9EF /* DefaultLogHandler.swift in Sources */,\n\t\t\t\t6EDFBBC82F5780EA0043D9EF /* LogLevel.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6E4A466A28EF44F600A25617 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EB8394C2BC8AB51006611C4 /* TestWorkManager.swift in Sources */,\n\t\t\t\t32F615A728F708980015696D /* MessageCenterListTests.swift in Sources */,\n\t\t\t\t32F615A828F708AD0015696D /* TestChannel.swift in Sources */,\n\t\t\t\t32F68CDC28F02A7100F7F52A /* MessageCenterStoreTest.swift in Sources */,\n\t\t\t\t6E07B5FB2D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */,\n\t\t\t\t6E9C2BD52D02683D000089A9 /* RuntimeConfig.swift in Sources */,\n\t\t\t\t27CCF77F2F16DA500018058F /* MessageViewAnalyticsTest.swift in Sources */,\n\t\t\t\t6EF13FFD2A16F390009A125D /* AirshipBaseTest.swift in Sources */,\n\t\t\t\t6E4A467928EF453400A25617 /* MessageCenterAPIClientTest.swift in Sources */,\n\t\t\t\t6EC824A22F33A5EC00E1C0C6 /* MessageCenterThemeLoaderTest.swift in Sources */,\n\t\t\t\t6E4A468128EF4FAF00A25617 /* TestAirshipRequestSession.swift in Sources */,\n\t\t\t\t60A292112CB7C5C30096F5EB /* TestDate.swift in Sources */,\n\t\t\t\t6EC824A42F33A5F600E1C0C6 /* MessageCenterMessageTest.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFF0267CD739007CD249 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t99560C2D2BB3855800F28BDC /* EmptySectionLabel.swift in Sources */,\n\t\t\t\t6E2486F22898341400657CE4 /* ConditionsMonitor.swift in Sources */,\n\t\t\t\t99303B062BD97F89002174CA /* ChannelListViewCell.swift in Sources */,\n\t\t\t\t99560C372BB38A5F00F28BDC /* ErrorLabel.swift in Sources */,\n\t\t\t\t99560C282BB3843600F28BDC /* PreferenceCenterUtils.swift in Sources */,\n\t\t\t\t322AAB212B5ACB2800652DAC /* ChannelListView.swift in Sources */,\n\t\t\t\t6E3B231328A32EC30005D46E /* PreferenceCenterViewExtensions.swift in Sources */,\n\t\t\t\t6E9B48912891B68C00C905B1 /* PreferenceCenterAlertView.swift in Sources */,\n\t\t\t\t99560C2B2BB384A700F28BDC /* BackgroundShape.swift in Sources */,\n\t\t\t\t6E9B488B2891962000C905B1 /* PreferenceCenterView.swift in Sources */,\n\t\t\t\t6E2486F728984D0D00657CE4 /* PreferenceCenterTheme.swift in Sources */,\n\t\t\t\t993F91FB2CA37874001B1C2E /* FooterView.swift in Sources */,\n\t\t\t\t99104DF32BA6689A0040C0FD /* PreferenceCloseButton.swift in Sources */,\n\t\t\t\t6E892F2E2E7A193E00FB0EC4 /* PreferenceCenterContent.swift in Sources */,\n\t\t\t\t6E9B48932891B6A700C905B1 /* ChannelSubscriptionView.swift in Sources */,\n\t\t\t\t6E9B48952891B6B400C905B1 /* ContactSubscriptionView.swift in Sources */,\n\t\t\t\t6EB5157128A4608C00870C5A /* PreferenceCenterViewControllerFactory.swift in Sources */,\n\t\t\t\t6E9B488F2891B57300C905B1 /* CommonSectionView.swift in Sources */,\n\t\t\t\t6E2486DF28945D3900657CE4 /* PreferenceCenterState.swift in Sources */,\n\t\t\t\t6EB5156E28A42B5800870C5A /* AirshipPreferenceCenterResources.swift in Sources */,\n\t\t\t\t841E7D12268617C800EA0317 /* PreferenceCenterResponse.swift in Sources */,\n\t\t\t\t6E3B230F28A318CD0005D46E /* PreferenceCenterThemeLoader.swift in Sources */,\n\t\t\t\t6E1892D5268E3D8500417887 /* PreferenceCenterDecoder.swift in Sources */,\n\t\t\t\t6E2486EC2894901E00657CE4 /* ConditionsViewModifier.swift in Sources */,\n\t\t\t\t6E6C3F9A27A47DB4007F55C7 /* PreferenceCenterConfig.swift in Sources */,\n\t\t\t\t6E9B488D2891B43F00C905B1 /* LabeledSectionBreakView.swift in Sources */,\n\t\t\t\t847B0013267CE558007CD249 /* PreferenceCenterSDKModule.swift in Sources */,\n\t\t\t\t6E9B48972891B6BF00C905B1 /* ContactSubscriptionGroupView.swift in Sources */,\n\t\t\t\t993AFDFE2C1B2D9A00AA875B /* PreferenceCenterConfig+ContactManagement.swift in Sources */,\n\t\t\t\t99CC0D952BC87868001D93D0 /* AddChannelPromptViewModel.swift in Sources */,\n\t\t\t\t322AAB1E2B5AB65700652DAC /* AddChannelPromptView.swift in Sources */,\n\t\t\t\t99F4FE5B2BC36A6700754F0F /* PreferenceCenterContentStyle.swift in Sources */,\n\t\t\t\t322AAB222B5FCB6B00652DAC /* ContactManagementView.swift in Sources */,\n\t\t\t\t99560C1E2BAE2FFA00F28BDC /* ChannelTextField.swift in Sources */,\n\t\t\t\t6E6802922B86732200F4591F /* PreferenceCenterComponent.swift in Sources */,\n\t\t\t\t6E2486FD2899C06100657CE4 /* PreferenceCenterContentLoader.swift in Sources */,\n\t\t\t\t84483A68267CF0C000D0DA7D /* PreferenceCenter.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t847BFFF8267CD73A007CD249 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E9C2BD82D0269AE000089A9 /* TestAirshipRequestSession.swift in Sources */,\n\t\t\t\t6E1892D7268E3F1800417887 /* PreferenceCenterConfigTest.swift in Sources */,\n\t\t\t\t6E1892C8268D15C300417887 /* PreferenceCenterTest.swift in Sources */,\n\t\t\t\t6E8BDF022A684FC700F816D9 /* TestRemoteData.swift in Sources */,\n\t\t\t\t6E9C2BD62D02683D000089A9 /* RuntimeConfig.swift in Sources */,\n\t\t\t\t6EF13FFE2A16F391009A125D /* AirshipBaseTest.swift in Sources */,\n\t\t\t\t60956D862CBE7CFA00950172 /* AirshipLock.swift in Sources */,\n\t\t\t\t6E07B5FC2D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */,\n\t\t\t\t6EB5159528A5B96300870C5A /* PreferenceThemeLoaderTest.swift in Sources */,\n\t\t\t\t6EB515A328A5F1C600870C5A /* PreferenceCenterStateTest.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA62058652A5841330041FBF9 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E2F5A742A60833700CABD3D /* FeatureFlagManager.swift in Sources */,\n\t\t\t\t6E2F5A762A60871E00CABD3D /* FeatureFlagPayload.swift in Sources */,\n\t\t\t\t6E4325F62B7B2F5800A9B000 /* FeatureFlagAnalytics.swift in Sources */,\n\t\t\t\t6ED7BE632D13D9FE00B6A124 /* FeatureFlagResultCache.swift in Sources */,\n\t\t\t\t6E2F5AB12A67434B00CABD3D /* FeatureFlag.swift in Sources */,\n\t\t\t\tA62058812A5842200041FBF9 /* AirshipFeatureFlagsSDKModule.swift in Sources */,\n\t\t\t\t6E6BD2762AF1C1D800B9DFC9 /* DeferredFlagResolver.swift in Sources */,\n\t\t\t\tA6E9AD982D4D12D00091BBAF /* FeatureFlagUpdateStatus.swift in Sources */,\n\t\t\t\t6E6802942B8673F900F4591F /* FeatureFlagComponent.swift in Sources */,\n\t\t\t\t6E2F5ABA2A675D3600CABD3D /* FeatureFlagsRemoteDataAccess.swift in Sources */,\n\t\t\t\t6EB214D22E7DBA61001A5660 /* FeatureFlagManagerProtocol.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA620586C2A5841330041FBF9 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tA61F3A752A5DA58500EE94CC /* FeatureFlagManagerTest.swift in Sources */,\n\t\t\t\t6E9C2BD72D0269AE000089A9 /* TestAirshipRequestSession.swift in Sources */,\n\t\t\t\t6E2811682BE406A50040D928 /* FeatureFlagDeferredResolverTest.swift in Sources */,\n\t\t\t\t6E8BDF012A679E5000F816D9 /* FeatureFlagRemoteDataAccessTest.swift in Sources */,\n\t\t\t\t6E28116C2BE40E860040D928 /* FeatureFlagVariablesTest.swift in Sources */,\n\t\t\t\t6E9C2BD42D02683D000089A9 /* RuntimeConfig.swift in Sources */,\n\t\t\t\t6E4326092B7C396F00A9B000 /* TestAnalytics.swift in Sources */,\n\t\t\t\t6E07B5FA2D925F2A0087EC47 /* TestPrivacyManager.swift in Sources */,\n\t\t\t\t6E938DBC2AC39A0500F691D9 /* FeatureFlagAnalyticsTest.swift in Sources */,\n\t\t\t\t6E8BDEFE2A67938200F816D9 /* FeatureFlagInfoTest.swift in Sources */,\n\t\t\t\t6E2F5AB52A67599400CABD3D /* TestRemoteData.swift in Sources */,\n\t\t\t\t6ED7BE602D13D9E300B6A124 /* TestCache.swift in Sources */,\n\t\t\t\t6E2F5AB22A67589400CABD3D /* TestDate.swift in Sources */,\n\t\t\t\t6E2F5AB82A675ADC00CABD3D /* TestAudienceChecker.swift in Sources */,\n\t\t\t\t6ED7BE652D13DA0400B6A124 /* FeatureFlagResultCacheTest.swift in Sources */,\n\t\t\t\t6E2F5AB32A6758ED00CABD3D /* TestNetworkMonitor.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tA641E1432BDBBDB400DE6FAA /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tCC64F0501D8B77E3009CEF27 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6ECB62862A36C1EE0095C85C /* NativeBridgeActionHandlerTest.swift in Sources */,\n\t\t\t\t6EBFA9B12D15F499002BA3E9 /* HashCheckerTest.swift in Sources */,\n\t\t\t\t6E5B1A052AFF090B0019CA61 /* SessionTrackerTest.swift in Sources */,\n\t\t\t\t6E97D6B42D84B1D70001CF7F /* ThomasStateTest.swift in Sources */,\n\t\t\t\t6E4325D32B7AD96800A9B000 /* AirshipTest.swift in Sources */,\n\t\t\t\t6068E0342B2B7CA100349E82 /* RetailEventTemplateTest.swift in Sources */,\n\t\t\t\t6E475CBA2F5B3E45003D8E42 /* VideoMediaWebViewTests.swift in Sources */,\n\t\t\t\t6087DB882B278F7600449BA8 /* JsonValueMatcherTest.swift in Sources */,\n\t\t\t\t6E1802F92C5C2DEC00198D0D /* AirshipAnalyticFeedTest.swift in Sources */,\n\t\t\t\t6E4325C52B7AC3F700A9B000 /* TestPush.swift in Sources */,\n\t\t\t\t6E8B4BF12888606D00AA336E /* ChannelTest.swift in Sources */,\n\t\t\t\t32C68D0529424449006BBB29 /* RemoteDataTest.swift in Sources */,\n\t\t\t\t6E1767F729B923D100D65F60 /* ChannelAuthTokenAPIClientTest.swift in Sources */,\n\t\t\t\t6E6ED1432683B8FA00A2CBD0 /* TestDate.swift in Sources */,\n\t\t\t\t6E92ECA7284AC1120038802D /* EnableFeatureActionTest.swift in Sources */,\n\t\t\t\t60D2B3352D9F0FCF00B0752D /* PagerDisableSwipeSelectorTest.swift in Sources */,\n\t\t\t\t6050738A2B32F85100209B51 /* ThomasViewModelTest.swift in Sources */,\n\t\t\t\t6E6EF9E7270625C400D30C35 /* AirshipEventsTest.swift in Sources */,\n\t\t\t\t6E52146F2DCC075500CF64B9 /* ThomasPagerTrackerTest.swift in Sources */,\n\t\t\t\t6E590E6E29A94CA90036DFAB /* AppStateTrackerTest.swift in Sources */,\n\t\t\t\t6ED2F5292B7FC59F000AFC80 /* AirshipColorTests.swift in Sources */,\n\t\t\t\t6E4A468028EF4FAF00A25617 /* TestAirshipRequestSession.swift in Sources */,\n\t\t\t\t6E66DDA62E95A68300D44555 /* WorkRateLimiterTests.swift in Sources */,\n\t\t\t\t6E4007182A153AFE0013C2DE /* RemoteDataURLFactoryTest.swift in Sources */,\n\t\t\t\t6E4007142A153AB20013C2DE /* AppRemoteDataProviderDelegateTest.swift in Sources */,\n\t\t\t\t6E25DD052D515F33009CF1A4 /* ThomasEmailRegistrationOptionsTest.swift in Sources */,\n\t\t\t\t6E1767F829B923D100D65F60 /* TestChannelAuthTokenAPIClient.swift in Sources */,\n\t\t\t\t6E92ECA1284A79AB0038802D /* PromptPermissionActionTest.swift in Sources */,\n\t\t\t\t6EC0CA722B4B897B00333A87 /* TestExperimentDataProvider.swift in Sources */,\n\t\t\t\t6E6363EA29DCECA1009C358A /* TestContactSubscriptionListAPIClient.swift in Sources */,\n\t\t\t\t6E6ED172268448EC00A2CBD0 /* TestNetworkMonitor.swift in Sources */,\n\t\t\t\t6058771D2AC73C940021628E /* AirshipMeteredUsageTest.swift in Sources */,\n\t\t\t\t27AFE7112E73477200767044 /* ModifyTagsActionTest.swift in Sources */,\n\t\t\t\t6E15B70326CDE40E0099C92D /* RemoteConfigManagerTest.swift in Sources */,\n\t\t\t\t6E6524502A4FD8D30019F353 /* JSONPredicateTest.swift in Sources */,\n\t\t\t\t6E97D6AF2D84B17D0001CF7F /* ThomasFormDataCollectorTest.swift in Sources */,\n\t\t\t\tA62C3354299FD509004DB0DA /* ShareActionTest.swift in Sources */,\n\t\t\t\t6EE49C262A1446B100AB1CF4 /* RemoteDataTestUtils.swift in Sources */,\n\t\t\t\t6E7DB38E28ECFCED002725F6 /* AirshipJSONTest.swift in Sources */,\n\t\t\t\t6E96ED1429417A600053CC91 /* EventStoreTest.swift in Sources */,\n\t\t\t\t6E299FD528D13D00001305A7 /* DefaultAirshipRequestSessionTest.swift in Sources */,\n\t\t\t\t6E146EDD2F52537000320A36 /* AishipFontTests.swift in Sources */,\n\t\t\t\t6E92ECA5284A7A5C0038802D /* TestPermissionPrompter.swift in Sources */,\n\t\t\t\t6068E0062B2A190300349E82 /* CustomEventTest.swift in Sources */,\n\t\t\t\t6E664BE526C5817B00A2C8E5 /* TestContact.swift in Sources */,\n\t\t\t\t6E9752562A5F79E200E67B1A /* ExperimentTest.swift in Sources */,\n\t\t\t\t6ED040EB278B5D7C00FCF773 /* ThomasFormPayloadGeneratorTest.swift in Sources */,\n\t\t\t\t6E1767FA29B92F1700D65F60 /* AirshipUtilsTest.swift in Sources */,\n\t\t\t\t6E1767F629B923D100D65F60 /* ChannelAuthTokenProviderTest.swift in Sources */,\n\t\t\t\t60E09FDB2B2780DB005A16EA /* JsonMatcherTest.swift in Sources */,\n\t\t\t\t6EC755AF2A4FCD8800851ABB /* AudienceHashSelectorTest.swift in Sources */,\n\t\t\t\t3299EF26294B222F00251E70 /* RemoteDataStoreTest.swift in Sources */,\n\t\t\t\t99C3CC792BCF3E5B00B5BED5 /* SMSValidatorAPIClientTest.swift in Sources */,\n\t\t\t\t6E1A1BB32D6F9D0F0056418B /* ThomasFormFieldProcessorTest.swift in Sources */,\n\t\t\t\tA629F7DA295B514C00671647 /* PasteboardActionTest.swift in Sources */,\n\t\t\t\t32E339E32A334A2000CD3BE5 /* AddCustomEventActionTest.swift in Sources */,\n\t\t\t\t6EFAFB8429561F23008AD187 /* ModifyAttributesActionTest.swift in Sources */,\n\t\t\t\t6018AF572B29C20A008E528B /* SearchEventTemplateTest.swift in Sources */,\n\t\t\t\t60A5CC0C2B29AE890017EDB2 /* ProximityRegionTest.swift in Sources */,\n\t\t\t\t6ED7BE612D13D9E300B6A124 /* TestCache.swift in Sources */,\n\t\t\t\t27F1E2122F0FF6EB00E317DB /* ThomasDisplayListenerTest.swift in Sources */,\n\t\t\t\t6E92ECAC284EA7DB0038802D /* AnalyticsTest.swift in Sources */,\n\t\t\t\t6ECB62842A36A7510095C85C /* DeepLinkActionTest.swift in Sources */,\n\t\t\t\t6E87BE1B26E2C59D0005D20D /* TestAnalytics.swift in Sources */,\n\t\t\t\t6E739D8226BB33A200BC6F6D /* ChannelBulkUpdateAPIClientTest.swift in Sources */,\n\t\t\t\t6ECB62822A36A45F0095C85C /* TestURLOpener.swift in Sources */,\n\t\t\t\t6EC0CA6F2B4B893500333A87 /* TestDeferredResolver.swift in Sources */,\n\t\t\t\t6E65244E2A4FD69F0019F353 /* DeviceAudienceSelectorTest.swift in Sources */,\n\t\t\t\t6E6363E629DCE9A2009C358A /* ContactSubscriptionListAPIClientTest.swift in Sources */,\n\t\t\t\t6032695B2BF75D69007F7F75 /* AirshipHTTPResponseTest.swift in Sources */,\n\t\t\t\t6E9C2BDC2D028034000089A9 /* APNSEnvironmentTest.swift in Sources */,\n\t\t\t\t6E15B6FA26CDCA6A0099C92D /* RuntimeConfigTest.swift in Sources */,\n\t\t\t\t6E4325C32B7A9D9A00A9B000 /* AirshipPrivacyManagerTest.swift in Sources */,\n\t\t\t\t6E87BE1626E29BC90005D20D /* TestAirshipInstance.swift in Sources */,\n\t\t\t\t6EB8394E2BC8B1F4006611C4 /* AirshipAsyncChannelTest.swift in Sources */,\n\t\t\t\t6068E0082B2A2A6700349E82 /* AccountEventTemplateTest.swift in Sources */,\n\t\t\t\t2726505B2E81B80E000B6FA3 /* PagerControllerTest.swift in Sources */,\n\t\t\t\t6068E0322B2B785A00349E82 /* MediaEventTemplateTest.swift in Sources */,\n\t\t\t\t6E032A502B210E6000404630 /* RemoteConfigTest.swift in Sources */,\n\t\t\t\tA63A567828F457FE004B8951 /* TestWorkRateLimiterActor.swift in Sources */,\n\t\t\t\t6EC7E46E269E2A4C0038CFDD /* AttributeEditorTest.swift in Sources */,\n\t\t\t\t6EF140212A269074009A125D /* AirshipDeviceIDTest.swift in Sources */,\n\t\t\t\t6E1C9C3A271E90EB009EF9EF /* LayoutModelsTest.swift in Sources */,\n\t\t\t\t6EC7E470269E33290038CFDD /* TagGroupsEditorTest.swift in Sources */,\n\t\t\t\t60A5CC102B29B4100017EDB2 /* RegionEventTest.swift in Sources */,\n\t\t\t\t6E7DB39228ED0D7C002725F6 /* LiveActivityRegistryTest.swift in Sources */,\n\t\t\t\t6EAC295A27580063006DFA63 /* ChannelRegistrarTest.swift in Sources */,\n\t\t\t\t6E49D7CD284028C600C7BB9D /* PermissionsManagerTests.swift in Sources */,\n\t\t\t\t27F1E19E2F0E846B00E317DB /* TestThomasLayoutEvent.swift in Sources */,\n\t\t\t\tC088383726E0244C00D40838 /* TestURLAllowList.swift in Sources */,\n\t\t\t\t6ED2F5252B7EE648000AFC80 /* AirshipBase64Test.swift in Sources */,\n\t\t\t\t27F1E18C2F0E828D00E317DB /* ThomasLayoutEventMessageIDTest.swift in Sources */,\n\t\t\t\t27F1E18D2F0E828D00E317DB /* ThomasLayoutEventRecorderTest.swift in Sources */,\n\t\t\t\t27F1E18E2F0E828D00E317DB /* ThomasLayoutEventContextTest.swift in Sources */,\n\t\t\t\t6ED735E026C74321003B0A7D /* TagEditorTest.swift in Sources */,\n\t\t\t\t6EFAFB8229555174008AD187 /* FetchDeviceInfoActionTest.swift in Sources */,\n\t\t\t\t6E96ED1229416E990053CC91 /* EventSchedulerTest.swift in Sources */,\n\t\t\t\t6E96ED0E29416E820053CC91 /* EventManagerTest.swift in Sources */,\n\t\t\t\t6EC7E474269E52600038CFDD /* ContactOperationTest.swift in Sources */,\n\t\t\t\t6EC7E47626A5EE910038CFDD /* AudienceUtilsTest.swift in Sources */,\n\t\t\t\t325D53DA295C7979003421B4 /* ActionRegistryTest.swift in Sources */,\n\t\t\t\t32F293D5295AFD94004A7D9C /* ActionArgumentsTest.swift in Sources */,\n\t\t\t\t6ED2F52D2B7FD403000AFC80 /* JavaScriptCommandTest.swift in Sources */,\n\t\t\t\t6EFAFB7C295525DF008AD187 /* ChannelRegistrationPayloadTest.swift in Sources */,\n\t\t\t\t6E2D6AF226B0B64E00B7C226 /* SubscriptionListAPIClientTest.swift in Sources */,\n\t\t\t\t60A5CC0E2B29B1B80017EDB2 /* CircularRegionTest.swift in Sources */,\n\t\t\t\t6E8CE762284137D600CF4B11 /* AirshipPushTest.swift in Sources */,\n\t\t\t\t6E07688C29F9F0830014E2A9 /* AirshipLocaleManagerTest.swift in Sources */,\n\t\t\t\t6E382C21276D3E990091A351 /* ThomasValidationTests.swift in Sources */,\n\t\t\t\t6EF1401F2A268CE6009A125D /* TestKeychainAccess.swift in Sources */,\n\t\t\t\t6E96ED1029416E8F0053CC91 /* EventAPIClientTest.swift in Sources */,\n\t\t\t\t6E92ECB6284ED7330038802D /* CachedListTest.swift in Sources */,\n\t\t\t\t6ED735E426CAE8AA003B0A7D /* TestChannelAudienceManager.swift in Sources */,\n\t\t\t\t6E15B70526CE07180099C92D /* TestRemoteData.swift in Sources */,\n\t\t\t\t6EFAFB7A295525CD008AD187 /* ChannelCaptureTest.swift in Sources */,\n\t\t\t\t6EFB7B342A14A0F400133115 /* RemoteDataProviderTest.swift in Sources */,\n\t\t\t\t6E2D6AF426B0C3C500B7C226 /* ChannelAudienceManagerTest.swift in Sources */,\n\t\t\t\t6EC7E48526A60CDF0038CFDD /* TestChannel.swift in Sources */,\n\t\t\t\t6E2F5AB72A675ADC00CABD3D /* TestAudienceChecker.swift in Sources */,\n\t\t\t\t6EF553E32B7EE40B00901A22 /* AirshipLocalizationUtilsTest.swift in Sources */,\n\t\t\t\t6E97D6B62D84B2350001CF7F /* ThomasFormFieldTest.swift in Sources */,\n\t\t\t\tA6CDD8D0269491BE0040A673 /* ContactAPIClientTest.swift in Sources */,\n\t\t\t\t6E64C88027331ABA000EB887 /* PreferenceDataStoreTest.swift in Sources */,\n\t\t\t\t6EC7E48726A60DD60038CFDD /* TestContactAPIClient.swift in Sources */,\n\t\t\t\t6E10A1482C2B825200ED9556 /* DefaultTaskSleeperTest.swift in Sources */,\n\t\t\t\t6E6ED1452683BC7F00A2CBD0 /* TestDispatcher.swift in Sources */,\n\t\t\t\tA6AF8D2D27E8D4910068C7EE /* SubscriptionListActionTest.swift in Sources */,\n\t\t\t\t6ED2F5382B7FFCEB000AFC80 /* AirshipDateFormatterTest.swift in Sources */,\n\t\t\t\t6E9C2BD02D02321D000089A9 /* RuntimeConfig.swift in Sources */,\n\t\t\t\t6E6BD2582AEC598C00B9DFC9 /* DeferredAPIClientTest.swift in Sources */,\n\t\t\t\t6EC7E47826A604080038CFDD /* AirshipContactTest.swift in Sources */,\n\t\t\t\t6EC7E472269E51030038CFDD /* AttributeUpdateTest.swift in Sources */,\n\t\t\t\t6ED2F52F2B7FD49B000AFC80 /* AirshipURLAllowListTest.swift in Sources */,\n\t\t\t\t6E698E62267C03C700654DB2 /* TestAppStateTracker.swift in Sources */,\n\t\t\t\t6E6BD25A2AEC626B00B9DFC9 /* DeferredResolverTest.swift in Sources */,\n\t\t\t\t6ED2F5272B7EE82B000AFC80 /* AirshipJSONUtilsTest.swift in Sources */,\n\t\t\t\t6E1A19242D6F8BDD0056418B /* AirshipInputValidationTest.swift in Sources */,\n\t\t\t\t6ED838AD2D0CEF4A009CBB0C /* CompoundDeviceAudienceSelectorTest.swift in Sources */,\n\t\t\t\t6ED735E226CAE2D7003B0A7D /* TestChannelRegistrar.swift in Sources */,\n\t\t\t\t6ED735E626CAEABC003B0A7D /* TestLocaleManager.swift in Sources */,\n\t\t\t\t605877202ACAC8700021628E /* MeteredUsageApiClientTest.swift in Sources */,\n\t\t\t\t607951222A1CD1A50086578F /* ExperimentManagerTest.swift in Sources */,\n\t\t\t\t6E96ED1A2941A0EC0053CC91 /* EventTestUtils.swift in Sources */,\n\t\t\t\t60A5CC082B28DC500017EDB2 /* NotificationCategoriesTest.swift in Sources */,\n\t\t\t\t99C3CC7E2BCF40B200B5BED5 /* CachingSMSValidatorAPIClientTest.swift in Sources */,\n\t\t\t\t6E739D7F26BAFCB800BC6F6D /* TestChannelBulkUpdateAPIClient.swift in Sources */,\n\t\t\t\t6E6C3F8D27A26992007F55C7 /* CachedValueTest.swift in Sources */,\n\t\t\t\t6E4007162A153ABE0013C2DE /* ContactRemoteDataProviderTest.swift in Sources */,\n\t\t\t\t6E87BD9D26DD78CC0005D20D /* DefaultAppIntegrationDelegateTest.swift in Sources */,\n\t\t\t\t6E6363E829DCEB84009C358A /* ContactManagerTest.swift in Sources */,\n\t\t\t\t6E15B73026CF4F6B0099C92D /* TestRemoteDataAPIClient.swift in Sources */,\n\t\t\t\t6ECB627C2A369F5B0095C85C /* OpenExternalURLActionTest.swift in Sources */,\n\t\t\t\t6E8746492D8A3C71002469D7 /* TestSMSValidatorAPIClient.swift in Sources */,\n\t\t\t\tA63A567628F449D8004B8951 /* TestWorkManager.swift in Sources */,\n\t\t\t\t6EFAFB8C29562866008AD187 /* RemoveTagsActionTest.swift in Sources */,\n\t\t\t\t6E87BD9F26DDDB250005D20D /* AppIntegrationTests.swift in Sources */,\n\t\t\t\t6014AD6C2C2032730072DCF0 /* ChallengeResolverTest.swift in Sources */,\n\t\t\t\t6E2D6AF626B0C6CA00B7C226 /* TestSubscriptionListAPIClient.swift in Sources */,\n\t\t\t\t6E07B5F82D925ED60087EC47 /* TestPrivacyManager.swift in Sources */,\n\t\t\t\t6E0B8760294CE0BF0064B7BD /* FarmHashFingerprint64Test.swift in Sources */,\n\t\t\t\t3299EF172948CBC100251E70 /* RemoteDataAPIClientTest.swift in Sources */,\n\t\t\t\t605073902B347B6400209B51 /* ThomasPresentationModelCodingTest.swift in Sources */,\n\t\t\t\t6EFAFB78295525C3008AD187 /* ChannelAPIClientTest.swift in Sources */,\n\t\t\t\t6E4326072B7C364300A9B000 /* AssociatedIdentifiersTest.swift in Sources */,\n\t\t\t\t6ED2F52B2B7FC5C8000AFC80 /* AirshipIvyVersionMatcherTest.swift in Sources */,\n\t\t\t\t9971A8852C125C0200092ED1 /* ContactChannelsProviderTest.swift in Sources */,\n\t\t\t\t6E65244C2A4FD4270019F353 /* DeviceTagSelectorTest.swift in Sources */,\n\t\t\t\t3299EF222949EC3E00251E70 /* AirshipBaseTest.swift in Sources */,\n\t\t\t\t6E7EACD12AF4192400DA286B /* AirshipCacheTest.swift in Sources */,\n\t\t\t\t6E6C84462A5C8CFE00DD83A2 /* AirshipConfigTest.swift in Sources */,\n\t\t\t\t6E97D6AD2D84B1660001CF7F /* ThomasFormStateTest.swift in Sources */,\n\t\t\t\t27F1E18F2F0E82C700E317DB /* ThomasLayoutResolutionEventTest.swift in Sources */,\n\t\t\t\t27F1E1902F0E82C700E317DB /* ThomasLayoutEventTestUtils.swift in Sources */,\n\t\t\t\t27F1E1912F0E82C700E317DB /* ThomasLayoutFormResultEventTest.swift in Sources */,\n\t\t\t\t27F1E1922F0E82C700E317DB /* ThomasLayoutPageSwipeEventAction.swift in Sources */,\n\t\t\t\t27F1E1932F0E82C700E317DB /* ThomasLayoutDisplayEventTest.swift in Sources */,\n\t\t\t\t27F1E1942F0E82C700E317DB /* ThomasLayoutButtonTapEventTest.swift in Sources */,\n\t\t\t\t27F1E1952F0E82C700E317DB /* ThomasLayoutPagerSummaryEventTest.swift in Sources */,\n\t\t\t\t27F1E1962F0E82C700E317DB /* ThomasLayoutPageActionEventTest.swift in Sources */,\n\t\t\t\t27F1E1972F0E82C700E317DB /* ThomasLayoutGestureEventTest.swift in Sources */,\n\t\t\t\t27F1E1992F0E82C700E317DB /* ThomasLayoutFormDisplayEventTest.swift in Sources */,\n\t\t\t\t27F1E19A2F0E82C700E317DB /* ThomasLayoutPermissionResultEventTest.swift in Sources */,\n\t\t\t\t27F1E19B2F0E82C700E317DB /* ThomasLayoutPageViewEventTest.swift in Sources */,\n\t\t\t\t27F1E19C2F0E82C700E317DB /* ThomasLayoutPagerCompletedEventTest.swift in Sources */,\n\t\t\t\t6EFAFB8A29562474008AD187 /* AddTagsActionTest.swift in Sources */,\n\t\t\t\t6E9B4878288F360C00C905B1 /* RateAppActionTest.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t3C39D3082384C8B6003C50D4 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 3CA0E346237E4A7B00EE76CF /* AirshipMessageCenter */;\n\t\t\ttargetProxy = 3C39D3072384C8B6003C50D4 /* PBXContainerItemProxy */;\n\t\t};\n\t\t3CA0E2DA237CD59100EE76CF /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 3CA0E2D9237CD59100EE76CF /* PBXContainerItemProxy */;\n\t\t};\n\t\t3CA0E347237E4A7B00EE76CF /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 3CA0E348237E4A7B00EE76CF /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E0B8734294A9C130064B7BD /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E0B8729294A9C120064B7BD /* AirshipAutomation */;\n\t\t\ttargetProxy = 6E0B8733294A9C130064B7BD /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E0B8743294A9C780064B7BD /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tplatformFilters = (\n\t\t\t\tios,\n\t\t\t\tmaccatalyst,\n\t\t\t\tmacos,\n\t\t\t\ttvos,\n\t\t\t\txros,\n\t\t\t);\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 6E0B8742294A9C780064B7BD /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E107F042B30B887007AFC4D /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 6E107F032B30B887007AFC4D /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E128B9D2D305C4600733024 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A641E1462BDBBDB400DE6FAA /* AirshipObjectiveC */;\n\t\t\ttargetProxy = 6E128B9C2D305C4600733024 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E29474A2AD47E0C009EC6DD /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */;\n\t\t\ttargetProxy = 6E2947492AD47E0C009EC6DD /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E2F5A922A67314A00CABD3D /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A62058682A5841330041FBF9 /* AirshipFeatureFlags */;\n\t\t\ttargetProxy = 6E2F5A912A67314A00CABD3D /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E2F5A962A67316C00CABD3D /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A62058682A5841330041FBF9 /* AirshipFeatureFlags */;\n\t\t\ttargetProxy = 6E2F5A952A67316C00CABD3D /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E43218B26EA891F009228AB /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E431F6B26EA814F009228AB /* AirshipBasement */;\n\t\t\ttargetProxy = 6E43218A26EA891F009228AB /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E4A467428EF44F600A25617 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 3CA0E346237E4A7B00EE76CF /* AirshipMessageCenter */;\n\t\t\ttargetProxy = 6E4A467328EF44F600A25617 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E4AEE0B2B6B24D1008AEAC1 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E0B8729294A9C120064B7BD /* AirshipAutomation */;\n\t\t\ttargetProxy = 6E4AEE0A2B6B24D1008AEAC1 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E5917892B28E93A0084BBBF /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E0B8729294A9C120064B7BD /* AirshipAutomation */;\n\t\t\ttargetProxy = 6E5917882B28E93A0084BBBF /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E6B493E2A787D0A00AF98D8 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A62058682A5841330041FBF9 /* AirshipFeatureFlags */;\n\t\t\ttargetProxy = 6E6B493D2A787D0A00AF98D8 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE87728C2AD80003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E431F6B26EA814F009228AB /* AirshipBasement */;\n\t\t\ttargetProxy = 6EAAE87628C2AD80003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE87928C2AD80003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 6EAAE87828C2AD80003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE87B28C2AD80003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 3CA0E298237CCE2600EE76CF /* AirshipDebug */;\n\t\t\ttargetProxy = 6EAAE87A28C2AD80003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE87D28C2AD80003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 3CA0E346237E4A7B00EE76CF /* AirshipMessageCenter */;\n\t\t\ttargetProxy = 6EAAE87C28C2AD80003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE87F28C2AD80003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */;\n\t\t\ttargetProxy = 6EAAE87E28C2AD80003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6ECCAD282CF55BC700423D86 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A62058682A5841330041FBF9 /* AirshipFeatureFlags */;\n\t\t\ttargetProxy = 6ECCAD292CF55BC700423D86 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6ECCAD2A2CF55BC700423D86 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E431F6B26EA814F009228AB /* AirshipBasement */;\n\t\t\ttargetProxy = 6ECCAD2B2CF55BC700423D86 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6ECCAD2C2CF55BC700423D86 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 6ECCAD2D2CF55BC700423D86 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6ECCAD322CF55BC700423D86 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */;\n\t\t\ttargetProxy = 6ECCAD332CF55BC700423D86 /* PBXContainerItemProxy */;\n\t\t};\n\t\t847B000E267CD85E007CD249 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = 847B000D267CD85E007CD249 /* PBXContainerItemProxy */;\n\t\t};\n\t\t847BFFFF267CD73A007CD249 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */;\n\t\t\ttargetProxy = 847BFFFE267CD73A007CD249 /* PBXContainerItemProxy */;\n\t\t};\n\t\tA60235362CCB9E3C00CF412B /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E431F6B26EA814F009228AB /* AirshipBasement */;\n\t\t\ttargetProxy = A60235352CCB9E3C00CF412B /* PBXContainerItemProxy */;\n\t\t};\n\t\tA61F3A772A5DBA0E00EE94CC /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tplatformFilters = (\n\t\t\t\tios,\n\t\t\t\tmaccatalyst,\n\t\t\t\tmacos,\n\t\t\t\ttvos,\n\t\t\t\txros,\n\t\t\t);\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = A61F3A762A5DBA0E00EE94CC /* PBXContainerItemProxy */;\n\t\t};\n\t\tA61F3A7B2A5DBA1800EE94CC /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tplatformFilters = (\n\t\t\t\tios,\n\t\t\t\tmaccatalyst,\n\t\t\t\tmacos,\n\t\t\t\ttvos,\n\t\t\t\txros,\n\t\t\t);\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = A61F3A7A2A5DBA1800EE94CC /* PBXContainerItemProxy */;\n\t\t};\n\t\tA62058732A5841330041FBF9 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A62058682A5841330041FBF9 /* AirshipFeatureFlags */;\n\t\t\ttargetProxy = A62058722A5841330041FBF9 /* PBXContainerItemProxy */;\n\t\t};\n\t\tA641E1572BDBF5FF00DE6FAA /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 6E0B8729294A9C120064B7BD /* AirshipAutomation */;\n\t\t\ttargetProxy = A641E1562BDBF5FF00DE6FAA /* PBXContainerItemProxy */;\n\t\t};\n\t\tA641E1592BDBF5FF00DE6FAA /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = A641E1582BDBF5FF00DE6FAA /* PBXContainerItemProxy */;\n\t\t};\n\t\tA641E15B2BDBF5FF00DE6FAA /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = A62058682A5841330041FBF9 /* AirshipFeatureFlags */;\n\t\t\ttargetProxy = A641E15A2BDBF5FF00DE6FAA /* PBXContainerItemProxy */;\n\t\t};\n\t\tA641E15D2BDBF5FF00DE6FAA /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 3CA0E346237E4A7B00EE76CF /* AirshipMessageCenter */;\n\t\t\ttargetProxy = A641E15C2BDBF5FF00DE6FAA /* PBXContainerItemProxy */;\n\t\t};\n\t\tA641E15F2BDBF5FF00DE6FAA /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 847BFFF3267CD739007CD249 /* AirshipPreferenceCenter */;\n\t\t\ttargetProxy = A641E15E2BDBF5FF00DE6FAA /* PBXContainerItemProxy */;\n\t\t};\n\t\tCC64F05B1D8B77E3009CEF27 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 494DD9561B0EB677009C134E /* AirshipCore */;\n\t\t\ttargetProxy = CC64F05A1D8B77E3009CEF27 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin PBXVariantGroup section */\n\t\t6E411C742538C60900FEE4E8 /* UrbanAirship.strings */ = {\n\t\t\tisa = PBXVariantGroup;\n\t\t\tchildren = (\n\t\t\t\t6E411C752538C60900FEE4E8 /* en */,\n\t\t\t\t6E411C862538C62B00FEE4E8 /* vi */,\n\t\t\t\t6E411C872538C62B00FEE4E8 /* ru */,\n\t\t\t\t6E411C882538C62B00FEE4E8 /* de */,\n\t\t\t\t6E411C892538C62B00FEE4E8 /* es-419 */,\n\t\t\t\t6E411C8A2538C62B00FEE4E8 /* ro */,\n\t\t\t\t6E411C8B2538C62B00FEE4E8 /* da */,\n\t\t\t\t6E411C8C2538C62B00FEE4E8 /* zh-Hant */,\n\t\t\t\t6E411C8D2538C62B00FEE4E8 /* ja */,\n\t\t\t\t6E411C8E2538C62C00FEE4E8 /* pt-PT */,\n\t\t\t\t6E411C8F2538C62C00FEE4E8 /* fr */,\n\t\t\t\t6E411C902538C62C00FEE4E8 /* zh-Hans */,\n\t\t\t\t6E411C912538C62C00FEE4E8 /* sv */,\n\t\t\t\t6E411C922538C62C00FEE4E8 /* it */,\n\t\t\t\t6E411C932538C62C00FEE4E8 /* pl */,\n\t\t\t\t6E411C942538C62C00FEE4E8 /* iw */,\n\t\t\t\t6E411C952538C62C00FEE4E8 /* fi */,\n\t\t\t\t6E411C962538C62C00FEE4E8 /* es */,\n\t\t\t\t6E411C972538C62D00FEE4E8 /* cs */,\n\t\t\t\t6E411C982538C64700FEE4E8 /* tr */,\n\t\t\t\t6E411C992538C64700FEE4E8 /* pt */,\n\t\t\t\t6E411C9A2538C64700FEE4E8 /* no */,\n\t\t\t\t6E411C9B2538C64700FEE4E8 /* th */,\n\t\t\t\t6E411C9C2538C64800FEE4E8 /* nl */,\n\t\t\t\t6E411C9D2538C65100FEE4E8 /* ar */,\n\t\t\t\t6E411C9E2538C65400FEE4E8 /* hi */,\n\t\t\t\t6E411C9F2538C65700FEE4E8 /* hu */,\n\t\t\t\t6E411CA02538C65900FEE4E8 /* id */,\n\t\t\t\t6E411CA12538C65C00FEE4E8 /* ko */,\n\t\t\t\t6E411CA22538C65F00FEE4E8 /* ms */,\n\t\t\t\t6E411CA32538C66100FEE4E8 /* sk */,\n\t\t\t\t03525B87280DE48100320AA9 /* af */,\n\t\t\t\t03525B88280DE49B00320AA9 /* am */,\n\t\t\t\t03525B89280DE4E200320AA9 /* bg */,\n\t\t\t\t03525B8A280DE4ED00320AA9 /* ca */,\n\t\t\t\t03525B8B280DE4F800320AA9 /* el */,\n\t\t\t\t03525B8C280DE50D00320AA9 /* et */,\n\t\t\t\t03525B8D280DE54900320AA9 /* fa */,\n\t\t\t\t03525B8E280DE55900320AA9 /* fr-CA */,\n\t\t\t\t03525B8F280DE58800320AA9 /* hr */,\n\t\t\t\t03525B90280DE5A200320AA9 /* lt */,\n\t\t\t\t03525B91280DE5B800320AA9 /* lv */,\n\t\t\t\t03525B92280DE5CE00320AA9 /* sl */,\n\t\t\t\t03525B93280DE5DD00320AA9 /* sr */,\n\t\t\t\t03525B94280DE5EA00320AA9 /* sw */,\n\t\t\t\t03525B95280DE5F400320AA9 /* uk */,\n\t\t\t\t03525B96280DE60200320AA9 /* zh-HK */,\n\t\t\t\t03525B97280DE61200320AA9 /* zu */,\n\t\t\t);\n\t\t\tname = UrbanAirship.strings;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXVariantGroup section */\n\n/* Begin XCBuildConfiguration section */\n\t\t3CA0E2AA237CCE2600EE76CF /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipDebug/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t3CA0E2AB237CCE2600EE76CF /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipDebug/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t3CA0E421237E4A7B00EE76CF /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipMessageCenter/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t3CA0E422237E4A7B00EE76CF /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipMessageCenter/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t494DD96B1B0EB677009C134E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tBUILD_LIBRARY_FOR_DISTRIBUTION = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_SYMBOLS_PRIVATE_EXTERN = NO;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = \"xrsimulator xros watchsimulator watchos iphonesimulator iphoneos appletvsimulator appletvos\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = complete;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tTVOS_DEPLOYMENT_TARGET = 18.0;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t\tWATCHOS_DEPLOYMENT_TARGET = 11.0;\n\t\t\t\tXROS_DEPLOYMENT_TARGET = 2.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t494DD96C1B0EB677009C134E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tALLOW_TARGET_PLATFORM_SPECIALIZATION = YES;\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tBUILD_LIBRARY_FOR_DISTRIBUTION = YES;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\t\"GCC_PREPROCESSOR_DEFINITIONS[arch=*]\" = \"$(inherited)\";\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tONLY_ACTIVE_ARCH = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSUPPORTED_PLATFORMS = \"xrsimulator xros watchsimulator watchos iphonesimulator iphoneos appletvsimulator appletvos\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = complete;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_IMPORT_OBJC_FORWARD_DECLS = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = YES;\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tTVOS_DEPLOYMENT_TARGET = 18.0;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t\tWATCHOS_DEPLOYMENT_TARGET = 11.0;\n\t\t\t\tXROS_DEPLOYMENT_TARGET = 2.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t494DD96E1B0EB677009C134E /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipCore/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,4,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t494DD96F1B0EB677009C134E /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipCore/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,4,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6E0B873A294A9C130064B7BD /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipAutomation;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6E0B873B294A9C130064B7BD /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipAutomation;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6E0B873D294A9C130064B7BD /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipAutomationTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6E0B873E294A9C130064B7BD /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipAutomationTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6E43204626EA814F009228AB /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipBasement/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,4,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6E43204726EA814F009228AB /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbaseConfigurationReference = CC40DDB31D8CAF7300BABD4F /* AirshipConfig.xcconfig */;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipBasement/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = \"-ObjC\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = \"com.urbanairship.$(PRODUCT_NAME:rfc1034identifier)\";\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx watchos watchsimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,4,7\";\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6E4A467628EF44F600A25617 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = airship.AirshipMessageCenterTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6E4A467728EF44F600A25617 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = airship.AirshipMessageCenterTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6EAAE85F28C2AD3A003CAE53 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"xrsimulator xros watchsimulator watchos iphonesimulator iphoneos\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6EAAE86028C2AD3A003CAE53 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6ECCAD352CF55BC700423D86 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvsimulator appletvos\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6ECCAD362CF55BC700423D86 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t847B0006267CD73A007CD249 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 14.5.0;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tINFOPLIST_FILE = AirshipPreferenceCenter/Info.plist;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipPreferenceCenter;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t847B0007267CD73A007CD249 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 14.5.0;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tINFOPLIST_FILE = AirshipPreferenceCenter/Info.plist;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipPreferenceCenter;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t847B0009267CD73A007CD249 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tINFOPLIST_FILE = AirshipPreferenceCenter/Tests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = Airship.AirshipPreferenceCenterTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t847B000A267CD73A007CD249 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tINFOPLIST_FILE = AirshipPreferenceCenter/Tests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = Airship.AirshipPreferenceCenterTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tA62058782A5841340041FBF9 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipFeatureFlags/Info.plist\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = \"\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.AirshipFeatureFlags;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tA62058792A5841340041FBF9 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipFeatureFlags/Info.plist\";\n\t\t\t\tINFOPLIST_KEY_NSPrincipalClass = \"\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.AirshipFeatureFlags;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tA620587A2A5841340041FBF9 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.AirshipFeatureFlagsTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tA620587B2A5841340041FBF9 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.AirshipFeatureFlagsTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = NO;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tA641E14B2BDBBDB400DE6FAA /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_MODULE_VERIFIER = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMODULE_VERIFIER_SUPPORTED_LANGUAGES = \"objective-c objective-c++\";\n\t\t\t\tMODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = \"gnu17 gnu++20\";\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipObjectiveC;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tA641E14C2BDBBDB400DE6FAA /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Manual;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = \"\";\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tENABLE_MODULE_VERIFIER = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMODULE_VERIFIER_SUPPORTED_LANGUAGES = \"objective-c objective-c++\";\n\t\t\t\tMODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = \"gnu17 gnu++20\";\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipObjectiveC;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME:c99extidentifier)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tCC64F05C1D8B77E3009CEF27 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"XCODE_VERSION_MAJOR=\\\"$(XCODE_VERSION_MAJOR)\\\"\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = AirshipCore/Tests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tOTHER_SWIFT_FLAGS = \"$(inherited)\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_NONISOLATED_NONSENDING_BY_DEFAULT = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tCC64F05D1D8B77E3009CEF27 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=macosx*]\" = \"-\";\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"XCODE_VERSION_MAJOR=\\\"$(XCODE_VERSION_MAJOR)\\\"\",\n\t\t\t\t);\n\t\t\t\tINFOPLIST_FILE = Core/Tests/Info.plist;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tOTHER_LDFLAGS = (\n\t\t\t\t\t\"-ObjC\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tOTHER_SWIFT_FLAGS = \"$(inherited)\";\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = minimal;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_MEMBER_IMPORT_VISIBILITY = NO;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_NONISOLATED_NONSENDING_BY_DEFAULT = NO;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVALID_ARCHS = \"$(inherited) x86_64\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t3CA0E2A9237CCE2600EE76CF /* Build configuration list for PBXNativeTarget \"AirshipDebug\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t3CA0E2AA237CCE2600EE76CF /* Debug */,\n\t\t\t\t3CA0E2AB237CCE2600EE76CF /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t3CA0E420237E4A7B00EE76CF /* Build configuration list for PBXNativeTarget \"AirshipMessageCenter\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t3CA0E421237E4A7B00EE76CF /* Debug */,\n\t\t\t\t3CA0E422237E4A7B00EE76CF /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t494DD9511B0EB677009C134E /* Build configuration list for PBXProject \"Airship\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t494DD96B1B0EB677009C134E /* Debug */,\n\t\t\t\t494DD96C1B0EB677009C134E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t494DD96D1B0EB677009C134E /* Build configuration list for PBXNativeTarget \"AirshipCore\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t494DD96E1B0EB677009C134E /* Debug */,\n\t\t\t\t494DD96F1B0EB677009C134E /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6E0B8739294A9C130064B7BD /* Build configuration list for PBXNativeTarget \"AirshipAutomation\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6E0B873A294A9C130064B7BD /* Debug */,\n\t\t\t\t6E0B873B294A9C130064B7BD /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6E0B873C294A9C130064B7BD /* Build configuration list for PBXNativeTarget \"AirshipAutomationTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6E0B873D294A9C130064B7BD /* Debug */,\n\t\t\t\t6E0B873E294A9C130064B7BD /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6E43204526EA814F009228AB /* Build configuration list for PBXNativeTarget \"AirshipBasement\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6E43204626EA814F009228AB /* Debug */,\n\t\t\t\t6E43204726EA814F009228AB /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6E4A467528EF44F600A25617 /* Build configuration list for PBXNativeTarget \"AirshipMessageCenterTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6E4A467628EF44F600A25617 /* Debug */,\n\t\t\t\t6E4A467728EF44F600A25617 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6EAAE85E28C2AD3A003CAE53 /* Build configuration list for PBXAggregateTarget \"AirshipRelease\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6EAAE85F28C2AD3A003CAE53 /* Debug */,\n\t\t\t\t6EAAE86028C2AD3A003CAE53 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6ECCAD342CF55BC700423D86 /* Build configuration list for PBXAggregateTarget \"AirshipRelease tvOS\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6ECCAD352CF55BC700423D86 /* Debug */,\n\t\t\t\t6ECCAD362CF55BC700423D86 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t847B0005267CD73A007CD249 /* Build configuration list for PBXNativeTarget \"AirshipPreferenceCenter\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t847B0006267CD73A007CD249 /* Debug */,\n\t\t\t\t847B0007267CD73A007CD249 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t847B0008267CD73A007CD249 /* Build configuration list for PBXNativeTarget \"AirshipPreferenceCenterTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t847B0009267CD73A007CD249 /* Debug */,\n\t\t\t\t847B000A267CD73A007CD249 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tA620587C2A5841340041FBF9 /* Build configuration list for PBXNativeTarget \"AirshipFeatureFlags\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tA62058782A5841340041FBF9 /* Debug */,\n\t\t\t\tA62058792A5841340041FBF9 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tA620587D2A5841340041FBF9 /* Build configuration list for PBXNativeTarget \"AirshipFeatureFlagsTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tA620587A2A5841340041FBF9 /* Debug */,\n\t\t\t\tA620587B2A5841340041FBF9 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tA641E14D2BDBBDB400DE6FAA /* Build configuration list for PBXNativeTarget \"AirshipObjectiveC\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tA641E14B2BDBBDB400DE6FAA /* Debug */,\n\t\t\t\tA641E14C2BDBBDB400DE6FAA /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tCC64F05E1D8B77E3009CEF27 /* Build configuration list for PBXNativeTarget \"AirshipTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tCC64F05C1D8B77E3009CEF27 /* Debug */,\n\t\t\t\tCC64F05D1D8B77E3009CEF27 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCVersionGroup section */\n\t\t3231128A29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t3231128B29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 3231128B29D5E69400CF0D86 /* UAFrequencyLimits.xcdatamodel */;\n\t\t\tpath = UAFrequencyLimits.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t3CA0E222237CCBA600EE76CF /* AirshipDebugEventData.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t3CA0E223237CCBA600EE76CF /* AirshipEventData.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 3CA0E223237CCBA600EE76CF /* AirshipEventData.xcdatamodel */;\n\t\t\tpath = AirshipDebugEventData.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t3CA0E304237E396100EE76CF /* UAInbox.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t27E419482EF484DB00D5C1A6 /* UAInbox 4.xcdatamodel */,\n\t\t\t\t325108C72B7A596F0028F508 /* UAInbox 3.xcdatamodel */,\n\t\t\t\tA649F50A252F4F39005453CB /* UAInbox 2.xcdatamodel */,\n\t\t\t\t3CA0E305237E396100EE76CF /* UAInbox.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 27E419482EF484DB00D5C1A6 /* UAInbox 4.xcdatamodel */;\n\t\t\tpath = UAInbox.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t6E1CBE2A2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t6ED7BE5E2D13D9B600B6A124 /* AirshipAutomation 3.xcdatamodel */,\n\t\t\t\t6E1CBE2B2BAA2AEA00519D9C /* AirshipAutomation 2.xcdatamodel */,\n\t\t\t\t6E1CBE2C2BAA2AEA00519D9C /* AirshipAutomation.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 6ED7BE5E2D13D9B600B6A124 /* AirshipAutomation 3.xcdatamodel */;\n\t\t\tpath = AirshipAutomation.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t6E411CA42538C6A500FEE4E8 /* UARemoteData.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t325108C82B7A5A220028F508 /* UARemoteData 4.xcdatamodel */,\n\t\t\t\t6EE49C1B2A0D544B00AB1CF4 /* UARemoteData 3.xcdatamodel */,\n\t\t\t\t6E411CA52538C6A500FEE4E8 /* UARemoteData.xcdatamodel */,\n\t\t\t\t6E411CA62538C6A500FEE4E8 /* UARemoteData 2.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 325108C82B7A5A220028F508 /* UARemoteData 4.xcdatamodel */;\n\t\t\tpath = UARemoteData.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t6E411CA72538C6A500FEE4E8 /* UAEvents.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t6E411CA82538C6A500FEE4E8 /* UAEvents.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 6E411CA82538C6A500FEE4E8 /* UAEvents.xcdatamodel */;\n\t\t\tpath = UAEvents.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t6E4AEE492B6B4358008AEAC1 /* UAAutomation.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4AEE4A2B6B4358008AEAC1 /* UAAutomation 7.xcdatamodel */,\n\t\t\t\t6E4AEE4B2B6B4358008AEAC1 /* UAAutomation 12.xcdatamodel */,\n\t\t\t\t6E4AEE4C2B6B4358008AEAC1 /* UAAutomation 2.xcdatamodel */,\n\t\t\t\t6E4AEE4D2B6B4358008AEAC1 /* UAAutomation 11.xcdatamodel */,\n\t\t\t\t6E4AEE4E2B6B4358008AEAC1 /* UAAutomation 8.xcdatamodel */,\n\t\t\t\t6E4AEE4F2B6B4358008AEAC1 /* UAAutomation 4.xcdatamodel */,\n\t\t\t\t6E4AEE502B6B4358008AEAC1 /* UAAutomation 3.xcdatamodel */,\n\t\t\t\t6E4AEE512B6B4358008AEAC1 /* UAAutomation 13.xcdatamodel */,\n\t\t\t\t6E4AEE522B6B4358008AEAC1 /* UAAutomation.xcdatamodel */,\n\t\t\t\t6E4AEE532B6B4358008AEAC1 /* UAAutomation 6.xcdatamodel */,\n\t\t\t\t6E4AEE542B6B4358008AEAC1 /* UAAutomation 5.xcdatamodel */,\n\t\t\t\t6E4AEE552B6B4358008AEAC1 /* UAAutomation 9.xcdatamodel */,\n\t\t\t\t6E4AEE562B6B4358008AEAC1 /* UAAutomation 10.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 6E4AEE512B6B4358008AEAC1 /* UAAutomation 13.xcdatamodel */;\n\t\t\tpath = UAAutomation.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t6E5A64D72AABC5A400574085 /* UAMeteredUsage.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t6E5A64D82AABC5A400574085 /* UAMeteredUsage.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 6E5A64D82AABC5A400574085 /* UAMeteredUsage.xcdatamodel */;\n\t\t\tpath = UAMeteredUsage.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t6E6BD2702AF1B05500B9DFC9 /* UAirshipCache.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t6E6BD2712AF1B05500B9DFC9 /* UAAirshipCache.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 6E6BD2712AF1B05500B9DFC9 /* UAAirshipCache.xcdatamodel */;\n\t\t\tpath = UAirshipCache.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n\t\t83A674F623AA7AA4005C0C8F /* AirshipDebugPushData.xcdatamodeld */ = {\n\t\t\tisa = XCVersionGroup;\n\t\t\tchildren = (\n\t\t\t\t83A674F723AA7AA4005C0C8F /* AirshipDebugPushData.xcdatamodel */,\n\t\t\t);\n\t\t\tcurrentVersion = 83A674F723AA7AA4005C0C8F /* AirshipDebugPushData.xcdatamodel */;\n\t\t\tpath = AirshipDebugPushData.xcdatamodeld;\n\t\t\tsourceTree = \"<group>\";\n\t\t\tversionGroupType = wrapper.xcdatamodel;\n\t\t};\n/* End XCVersionGroup section */\n\t};\n\trootObject = 494DD94E1B0EB677009C134E /* Project object */;\n}\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipAutomation.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"6E0B8729294A9C120064B7BD\"\n               BuildableName = \"AirshipAutomation.framework\"\n               BlueprintName = \"AirshipAutomation\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"6E0B8730294A9C130064B7BD\"\n               BuildableName = \"AirshipAutomationTests.xctest\"\n               BlueprintName = \"AirshipAutomationTests\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"6E0B8729294A9C120064B7BD\"\n            BuildableName = \"AirshipAutomation.framework\"\n            BlueprintName = \"AirshipAutomation\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipBasement.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"6E431F6B26EA814F009228AB\"\n               BuildableName = \"AirshipBasement.framework\"\n               BlueprintName = \"AirshipBasement\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"6E431F6B26EA814F009228AB\"\n            BuildableName = \"AirshipBasement.framework\"\n            BlueprintName = \"AirshipBasement\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipCore.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"494DD9561B0EB677009C134E\"\n               BuildableName = \"AirshipCore.framework\"\n               BlueprintName = \"AirshipCore\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"CC64F0531D8B77E3009CEF27\"\n               BuildableName = \"AirshipTests.xctest\"\n               BlueprintName = \"AirshipTests\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"494DD9561B0EB677009C134E\"\n            BuildableName = \"AirshipCore.framework\"\n            BlueprintName = \"AirshipCore\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipDebug.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"3CA0E298237CCE2600EE76CF\"\n               BuildableName = \"AirshipDebug.framework\"\n               BlueprintName = \"AirshipDebug\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"3CA0E298237CCE2600EE76CF\"\n            BuildableName = \"AirshipDebug.framework\"\n            BlueprintName = \"AirshipDebug\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipFeatureFlags.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"A62058682A5841330041FBF9\"\n               BuildableName = \"AirshipFeatureFlags.framework\"\n               BlueprintName = \"AirshipFeatureFlags\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"A620586F2A5841330041FBF9\"\n               BuildableName = \"AirshipFeatureFlagsTests.xctest\"\n               BlueprintName = \"AirshipFeatureFlagsTests\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"A62058682A5841330041FBF9\"\n            BuildableName = \"AirshipFeatureFlags.framework\"\n            BlueprintName = \"AirshipFeatureFlags\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipMessageCenter.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"3CA0E346237E4A7B00EE76CF\"\n               BuildableName = \"AirshipMessageCenter.framework\"\n               BlueprintName = \"AirshipMessageCenter\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"6E4A466D28EF44F600A25617\"\n               BuildableName = \"AirshipMessageCenterTests.xctest\"\n               BlueprintName = \"AirshipMessageCenterTests\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"3CA0E346237E4A7B00EE76CF\"\n            BuildableName = \"AirshipMessageCenter.framework\"\n            BlueprintName = \"AirshipMessageCenter\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipObjectiveC.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.7\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\"\n      buildArchitectures = \"Automatic\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"A641E1462BDBBDB400DE6FAA\"\n               BuildableName = \"AirshipObjectiveC.framework\"\n               BlueprintName = \"AirshipObjectiveC\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      shouldAutocreateTestPlan = \"YES\">\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"A641E1462BDBBDB400DE6FAA\"\n            BuildableName = \"AirshipObjectiveC.framework\"\n            BlueprintName = \"AirshipObjectiveC\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/Airship.xcodeproj/xcshareddata/xcschemes/AirshipPreferenceCenter.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"847BFFF3267CD739007CD249\"\n               BuildableName = \"AirshipPreferenceCenter.framework\"\n               BlueprintName = \"AirshipPreferenceCenter\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"NO\"\n            buildForProfiling = \"NO\"\n            buildForArchiving = \"NO\"\n            buildForAnalyzing = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"847BFFFB267CD73A007CD249\"\n               BuildableName = \"AirshipPreferenceCenterTests.xctest\"\n               BlueprintName = \"AirshipPreferenceCenterTests\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"847BFFFB267CD73A007CD249\"\n               BuildableName = \"AirshipPreferenceCenterTests.xctest\"\n               BlueprintName = \"AirshipPreferenceCenterTests\"\n               ReferencedContainer = \"container:Airship.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"847BFFF3267CD739007CD249\"\n            BuildableName = \"AirshipPreferenceCenter.framework\"\n            BlueprintName = \"AirshipPreferenceCenter\"\n            ReferencedContainer = \"container:Airship.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/AirshipAutomation.xcdatamodeld/.xccurrentversion",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>_XCCurrentVersionName</key>\n\t<string>AirshipAutomation 3.xcdatamodel</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/AirshipAutomation.xcdatamodeld/AirshipAutomation 2.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"23507\" systemVersion=\"24B91\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleEntity\" representedClassName=\"UAScheduleEntity\" syncable=\"YES\">\n        <attribute name=\"associatedData\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"executionCount\" attributeType=\"Integer 64\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" attributeType=\"String\"/>\n        <attribute name=\"preparedScheduleInfo\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"schedule\" attributeType=\"Binary\"/>\n        <attribute name=\"scheduleState\" attributeType=\"String\"/>\n        <attribute name=\"scheduleStateChangeDate\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerInfo\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"triggerSessionID\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n    <entity name=\"UATriggerEntity\" representedClassName=\"UATriggerEntity\" syncable=\"YES\">\n        <attribute name=\"scheduleID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"state\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"triggerID\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/AirshipAutomation.xcdatamodeld/AirshipAutomation 3.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"23507\" systemVersion=\"24B91\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleEntity\" representedClassName=\"UAScheduleEntity\" syncable=\"YES\">\n        <attribute name=\"associatedData\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"executionCount\" attributeType=\"Integer 64\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" attributeType=\"String\"/>\n        <attribute name=\"lastScheduleModifiedDate\" optional=\"YES\" attributeType=\"Date\" defaultDateTimeInterval=\"755651940\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"preparedScheduleInfo\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"schedule\" attributeType=\"Binary\"/>\n        <attribute name=\"scheduleState\" attributeType=\"String\"/>\n        <attribute name=\"scheduleStateChangeDate\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerInfo\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"triggerSessionID\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n    <entity name=\"UATriggerEntity\" representedClassName=\"UATriggerEntity\" syncable=\"YES\">\n        <attribute name=\"scheduleID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"state\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"triggerID\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/AirshipAutomation.xcdatamodeld/AirshipAutomation.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22522\" systemVersion=\"23A344\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleEntity\" representedClassName=\"UAScheduleEntity\" syncable=\"YES\">\n        <attribute name=\"executionCount\" attributeType=\"Integer 64\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" attributeType=\"String\"/>\n        <attribute name=\"preparedScheduleInfo\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"schedule\" attributeType=\"Binary\"/>\n        <attribute name=\"scheduleState\" attributeType=\"String\"/>\n        <attribute name=\"scheduleStateChangeDate\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerInfo\" optional=\"YES\" attributeType=\"Binary\"/>\n    </entity>\n    <entity name=\"UATriggerEntity\" representedClassName=\"UATriggerEntity\" syncable=\"YES\">\n        <attribute name=\"scheduleID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"state\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"triggerID\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/.xccurrentversion",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>_XCCurrentVersionName</key>\n\t<string>UAAutomation 13.xcdatamodel</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 10.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"21754\" systemVersion=\"22F66\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"frequencyConstraintIDs\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSArrayValueTransformer\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"reportingContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggeredTime\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 11.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22222\" systemVersion=\"22G120\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"bypassHoldoutGroups\" optional=\"YES\" attributeType=\"Boolean\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"frequencyConstraintIDs\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSArrayValueTransformer\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"isNewUserEvaluationDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageType\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"reportingContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggeredTime\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 12.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22522\" systemVersion=\"23A344\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"bypassHoldoutGroups\" optional=\"YES\" attributeType=\"Boolean\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"frequencyConstraintIDs\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"isNewUserEvaluationDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageType\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"productId\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"reportingContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggeredTime\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n</model>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 13.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22522\" systemVersion=\"23A344\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"bypassHoldoutGroups\" optional=\"YES\" attributeType=\"Boolean\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"frequencyConstraintIDs\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"isNewUserEvaluationDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageType\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"productId\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"reportingContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggeredTime\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 2.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"13240\" systemVersion=\"16G29\" minimumToolsVersion=\"Xcode 7.0\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAActionScheduleData\" representedClassName=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"actions\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"isPendingExecution\" attributeType=\"Boolean\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\" syncable=\"YES\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"screen\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAActionScheduleData\" inverseName=\"delay\" inverseEntity=\"UAActionScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAActionScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAActionScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <elements>\n        <element name=\"UAActionScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"210\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"135\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"150\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 3.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"13772\" systemVersion=\"16G1114\" minimumToolsVersion=\"Xcode 7.0\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\" syncable=\"YES\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\" syncable=\"YES\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\" syncable=\"YES\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\" syncable=\"YES\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"270\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"135\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"150\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 4.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"14460.32\" systemVersion=\"17G2307\" minimumToolsVersion=\"Xcode 7.0\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\" syncable=\"YES\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\" syncable=\"YES\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\" syncable=\"YES\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\" syncable=\"YES\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"285\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"135\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"150\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 5.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"14877.5\" systemVersion=\"18G95\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"300\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"135\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"150\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 6.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"16119\" systemVersion=\"19E287\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"28\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"133\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"148\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 7.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"17192\" systemVersion=\"19G2021\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"358\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"133\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"148\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 8.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"19461\" systemVersion=\"20F71\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"frequencyConstraintIDs\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSArrayValueTransformer\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"373\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"133\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"148\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation 9.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"19461\" systemVersion=\"20F71\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAScheduleData\" representedClassName=\"UAScheduleData\" elementID=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"audience\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"campaigns\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\" elementID=\"actions\"/>\n        <attribute name=\"dataVersion\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"delayedExecutionDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"editGracePeriod\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"executionState\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" elementID=\"isPendingExecution\"/>\n        <attribute name=\"executionStateChangeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"frequencyConstraintIDs\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSArrayValueTransformer\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"interval\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"priority\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"reportingContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"triggerContext\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAScheduleTriggerContextTransformer\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <uniquenessConstraints>\n            <uniquenessConstraint>\n                <constraint value=\"identifier\"/>\n            </uniquenessConstraint>\n        </uniquenessConstraints>\n    </entity>\n    <entity name=\"UAScheduleDelayData\" representedClassName=\"UAScheduleDelayData\" syncable=\"YES\">\n        <attribute name=\"appState\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"regionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"screens\" optional=\"YES\" attributeType=\"String\" elementID=\"screen\"/>\n        <attribute name=\"seconds\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"cancellationTriggers\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"delay\" inverseEntity=\"UAScheduleTriggerData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"delay\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"delay\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleDelayData\" inverseName=\"cancellationTriggers\" inverseEntity=\"UAScheduleDelayData\"/>\n        <relationship name=\"schedule\" optional=\"YES\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAScheduleData\"/>\n    </entity>\n    <elements>\n        <element name=\"UAScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"374\"/>\n        <element name=\"UAScheduleDelayData\" positionX=\"-234\" positionY=\"-27\" width=\"128\" height=\"133\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-191\" positionY=\"378\" width=\"128\" height=\"148\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAAutomation.xcdatamodeld/UAAutomation.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"16119\" systemVersion=\"19F101\" minimumToolsVersion=\"Xcode 7.0\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAActionScheduleData\" representedClassName=\"UAActionScheduleData\" syncable=\"YES\">\n        <attribute name=\"actions\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"end\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"group\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n        <attribute name=\"limit\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"triggeredCount\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"triggers\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAScheduleTriggerData\" inverseName=\"schedule\" inverseEntity=\"UAScheduleTriggerData\" syncable=\"YES\"/>\n    </entity>\n    <entity name=\"UAScheduleTriggerData\" representedClassName=\"UAScheduleTriggerData\" syncable=\"YES\">\n        <attribute name=\"goal\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"goalProgress\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"predicateData\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UAJSONPredicateTransformer\" syncable=\"YES\"/>\n        <attribute name=\"start\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <relationship name=\"schedule\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAActionScheduleData\" inverseName=\"triggers\" inverseEntity=\"UAActionScheduleData\" syncable=\"YES\"/>\n    </entity>\n    <elements>\n        <element name=\"UAActionScheduleData\" positionX=\"-540\" positionY=\"-63\" width=\"128\" height=\"165\"/>\n        <element name=\"UAScheduleTriggerData\" positionX=\"-36\" positionY=\"9\" width=\"128\" height=\"135\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Resources/UAFrequencyLimits.xcdatamodeld/UAFrequencyLimits.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"17192\" systemVersion=\"19F101\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAFrequencyConstraintData\" representedClassName=\"UAFrequencyConstraintData\" syncable=\"YES\">\n        <attribute name=\"count\" attributeType=\"Integer 64\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"identifier\" attributeType=\"String\"/>\n        <attribute name=\"range\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"YES\"/>\n        <relationship name=\"occurrence\" optional=\"YES\" toMany=\"YES\" deletionRule=\"Cascade\" destinationEntity=\"UAOccurrenceData\" inverseName=\"constraint\" inverseEntity=\"UAOccurrenceData\"/>\n    </entity>\n    <entity name=\"UAOccurrenceData\" representedClassName=\"UAOccurrenceData\" syncable=\"YES\">\n        <attribute name=\"timestamp\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <relationship name=\"constraint\" maxCount=\"1\" deletionRule=\"Nullify\" destinationEntity=\"UAFrequencyConstraintData\" inverseName=\"occurrence\" inverseEntity=\"UAFrequencyConstraintData\"/>\n    </entity>\n    <elements>\n        <element name=\"UAFrequencyConstraintData\" positionX=\"-63\" positionY=\"-18\" width=\"128\" height=\"103\"/>\n        <element name=\"UAOccurrenceData\" positionX=\"348.53125\" positionY=\"-163.16796875\" width=\"128\" height=\"73\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipAutomation/Source/ActionAutomation/ActionAutomationExecutor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct ActionAutomationExecutor: AutomationExecutorDelegate {\n    typealias PrepareDataIn = AirshipJSON\n    typealias PrepareDataOut = AirshipJSON\n    typealias ExecutionData = AirshipJSON\n\n    private let actionRunner: any AutomationActionRunnerProtocol\n\n    init(actionRunner: any AutomationActionRunnerProtocol = AutomationActionRunner()) {\n        self.actionRunner = actionRunner\n    }\n\n    func isReady(data: AirshipJSON, preparedScheduleInfo: PreparedScheduleInfo) -> ScheduleReadyResult {\n        return .ready\n    }\n\n    func execute(data: AirshipJSON, preparedScheduleInfo: PreparedScheduleInfo) async -> ScheduleExecuteResult {\n        guard preparedScheduleInfo.additionalAudienceCheckResult else {\n            return .finished\n        }\n\n        await actionRunner.runActions(data, situation: .automation, metadata: [:])\n        return .finished\n    }\n\n    func interrupted(schedule: AutomationSchedule, preparedScheduleInfo: PreparedScheduleInfo) async -> InterruptedBehavior {\n        return .retry\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/ActionAutomation/ActionAutomationPreparer.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct ActionAutomationPreparer: AutomationPreparerDelegate {\n    typealias PrepareDataIn = AirshipJSON\n    typealias PrepareDataOut = AirshipJSON\n\n    func prepare(data: AirshipJSON, preparedScheduleInfo: PreparedScheduleInfo) async throws -> AirshipJSON {\n        return data\n    }\n\n    func cancelled(scheduleID: String) async {\n        // no-op\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Actions/CancelSchedulesAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/**\n * Action to cancel automation schedules.\n *\n * This action is registered under the names cancel_scheduled_actions and ^csa.\n *\n * Expected argument values: String with the value \"all\" or a Dictionary with:\n *  - \"groups\": A schedule group or an array of schedule groups.\n *  - \"ids\": A schedule ID or an array of schedule IDs.\n *\n * Valid situations: ActionSituation.backgroundPush, .foregroundPush, .webViewInvocation, .manualInvocation, and .automation\n *\n * Result value: nil.\n */\n\npublic final class CancelSchedulesAction: AirshipAction {\n    \n    //used for tests\n    private let overrideAutomation: (any InternalInAppAutomation)?\n\n    /// Cancel schedules action names.\n    public static let defaultNames: [String] = [\"cancel_scheduled_actions\", \"^csa\"]\n    \n    init(overrideAutomation: (any InternalInAppAutomation)? = nil) {\n        self.overrideAutomation = overrideAutomation\n    }\n    \n    var automation: any InternalInAppAutomation {\n        return overrideAutomation ?? Airship.requireComponent(ofType: InAppAutomationComponent.self).inAppAutomation\n    }\n    \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .manualInvocation, \n            .backgroundPush,\n            .foregroundPush,\n            .webViewInvocation,\n            .automation:\n                return true\n        default: return false\n        }\n    }\n    \n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let args: Arguments = try arguments.value.decode()\n        \n        let automation = self.automation\n        \n        if args.cancellAll {\n            try await automation.cancelSchedulesWith(type: .actions)\n            return nil\n        }\n        \n        if let groups = args.groups {\n            for item in groups {\n                try await automation.cancelSchedules(group: item)\n            }\n        } \n        \n        if let ids = args.scheduleIDs {\n            try await automation.cancelSchedule(identifiers: ids)\n        }\n        \n        return nil\n    }\n    \n    fileprivate struct Arguments: Decodable, Sendable {\n        static let all = \"all\"\n        \n        let cancellAll: Bool\n        let scheduleIDs: [String]?\n        let groups: [String]?\n        \n        enum CodingKeys: String, CodingKey {\n            case ids = \"ids\"\n            case groups = \"groups\"\n        }\n        \n        init(from decoder: any Decoder) throws {\n            func decodeSingleOrArray<T, K>(from container: KeyedDecodingContainer<K>, key: K) throws -> [T]? where T: Decodable {\n                guard container.contains(key) else { return nil }\n                do {\n                    return try container.decode([T].self, forKey: key)\n                } catch {\n                    let value = try container.decode(T.self, forKey: key)\n                    return [value]\n                }\n            }\n            \n            var scheduleIds: [String]?\n            var groups: [String]?\n            \n            do {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                scheduleIds = try decodeSingleOrArray(from: container, key: .ids)\n                groups = try decodeSingleOrArray(from: container, key: .groups)\n                self.cancellAll = false\n            } catch {\n                let container = try decoder.singleValueContainer()\n                let value = try container.decode(String.self)\n                guard value == Self.all else {\n                    throw DecodingError.dataCorrupted(.init(codingPath: container.codingPath, debugDescription: \"Invalid cancel action\"))\n                }\n                self.cancellAll = true\n            }\n            \n            if !cancellAll, scheduleIds == nil, groups == nil {\n                throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: \"Invalid cacncel action\"))\n            }\n            \n            self.scheduleIDs = scheduleIds\n            self.groups = groups\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Actions/LandingPageAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Landing page action\npublic final class LandingPageAction: AirshipAction {\n\n    private static let productID: String = \"landing_page\"\n    private static let queue: String = \"landing_page\"\n\n    /// Landing page action names.\n    public static let defaultNames: [String] = [\"landing_page_action\", \"^p\"]\n\n    /// Default predicate - rejects `ActionSituation.foregroundPush`\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.situation != .foregroundPush\n    }\n\n    /// Schedule extender block.\n    public typealias ScheduleExtender = @Sendable (ActionArguments, inout AutomationSchedule) -> Void\n\n    private let borderRadius: Double\n    private let scheduleExtender: ScheduleExtender?\n    private let allowListChecker: @Sendable @MainActor (URL) -> Bool\n    private let scheduler: @Sendable (AutomationSchedule) async throws -> Void\n\n    /// Default constructor\n    /// - Parameters:\n    ///     - borderRadius: Optional border radius in points. Defaults to 2.\n    ///     - scheduleExtender: Optional extender. Can be used to modify the landing page action schedule.\n    public convenience init(\n        borderRadius: Double = 2.0,\n        scheduleExtender: ScheduleExtender? = nil\n    ) {\n        self.init(\n            borderRadius: borderRadius,\n            scheduleExtender: scheduleExtender,\n            allowListChecker: { Airship.urlAllowList.isAllowed($0, scope: .openURL) },\n            scheduler: { try await Airship.inAppAutomation.upsertSchedules([$0]) }\n        )\n    }\n\n    init(\n        borderRadius: Double,\n        scheduleExtender: ScheduleExtender?,\n        allowListChecker: @escaping @MainActor @Sendable (URL) -> Bool,\n        scheduler: @escaping @Sendable (AutomationSchedule) async throws -> Void\n    ) {\n        self.borderRadius = borderRadius\n        self.scheduleExtender = scheduleExtender\n        self.allowListChecker = allowListChecker\n        self.scheduler = scheduler\n    }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch(arguments.situation) {\n        case .manualInvocation: return true\n        case .launchedFromPush: return true\n        case .foregroundPush: return true\n        case .backgroundPush: return false\n        case .webViewInvocation: return true\n        case .foregroundInteractiveButton: return true\n        case .backgroundInteractiveButton: return false\n        case .automation: return true\n#if canImport(AirshipCore)\n        @unknown default:\n            return false\n#endif\n        }\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let pushMetadata = arguments.metadata[ActionArguments.pushPayloadJSONMetadataKey] as? AirshipJSON\n        let messageID = pushMetadata?.object?[\"_\"]?.string\n        let args: LandingPageArgs = try arguments.value.decode()\n\n        guard self.allowListChecker(args.url) else {\n            throw AirshipErrors.error(\"Landing page URL not allowed \\(args.url)\")\n        }\n\n        let message = InAppMessage(\n            name: \"Landing Page \\(args.url)\",\n            displayContent: .html(\n                .init(\n                    url: args.url.absoluteString,\n                    height: args.height,\n                    width: args.width,\n                    aspectLock: args.aspectLock,\n                    requiresConnectivity: false,\n                    borderRadius: self.borderRadius\n                )\n            ),\n            isReportingEnabled: messageID != nil,\n            displayBehavior: .immediate\n        )\n\n        var schedule = AutomationSchedule(\n            identifier: messageID ?? UUID().uuidString,\n            data: .inAppMessage(message),\n            triggers: [AutomationTrigger.activeSession(count: 1)],\n            priority: Int.min,\n            bypassHoldoutGroups: true,\n            productID: Self.productID,\n            queue: Self.queue\n        )\n\n        self.scheduleExtender?(arguments, &schedule)\n        try await self.scheduler(schedule)\n        return nil\n    }\n\n    fileprivate struct LandingPageArgs: Decodable, Sendable {\n        var url: URL\n        var height: Double?\n        var width: Double?\n        var aspectLock: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case url\n            case height\n            case width\n            case aspectLock = \"aspect_lock\"\n            case aspectLockLegacy = \"aspectLock\"\n        }\n\n        init(from decoder: any Decoder) throws {\n            do {\n                let container: KeyedDecodingContainer<Self.CodingKeys> = try decoder.container(keyedBy: Self.CodingKeys.self)\n                self.url = try Self.parseURL(\n                    string: try container.decode(String.self, forKey: CodingKeys.url)\n                )\n\n                self.height = try container.decodeIfPresent(Double.self, forKey: CodingKeys.height)\n                self.width = try container.decodeIfPresent(Double.self, forKey: CodingKeys.width)\n                self.aspectLock = try container.decodeIfPresent(Bool.self, forKey: CodingKeys.aspectLock)\n            } catch {\n                let container = try decoder.singleValueContainer()\n                self.url = try Self.parseURL(\n                    string: try container.decode(String.self)\n                )\n                self.height = nil\n                self.width = nil\n                self.aspectLock = nil\n            }\n        }\n\n        private static func parseURL(string: String) throws -> URL {\n            guard let url = URL(string: string) else {\n                throw AirshipErrors.error(\"Invalid URL \\(string)\")\n            }\n\n            if !url.absoluteString.isEmpty, url.scheme?.isEmpty ?? true {\n                return URL(string: \"https://\" + string) ?? url\n            }\n\n            return url\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Actions/ScheduleAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/**\n * Action to schedule other actions.\n *\n * This action is registered under the names schedule_actions and ^sa.\n *\n * Expected argument values: Dictionary representing a schedule info JSON.\n *\n * Valid situations: ActionSituation.backgroundPush, .foregroundPush, .webViewInvocation, .manualInvocation, and .automation\n *\n * Result value: Schedule ID or throw if the schedule failed.\n */\n\npublic final class ScheduleAction: AirshipAction {\n    \n    //used for tests\n    private let overrideAutomation: (any InAppAutomation)?\n\n    /// Cancel schedules action names.\n    public static let defaultNames: [String] = [\"schedule_actions\", \"^sa\"]\n    \n    init(overrideAutomation: (any InAppAutomation)? = nil) {\n        self.overrideAutomation = overrideAutomation\n    }\n    \n    var automation: any InAppAutomation {\n        return overrideAutomation ?? Airship.inAppAutomation\n    }\n    \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .manualInvocation,\n            .backgroundPush,\n            .foregroundPush,\n            .webViewInvocation,\n            .automation:\n                return true\n        default: return false\n        }\n    }\n    \n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let schedule: AutomationSchedule = try arguments.value.decode()\n        \n        try await self.automation.upsertSchedules([schedule])\n        \n        return .string(schedule.identifier)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/AirshipAutomationResources.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Resources for AirshipAutomation\npublic final class AirshipAutomationResources {\n    /// Module bundle\n    public static let bundle: Bundle = resolveBundle()\n\n    private static func resolveBundle() -> Bundle {\n#if SWIFT_PACKAGE\n        AirshipLogger.trace(\"Using Bundle.module for AirshipAutomation\")\n        let bundle = Bundle.module\n#if DEBUG\n        if bundle.resourceURL == nil {\n            assertionFailure(\"\"\"\n            AirshipAutomation module was built with SWIFT_PACKAGE\n            but no resources were found. Check your build configuration.\n            \"\"\")\n        }\n#endif\n        return bundle\n#endif\n\n        return Bundle.airshipFindModule(\n            moduleName: \"AirshipAutomation\",\n            sourceBundle: Bundle(for: Self.self)\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/AudienceCheck/AdditionalAudienceCheckerApiClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AdditionalAudienceCheckerAPIClientProtocol: Sendable {\n    func resolve(\n        info: AdditionalAudienceCheckResult.Request\n    ) async throws -> AirshipHTTPResponse<AdditionalAudienceCheckResult>\n}\n\nstruct AdditionalAudienceCheckResult: Codable, Sendable, Equatable {\n    let isMatched: Bool\n    let cacheTTL: TimeInterval\n    \n    enum CodingKeys: String, CodingKey {\n        case isMatched = \"allowed\"\n        case cacheTTL = \"cache_seconds\"\n    }\n    \n    struct Request: Sendable {\n        let url: URL\n        let channelID: String\n        let contactID: String\n        let namedUserID: String?\n        let context: AirshipJSON?\n    }\n}\n\nfinal class AdditionalAudienceCheckerAPIClient: AdditionalAudienceCheckerAPIClientProtocol {\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n    private let encoder: JSONEncoder\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession, encoder: JSONEncoder = JSONEncoder()) {\n        self.config = config\n        self.session = session\n        self.encoder = encoder\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n    \n    func resolve(\n        info: AdditionalAudienceCheckResult.Request\n    ) async throws -> AirshipHTTPResponse<AdditionalAudienceCheckResult> {\n        \n        let body = RequestBody(\n            channelID: info.channelID,\n            contactID: info.contactID,\n            namedUserID: info.namedUserID,\n            context: info.context\n        )\n\n        let request = AirshipRequest(\n            url: info.url,\n            headers: [\n                \"Content-Type\": \"application/json\",\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"X-UA-Contact-ID\": info.contactID,\n                \"X-UA-Device-Family\": \"ios\",\n            ],\n            method: \"POST\",\n            auth: .contactAuthToken(identifier: info.contactID),\n            body: try encoder.encode(body)\n        )\n        \n        AirshipLogger.trace(\"Performing additional audience check with request \\(request), body: \\(body)\")\n        \n        return try await session.performHTTPRequest(request) { data, response in\n            AirshipLogger.debug(\"Additional audience check response finished with response: \\(response)\")\n            \n            guard (200..<300).contains(response.statusCode) else {\n                return nil\n            }\n            \n            guard let data = data else {\n                throw AirshipErrors.error(\"Invalid response body \\(String(describing: data))\")\n            }\n            \n            return try JSONDecoder().decode(AdditionalAudienceCheckResult.self, from: data)\n        }\n    }\n    \n    fileprivate struct RequestBody: Encodable {\n        let channelID: String\n        let contactID: String\n        let namedUserID: String?\n        let context: AirshipJSON?\n\n        enum CodingKeys: String, CodingKey {\n            case channelID = \"channel_id\"\n            case contactID = \"contact_id\"\n            case namedUserID = \"named_user_id\"\n            case context\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/AudienceCheck/AdditionalAudienceCheckerResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AdditionalAudienceCheckerResolverProtocol: Actor {\n    func resolve(\n        deviceInfoProvider: any AudienceDeviceInfoProvider,\n        additionalAudienceCheckOverrides: AdditionalAudienceCheckOverrides?\n    ) async throws -> Bool\n}\n\nactor AdditionalAudienceCheckerResolver: AdditionalAudienceCheckerResolverProtocol {\n    private let cache: any AirshipCache\n    private let apiClient: any AdditionalAudienceCheckerAPIClientProtocol\n\n    private let date: any AirshipDateProtocol\n    private var inProgress: Task<Bool, any Error>?\n    private let configProvider: () -> RemoteConfig.AdditionalAudienceCheckConfig?\n\n    private var additionalAudienceConfig: RemoteConfig.AdditionalAudienceCheckConfig? {\n        get {\n            configProvider()\n        }\n    }\n\n    init(\n        config: RuntimeConfig,\n        cache: any AirshipCache,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.cache = cache\n        self.apiClient = AdditionalAudienceCheckerAPIClient(config: config)\n        self.date = date\n        self.configProvider = {\n            config.remoteConfig.iaaConfig?.additionalAudienceConfig\n        }\n    }\n    \n    /// Testing\n    init(\n        cache: any AirshipCache,\n        apiClient: any AdditionalAudienceCheckerAPIClientProtocol,\n        date: any AirshipDateProtocol,\n        configProvider: @escaping () -> RemoteConfig.AdditionalAudienceCheckConfig?\n    ) {\n        self.cache = cache\n        self.apiClient = apiClient\n        self.date = date\n        self.configProvider = configProvider\n    }\n    \n    func resolve(\n        deviceInfoProvider: any AudienceDeviceInfoProvider,\n        additionalAudienceCheckOverrides: AdditionalAudienceCheckOverrides?\n    ) async throws -> Bool {\n        \n        guard\n            let config = additionalAudienceConfig,\n            config.isEnabled\n        else {\n            return true\n        }\n\n        guard\n            let urlString = additionalAudienceCheckOverrides?.url ?? config.url,\n            let url = URL(string: urlString)\n        else {\n            AirshipLogger.warn(\"Failed to parse additional audience check url \" +\n                               String(describing: additionalAudienceCheckOverrides) + \", \" +\n                               String(describing: config) + \")\")\n            throw AirshipErrors.error(\"Missing additional audience check url\")\n        }\n        \n        guard additionalAudienceCheckOverrides?.bypass != true else {\n            AirshipLogger.trace(\"Additional audience check is bypassed\")\n            return true\n        }\n        let context = additionalAudienceCheckOverrides?.context ?? config.context\n\n        _ = try? await inProgress?.value\n        let task = Task {\n            return try await doResolve(\n                url: url,\n                context: context,\n                deviceInfoProvider: deviceInfoProvider\n            )\n        }\n\n        inProgress = task\n        return try await task.value\n    }\n\n    private func doResolve(\n        url: URL,\n        context: AirshipJSON?,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> Bool {\n\n        let channelID = try await deviceInfoProvider.channelID\n        let contactInfo = await deviceInfoProvider.stableContactInfo\n\n        let cacheKey = try cacheKey(\n            url: url.absoluteString,\n            context: context ?? .null,\n            contactID: contactInfo.contactID,\n            channelID: channelID\n        )\n\n        if let cached: AdditionalAudienceCheckResult = await cache.getCachedValue(key: cacheKey) {\n            return cached.isMatched\n        }\n\n        let request = AdditionalAudienceCheckResult.Request(\n            url: url,\n            channelID: channelID,\n            contactID: contactInfo.contactID,\n            namedUserID: contactInfo.namedUserID,\n            context: context\n        )\n\n        let response = try await apiClient.resolve(info: request)\n\n        if response.isSuccess, let result = response.result {\n            await cache.setCachedValue(result, key: cacheKey, ttl: result.cacheTTL)\n            return result.isMatched\n        } else if response.isServerError {\n            throw AirshipErrors.error(\"Failed to perform additional check due to server error \\(response)\")\n        } else {\n            return false\n        }\n    }\n\n    private func cacheKey(url: String, context: AirshipJSON, contactID: String, channelID: String) throws -> String {\n        return String([url, try context.toString(), contactID, channelID].joined(separator: \":\"))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/ApplicationMetrics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol ApplicationMetricsProtocol: Sendable {\n    var isAppVersionUpdated: Bool { get }\n    var currentAppVersion: String? { get }\n\n}\n\n/// The ApplicationMetrics class keeps track of application-related metrics.\nfinal class ApplicationMetrics: ApplicationMetricsProtocol {\n    private static let lastOpenDataKey = \"UAApplicationMetricLastOpenDate\"\n    private static let lastAppVersionKey = \"UAApplicationMetricsLastAppVersion\"\n\n    private let dataStore: PreferenceDataStore\n    private let privacyManager: any AirshipPrivacyManager\n\n    /**\n     * Determines whether the application's short version string has been updated.\n     * Only tracked if Feature.inAppAutomation or Feature.analytics are enabled in the privacy manager.\n     */\n    public var isAppVersionUpdated: Bool {\n        guard\n            self.privacyManager.isApplicationMetricsEnabled,\n            let currentVersion = self.currentAppVersion,\n            let lastVersion = self.lastAppVersion,\n            AirshipUtils.compareVersion(lastVersion, toVersion: currentVersion) == .orderedAscending\n        else {\n            return false\n        }\n\n        return true\n    }\n\n    /**\n     * The application's current short version string also known as the marketing version.\n     */\n    public let currentAppVersion: String?\n\n    /**\n     * The application's last short version string also known as the marketing version.\n     */\n    public let lastAppVersion: String?\n\n    public init(\n        dataStore: PreferenceDataStore,\n        privacyManager: any AirshipPrivacyManager,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        appVersion: String? = AirshipUtils.bundleShortVersionString()\n    ) {\n        self.dataStore = dataStore\n        self.privacyManager = privacyManager\n        self.currentAppVersion = appVersion\n\n\n        self.lastAppVersion = if privacyManager.isApplicationMetricsEnabled {\n            self.dataStore.string(\n                forKey: ApplicationMetrics.lastAppVersionKey\n            )\n        } else {\n            nil\n        }\n\n        // Delete old\n        self.dataStore.removeObject(\n            forKey: ApplicationMetrics.lastOpenDataKey\n        )\n\n        updateData()\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(updateData),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n    }\n\n    @objc\n    func updateData() {\n        if self.privacyManager.isApplicationMetricsEnabled {\n            guard let currentVersion = self.currentAppVersion else {\n                return\n            }\n\n            self.dataStore.setObject(\n                currentVersion,\n                forKey: ApplicationMetrics.lastAppVersionKey\n            )\n        } else {\n            self.dataStore.removeObject(\n                forKey: ApplicationMetrics.lastAppVersionKey\n            )\n        }\n    }\n}\n\n\nfileprivate extension AirshipPrivacyManager {\n    var isApplicationMetricsEnabled: Bool {\n        self.isEnabled(.inAppAutomation) || self.isEnabled(.analytics)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/AutomationAudience.swift",
    "content": "import Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Automation device audience\npublic struct AutomationAudience: Codable, Sendable, Equatable {\n\n    /// Miss behavior when the audience is not a match\n    public enum MissBehavior: String, Codable, Sendable {\n        /// Cancel the schedule\n        case cancel\n        /// Skip the execution\n        case skip\n        /// Skip the execution but count towards the limit\n        case penalize\n    }\n\n    let audienceSelector: DeviceAudienceSelector\n    let missBehavior: MissBehavior?\n\n    enum CodingKeys: String, CodingKey {\n        case missBehavior = \"miss_behavior\"\n    }\n\n    /// Automation audience initialized\n    /// - Parameters:\n    ///   - audienceSelector: The audience selector\n    ///   - missBehavior: Behavior when audience selector is not a match\n    public init(\n        audienceSelector: DeviceAudienceSelector,\n        missBehavior: MissBehavior? = nil\n    ) {\n        self.audienceSelector = audienceSelector\n        self.missBehavior = missBehavior\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        try audienceSelector.encode(to: encoder)\n\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encodeIfPresent(self.missBehavior, forKey: .missBehavior)\n    }\n\n    public init(from decoder: any Decoder) throws {\n        self.audienceSelector = try DeviceAudienceSelector(from: decoder)\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.missBehavior = try container.decodeIfPresent(AutomationAudience.MissBehavior.self, forKey: .missBehavior)\n    }\n}\n\nstruct AdditionalAudienceCheckOverrides: Codable, Sendable, Equatable {\n    let bypass: Bool?\n    let context: AirshipJSON?\n    let url: String?\n    \n    enum CodingKeys: String, CodingKey {\n        case bypass, context, url\n    }\n}\n\nextension AutomationAudience.MissBehavior {\n    var schedulePrepareResult: SchedulePrepareResult {\n        switch self {\n        case .cancel: return .cancel\n        case .penalize: return .penalize\n        case .skip: return .skip\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/AutomationCompoundAudience.swift",
    "content": "import Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Automation compound audience\npublic struct AutomationCompoundAudience: Codable, Sendable, Equatable {\n    let selector: CompoundDeviceAudienceSelector\n    let missBehavior: AutomationAudience.MissBehavior\n    \n    enum CodingKeys: String, CodingKey {\n        case selector = \"selector\"\n        case missBehavior = \"miss_behavior\"\n    }\n    \n    /// Automation compound audience initialized\n    /// - Parameters:\n    ///   - audienceSelector: The audience selector\n    ///   - missBehavior: Behavior when audience selector is not a match\n    public init(\n        selector: CompoundDeviceAudienceSelector,\n        missBehavior: AutomationAudience.MissBehavior\n    ) {\n        self.selector = selector\n        self.missBehavior = missBehavior\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/AutomationDelay.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Automation app state\npublic enum AutomationAppState: String, Sendable, Codable {\n    /// App is in the foreground (active/inactive)\n    case foreground\n\n    /// App is in the background\n    case background\n}\n\n/// Automation delay\npublic struct AutomationDelay: Sendable, Codable, Equatable {\n    /// Number of seconds to delay the execution of the IAA\n    var seconds: TimeInterval?\n\n    /// Screen restrictions\n    var screens: [String]?\n\n    /// If a region ID restriction\n    public var regionID: String?\n\n    /// App state restriction\n    public var appState: AutomationAppState?\n\n    /// Cancellation triggers. These triggers only cancel the execution of the schedule not the entire schedule\n    public var cancellationTriggers: [AutomationTrigger]?\n    \n    public var executionWindow: ExecutionWindow?\n\n    enum CodingKeys: String, CodingKey {\n        case seconds\n        case screens = \"screen\"\n        case regionID = \"region\"\n        case appState = \"app_state\"\n        case cancellationTriggers = \"cancellation_triggers\"\n        case executionWindow = \"execution_window\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/AutomationSchedule.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n\n/// Automation schedule\npublic struct AutomationSchedule: Sendable, Codable, Equatable {\n\n    /// Schedule data\n    public enum ScheduleData: Sendable, Equatable {\n        /// Actions\n        case actions(AirshipJSON)\n\n        /// In-App message\n        case inAppMessage(InAppMessage)\n\n        /// Deferred\n        /// NOTE: For internal use only. :nodoc:\n        case deferred(DeferredAutomationData)\n    }\n\n    ///  The schedule ID.\n    public let identifier: String\n\n    /// List of triggers\n    public var triggers: [AutomationTrigger]\n\n    /// Optional schedule group. Can be used to cancel a set of schedules.\n    public var group: String?\n\n    /// Priority for determining order during simultaneous schedule processing\n    public var priority: Int?\n\n    /// Number of times the schedule can execute.\n    public var limit: UInt?\n\n    /// Start date\n    public var start: Date?\n\n    /// End date\n    public var end: Date?\n\n    /// On device automation selector\n    public var audience: AutomationAudience?\n\n    /// Compound audience. If both `audience` and `compoundAudience`, they will both\n    /// be evaluated to determine if the schedule should be executed.\n    public var compoundAudience: AutomationCompoundAudience?\n\n    /// Delay after trigger and prepare steps before execution\n    public var delay: AutomationDelay?\n\n    ///  Execution interval.\n    public var interval: TimeInterval?\n\n    /// Schedule data\n    public var data: ScheduleData\n\n    /// If the schedule should bypass holdout groups or not\n    public var bypassHoldoutGroups: Bool?\n\n\n    /// After the schedule ends or is finished, how long to hold on to the schedule before\n    /// deleting it. This is used to keep schedule state around for a period of time\n    /// after the schedule finishes to allow for extending the schedule.\n    public var editGracePeriodDays: UInt?\n\n    /// internal\n    let additionalAudienceCheckOverrides: AdditionalAudienceCheckOverrides?\n    var metadata: AirshipJSON?\n    var frequencyConstraintIDs: [String]?\n    var messageType: String?\n    var campaigns: AirshipJSON?\n    var reportingContext: AirshipJSON?\n    var productID: String?\n    var minSDKVersion: String?\n    var created: Date?\n    var queue: String?\n\n\n    enum CodingKeys: String, CodingKey {\n        case identifier = \"id\"\n        case triggers\n        case created\n        case group\n        case metadata\n        case priority\n        case limit\n        case start\n        case end\n        case audience\n        case compoundAudience = \"compound_audience\"\n        case delay\n        case interval\n        case campaigns\n        case reportingContext = \"reporting_context\"\n        case productID = \"product_id\"\n        case bypassHoldoutGroups = \"bypass_holdout_groups\"\n        case editGracePeriodDays = \"edit_grace_period\"\n        case frequencyConstraintIDs = \"frequency_constraint_ids\"\n        case messageType = \"message_type\"\n        case scheduleType = \"type\"\n        case actions\n        case deferred\n        case message\n        case minSDKVersion = \"min_sdk_version\"\n        case queue\n        case additionalAudienceCheckOverrides = \"additional_audience_check_overrides\"\n    }\n\n    enum ScheduleType: String, Codable {\n        case actions\n        case inAppMessage = \"in_app_message\"\n        case deferred\n    }\n\n\n    /// <#Description#>\n    /// - Parameters:\n    ///   - identifier: The schedule ID\n    ///   - triggers: List of triggers for the schedule\n    ///   - data: Schedule data\n    ///   - group: Schedule group that can be used to cancel a set of schedules\n    ///   - priority: Priority for determining order during simultaneous schedule processing\n    ///   - limit: Number of times the schedule can execute\n    ///   - start: Start date\n    ///   - end: End date\n    ///   - audience: On device automation selector\n    ///   - compoundAudience: Compound automation selector\n    ///   - delay: Duration after trigger and prepare steps after which execution occurs\n    ///   - interval: Execution interval\n    ///   - bypassHoldoutGroups: If the schedule should bypass holdout groups or not\n    ///   - editGracePeriodDays: Duration after which post-execution deletion occurs\n    public init(\n        identifier: String,\n        triggers: [AutomationTrigger],\n        data: ScheduleData,\n        group: String? = nil,\n        priority: Int? = nil,\n        limit: UInt? = nil,\n        start: Date? = nil,\n        end: Date? = nil,\n        audience: AutomationAudience? = nil,\n        compoundAudience: AutomationCompoundAudience? = nil,\n        delay: AutomationDelay? = nil,\n        interval: TimeInterval? = nil,\n        bypassHoldoutGroups: Bool? = nil,\n        editGracePeriodDays: UInt? = nil\n    ) {\n        self.identifier = identifier\n        self.triggers = triggers\n        self.created = Date()\n        self.group = group\n        self.priority = priority\n        self.limit = limit\n        self.start = start\n        self.end = end\n        self.audience = audience\n        self.compoundAudience = compoundAudience\n        self.delay = delay\n        self.interval = interval\n        self.data = data\n        self.bypassHoldoutGroups = bypassHoldoutGroups\n        self.editGracePeriodDays = editGracePeriodDays\n\n        self.metadata = nil\n        self.frequencyConstraintIDs = nil\n        self.messageType = nil\n        self.campaigns = nil\n        self.reportingContext = nil\n        self.productID = nil\n        self.queue = nil\n        self.additionalAudienceCheckOverrides = nil\n    }\n\n    init(\n        identifier: String,\n        data: ScheduleData,\n        triggers: [AutomationTrigger],\n        created: Date? = Date(),\n        lastUpdated: Date? = nil,\n        group: String? = nil,\n        priority: Int? = nil,\n        limit: UInt? = nil,\n        start: Date? = nil,\n        end: Date? = nil,\n        audience: AutomationAudience? = nil,\n        compoundAudience: AutomationCompoundAudience? = nil,\n        delay: AutomationDelay? = nil,\n        interval: TimeInterval? = nil,\n        bypassHoldoutGroups: Bool? = nil,\n        editGracePeriodDays: UInt? = nil,\n        metadata: AirshipJSON? = nil,\n        campaigns: AirshipJSON? = nil,\n        reportingContext: AirshipJSON? = nil,\n        productID: String? = nil,\n        frequencyConstraintIDs: [String]? = nil,\n        messageType: String? = nil,\n        minSDKVersion: String? = nil,\n        queue: String? = nil,\n        additionalAudienceCheckOverrides: AdditionalAudienceCheckOverrides? = nil\n    ) {\n        self.identifier = identifier\n        self.triggers = triggers\n        self.group = group\n        self.priority = priority\n        self.limit = limit\n        self.start = start\n        self.end = end\n        self.audience = audience\n        self.compoundAudience = compoundAudience\n        self.delay = delay\n        self.interval = interval\n        self.data = data\n        self.bypassHoldoutGroups = bypassHoldoutGroups\n        self.editGracePeriodDays = editGracePeriodDays\n        self.metadata = metadata\n        self.campaigns = campaigns\n        self.reportingContext = reportingContext\n        self.productID = productID\n        self.frequencyConstraintIDs = frequencyConstraintIDs\n        self.messageType = messageType\n        self.created = created\n        self.minSDKVersion = minSDKVersion\n        self.queue = queue\n        self.additionalAudienceCheckOverrides = additionalAudienceCheckOverrides\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        self.identifier = try container.decode(String.self, forKey: .identifier)\n        self.triggers = try container.decode([AutomationTrigger].self, forKey: .triggers)\n        self.group = try container.decodeIfPresent(String.self, forKey: .group)\n        self.metadata = try container.decodeIfPresent(AirshipJSON.self, forKey: .metadata)\n        self.priority = try container.decodeIfPresent(Int.self, forKey: .priority)\n        self.limit = try container.decodeIfPresent(UInt.self, forKey: .limit)\n        self.start = try container.decodeIfPresent(String.self, forKey: .start)?.toDate()\n        self.end = try container.decodeIfPresent(String.self, forKey: .end)?.toDate()\n        self.audience = try container.decodeIfPresent(AutomationAudience.self, forKey: .audience)\n        self.compoundAudience = try container.decodeIfPresent(AutomationCompoundAudience.self, forKey: .compoundAudience)\n        self.delay = try container.decodeIfPresent(AutomationDelay.self, forKey: .delay)\n        self.interval = try container.decodeIfPresent(TimeInterval.self, forKey: .interval)\n        self.campaigns = try container.decodeIfPresent(AirshipJSON.self, forKey: .campaigns)\n        self.reportingContext = try container.decodeIfPresent(AirshipJSON.self, forKey: .reportingContext)\n        self.productID = try container.decodeIfPresent(String.self, forKey: .productID)\n        self.bypassHoldoutGroups = try container.decodeIfPresent(Bool.self, forKey: .bypassHoldoutGroups)\n        self.editGracePeriodDays = try container.decodeIfPresent(UInt.self, forKey: .editGracePeriodDays)\n        self.frequencyConstraintIDs = try container.decodeIfPresent([String].self, forKey: .frequencyConstraintIDs)\n        self.messageType = try container.decodeIfPresent(String.self, forKey: .messageType)\n        self.minSDKVersion = try container.decodeIfPresent(String.self, forKey: .minSDKVersion)\n        self.queue = try container.decodeIfPresent(String.self, forKey: .queue)\n        self.additionalAudienceCheckOverrides = try container.decodeIfPresent(AdditionalAudienceCheckOverrides.self, forKey: .additionalAudienceCheckOverrides)\n\n        let scheduleType = try container.decode(ScheduleType.self, forKey: .scheduleType)\n        switch(scheduleType) {\n        case .actions:\n            let actions = try container.decode(AirshipJSON.self, forKey: .actions)\n            self.data = .actions(actions)\n        case .inAppMessage:\n            let inAppMessage = try container.decode(InAppMessage.self, forKey: .message)\n            self.data = .inAppMessage(inAppMessage)\n        case .deferred:\n            let deferred = try container.decode(DeferredAutomationData.self, forKey: .deferred)\n            self.data = .deferred(deferred)\n        }\n\n        let created = try container.decodeIfPresent(String.self, forKey: .created)\n\n        if let created = created {\n            guard let date = created.toDate() else {\n                throw DecodingError.typeMismatch(\n                    AutomationSchedule.self,\n                    DecodingError.Context(\n                        codingPath: container.codingPath,\n                        debugDescription: \"Invalid created date string.\",\n                        underlyingError: nil\n                    )\n                )\n            }\n            self.created = date\n        }\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.identifier, forKey: .identifier)\n        try container.encode(self.triggers, forKey: .triggers)\n        try container.encodeIfPresent(self.created?.toISOString(), forKey: .created)\n        try container.encodeIfPresent(self.group, forKey: .group)\n        try container.encodeIfPresent(self.metadata, forKey: .metadata)\n        try container.encodeIfPresent(self.priority, forKey: .priority)\n        try container.encodeIfPresent(self.limit, forKey: .limit)\n        try container.encodeIfPresent(self.start?.toISOString(), forKey: .start)\n        try container.encodeIfPresent(self.end?.toISOString(), forKey: .end)\n        try container.encodeIfPresent(self.audience, forKey: .audience)\n        try container.encodeIfPresent(self.compoundAudience, forKey: .compoundAudience)\n        try container.encodeIfPresent(self.delay, forKey: .delay)\n        try container.encodeIfPresent(self.interval, forKey: .interval)\n        try container.encodeIfPresent(self.campaigns, forKey: .campaigns)\n        try container.encodeIfPresent(self.reportingContext, forKey: .reportingContext)\n        try container.encodeIfPresent(self.productID, forKey: .productID)\n        try container.encodeIfPresent(self.bypassHoldoutGroups, forKey: .bypassHoldoutGroups)\n        try container.encodeIfPresent(self.editGracePeriodDays, forKey: .editGracePeriodDays)\n        try container.encodeIfPresent(self.frequencyConstraintIDs, forKey: .frequencyConstraintIDs)\n        try container.encodeIfPresent(self.messageType, forKey: .messageType)\n        try container.encodeIfPresent(self.minSDKVersion, forKey: .minSDKVersion)\n        try container.encodeIfPresent(self.queue, forKey: .queue)\n        try container.encodeIfPresent(self.additionalAudienceCheckOverrides, forKey: .additionalAudienceCheckOverrides)\n\n        switch(self.data) {\n        case .actions(let actions):\n            try container.encode(ScheduleType.actions, forKey: .scheduleType)\n            try container.encode(actions, forKey: .actions)\n        case .inAppMessage(let message):\n            try container.encode(ScheduleType.inAppMessage, forKey: .scheduleType)\n            try container.encode(message, forKey: .message)\n        case .deferred(let deferred):\n            try container.encode(ScheduleType.deferred, forKey: .scheduleType)\n            try container.encode(deferred, forKey: .deferred)\n        }\n    }\n}\n\nfileprivate extension String {\n    func toDate() -> Date? {\n        return AirshipDateFormatter.date(fromISOString: self)\n    }\n}\n\nfileprivate extension Date {\n    func toISOString() -> String {\n        return AirshipDateFormatter.string(fromDate: self, format: .iso)\n    }\n}\n\nextension AutomationSchedule {\n    var isInAppMessageType: Bool {\n        switch (data) {\n        case .actions(_): return false\n        case .inAppMessage(_): return true\n        case .deferred(let deferred):\n            switch(deferred.type) {\n            case .actions: return false\n            case .inAppMessage: return true\n            }\n        }\n    }\n\n    static func isNewSchedule(\n        created: Date?,\n        minSDKVersion: String?,\n        sinceDate: Date,\n        lastSDKVersion: String?\n    ) -> Bool {\n        guard let created = created else { return false }\n\n        if created > sinceDate {\n            return true\n        }\n\n        guard let minSDKVersion = minSDKVersion else { return false }\n\n        // We can skip checking if the min_sdk_version is newer than the current SDK version since\n        // remote-data will filter them out. This flag is only a hint to the SDK to treat a schedule with\n        // an older created timestamp as a new schedule.\n\n        let constraint = if let lastSDKVersion = lastSDKVersion {\n            \"]\\(lastSDKVersion),)\"\n        } else {\n            // If we do not have a last SDK version, then we are coming from an SDK older than\n            // 16.2.0. Check for a min SDK version newer or equal to 16.2.0.\n            \"[16.2.0,)\"\n        }\n\n        guard let matcher = try? AirshipIvyVersionMatcher(versionConstraint: constraint) else { return false }\n        return matcher.evaluate(version: minSDKVersion)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/AutomationTrigger.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Automation trigger types\npublic enum EventAutomationTriggerType: String, Sendable, Codable, Equatable, CaseIterable {\n    /// Foreground\n    case foreground\n\n    /// Background\n    case background\n\n    /// Screen view\n    case screen\n\n    /// Version update\n    case version\n\n    /// App init\n    case appInit = \"app_init\"\n\n    // Region enter\n    case regionEnter = \"region_enter\"\n\n    /// Region exit\n    case regionExit = \"region_exit\"\n\n    /// Custom event count\n    case customEventCount = \"custom_event_count\"\n\n    /// Custom event value\n    case customEventValue = \"custom_event_value\"\n\n    /// Feature flag interaction\n    case featureFlagInteraction = \"feature_flag_interaction\"\n\n    /// Active session\n    case activeSession = \"active_session\"\n    \n    /// IAX display\n    case inAppDisplay = \"in_app_display\"\n    \n    /// IAX resolution\n    case inAppResolution = \"in_app_resolution\"\n    \n    /// IAX button tap\n    case inAppButtonTap = \"in_app_button_tap\"\n    \n    /// IAX permission result\n    case inAppPermissionResult = \"in_app_permission_result\"\n    \n    /// IAX form display\n    case inAppFormDisplay = \"in_app_form_display\"\n    \n    /// IAX form result\n    case inAppFormResult = \"in_app_form_result\"\n    \n    /// IAX gesture\n    case inAppGesture = \"in_app_gesture\"\n    \n    /// IAX pager completed\n    case inAppPagerCompleted = \"in_app_pager_completed\"\n    \n    /// IAX pager summary\n    case inAppPagerSummary = \"in_app_pager_summary\"\n    \n    /// IAX page swipe\n    case inAppPageSwipe = \"in_app_page_swipe\"\n    \n    /// IAX page view\n    case inAppPageView = \"in_app_page_view\"\n    \n    /// IAX page action\n    case inAppPageAction = \"in_app_page_action\"\n}\n\n/// Logical operator for combining multiple automation triggers.\npublic enum CompoundAutomationTriggerType: String, Sendable, Codable, Equatable {\n    /// Any of the triggers must fire.\n    case or\n    /// All triggers must fire.\n    case and\n    /// Triggers must fire in sequence.\n    case chain\n}\n\n/// Defines what causes an automation to run: a single event trigger or a compound of triggers.\npublic enum AutomationTrigger: Sendable, Codable, Equatable {\n    /// Trigger based on a single event (e.g. foreground, custom event).\n    case event(EventAutomationTrigger)\n    /// Trigger that combines multiple triggers with a logical operator (and/or/chain).\n    case compound(CompoundAutomationTrigger)\n\n\n    public func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .event(let trigger):\n            try trigger.encode(to: encoder)\n        case .compound(let trigger):\n            try trigger.encode(to: encoder)\n        }\n    }\n\n    enum CodingKeys: CodingKey {\n        case type\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(String.self, forKey: .type)\n        if CompoundAutomationTriggerType(rawValue: type) != nil {\n            self = try .compound(CompoundAutomationTrigger(from: decoder))\n        } else {\n            self = try .event(EventAutomationTrigger(from: decoder))\n        }\n    }\n\n    var id: String {\n        switch self {\n        case .compound(let trigger): return trigger.id\n        case .event(let trigger): return trigger.id\n        }\n    }\n\n    var goal: Double {\n        switch self {\n        case .compound(let trigger): return trigger.goal\n        case .event(let trigger): return trigger.goal\n        }\n    }\n\n    var type: String {\n        switch self {\n        case .compound(let trigger): return trigger.type.rawValue\n        case .event(let trigger): return trigger.type.rawValue\n        }\n    }\n}\n\nextension AutomationTrigger {\n    var shouldBackFillIdentifier: Bool {\n        switch self {\n        case .compound(_): return false\n        case .event(let trigger): return trigger.allowBackfillID\n        }\n    }\n\n    func backfilledIdentifier(executionType: TriggerExecutionType) -> AutomationTrigger {\n        /// compound triggers should have IDs\n        guard case .event(var eventTrigger) = self else { return self }\n        eventTrigger.backfillIdentifier(executionType: executionType)\n        return .event(eventTrigger)\n    }\n}\n\n\n/// Model for defining when an automation is triggered.\npublic struct EventAutomationTrigger: Sendable, Codable, Equatable {\n    /// The trigger type\n    public var type: EventAutomationTriggerType\n\n    /// The trigger goal\n    public var goal: Double\n\n    /// Predicate to run on the event's data\n    public var predicate: JSONPredicate?\n\n    var id: String\n\n    /// Tracks if we should allow backfilling the ID. Will be true if the id was generated,, otherwise false.\n    /// This field is only relevant when parsing JSON without an ID. Once it encodes/decodes\n    /// an ID will be set and this will always be false.\n    var allowBackfillID: Bool\n\n\n    /// Event automation trigger initializer\n    /// - Parameters:\n    ///   - type: Trigger type\n    ///   - goal: Trigger goal\n    ///   - predicate: Predicate to run on the event data\n    public init(\n        type: EventAutomationTriggerType,\n        goal: Double,\n        predicate: JSONPredicate? = nil\n    ) {\n        self.type = type\n        self.goal = goal\n        self.predicate = predicate\n        self.id = UUID().uuidString\n\n        // Programatically generated triggers should not allow backfilling the ID\n        // even though we generated an ID. These triggers are not created from\n        // remote-data so we just need to ensure they are unique.\n        self.allowBackfillID = false\n    }\n\n    /// Used for tests\n    init(\n        id: String,\n        type: EventAutomationTriggerType,\n        goal: Double,\n        predicate: JSONPredicate? = nil,\n        children: [AutomationTrigger] = []\n    ) {\n        self.type = type\n        self.goal = goal\n        self.predicate = predicate\n        self.id = id\n\n        // Programatically generated triggers should not allow backfilling the ID\n        // even though we generated an ID. These triggers are not created from\n        // remote-data so we just need to ensure they are unique.\n        self.allowBackfillID = false\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.type = try container.decode(EventAutomationTriggerType.self, forKey: .type)\n        self.goal = try container.decode(Double.self, forKey: .goal)\n        self.predicate = try container.decodeIfPresent(JSONPredicate.self, forKey: .predicate)\n        let id = try container.decodeIfPresent(String.self, forKey: .id)\n        if let id = id {\n            self.id = id\n            self.allowBackfillID = false\n        } else {\n            self.id = UUID().uuidString\n            self.allowBackfillID = true\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case type\n        case goal\n        case predicate\n        case id\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n           var container = encoder.container(keyedBy: CodingKeys.self)\n           try container.encode(self.type, forKey: .type)\n           try container.encode(self.goal, forKey: .goal)\n           try container.encodeIfPresent(self.predicate, forKey: .predicate)\n           try container.encode(self.id, forKey: .id)\n       }\n\n    mutating func backfillIdentifier(executionType: TriggerExecutionType) {\n        guard self.allowBackfillID else {\n            return\n        }\n\n        // Sha256(trigger_type:goal:execution_type:<stable json string of the predicate>?)\n\n        var components: [String] = []\n        components.append(contentsOf: [self.type.rawValue, String(self.goal), executionType.rawValue])\n\n        if let predicate = predicate, let json = try? AirshipJSONUtils.string(predicate, options: .sortedKeys) {\n            components.append(json)\n        }\n\n        self.id = AirshipUtils.sha256Hash(\n            input: components.joined(separator: \":\")\n        )\n        self.allowBackfillID = false\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic struct CompoundAutomationTrigger: Sendable, Codable, Equatable {\n    /// The ID\n    var id: String\n\n    /// The type\n    var type: CompoundAutomationTriggerType\n\n    /// The trigger goal\n    var goal: Double\n\n    var children: [Child]\n\n    public struct Child: Sendable, Codable, Equatable {\n        var trigger: AutomationTrigger\n        var isSticky: Bool?\n        var resetOnIncrement: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case trigger\n            case isSticky = \"is_sticky\"\n            case resetOnIncrement = \"reset_on_increment\"\n        }\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic extension AutomationTrigger {\n    static func activeSession(count: UInt) -> AutomationTrigger {\n        return .event(EventAutomationTrigger(type: .activeSession, goal: Double(count)))\n    }\n    \n    static func foreground(count: UInt) -> AutomationTrigger {\n        return .event(EventAutomationTrigger(type: .foreground, goal: Double(count)))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/DeferredAutomationData.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// NOTE: For internal use only. :nodoc:\npublic struct DeferredAutomationData: Sendable, Codable, Equatable {\n    enum DeferredType: String, Codable, Sendable {\n        case inAppMessage = \"in_app_message\"\n        case actions\n    }\n\n    let url: URL\n    let retryOnTimeOut: Bool?\n    let type: DeferredType\n\n    enum CodingKeys: String, CodingKey {\n        case url\n        case retryOnTimeOut = \"retry_on_timeout\"\n        case type\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationDelayProcessor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AutomationDelayProcessorProtocol: Sendable {\n    // Waits for the delay\n    @MainActor\n    func process(delay: AutomationDelay?, triggerDate: Date) async\n\n    // Waits for any delay - 30s and to be a display window if set\n    func preprocess(delay: AutomationDelay?, triggerDate: Date) async throws\n\n    // Checks if conditions are met\n    @MainActor\n    func areConditionsMet(delay: AutomationDelay?) -> Bool\n}\n\nfinal class AutomationDelayProcessor: AutomationDelayProcessorProtocol {\n    private let analytics: any InternalAirshipAnalytics\n    private let appStateTracker: any AppStateTrackerProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n    private let date: any AirshipDateProtocol\n    private let screen: AirshipMainActorValue<String?> = AirshipMainActorValue(nil)\n    private let executionWindowProcessor: any ExecutionWindowProcessorProtocol\n\n    private static let preprocessSecondsDelayAllowance: TimeInterval = 30.0\n\n    @MainActor\n    init(\n        analytics: any InternalAirshipAnalytics,\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        taskSleeper: any AirshipTaskSleeper = .shared,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        executionWindowProcessor: (any ExecutionWindowProcessorProtocol)? = nil\n    ) {\n        self.analytics = analytics\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n        self.taskSleeper = taskSleeper\n        self.date = date\n        self.executionWindowProcessor = executionWindowProcessor ?? ExecutionWindowProcessor(\n            taskSleeper: taskSleeper,\n            date: date\n        )\n    }\n\n    private func remainingSeconds(delay: AutomationDelay, triggerDate: Date) -> TimeInterval {\n        guard let seconds = delay.seconds else { return 0 }\n        let remaining = seconds - date.now.timeIntervalSince(triggerDate)\n        return remaining > 0 ? remaining : 0\n    }\n\n    @MainActor\n    func process(delay: AutomationDelay?, triggerDate: Date) async {\n        guard let delay = delay else { return }\n\n        /// Seconds\n        let seconds = remainingSeconds(delay: delay, triggerDate: triggerDate)\n        if seconds > 0 {\n            try? await self.taskSleeper.sleep(timeInterval: seconds)\n        }\n\n        while !Task.isCancelled, !areConditionsMet(delay:delay) {\n            /// App state\n            if let appState = delay.appState {\n                for await update in self.appStateTracker.stateUpdates {\n                    guard !Task.isCancelled else { return }\n                    if (appState == update.automationAppState) {\n                        break\n                    }\n                }\n            }\n\n            guard !Task.isCancelled else { return }\n\n            // Screen\n            if let screens = delay.screens {\n                for await update in self.analytics.screenUpdates {\n                    guard !Task.isCancelled else { return }\n                    if let update = update, screens.contains(update) {\n                        break\n                    }\n                }\n            }\n\n            guard !Task.isCancelled else { return }\n\n            // Region\n            if let regionID = delay.regionID {\n                guard !Task.isCancelled else { return }\n                for await update in self.analytics.regionUpdates {\n                    if update.contains(regionID) {\n                        break\n                    }\n                }\n            }\n            \n            guard !Task.isCancelled else { return }\n            \n            if let window = delay.executionWindow {\n                try? await executionWindowProcessor.process(window: window)\n            }\n        }\n    }\n\n    func preprocess(delay: AutomationDelay?, triggerDate: Date) async throws {\n        guard let delay = delay else { return }\n\n        // Handle delay - preprocessSecondsDelayAllowance\n        let seconds = remainingSeconds(delay: delay, triggerDate: triggerDate) - Self.preprocessSecondsDelayAllowance\n        if seconds > 0 {\n            try await self.taskSleeper.sleep(timeInterval: seconds)\n        }\n\n        try Task.checkCancellation()\n\n        if let window = delay.executionWindow {\n            try await executionWindowProcessor.process(window: window)\n        }\n    }\n\n    @MainActor\n    func areConditionsMet(delay: AutomationDelay?) -> Bool {\n        guard let delay = delay else { return true }\n\n        // State\n        if let appState = delay.appState {\n            guard appState == self.appStateTracker.state.automationAppState else {\n                return false\n            }\n        }\n\n        // Screen\n        if let screens = delay.screens {\n            guard \n                let currentScreen = analytics.currentScreen,\n                screens.contains(currentScreen)\n            else {\n                return false\n            }\n        }\n\n        // Region\n        if let regionID = delay.regionID {\n            guard self.analytics.currentRegions.contains(regionID) else {\n                return false\n            }\n        }\n        \n        if let window = delay.executionWindow {\n            guard executionWindowProcessor.isActive(window: window) else {\n                return false\n            }\n        }\n\n        return true\n    }\n}\n\nfileprivate extension ApplicationState {\n    @MainActor\n    var automationAppState: AutomationAppState {\n        if self == .active {\n            return .foreground\n        } else {\n            return .background\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationEngine.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nactor AutomationEngine : AutomationEngineProtocol {\n    internal var startTask: Task<Void, Never>?\n    internal var listenerTask: Task<Void, Never>?\n\n    nonisolated internal let isEnginePaused: AirshipMainActorValue<Bool> = AirshipMainActorValue(false)\n    nonisolated internal let isExecutionPaused: AirshipMainActorValue<Bool> = AirshipMainActorValue(false)\n    private let triggerQueue: AirshipSerialQueue = AirshipSerialQueue()\n\n    private let store: AutomationStore\n    private let executor: AutomationExecutor\n    private let preparer: AutomationPreparer\n    private let scheduleConditionsChangedNotifier: any ScheduleConditionsChangedNotifierProtocol\n    private let eventFeed: any AutomationEventFeedProtocol\n    private let triggersProcessor: any AutomationTriggerProcessorProtocol\n    private let delayProcessor: any AutomationDelayProcessorProtocol\n    private let date: any AirshipDateProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n    private let eventsHistory: any AutomationEventsHistory\n\n    private var processPendingExecutionTask: Task<Void, Never>?\n    private var pendingExecution: [String: PreparedData] = [:]\n    private var preprocessDelayTasks: Set<Task<Bool, any Error>> = Set()\n\n\n    init(\n        store: AutomationStore,\n        executor: AutomationExecutor,\n        preparer: AutomationPreparer,\n        scheduleConditionsChangedNotifier: any ScheduleConditionsChangedNotifierProtocol,\n        eventFeed: any AutomationEventFeedProtocol,\n        triggersProcessor: any AutomationTriggerProcessorProtocol,\n        delayProcessor: any AutomationDelayProcessorProtocol,\n        eventsHistory: any AutomationEventsHistory,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared\n    ) {\n        self.store = store\n        self.executor = executor\n        self.preparer = preparer\n        self.scheduleConditionsChangedNotifier = scheduleConditionsChangedNotifier\n        self.eventFeed = eventFeed\n        self.triggersProcessor = triggersProcessor\n        self.delayProcessor = delayProcessor\n        self.date = date\n        self.taskSleeper = taskSleeper\n        self.eventsHistory = eventsHistory\n    }\n\n    @MainActor\n    func setEnginePaused(_ paused: Bool) {\n        self.isEnginePaused.set(paused)\n        self.triggersProcessor.setPaused(paused)\n\n        if !isExecutionPaused.value && !isEnginePaused.value {\n            self.scheduleConditionsChangedNotifier.notify()\n        }\n    }\n\n    @MainActor\n    func setExecutionPaused(_ paused: Bool) {\n        self.isExecutionPaused.set(paused)\n\n        if !isExecutionPaused.value && !isEnginePaused.value {\n            self.scheduleConditionsChangedNotifier.notify()\n        }\n    }\n\n    func start() async {\n        self.startTask = Task {\n            do {\n                try await self.restoreSchedules()\n            } catch {\n                AirshipLogger.error(\"Failed to restore schedules \\(error)\")\n            }\n        }\n\n        self.listenerTask = Task {\n            await self.startTask?.value\n\n            await withTaskGroup(of: Void.self) { group in\n                group.addTask { [weak self] in\n                    guard\n                        !Task.isCancelled,\n                        let resultsStream = await self?.triggersProcessor.triggerResults\n                    else {\n                        return\n                    }\n                    \n                    for await result in resultsStream {\n                        guard !Task.isCancelled else { return }\n                        await self?.processTriggerResult(result)\n                    }\n                }\n\n                group.addTask { [weak self] in\n                    guard\n                        !Task.isCancelled,\n                        let eventsFeed = self?.eventFeed.feed\n                    else {\n                        return\n                    }\n                    \n                    for await event in eventsFeed {\n                        guard !Task.isCancelled else { return }\n                        await self?.triggersProcessor.processEvent(event)\n                        await self?.eventsHistory.add(event)\n                    }\n                }\n            }\n        }\n\n        Task {\n            while true {\n                await self.scheduleConditionsChangedNotifier.wait()\n                await startProcessingPendingExecution()\n            }\n        }\n    }\n\n    func stop() async {\n        self.listenerTask?.cancel()\n        self.listenerTask = nil\n        \n        self.startTask?.cancel()\n        self.startTask = nil\n    }\n\n    func stopSchedules(identifiers: [String]) async throws {\n        AirshipLogger.debug(\"Stopping schedules \\(identifiers)\")\n\n        await self.startTask?.value\n        let now = self.date.now\n        for identifier in identifiers {\n            try await self.updateState(identifier: identifier) { data in\n                data.schedule.end = now\n                data.lastScheduleModifiedDate = now\n                data.finished(date: now)\n            }\n        }\n    }\n\t\t\n    func upsertSchedules(_ schedules: [AutomationSchedule]) async throws {\n        await self.startTask?.value\n        let map = schedules.reduce(into: [String: AutomationSchedule]()) {\n            $0[$1.identifier] = $1\n        }\n        \n        AirshipLogger.debug(\"Upserting schedules \\(map.keys)\")\n\n        let updated = try await store.upsertSchedules(scheduleIDs: Array(map.keys)) { [date] identifier, data in\n            guard let schedule = map[identifier] else {\n                throw AirshipErrors.error(\"Failed to upsert\")\n            }\n\n            var updated = try schedule.updateOrCreate(data: data, date: self.date.now)\n            updated.updateState(date: date.now)\n            updated.lastScheduleModifiedDate = date.now\n            return updated\n        }\n\n        await self.triggersProcessor.updateSchedules(updated)\n        self.cancelPreprocessDelayTasks()\n    }\n\n    func cancelSchedules(identifiers: [String]) async throws {\n        AirshipLogger.debug(\"Cancelling schedules \\(identifiers)\")\n\n        await self.startTask?.value\n        try await store.deleteSchedules(scheduleIDs: identifiers)\n        await self.triggersProcessor.cancel(scheduleIDs: identifiers)\n    }\n\n    func cancelSchedules(group: String) async throws {\n        AirshipLogger.debug(\"Cancelling schedules with group \\(group)\")\n\n        await self.startTask?.value\n        try await store.deleteSchedules(group: group)\n        await self.triggersProcessor.cancel(group: group)\n    }\n    \n    func cancelSchedulesWith(type: AutomationSchedule.ScheduleType) async throws {\n        AirshipLogger.debug(\"Cancelling schedules with type \\(type)\")\n\n        await self.startTask?.value\n\n        //we don't store schedule type as a separate field, but it's a part of airship json, so we\n        // can't utilize core data to filter out our results\n        let ids = try await self.schedules.compactMap { schedule in\n            switch schedule.data {\n            case .actions: return type == .actions ? schedule.identifier : nil\n            case .inAppMessage: return type == .inAppMessage ? schedule.identifier : nil\n            case .deferred: return type == .deferred ? schedule.identifier : nil\n            }\n        }\n\n        try await store.deleteSchedules(scheduleIDs: ids)\n        await self.triggersProcessor.cancel(scheduleIDs: ids)\n    }\n\n    var schedules: [AutomationSchedule] {\n        get async throws {\n            return try await self.store.getSchedules()\n                .filter { !$0.shouldDelete(date: self.date.now) }\n                .map { $0.schedule }\n        }\n    }\n\n    func getSchedule(identifier: String) async throws -> AutomationSchedule? {\n        guard\n            let data = try await self.store.getSchedule(scheduleID: identifier),\n            !data.shouldDelete(date: self.date.now)\n        else {\n            return nil\n        }\n\n        return data.schedule\n    }\n\n    func getSchedules(group: String) async throws -> [AutomationSchedule] {\n        return try await self.store.getSchedules(group: group)\n            .filter {\n                !$0.shouldDelete(date: self.date.now)\n            }\n            .map {\n                $0.schedule\n            }\n    }\n\n    private func restoreSchedules() async throws {\n        let now = self.date.now\n\n        let schedules = try await self.store.getSchedules()\n            .sorted { left, right in\n                if (left.schedule.priority ?? 0) < (right.schedule.priority ?? 0) {\n                    return true\n                }\n\n                let leftDate = left.triggerInfo?.date ?? now\n                let rightDate = left.triggerInfo?.date ?? now\n                return leftDate > rightDate\n            }\n\n        \n        // Restore triggers\n        try await self.triggersProcessor.restoreSchedules(schedules)\n\n        // Handle interrupted\n        let interrupted = schedules.filter {\n            $0.isInState([.executing, .prepared, .triggered])\n        }\n\n        for data in interrupted {\n            var updated: AutomationScheduleData?\n\n            if data.scheduleState == .executing, let preparedInfo = data.preparedScheduleInfo {\n                let behavior = await self.executor.interrupted(schedule: data.schedule, preparedScheduleInfo: preparedInfo)\n\n                updated = try await self.updateState(data: data) {  data in\n                    data.executionInterrupted(date: now, retry: behavior == .retry)\n                }\n                if (updated?.scheduleState == .paused) {\n                    handleInterval(updated?.schedule.interval ?? 0.0, scheduleID: data.schedule.identifier)\n                }\n            } else {\n                updated = try await self.updateState(data: data) {  data in\n                    data.prepareInterrupted(date: now)\n                }\n            }\n\n            if (updated?.scheduleState == .triggered) {\n                await startTaskToProcessTriggeredSchedule(scheduleID: data.schedule.identifier)\n            }\n        }\n\n        // Restore Intervals\n        let paused = schedules.filter { $0.scheduleState == .paused }\n        for data in paused {\n            let interval = data.schedule.interval ?? 0.0\n            let remaining = interval - self.date.now.timeIntervalSince(data.scheduleStateChangeDate)\n            handleInterval(remaining, scheduleID: data.schedule.identifier)\n        }\n\n        /// Delete finished schedules\n        let shouldDelete = schedules\n            .filter { $0.shouldDelete(date: now) }\n            .map { $0.schedule.identifier }\n\n        if !shouldDelete.isEmpty {\n            try await self.store.deleteSchedules(scheduleIDs: shouldDelete)\n            await self.triggersProcessor.cancel(scheduleIDs: shouldDelete)\n        }\n    }\n\n    private func handleInterval(_ interval: TimeInterval, scheduleID: String) {\n        Task { [weak self, date] in\n            try await self?.taskSleeper.sleep(timeInterval: interval)\n            try await self?.updateState(identifier: scheduleID) { data in\n                data.idle(date: date.now)\n            }\n        }\n    }\n}\n\n/// Schedule processing\nfileprivate extension AutomationEngine {\n    private func processTriggerResult(_ result: TriggerResult) async {\n        let now = self.date.now\n        await self.triggerQueue.runSafe {\n            do {\n                switch (result.triggerExecutionType) {\n                case .delayCancellation:\n                    let updated = try await self.updateState(identifier: result.scheduleID) { data in\n                        data.executionCancelled(date: now)\n                    }\n\n                    if let updated = updated {\n                        await self.preparer.cancelled(schedule: updated.schedule)\n                    }\n                    break\n\n                case .execution:\n                    try await self.updateState(identifier: result.scheduleID) { data in\n                        data.triggered(triggerInfo: result.triggerInfo, date: now)\n                    }\n\n                    await self.startTaskToProcessTriggeredSchedule(\n                        scheduleID: result.scheduleID\n                    )\n                }\n            } catch {\n                AirshipLogger.error(\"Failed to process trigger result: \\(result), error: \\(error)\")\n            }\n        }\n    }\n\n    private func startTaskToProcessTriggeredSchedule(scheduleID: String) async {\n        AirshipLogger.trace(\"Starting task to process schedule \\(scheduleID)\")\n\n        // pause the current context\n        await withUnsafeContinuation { continuation in\n            Task {\n                // actor context\n                continuation.resume()\n                do {\n                    AirshipLogger.trace(\"Processing triggered schedule \\(scheduleID)\")\n                    try await self.processTriggeredSchedule(scheduleID: scheduleID)\n                } catch {\n                    AirshipLogger.error(\"Failed to process triggered schedule \\(scheduleID) error: \\(error)\")\n                }\n            }\n        }\n    }\n\n    private func preprocessDelay(data: AutomationScheduleData) async -> Bool {\n        guard let delay = data.schedule.delay else { return true }\n        let scheduleID = data.schedule.identifier\n        let triggerDate = data.triggerInfo?.date ?? data.scheduleStateChangeDate\n\n        let task = Task {\n            AirshipLogger.trace(\"Preprocessing delay \\(scheduleID)\")\n            try await self.delayProcessor.preprocess(\n                delay: delay,\n                triggerDate: triggerDate\n            )\n            AirshipLogger.trace(\"Finished preprocessing delay \\(scheduleID)\")\n            return true\n        }\n\n        preprocessDelayTasks.insert(task)\n        let result = try? await task.value\n        preprocessDelayTasks.remove(task)\n        return result ?? false\n    }\n\n    private func cancelPreprocessDelayTasks() {\n        preprocessDelayTasks.forEach { $0.cancel() }\n        preprocessDelayTasks.removeAll()\n    }\n\n    private func processTriggeredSchedule(scheduleID: String) async throws {\n\n        if await self.isEnginePaused.value {\n            // Wait for resume\n            _ = await self.isExecutionPaused.updates.first(where: { paused in paused == false })\n        }\n\n        guard\n            let data = try await self.store.getSchedule(scheduleID: scheduleID)\n        else {\n            AirshipLogger.trace(\"Aborting processing schedule \\(scheduleID), no longer in database.\")\n            return\n        }\n\n        guard\n            data.isInState([.triggered])\n        else {\n            AirshipLogger.trace(\"Aborting processing schedule \\(data), no longer triggered.\")\n            return\n        }\n\n        guard \n            await preprocessDelay(data: data)\n        else {\n            AirshipLogger.trace(\"Preprocess delay was interrupted, retrying \\(scheduleID)\")\n            try await processTriggeredSchedule(scheduleID: scheduleID)\n            return\n        }\n\n        guard\n            let isCurrent = try? await self.store.isCurrent(\n                scheduleID: scheduleID,\n                lastScheduleModifiedDate: data.lastScheduleModifiedDate,\n                scheduleState: data.scheduleState\n            ),\n            isCurrent\n        else {\n            AirshipLogger.trace(\"Trigger data has changed since preprocessing, retrying \\(scheduleID)\")\n            try await processTriggeredSchedule(scheduleID: scheduleID)\n            return\n        }\n\n        guard data.isActive(date: self.date.now) else {\n            AirshipLogger.trace(\"Aborting processing schedule \\(data), no longer active.\")\n            await self.preparer.cancelled(schedule: data.schedule)\n            return\n        }\n\n        /// Prepare\n        guard let prepared = try await self.prepareSchedule(data: data) else {\n            return\n        }\n\n        try await processPrepared(preparedData: prepared)\n    }\n\n\n    private func processPrepared(preparedData: PreparedData) async throws {\n        await waitForConditions(preparedData: preparedData)\n\n        guard await checkStillValid(prepared: preparedData) else {\n            let updated = try await self.updateState(data: preparedData.scheduleData) { [date] data in\n                data.executionInvalidated(date: date.now)\n            }\n\n            if updated?.scheduleState == .triggered {\n                await self.startTaskToProcessTriggeredSchedule(\n                    scheduleID: preparedData.scheduleID\n                )\n            } else {\n                await self.preparer.cancelled(schedule: preparedData.scheduleData.schedule)\n            }\n            return\n        }\n\n        self.addPending(preparedData: preparedData)\n        await self.startProcessingPendingExecution()\n    }\n\n    private func startProcessingPendingExecution() async {\n        await self.processPendingExecutionTask?.value\n        self.processPendingExecutionTask = Task {\n            await processPendingExecution()\n        }\n    }\n\n    private func processPendingExecution() async {\n        var processedScheduleIDs = Set<String>()\n\n        while true {\n            let next = self.pendingExecution.values.filter { data in\n                !processedScheduleIDs.contains(data.scheduleID)\n            }.sorted { l, r in\n                l.priority < r.priority\n            }.first\n\n            guard let next else { return }\n\n            processedScheduleIDs.insert(next.scheduleID)\n\n            guard\n                await checkStillValid(prepared: next),\n                await self.delayProcessor.areConditionsMet(delay: next.scheduleData.schedule.delay)\n            else {\n                self.pendingExecution.removeValue(forKey: next.scheduleID)\n                Task {\n                    do {\n                        try await processPrepared(preparedData: next)\n                    } catch {\n                        AirshipLogger.error(\"Failed to execute schedule \\(next.scheduleData) \\(error)\")\n                    }\n                }\n                continue\n            }\n\n            self.pendingExecution.removeValue(forKey: next.scheduleID)\n\n            Task { @MainActor in\n                do {\n                    let handled = try await attemptExecution(\n                        data: next.scheduleData,\n                        preparedSchedule: next.preparedSchedule\n                    )\n\n                    if (!handled) {\n                        await addPending(preparedData: next)\n                    }\n                } catch {\n                    AirshipLogger.error(\"Failed to execute schedule \\(next.scheduleData) \\(error)\")\n                }\n            }\n        }\n    }\n\n    private func addPending(preparedData: PreparedData) {\n        AirshipLogger.trace(\"Adding \\(preparedData.scheduleID) to pending execution queue\")\n        self.pendingExecution[preparedData.scheduleID] = preparedData\n    }\n\n\n    private func checkStillValid(prepared: PreparedData) async -> Bool {\n        // Make sure we are still up to date. Data might change due to a change\n        // in the data, schedule was cancelled, or if a delay cancellation trigger\n        // was fired.\n        guard\n            let isCurrent = try? await self.store.isCurrent(\n                scheduleID: prepared.scheduleID,\n                lastScheduleModifiedDate: prepared.scheduleData.lastScheduleModifiedDate,\n                scheduleState: prepared.scheduleData.scheduleState\n            ),\n            isCurrent\n        else {\n            AirshipLogger.trace(\"Prepared schedule no longer up to date, no longer valid \\(prepared.scheduleData)\")\n            return false\n        }\n\n        guard prepared.scheduleData.isActive(date: self.date.now) else {\n            AirshipLogger.trace(\"Prepared schedule no longer active, no longer valid \\(prepared.scheduleData)\")\n            return false\n        }\n\n        guard await self.executor.isValid(\n            schedule: prepared.scheduleData.schedule\n        ) else {\n            AirshipLogger.trace(\"Prepared schedule no longer valid \\(prepared.scheduleData)\")\n            return false\n        }\n\n        return true\n    }\n\n    private func waitForConditions(preparedData: PreparedData) async  {\n        let triggerDate = preparedData.scheduleData.triggerInfo?.date ?? preparedData.scheduleData.scheduleStateChangeDate\n\n        // Wait for conditions\n        AirshipLogger.trace(\"Waiting for delay conditions \\(preparedData.scheduleID)\")\n        await self.delayProcessor.process(\n            delay: preparedData.scheduleData.schedule.delay,\n            triggerDate: triggerDate\n        )\n\n        AirshipLogger.trace(\"Delay conditions met \\(preparedData.scheduleID)\")\n    }\n\n\n    private func prepareSchedule(data: AutomationScheduleData) async throws -> PreparedData? {\n        AirshipLogger.trace(\"Preparing schedule \\(data.schedule.identifier)\")\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: data.schedule,\n            triggerContext: data.triggerInfo?.context,\n            triggerSessionID: data.triggerSessionID\n        )\n\n        AirshipLogger.trace(\"Finished preparing schedule \\(data.schedule.identifier) result: \\(prepareResult)\")\n\n        switch prepareResult {\n        case .prepared(let preparedSchedule):\n            let updated = try await self.updateState(data: data) { [date] data in\n                data.prepared(info: preparedSchedule.info, date: date.now)\n            }\n\n            // Make sure its updated\n            guard let updated else {\n                await preparer.cancelled(schedule: data.schedule)\n                return nil\n            }\n\n            return PreparedData(\n                scheduleData: updated,\n                preparedSchedule: preparedSchedule\n            )\n        case .invalidate:\n            await self.startTaskToProcessTriggeredSchedule(\n                scheduleID: data.schedule.identifier\n            )\n            return nil\n        case .cancel:\n            try await self.store.deleteSchedules(scheduleIDs: [data.schedule.identifier])\n            return nil\n        case .skip:\n            _ = try await self.updateState(data: data) { [date] data in\n                data.prepareCancelled(date: date.now, penalize: false)\n            }\n            return nil\n        case .penalize:\n            _ = try await self.updateState(data: data) { [date] data in\n                data.prepareCancelled(date: date.now, penalize: true)\n            }\n            return nil\n        }\n    }\n\n    @MainActor\n    private func attemptExecution(data: AutomationScheduleData, preparedSchedule: PreparedSchedule) async throws -> Bool {\n        AirshipLogger.trace(\"Starting to execute schedule \\(data)\")\n\n\n        let readyResult = self.checkReady(data: data, preparedSchedule: preparedSchedule)\n        switch (readyResult) {\n        case .ready:\n            break\n\n        case .invalidate:\n            let updated = try await self.updateState(data: data) { [date] data in\n                data.executionInvalidated(date: date.now)\n            }\n\n            if updated?.scheduleState == .triggered {\n                await self.startTaskToProcessTriggeredSchedule(\n                    scheduleID: data.schedule.identifier\n                )\n            } else {\n                await self.preparer.cancelled(schedule: data.schedule)\n            }\n            return true\n\n        case .notReady:\n            return false\n\n        case .skip:\n            try await self.updateState(data: data) { [date] data in\n                data.executionSkipped(date: date.now)\n            }\n            await self.preparer.cancelled(schedule: data.schedule)\n            return true\n        }\n\n\n        let executeResult = try await self.execute(preparedSchedule: preparedSchedule)\n        let scheduleID = data.schedule.identifier\n\n        switch (executeResult) {\n        case .cancel:\n            try await self.store.deleteSchedules(scheduleIDs: [scheduleID])\n            await self.triggersProcessor.cancel(scheduleIDs: [scheduleID])\n            return true\n\n        case .finished:\n            let updated = try await self.updateState(identifier: scheduleID) {  [date] data in\n                data.finishedExecuting(date: date.now)\n            }\n\n            if let updated = updated, updated.scheduleState == .paused {\n                await handleInterval(updated.schedule.interval ?? 0.0, scheduleID: updated.schedule.identifier)\n            }\n            return true\n\n        case .retry:\n            return false\n        }\n    }\n\n    @MainActor\n    private func execute(preparedSchedule: PreparedSchedule) async throws -> ScheduleExecuteResult {\n        AirshipLogger.trace(\"Executing schedule \\(preparedSchedule.info.scheduleID)\")\n\n        // Execute\n        let updateStateTask = Task {\n            try await self.updateState(identifier: preparedSchedule.info.scheduleID) { [date] data in\n                data.executing(date: date.now)\n            }\n        }\n\n        let executeResult = await self.executor.execute(\n            preparedSchedule: preparedSchedule\n        )\n\n        _ = try await updateStateTask.value\n\n        AirshipLogger.trace(\"Executing result \\(preparedSchedule.info.scheduleID) \\(executeResult)\")\n\n        return executeResult\n    }\n\n    @MainActor\n    private func checkReady(data: AutomationScheduleData, preparedSchedule: PreparedSchedule) -> ScheduleReadyResult {\n        AirshipLogger.trace(\"Checking if schedule is ready \\(data)\")\n\n        // Execution should not be paused\n        guard !self.isExecutionPaused.value, !self.isEnginePaused.value else {\n            AirshipLogger.trace(\"Executor paused, not ready \\(data)\")\n            return .notReady\n        }\n\n        // Still active\n        guard data.isActive(date: self.date.now) else {\n            AirshipLogger.trace(\"Schedule no longer active, Invalidating \\(data)\")\n            return .invalidate\n        }\n\n        let result = self.executor.isReady(preparedSchedule: preparedSchedule)\n        if result != .ready {\n            AirshipLogger.trace(\"Schedule not ready \\(data)\")\n        }\n        return result\n    }\n\n    /// Same as updateState(identifier:block) but optimized to skip parsing the schedule if last modified time\n    /// is unchanged. This reduces energy usage by avoiding unnecessary schedule parsing.\n    /// TODO: Move start/end to top level of schedule to allow state-only mutations without full parsing.\n    @discardableResult\n    func updateState(\n        data: AutomationScheduleData,\n        block: @escaping @Sendable (inout AutomationScheduleData) throws -> Void\n    ) async throws -> AutomationScheduleData? {\n        let updated = try await self.store.updateSchedule(scheduleData: data, block:block)\n        if let updated  {\n            try await self.triggersProcessor.updateScheduleState(\n                scheduleID: updated.schedule.identifier,\n                state: updated.scheduleState\n            )\n        }\n        return updated\n    }\n\n    @discardableResult\n    func updateState(\n        identifier: String,\n        block: @escaping @Sendable (inout AutomationScheduleData) throws -> Void\n    ) async throws -> AutomationScheduleData? {\n        let updated = try await self.store.updateSchedule(scheduleID: identifier, block: block)\n        if let updated  {\n            try await self.triggersProcessor.updateScheduleState(\n                scheduleID: identifier,\n                state: updated.scheduleState\n            )\n        }\n        return updated\n    }\n}\n\nfileprivate extension AutomationSchedule {\n    func updateOrCreate(data: AutomationScheduleData?, date: Date) throws -> AutomationScheduleData {\n        guard var existing = data else {\n            return AutomationScheduleData(\n                schedule: self,\n                scheduleState: .idle,\n                lastScheduleModifiedDate: date,\n                scheduleStateChangeDate: date,\n                executionCount: 0,\n                triggerSessionID: UUID().uuidString\n            )\n        }\n\n        existing.schedule = self\n        return existing\n    }\n}\n\nfileprivate struct PreparedData: Sendable {\n    let scheduleData: AutomationScheduleData\n    let preparedSchedule: PreparedSchedule\n\n    var scheduleID: String {\n        return scheduleData.schedule.identifier\n    }\n\n    var priority: Int {\n        return scheduleData.schedule.priority ?? 0\n    }\n}\n\n/// Automation engine\nprotocol AutomationEngineProtocol: Actor, Sendable {\n    @MainActor\n    func setEnginePaused(_ paused: Bool)\n\n    @MainActor\n    func setExecutionPaused(_ paused: Bool)\n    func start() async\n\n    func upsertSchedules(_ schedules: [AutomationSchedule]) async throws\n\n    func stopSchedules(identifiers: [String]) async throws\n    func cancelSchedules(identifiers: [String]) async throws\n    func cancelSchedules(group: String) async throws\n    func cancelSchedulesWith(type: AutomationSchedule.ScheduleType) async throws\n\n    var schedules: [AutomationSchedule] { get async throws }\n    func getSchedule(identifier: String) async throws -> AutomationSchedule?\n    func getSchedules(group: String) async throws -> [AutomationSchedule]\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationEventFeed.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AutomationEventFeedProtocol: Sendable {\n    var feed: AsyncStream<AutomationEvent> { get }\n}\n\nstruct TriggerableState: Equatable, Codable {\n    var appSessionID: String? // set on foreground event, resets on background\n    var versionUpdated: String? // versionUpdate event\n}\n\nenum AutomationEvent: Sendable, Equatable {\n    case stateChanged(state: TriggerableState)\n    case event(type: EventAutomationTriggerType, data: AirshipJSON? = nil, value: Double = 1.0)\n    \n    var eventData: AirshipJSON? {\n        switch self {\n        case .event(_, let data, _): return data\n        default: return nil\n        }\n    }\n}\n\n@MainActor\nfinal class AutomationEventFeed: AutomationEventFeedProtocol {\n    typealias Stream = AsyncStream<AutomationEvent>\n    \n    private let continuation: Stream.Continuation\n    private var observers: [AnyObject] = []\n    private var isFirstAttach = false\n    private var listenerTask: Task<Void, Never>?\n\n    private let applicationMetrics: any ApplicationMetricsProtocol\n    private let applicationStateTracker: any AppStateTrackerProtocol\n    private let analyticsFeed: AirshipAnalyticsFeed\n\n    private var appSessionState = TriggerableState()\n    private var regions: Set<String> = Set()\n\n    nonisolated let feed: Stream\n\n    init(\n        applicationMetrics: any ApplicationMetricsProtocol,\n        applicationStateTracker: any AppStateTrackerProtocol,\n        analyticsFeed: AirshipAnalyticsFeed\n    ) {\n        self.applicationMetrics = applicationMetrics\n        self.applicationStateTracker = applicationStateTracker\n        self.analyticsFeed = analyticsFeed\n\n        (self.feed, self.continuation) = Stream.airshipMakeStreamWithContinuation()\n    }\n    \n    @discardableResult\n    func attach() -> Self {\n        guard listenerTask == nil else { return self }\n\n        if !isFirstAttach {\n            isFirstAttach = true\n\n            self.continuation.yield(.event(type: .appInit))\n\n            if\n                applicationMetrics.isAppVersionUpdated,\n                let version = applicationMetrics.currentAppVersion\n            {\n                self.appSessionState.versionUpdated = version\n                self.continuation.yield(.stateChanged(state: self.appSessionState))\n            }\n        }\n\n        self.listenerTask = startListenerTask { [weak self] event in\n            self?.emit(event: event)\n        }\n\n        return self\n    }\n    \n    @discardableResult\n    func detach() -> Self {\n        self.listenerTask?.cancel()\n        return self\n    }\n    \n    deinit {\n        self.listenerTask?.cancel()\n        self.continuation.finish()\n    }\n\n    private func startListenerTask(\n        onEvent: @escaping @Sendable @MainActor (AutomationEvent) -> Void\n    ) -> Task<Void, Never> {\n        return Task { [analyticsFeed, applicationStateTracker] in\n            await withTaskGroup(of: Void.self) { group in\n                group.addTask {\n                    for await state in await applicationStateTracker.stateUpdates {\n                        guard !Task.isCancelled else { return }\n\n                        if (state == .active) {\n                            await onEvent(.event(type: .foreground))\n                        }\n\n                        if (state == .background) {\n                            await onEvent(.event(type: .background))\n                        }\n                    }\n                }\n\n                group.addTask {\n                    for await event in await analyticsFeed.updates {\n                        guard !Task.isCancelled else { return }\n                        guard let converted = event.toAutomationEvent() else { continue }\n                        \n                        for item in converted {\n                            await onEvent(item)\n                        }\n                    }\n                }\n            }\n        }\n    }\n    \n    private func setAppSessionID(_ id: String?) {\n        guard self.appSessionState.appSessionID != id else { return }\n        self.appSessionState.appSessionID = id\n        emit(event: .stateChanged(state: self.appSessionState))\n    }\n\n    private func emit(event: AutomationEvent) {\n        self.continuation.yield(event)\n        \n        switch event {\n        case .event(let type, _, _):\n            switch type {\n            case .foreground:\n                self.setAppSessionID(UUID().uuidString)\n            case .background:\n                self.setAppSessionID(nil)\n            default: break\n            }\n        default: break\n        }\n    }\n}\n\nprivate extension AirshipAnalyticsFeed.Event {\n    \n    func toAutomationEvent() -> [AutomationEvent]? {\n        switch self {\n        case .screen(let screen):\n            return [.event(type: .screen, data: try? AirshipJSON.wrap(screen))]\n        case .analytics(let eventType, let body, let value):\n            switch eventType {\n            case .regionEnter:\n                return [.event(type: .regionEnter, data: body)]\n            case .regionExit:\n                return [.event(type: .regionExit, data: body)]\n            case .customEvent:\n                return [\n                    .event(type: .customEventCount, data: body),\n                    .event(type: .customEventValue, data: body, value: value ?? 1.0)\n                ]\n            case .featureFlagInteraction:\n                return [.event(type: .featureFlagInteraction, data: body)]\n            case .inAppDisplay:\n                return [.event(type: .inAppDisplay, data: body)]\n            case .inAppResolution:\n                return [.event(type: .inAppResolution, data: body)]\n            case .inAppButtonTap:\n                return [.event(type: .inAppButtonTap, data: body)]\n            case .inAppPermissionResult:\n                return [.event(type: .inAppPermissionResult, data: body)]\n            case .inAppFormDisplay:\n                return [.event(type: .inAppFormDisplay, data: body)]\n            case .inAppFormResult:\n                return [.event(type: .inAppFormResult, data: body)]\n            case .inAppGesture:\n                return [.event(type: .inAppGesture, data: body)]\n            case .inAppPagerCompleted:\n                return [.event(type: .inAppPagerCompleted, data: body)]\n            case .inAppPagerSummary:\n                return [.event(type: .inAppPagerSummary, data: body)]\n            case .inAppPageSwipe:\n                return [.event(type: .inAppPageSwipe, data: body)]\n            case .inAppPageView:\n                return [.event(type: .inAppPageView, data: body)]\n            case .inAppPageAction:\n                return [.event(type: .inAppPageAction, data: body)]\n            default:\n                return nil\n            }\n#if canImport(AirshipCore)\n        @unknown default:\n            return nil\n#endif\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationEventsHistory.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AutomationEventsHistory: Sendable {\n    var events: [AutomationEvent] { get async }\n\n    func add(_ event: AutomationEvent) async\n}\n\nfinal actor DefaultAutomationEventsHistory: AutomationEventsHistory {\n    private static let maxEvents: Int = 100\n    private static let maxDuration: TimeInterval = 30 // seconds\n    \n    private let clock: any AirshipDateProtocol\n    private var eventsHistory: [Entry] = []\n    \n    init(clock: any AirshipDateProtocol = AirshipDate()) {\n        self.clock = clock\n    }\n    \n    private struct Entry {\n        let event: AutomationEvent\n        let timestamp: Date\n    }\n    \n    var events: [AutomationEvent] {\n        return prunedEvents().map(\\.event)\n    }\n    \n    func add(_ event: AutomationEvent) {\n        var filtered = prunedEvents()\n        filtered.append(Entry(event: event, timestamp: clock.now))\n        eventsHistory = filtered\n    }\n    \n    private func prunedEvents() -> [Entry] {\n        return eventsHistory\n            .suffix(Self.maxEvents)\n            .filter { self.clock.now.timeIntervalSince($0.timestamp) < Self.maxDuration }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationExecutor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AutomationExecutorProtocol: Sendable {\n    @MainActor\n    func isValid(\n        schedule: AutomationSchedule\n    ) async -> Bool\n\n    @MainActor\n    func isReady(preparedSchedule: PreparedSchedule) -> ScheduleReadyResult\n\n    @MainActor\n    func execute(preparedSchedule: PreparedSchedule) async -> ScheduleExecuteResult\n\n    func interrupted(\n        schedule: AutomationSchedule,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async -> InterruptedBehavior\n}\n\nprotocol AutomationExecutorDelegate<ExecutionData>: Sendable {\n    associatedtype ExecutionData: Sendable\n    \n    @MainActor\n    func isReady(\n        data: ExecutionData,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) -> ScheduleReadyResult\n\n    @MainActor\n    func execute(\n        data: ExecutionData,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async throws -> ScheduleExecuteResult\n\n    @MainActor\n    func interrupted(\n        schedule: AutomationSchedule,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async -> InterruptedBehavior\n}\n\n\nfinal class AutomationExecutor: AutomationExecutorProtocol {\n    private let actionExecutor: any AutomationExecutorDelegate<AirshipJSON>\n    private let messageExecutor: any AutomationExecutorDelegate<PreparedInAppMessageData>\n    private let remoteDataAccess: any AutomationRemoteDataAccessProtocol\n\n    init(\n        actionExecutor: any AutomationExecutorDelegate<AirshipJSON>,\n        messageExecutor: any AutomationExecutorDelegate<PreparedInAppMessageData>,\n        remoteDataAccess: any AutomationRemoteDataAccessProtocol\n    ) {\n        self.actionExecutor = actionExecutor\n        self.messageExecutor = messageExecutor\n        self.remoteDataAccess = remoteDataAccess\n    }\n\n    @MainActor\n    func isValid(schedule: AutomationSchedule) async -> Bool {\n        return await self.remoteDataAccess.isCurrent(schedule: schedule)\n    }\n\n    @MainActor\n    func isReady(preparedSchedule: PreparedSchedule) -> ScheduleReadyResult {\n        let result = switch (preparedSchedule.data) {\n        case .inAppMessage(let data):\n            self.messageExecutor.isReady(\n                data: data,\n                preparedScheduleInfo: preparedSchedule.info\n            )\n        case .actions(let data):\n            self.actionExecutor.isReady(\n                data: data,\n                preparedScheduleInfo: preparedSchedule.info\n            )\n        }\n\n        guard result == .ready else {\n            return result\n        }\n\n        if (preparedSchedule.frequencyChecker?.checkAndIncrement() == false) {\n            return .skip\n        }\n\n        return .ready\n    }\n\n    @MainActor\n    func execute(preparedSchedule: PreparedSchedule) async -> ScheduleExecuteResult {\n        do {\n            switch (preparedSchedule.data) {\n            case .inAppMessage(let data):\n                return try await self.messageExecutor.execute(\n                    data: data,\n                    preparedScheduleInfo: preparedSchedule.info\n                )\n            case .actions(let data):\n                return try await self.actionExecutor.execute(\n                    data: data,\n                    preparedScheduleInfo: preparedSchedule.info\n                )\n            }\n        } catch {\n            AirshipLogger.warn(\"Failed to execute automation: \\(preparedSchedule.info.scheduleID) error:\\(error)\")\n            return .retry\n        }\n    }\n\n    func interrupted(\n        schedule: AutomationSchedule,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async -> InterruptedBehavior {\n        return if schedule.isInAppMessageType {\n            await self.messageExecutor.interrupted(\n                schedule: schedule,\n                preparedScheduleInfo: preparedScheduleInfo\n            )\n        } else {\n            await self.actionExecutor.interrupted(\n                schedule: schedule,\n                preparedScheduleInfo: preparedScheduleInfo\n            )\n        }\n    }\n}\n\nenum InterruptedBehavior: Sendable {\n    case retry\n    case finish\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationPreparer.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AutomationPreparerProtocol: Sendable {\n    func prepare(\n        schedule: AutomationSchedule,\n        triggerContext: AirshipTriggerContext?,\n        triggerSessionID: String\n    ) async -> SchedulePrepareResult\n\n    func cancelled(schedule: AutomationSchedule) async\n}\n\nprotocol AutomationPreparerDelegate<PrepareDataIn, PrepareDataOut>: Sendable {\n    associatedtype PrepareDataIn: Sendable\n    associatedtype PrepareDataOut: Sendable\n\n    func prepare(\n        data: PrepareDataIn,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async throws -> PrepareDataOut\n\n    func cancelled(scheduleID: String) async\n}\n\nstruct AutomationPreparer: AutomationPreparerProtocol {\n    private let actionPreparer: any AutomationPreparerDelegate<AirshipJSON, AirshipJSON>\n    private let messagePreparer: any AutomationPreparerDelegate<InAppMessage, PreparedInAppMessageData>\n\n    private let deferredResolver: any AirshipDeferredResolverProtocol\n    private let frequencyLimits: any FrequencyLimitManagerProtocol\n    private let audienceChecker: any DeviceAudienceChecker\n    private let experiments: any ExperimentDataProvider\n    private let remoteDataAccess: any AutomationRemoteDataAccessProtocol\n    private let queues: Queues\n    private let config: RuntimeConfig\n    private let additionalAudienceResolver: any AdditionalAudienceCheckerResolverProtocol\n\n    private static let deferredResultKey: String = \"AirshipAutomation#deferredResult\"\n    private static let defaultMessageType: String = \"transactional\"\n    private let deviceInfoProviderFactory: @Sendable (String?) -> any AudienceDeviceInfoProvider\n\n    @MainActor\n    init(\n        actionPreparer: any AutomationPreparerDelegate<AirshipJSON, AirshipJSON>,\n        messagePreparer: any AutomationPreparerDelegate<InAppMessage, PreparedInAppMessageData>,\n        deferredResolver: any AirshipDeferredResolverProtocol,\n        frequencyLimits: any FrequencyLimitManagerProtocol,\n        audienceChecker: any DeviceAudienceChecker,\n        experiments: any ExperimentDataProvider,\n        remoteDataAccess: any AutomationRemoteDataAccessProtocol,\n        config: RuntimeConfig,\n        deviceInfoProviderFactory: @escaping @Sendable (String?) -> any AudienceDeviceInfoProvider = { contactID in\n            CachingAudienceDeviceInfoProvider(contactID: contactID)\n        },\n        additionalAudienceResolver: any AdditionalAudienceCheckerResolverProtocol\n    ) {\n        self.actionPreparer = actionPreparer\n        self.messagePreparer = messagePreparer\n        self.deferredResolver = deferredResolver\n        self.frequencyLimits = frequencyLimits\n        self.audienceChecker = audienceChecker\n        self.experiments = experiments\n        self.remoteDataAccess = remoteDataAccess\n        self.deviceInfoProviderFactory = deviceInfoProviderFactory\n        self.config = config\n        self.queues = Queues(config: config)\n        self.additionalAudienceResolver = additionalAudienceResolver\n    }\n\n    func cancelled(schedule: AutomationSchedule) async {\n        if schedule.isInAppMessageType {\n            await self.messagePreparer.cancelled(scheduleID: schedule.identifier)\n        } else {\n            await self.actionPreparer.cancelled(scheduleID: schedule.identifier)\n        }\n    }\n\n    func prepare(\n        schedule: AutomationSchedule,\n        triggerContext: AirshipTriggerContext?,\n        triggerSessionID: String\n    ) async -> SchedulePrepareResult {\n        AirshipLogger.trace(\"Preparing \\(schedule.identifier)\")\n\n        let queue = await self.queues.queue(id: schedule.queue)\n        \n        return await queue.run(name: \"schedule: \\(schedule.identifier)\") { retryState in\n            guard await !self.remoteDataAccess.requiresUpdate(schedule: schedule) else {\n                AirshipLogger.trace(\"Schedule out of date \\(schedule.identifier)\")\n                await self.remoteDataAccess.waitFullRefresh(schedule: schedule)\n                return .success(result: .invalidate)\n            }\n\n            guard await self.remoteDataAccess.bestEffortRefresh(schedule: schedule) else {\n                AirshipLogger.trace(\"Schedule out of date \\(schedule.identifier)\")\n                return .success(result: .invalidate)\n            }\n\n            var frequencyChecker: (any FrequencyCheckerProtocol)!\n            do {\n                frequencyChecker = try await self.frequencyLimits.getFrequencyChecker(\n                    constraintIDs: schedule.frequencyConstraintIDs\n                )\n            } catch {\n                AirshipLogger.error(\"Failed to fetch frequency checker for schedule \\(schedule.identifier) error: \\(error)\")\n                return .success(result: .skip)\n            }\n\n            let deviceInfoProvider = self.deviceInfoProviderFactory(\n                self.remoteDataAccess.contactID(forSchedule: schedule)\n            )\n\n            let audience = CompoundDeviceAudienceSelector.combine(\n                compoundSelector: schedule.compoundAudience?.selector,\n                deviceSelector: schedule.audience?.audienceSelector\n            )\n\n            if let audience {\n                let match = try await self.audienceChecker.evaluate(\n                    audienceSelector: audience,\n                    newUserEvaluationDate: schedule.created ?? .distantPast,\n                    deviceInfoProvider: deviceInfoProvider\n                )\n\n                if (!match.isMatch) {\n                    AirshipLogger.trace(\"Local audience miss \\(schedule.identifier)\")\n                    return .success(\n                        result: schedule.audienceMissBehaviorResult,\n                        ignoreReturnOrder: true\n                    )\n                }\n            }\n\n\n            let experimentResult: ExperimentResult? = if schedule.evaluateExperiments {\n                try await self.experiments.evaluateExperiments(\n                    info: MessageInfo(\n                        messageType: schedule.messageType ?? Self.defaultMessageType,\n                        campaigns: schedule.campaigns\n                    ),\n                    deviceInfoProvider: deviceInfoProvider\n                )\n            } else {\n                nil\n            }\n\n            AirshipLogger.trace(\"Preparing data \\(schedule.identifier)\")\n\n            return try await self.prepareData(\n                data: schedule.data,\n                schedule: schedule,\n                retryState: retryState,\n                deferredRequest: { url in\n                    DeferredRequest(\n                        url: url,\n                        channelID: try await deviceInfoProvider.channelID,\n                        contactID: await deviceInfoProvider.stableContactInfo.contactID,\n                        triggerContext: triggerContext,\n                        locale: deviceInfoProvider.locale,\n                        notificationOptIn: await deviceInfoProvider.isUserOptedInPushNotifications\n                    )\n                },\n                prepareScheduleInfo: {\n                    let result = try await additionalAudienceResolver.resolve(\n                        deviceInfoProvider: deviceInfoProvider,\n                        additionalAudienceCheckOverrides: schedule.additionalAudienceCheckOverrides\n                    )\n\n                    return PreparedScheduleInfo(\n                        scheduleID: schedule.identifier,\n                        productID: schedule.productID,\n                        campaigns: schedule.campaigns,\n                        contactID: await deviceInfoProvider.stableContactInfo.contactID,\n                        experimentResult: experimentResult,\n                        reportingContext: schedule.reportingContext,\n                        triggerSessionID: triggerSessionID,\n                        additionalAudienceCheckResult: result,\n                        priority: schedule.priority ?? 0\n                    )\n                },\n                prepareSchedule: { [frequencyChecker] scheduleInfo, data in\n                    return PreparedSchedule(\n                        info: scheduleInfo,\n                        data: data,\n                        frequencyChecker: frequencyChecker\n                    )\n                }\n            )\n        }\n    }\n\n    private func prepareData(\n        data: AutomationSchedule.ScheduleData,\n        schedule: AutomationSchedule,\n        retryState: RetryingQueue<SchedulePrepareResult>.State,\n        deferredRequest:  @escaping @Sendable (URL) async throws -> DeferredRequest,\n        prepareScheduleInfo:  @escaping @Sendable () async throws -> PreparedScheduleInfo,\n        prepareSchedule:  @escaping @Sendable (PreparedScheduleInfo, PreparedScheduleData) -> PreparedSchedule\n    ) async throws -> RetryingQueue<SchedulePrepareResult>.Result {\n        switch (data) {\n        case .actions(let data):\n            let preparedInfo = try await prepareScheduleInfo()\n            let result = try await self.actionPreparer.prepare(\n                data: data,\n                preparedScheduleInfo: preparedInfo\n            )\n\n            let preparedSchedule = prepareSchedule(preparedInfo, .actions(result))\n            return .success(result: .prepared(preparedSchedule))\n\n        case .inAppMessage(let data):\n            guard data.displayContent.validate() else {\n                AirshipLogger.debug(\"⚠️ Message did not pass validation: \\(data.name) - skipping.\")\n                return .success(result: .skip)\n            }\n\n            let preparedInfo = try await prepareScheduleInfo()\n            let result = try await self.messagePreparer.prepare(\n                data: data,\n                preparedScheduleInfo: preparedInfo\n            )\n\n            let preparedSchedule = prepareSchedule(preparedInfo, .inAppMessage(result))\n            return .success(result: .prepared(preparedSchedule))\n\n        case .deferred(let deferred):\n            return try await self.prepareDeferred(\n                deferred: deferred,\n                schedule: schedule,\n                retryState: retryState,\n                deferredRequest: deferredRequest\n            ) { data in\n                try await self.prepareData(\n                    data: data,\n                    schedule: schedule,\n                    retryState: retryState,\n                    deferredRequest: deferredRequest,\n                    prepareScheduleInfo: prepareScheduleInfo,\n                    prepareSchedule: prepareSchedule\n                )\n            }\n        }\n    }\n\n\n    private func prepareDeferred(\n        deferred: DeferredAutomationData,\n        schedule: AutomationSchedule,\n        retryState: RetryingQueue<SchedulePrepareResult>.State,\n        deferredRequest:  @escaping @Sendable (URL) async throws -> DeferredRequest,\n        onResult: @escaping @Sendable (AutomationSchedule.ScheduleData) async throws -> RetryingQueue<SchedulePrepareResult>.Result\n    ) async throws -> RetryingQueue<SchedulePrepareResult>.Result {\n\n        AirshipLogger.trace(\"Resolving deferred \\(schedule.identifier)\")\n\n        let request = try await deferredRequest(deferred.url)\n\n        if let cached: AutomationSchedule.ScheduleData = await retryState.value(key: Self.deferredResultKey) {\n            AirshipLogger.trace(\"Deferred resolved from cache \\(schedule.identifier)\")\n            return try await onResult(cached)\n        }\n\n        let result: AirshipDeferredResult<DeferredScheduleResult> = await deferredResolver.resolve(request: request) { data in\n            return try JSONDecoder().decode(DeferredScheduleResult.self, from: data)\n        }\n\n        AirshipLogger.trace(\"Deferred result \\(schedule.identifier) \\(result)\")\n\n        switch (result) {\n        case .success(let result):\n            if (result.isAudienceMatch) {\n                switch (deferred.type) {\n                case .actions:\n                    guard let actions = result.actions else {\n                        AirshipLogger.error(\"Failed to get result for deferred.\")\n                        return .retry\n                    }\n                    return try await onResult(.actions(actions))\n                case .inAppMessage:\n                    guard var message = result.message else {\n                        AirshipLogger.error(\"Failed to get result for deferred.\")\n                        return .retry\n                    }\n                    message.source = .remoteData\n                    return try await onResult(.inAppMessage(message))\n                }\n            } else {\n                return .success(\n                    result: schedule.audienceMissBehaviorResult,\n                    ignoreReturnOrder: true\n                )\n            }\n        case .timedOut:\n            if (deferred.retryOnTimeOut != false) {\n                return .retry\n            }\n            return .success(result: .penalize, ignoreReturnOrder: true)\n        case .outOfDate:\n            await self.remoteDataAccess.notifyOutdated(schedule: schedule)\n            return .success(result: .invalidate)\n        case .notFound:\n            await self.remoteDataAccess.notifyOutdated(schedule: schedule)\n            return .success(result: .invalidate)\n        case .retriableError(retryAfter: let retryAfter, statusCode: _):\n            if let retryAfter {\n                return .retryAfter(retryAfter)\n            } else {\n                return .retry\n            }\n#if canImport(AirshipCore)\n        @unknown default:\n            // Not possible\n            return .retry\n#endif\n        }\n    }\n}\n\nfileprivate extension AutomationSchedule {\n    var audienceMissBehaviorResult: SchedulePrepareResult {\n        if let compoundAudience {\n            return compoundAudience.missBehavior.schedulePrepareResult\n        } else if let audienceMiss = audience?.missBehavior {\n            return audienceMiss.schedulePrepareResult\n        } else {\n            return .penalize\n        }\n    }\n\n    var evaluateExperiments: Bool {\n        return self.isInAppMessageType && self.bypassHoldoutGroups != true\n    }\n}\n\nfileprivate actor Queues {\n    var queues: [String: RetryingQueue<SchedulePrepareResult>] = [:]\n    lazy var defaultQueue: RetryingQueue<SchedulePrepareResult>  = {\n        return RetryingQueue(\n            id: \"default\",\n            config: config.remoteConfig.iaaConfig?.retryingQueue\n        )\n    }()\n    private let config: RuntimeConfig\n\n    @MainActor\n    init(config: RuntimeConfig) {\n        self.config = config\n    }\n    \n    func queue(id: String?) -> RetryingQueue<SchedulePrepareResult> {\n        guard let id, !id.isEmpty else {\n            return defaultQueue\n        }\n\n        if let queue = queues[id] {\n            return queue\n        }\n\n        let queue: RetryingQueue<SchedulePrepareResult> = RetryingQueue(\n            id: id,\n            config: config.remoteConfig.iaaConfig?.retryingQueue\n        )\n        queues[id] = queue\n        return queue\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationScheduleData.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nimport Foundation\n\nstruct AutomationScheduleData: Sendable, Equatable, CustomDebugStringConvertible {\n    var schedule: AutomationSchedule\n    var scheduleState: AutomationScheduleState\n\n    /// The last time the `schedule` field was updated.\n    var lastScheduleModifiedDate: Date\n\n    var scheduleStateChangeDate: Date\n    var executionCount: Int\n    var triggerInfo: TriggeringInfo?\n    var preparedScheduleInfo: PreparedScheduleInfo?\n    var associatedData: Data?\n    var triggerSessionID: String\n\n    var debugDescription: String {\n        return \"AutomationSchedule(id: \\(schedule.identifier), state: \\(scheduleState))\"\n    }\n}\n\nextension AutomationScheduleData {\n    func isInState(_ state: [AutomationScheduleState]) -> Bool {\n        return state.contains(self.scheduleState)\n    }\n\n    func isActive(date: Date) -> Bool {\n        guard !self.isExpired(date: date) else { return false }\n        guard let start = self.schedule.start else { return true }\n        return date >= start\n    }\n\n    func isExpired(date: Date) -> Bool {\n        guard let end = self.schedule.end else { return false }\n        return end <= date\n    }\n\n    var isOverLimit: Bool {\n        // 0 means no limit\n        guard self.schedule.limit != 0 else { return false }\n        return (self.schedule.limit ?? 1) <= self.executionCount\n    }\n\n    private mutating func setState(_ state: AutomationScheduleState, date: Date) {\n        guard scheduleState != state else { return }\n        self.scheduleState = state\n        self.scheduleStateChangeDate = date\n    }\n\n    mutating func finished(date: Date) {\n        self.setState(.finished, date: date)\n        self.preparedScheduleInfo = nil\n        self.triggerInfo = nil\n    }\n\n    mutating func idle(date: Date) {\n        self.setState(.idle, date: date)\n        self.preparedScheduleInfo = nil\n        self.triggerInfo = nil\n    }\n\n    mutating func paused(date: Date) {\n        self.setState(.paused, date: date)\n        self.preparedScheduleInfo = nil\n        self.triggerInfo = nil\n    }\n\n    mutating func updateState(date: Date) {\n        if isOverLimit || isExpired(date: date) {\n            finished(date: date)\n        } else if isInState([.finished]) {\n            self.idle(date: date)\n        }\n    }\n\n    mutating func prepareCancelled(date: Date, penalize: Bool) {\n        guard self.isInState([.triggered]) else {\n            return\n        }\n\n        if (penalize) {\n            self.executionCount += 1\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        idle(date: date)\n    }\n\n    mutating func prepareInterrupted(date: Date) {\n        guard self.isInState([.prepared, .triggered]) else {\n            return\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        setState(.triggered, date: date)\n    }\n\n    mutating func prepared(info: PreparedScheduleInfo, date: Date) {\n        guard self.isInState([.triggered]) else {\n            return\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        self.preparedScheduleInfo = info\n        self.setState(.prepared, date: date)\n    }\n\n    mutating func executionCancelled(date: Date) {\n        guard self.isInState([.prepared]) else {\n            return\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        idle(date: date)\n    }\n\n    mutating func executionSkipped(date: Date) {\n        guard self.isInState([.prepared]) else {\n            return\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        if self.schedule.interval != nil {\n            paused(date: date)\n        } else {\n            idle(date: date)\n        }\n    }\n\n    mutating func executionInvalidated(date: Date) {\n        guard self.isInState([.prepared]) else {\n            return\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        self.preparedScheduleInfo = nil\n        self.setState(.triggered, date: date)\n    }\n\n    mutating func executing(date: Date) {\n        guard self.isInState([.prepared]) else {\n            return\n        }\n\n        self.scheduleState = .executing\n        self.scheduleStateChangeDate = date\n    }\n\n    mutating func executionInterrupted(date: Date, retry: Bool) {\n        guard self.isInState([.executing]) else {\n            return\n        }\n\n        if (retry) {\n            guard !isOverLimit, !isExpired(date: date) else {\n                finished(date: date)\n                return\n            }\n\n            self.preparedScheduleInfo = nil\n            self.setState(.triggered, date: date)\n        } else {\n            finishedExecuting(date: date)\n        }\n    }\n\n    mutating func finishedExecuting(date: Date) {\n        guard self.isInState([.executing]) else {\n            return\n        }\n\n        self.executionCount += 1\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            finished(date: date)\n            return\n        }\n\n        if self.schedule.interval != nil {\n            paused(date: date)\n        } else {\n            idle(date: date)\n        }\n    }\n\n    func shouldDelete(date: Date) -> Bool {\n        guard self.scheduleState == .finished else { return false }\n        guard let editGracePeriod = self.schedule.editGracePeriodDays else {\n            return true\n        }\n\n        let timeSinceFinished = date.timeIntervalSince(self.scheduleStateChangeDate)\n        return timeSinceFinished >= Double(editGracePeriod * 86400) // days to seconds\n    }\n\n    mutating func triggered(\n        triggerInfo: TriggeringInfo,\n        date: Date\n    ) {\n        guard self.scheduleState == .idle else {\n            return\n        }\n\n        guard !isOverLimit, !isExpired(date: date) else {\n            self.finished(date: date)\n            return\n        }\n\n        self.triggerSessionID = UUID().uuidString\n        self.preparedScheduleInfo = nil\n        self.triggerInfo = triggerInfo\n        setState(.triggered, date: date)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationScheduleState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum AutomationScheduleState: String, Equatable, Sendable {\n\n    case idle\n    case triggered\n    case prepared\n    case executing\n\n    // interval\n    case paused\n\n    // waiting to be cleaned up after grace period\n    case finished\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/AutomationStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport CoreData\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol TriggerStoreProtocol: Sendable {\n    func getTrigger(scheduleID: String, triggerID: String) async throws -> TriggerData?\n    func upsertTriggers(_ triggers: [TriggerData]) async throws\n    func deleteTriggers(excludingScheduleIDs: Set<String>) async throws\n    func deleteTriggers(scheduleIDs: [String]) async throws\n    func deleteTriggers(scheduleID: String, triggerIDs: Set<String>) async throws\n}\n\nprotocol ScheduleStoreProtocol: Sendable {\n    func getSchedules() async throws -> [AutomationScheduleData]\n\n    @discardableResult\n    func updateSchedule(\n        scheduleID: String,\n        block: @escaping @Sendable (inout AutomationScheduleData) throws -> Void\n    ) async throws -> AutomationScheduleData?\n\n    @discardableResult\n    func updateSchedule(\n        scheduleData: AutomationScheduleData,\n        block: @escaping @Sendable (inout AutomationScheduleData) throws -> Void\n    ) async throws -> AutomationScheduleData?\n\n    @discardableResult\n    func upsertSchedules(\n        scheduleIDs: [String],\n        updateBlock: @Sendable @escaping (String, AutomationScheduleData?) throws -> AutomationScheduleData\n    ) async throws -> [AutomationScheduleData]\n\n    func deleteSchedules(scheduleIDs: [String]) async throws\n    func deleteSchedules(group: String) async throws\n\n    func getSchedule(scheduleID: String) async throws -> AutomationScheduleData?\n    func getSchedules(group: String) async throws -> [AutomationScheduleData]\n    func getSchedules(scheduleIDs: [String]) async throws -> [AutomationScheduleData]\n    func isCurrent(scheduleID: String, lastScheduleModifiedDate: Date, scheduleState: AutomationScheduleState) async throws -> Bool\n}\n\nactor AutomationStore: ScheduleStoreProtocol, TriggerStoreProtocol {\n    private let coreData: UACoreData?\n    private let inMemory: Bool\n    private let legacyStore: LegacyAutomationStore\n    private var migrationTask: Task<Void, any Error>?\n\n    init(appKey: String, inMemory: Bool = false) {\n        let modelURL = AirshipAutomationResources.bundle.url(\n            forResource: \"AirshipAutomation\",\n            withExtension:\"momd\"\n        )\n\n        self.coreData = if let modelURL = modelURL {\n           UACoreData(\n            name: \"AirshipAutomation\",\n                modelURL: modelURL,\n                inMemory: inMemory,\n                stores: [\"AirshipAutomation-\\(appKey).sqlite\"]\n            )\n        } else {\n            nil\n        }\n\n        self.inMemory = inMemory\n        self.legacyStore = LegacyAutomationStore(appKey: appKey, inMemory: inMemory)\n    }\n\n    init(config: RuntimeConfig) {\n        self.init(appKey: config.appCredentials.appKey)\n    }\n\n    func getSchedules() async throws -> [AutomationScheduleData] {\n        return try await prepareCoreData().performWithResult { context in\n            return try self.fetchSchedules(context: context)\n        }\n    }\n\n    @discardableResult\n    func updateSchedule(\n        scheduleID: String,\n        block: @escaping @Sendable (inout AutomationScheduleData) throws -> Void\n    ) async throws -> AutomationScheduleData? {\n        return try await prepareCoreData().performWithResult { context in\n\n            let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n            request.includesPropertyValues = true\n            request.predicate = NSPredicate(format: \"identifier == %@\", scheduleID)\n\n            guard let entity = try context.fetch(request).first else {\n                return nil\n            }\n\n            var data = try entity.toScheduleData()\n            try block(&data)\n            try entity.update(data: data)\n            return data\n        }\n    }\n\n    @discardableResult\n    func updateSchedule(\n        scheduleData: AutomationScheduleData,\n        block: @escaping @Sendable (inout AutomationScheduleData) throws -> Void\n    ) async throws -> AutomationScheduleData? {\n        return try await prepareCoreData().performWithResult { context in\n\n            let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n            request.includesPropertyValues = true\n            request.predicate = NSPredicate(format: \"identifier == %@\", scheduleData.schedule.identifier)\n\n            guard let entity = try context.fetch(request).first else {\n                return nil\n            }\n\n            var data = try entity.toScheduleData(existingData: scheduleData)\n            try block(&data)\n            try entity.update(data: data)\n            return data\n        }\n    }\n\n    @discardableResult\n    func upsertSchedules(\n        scheduleIDs: [String],\n        updateBlock: @Sendable @escaping (String, AutomationScheduleData?) throws -> AutomationScheduleData\n    ) async throws -> [AutomationScheduleData] {\n        return try await prepareCoreData().performWithResult { context in\n            let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n            request.includesPropertyValues = true\n            request.predicate = NSPredicate(format: \"identifier in %@\", scheduleIDs)\n\n            let entityMap = try context.fetch(request).reduce(into: [String: ScheduleEntity]()) {\n                $0[$1.identifier] = $1\n            }\n\n            var result: [AutomationScheduleData] = []\n\n            for identifier in scheduleIDs {\n                let existing: AutomationScheduleData? = if let entity = entityMap[identifier] {\n                    try entity.toScheduleData()\n                } else {\n                    nil\n                }\n                let data = try updateBlock(identifier, existing)\n                let entity = try (entityMap[identifier] ?? ScheduleEntity.make(context: context))\n\n                try entity.update(data: data)\n                result.append(data)\n            }\n            return result\n        }\n    }\n\n    func deleteSchedules(scheduleIDs: [String]) async throws {\n        return try await prepareCoreData().performWithResult { context in\n            let predicate = NSPredicate(format: \"identifier in %@\", scheduleIDs)\n            return try self.deleteSchedules(predicate: predicate, context: context)\n        }\n    }\n\n    func deleteSchedules(group: String) async throws {\n        return try await prepareCoreData().performWithResult { context in\n            let predicate = NSPredicate(format: \"group == %@\", group)\n            return try self.deleteSchedules(predicate: predicate, context: context)\n        }\n    }\n\n    func isCurrent(scheduleID: String, lastScheduleModifiedDate: Date, scheduleState: AutomationScheduleState) async throws -> Bool {\n        return try await prepareCoreData().performWithResult { context in\n            let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n            request.predicate = NSPredicate(format: \"identifier == %@\", scheduleID)\n            request.propertiesToFetch = [\"lastScheduleModifiedDate\", \"scheduleState\"]\n            request.includesPropertyValues = true\n\n            let entity = try context.fetch(request).first\n            return entity?.lastScheduleModifiedDate == lastScheduleModifiedDate &&  entity?.scheduleState == scheduleState.rawValue\n        }\n    }\n\n    func getSchedule(scheduleID: String) async throws -> AutomationScheduleData? {\n        return try await prepareCoreData().performWithResult { context in\n            let predicate = NSPredicate(format: \"identifier == %@\", scheduleID)\n            return try self.fetchSchedules(predicate: predicate, context: context).first\n        }\n    }\n\n    func getAssociatedData(scheduleID: String) async throws -> Data? {\n        return try await prepareCoreData().performWithResult { context in\n            let predicate = NSPredicate(format: \"identifier == %@\", scheduleID)\n\n            let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n            request.includesPropertyValues = true\n            request.predicate = predicate\n\n            return try context.fetch(request).first?.associatedData\n        }\n    }\n\n    func getSchedules(group: String) async throws -> [AutomationScheduleData] {\n        return try await prepareCoreData().performWithResult { context in\n            let predicate = NSPredicate(format: \"group == %@\", group)\n            return try self.fetchSchedules(predicate: predicate, context: context)\n        }\n    }\n\n    func getSchedules(scheduleIDs: [String]) async throws -> [AutomationScheduleData] {\n        return try await prepareCoreData().performWithResult { context in\n            let predicate = NSPredicate(format: \"identifier in %@\", scheduleIDs)\n            return try self.fetchSchedules(predicate: predicate, context: context)\n        }\n    }\n\n    func getTrigger(scheduleID: String, triggerID: String) async throws -> TriggerData? {\n        return try await prepareCoreData().performWithResult { context in\n            let request: NSFetchRequest<TriggerEntity> = TriggerEntity.fetchRequest()\n            request.predicate = NSPredicate(format: \"scheduleID == %@ AND triggerID == %@\", scheduleID, triggerID)\n            return try context.fetch(request).first?.toTriggerData()\n        }\n    }\n\n    func upsertTriggers(_ triggers: [TriggerData]) async throws {\n        guard !triggers.isEmpty else { return }\n        \n        let groupedTriggers = triggers.reduce(into: [String: [TriggerData]]()) { result, trigger in\n            var array = result[trigger.scheduleID] ?? []\n            array.append(trigger)\n            result[trigger.scheduleID] = array\n        }\n        \n        try await prepareCoreData().perform { context in\n            let request: NSFetchRequest<TriggerEntity> = TriggerEntity.fetchRequest()\n            \n            try groupedTriggers.forEach { scheduleID, triggers in\n                request.predicate = NSPredicate(format: \"scheduleID == %@ AND triggerID in %@\", scheduleID, triggers.map { $0.triggerID })\n\n                let entityMap = try context.fetch(request).reduce(into: [String: TriggerEntity]()) {\n                    $0[$1.triggerID] = $1\n                }\n\n                for trigger in triggers {\n                    let entity = try (entityMap[trigger.triggerID] ?? TriggerEntity.make(context: context))\n                    try entity.update(data: trigger)\n                }\n            }\n        }\n    }\n\n    func deleteTriggers(scheduleID: String, triggerIDs: Set<String>) async throws {\n        return try await prepareCoreData().perform { context in\n            let predicate = NSPredicate(format: \"(scheduleID == %@) AND (triggerID in %@)\", scheduleID, triggerIDs)\n            try self.deleteTriggers(predicate: predicate, context: context)\n        }\n    }\n\n    func deleteTriggers(excludingScheduleIDs: Set<String>) async throws {\n        return try await prepareCoreData().perform { context in\n            let predicate = NSPredicate(format: \"not (scheduleID in %@)\", excludingScheduleIDs)\n            try self.deleteTriggers(predicate: predicate, context: context)\n        }\n    }\n\n    func deleteTriggers(scheduleIDs: [String]) async throws {\n        try await prepareCoreData().perform { context in\n            let predicate = NSPredicate(format: \"scheduleID in %@\", scheduleIDs)\n            try self.deleteTriggers(predicate: predicate, context: context)\n        }\n    }\n\n    private nonisolated func deleteTriggers(\n        predicate: NSPredicate? = nil,\n        context: NSManagedObjectContext\n    ) throws  {\n        let request: NSFetchRequest<any NSFetchRequestResult> = TriggerEntity.fetchRequest()\n        request.predicate = predicate\n\n        if self.inMemory {\n            request.includesPropertyValues = false\n            let results = try context.fetch(request) as? [NSManagedObject]\n            results?.forEach(context.delete)\n        } else {\n            let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n            try context.execute(deleteRequest)\n        }\n    }\n\n    private nonisolated func fetchSchedules(\n        predicate: NSPredicate? = nil,\n        context: NSManagedObjectContext\n    ) throws -> [AutomationScheduleData] {\n        let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n        request.includesPropertyValues = true\n        request.predicate = predicate\n\n        return try context.fetch(request).map { entity in\n            try entity.toScheduleData()\n        }\n    }\n\n    private nonisolated func deleteSchedules(\n        predicate: NSPredicate? = nil,\n        context: NSManagedObjectContext\n    ) throws  {\n        let request = NSFetchRequest<any NSFetchRequestResult>(\n            entityName: ScheduleEntity.entityName\n        )\n        request.predicate = predicate\n\n        if self.inMemory {\n            request.includesPropertyValues = false\n            let schedules = try context.fetch(request) as? [NSManagedObject]\n            schedules?.forEach { schedule in\n                context.delete(schedule)\n            }\n        } else {\n            let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n            try context.execute(deleteRequest)\n        }\n    }\n\n    private func migrateData() async throws {\n        guard let coredata = self.coreData else {\n            throw AirshipErrors.error(\"Failed to create core data.\")\n        }\n        do {\n            if let migrationTask = migrationTask {\n                try await migrationTask.value\n                return\n            }\n        } catch {}\n\n        self.migrationTask = Task {\n            let legacyData = try await self.legacyStore.legacyScheduleData\n            guard !legacyData.isEmpty else { return }\n\n            let identifiers = legacyData.map { $0.scheduleData.schedule.identifier }\n\n            try await coredata.perform { context in\n                let request: NSFetchRequest<ScheduleEntity> = ScheduleEntity.fetchRequest()\n                request.includesPropertyValues = true\n                request.predicate = NSPredicate(format: \"identifier in %@\", identifiers)\n\n                guard try context.fetch(request).isEmpty else {\n                    // Migration already happened, probably failed to delete before\n                    return\n                }\n\n                do {\n                    for legacy in legacyData {\n                        let scheduleEntity = try ScheduleEntity.make(context: context)\n                        try scheduleEntity.update(data: legacy.scheduleData)\n\n                        for triggerData in legacy.triggerDatas {\n                            let triggerEntity = try TriggerEntity.make(context: context)\n                            try triggerEntity.update(data: triggerData)\n                        }\n                    }\n                } catch {\n                    context.rollback()\n                    throw error\n                }\n            }\n\n            do {\n                try await self.legacyStore.deleteAll()\n            } catch {\n                AirshipLogger.error(\"Failed to delete legacy store \\(error)\")\n            }\n        }\n\n        try await self.migrationTask?.value\n    }\n\n    func prepareCoreData() async throws -> UACoreData {\n        guard let coreData = coreData else {\n            throw AirshipErrors.error(\"Failed to create core data.\")\n        }\n\n        try await migrateData()\n        return coreData\n    }\n}\n\n\n@objc(UAScheduleEntity)\nfileprivate class ScheduleEntity: NSManagedObject {\n\n    static let entityName = \"UAScheduleEntity\"\n\n    @nonobjc class func fetchRequest<T>() -> NSFetchRequest<T> {\n        return NSFetchRequest<T>(entityName: ScheduleEntity.entityName)\n    }\n\n    @NSManaged var identifier: String\n    @NSManaged var group: String?\n    @NSManaged var schedule: Data\n    @NSManaged var scheduleState: String\n    @NSManaged var scheduleStateChangeDate: Date\n    @NSManaged var lastScheduleModifiedDate: Date?\n    @NSManaged var executionCount: Int\n    @NSManaged var triggerInfo: Data?\n    @NSManaged var preparedScheduleInfo: Data?\n    @NSManaged var triggerSessionID: String?\n    @NSManaged var associatedData: Data?\n\n    class func make(context: NSManagedObjectContext) throws -> Self {\n        guard let data = NSEntityDescription.insertNewObject(\n            forEntityName: ScheduleEntity.entityName,\n            into:context) as? Self\n        else {\n            throw AirshipErrors.error(\"Failed to make schedule entity\")\n        }\n\n        return data\n    }\n\n    func update(data: AutomationScheduleData) throws {\n        let encoder = JSONEncoder()\n        self.identifier = data.schedule.identifier\n        self.group = data.schedule.group\n        self.scheduleState = data.scheduleState.rawValue\n        self.scheduleStateChangeDate = data.scheduleStateChangeDate\n        self.executionCount = data.executionCount\n        self.triggerSessionID = data.triggerSessionID\n        self.associatedData = data.associatedData\n        self.lastScheduleModifiedDate = data.lastScheduleModifiedDate\n        self.schedule = try encoder.encode(data.schedule)\n\n        self.preparedScheduleInfo = if let info = data.preparedScheduleInfo {\n            try encoder.encode(info)\n        } else {\n            nil\n        }\n\n        self.triggerInfo = if let info = data.triggerInfo {\n            try encoder.encode(info)\n        } else {\n            nil\n        }\n\n    }\n\n    func toScheduleData(existingData: AutomationScheduleData? = nil) throws -> AutomationScheduleData {\n        let decoder = JSONDecoder()\n        let existingScheduleMatch = existingData?.scheduleStateChangeDate == self.scheduleStateChangeDate\n        let schedule: AutomationSchedule = if let existingData, existingScheduleMatch {\n            existingData.schedule\n        } else {\n            try decoder.decode(AutomationSchedule.self, from: self.schedule)\n        }\n\n        let triggerInfo: TriggeringInfo? = if let data = self.triggerInfo {\n            try decoder.decode(TriggeringInfo.self, from: data)\n        } else {\n            nil\n        }\n\n        let preparedScheduleInfo: PreparedScheduleInfo? = if let data = self.preparedScheduleInfo {\n            try decoder.decode(PreparedScheduleInfo.self, from: data)\n        } else {\n            nil\n        }\n\n        guard let scheduleState = AutomationScheduleState(rawValue: self.scheduleState) else {\n            throw AirshipErrors.error(\"Invalid schedule state \\(self.scheduleState)\")\n        }\n\n        return AutomationScheduleData(\n            schedule: schedule,\n            scheduleState: scheduleState,\n            lastScheduleModifiedDate: self.lastScheduleModifiedDate ?? .distantPast,\n            scheduleStateChangeDate: self.scheduleStateChangeDate,\n            executionCount: executionCount,\n            triggerInfo: triggerInfo,\n            preparedScheduleInfo: preparedScheduleInfo,\n            associatedData: associatedData,\n            triggerSessionID: self.triggerSessionID ?? UUID().uuidString\n        )\n    }\n}\n\n\n@objc(UATriggerEntity)\nfileprivate class TriggerEntity: NSManagedObject {\n    static let entityName = \"UATriggerEntity\"\n\n    @nonobjc class func fetchRequest() -> NSFetchRequest<TriggerEntity> {\n        return NSFetchRequest<TriggerEntity>(entityName: Self.entityName)\n    }\n\n    @NSManaged var state: Data\n    @NSManaged var scheduleID: String\n    @NSManaged var triggerID: String\n\n    class func make(context: NSManagedObjectContext) throws -> Self {\n        guard let result = NSEntityDescription.insertNewObject(\n            forEntityName: Self.entityName,\n            into:context) as? Self\n        else {\n            throw AirshipErrors.error(\"Failed to make schedule entity\")\n        }\n\n        return result\n    }\n\n    func update(data: TriggerData) throws {\n        self.triggerID = data.triggerID\n        self.scheduleID = data.scheduleID\n        self.state = try JSONEncoder().encode(data)\n    }\n\n    func toTriggerData() throws -> TriggerData {\n        try JSONDecoder().decode(TriggerData.self, from: self.state)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/ExecutionWindowProcessor.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nimport Foundation\n\n\nprotocol ExecutionWindowProcessorProtocol: Actor {\n    func process(window: ExecutionWindow) async throws\n    @MainActor\n    func isActive(window: ExecutionWindow) -> Bool\n}\n\nactor ExecutionWindowProcessor: ExecutionWindowProcessorProtocol {\n    private let taskSleeper: any AirshipTaskSleeper\n    private let date: any AirshipDateProtocol\n    private let onEvaluate: @Sendable (ExecutionWindow, Date) throws -> ExecutionWindowResult\n\n    private var sleepTasks: [String: Task<Void, any Error>] = [:]\n\n    init(\n        taskSleeper: any AirshipTaskSleeper,\n        date: any AirshipDateProtocol,\n        notificationCenter: NotificationCenter = NotificationCenter.default,\n        onEvaluate: @escaping @Sendable (ExecutionWindow, Date) throws -> ExecutionWindowResult = { window, date in\n            try window.nextAvailability(date: date)\n        }\n    ) {\n        self.taskSleeper = taskSleeper\n        self.date = date\n        self.onEvaluate = onEvaluate\n\n        notificationCenter.addObserver(forName: .NSSystemTimeZoneDidChange, object: nil, queue: nil) { [weak self] _ in\n            Task { [weak self] in\n                await self?.timeZoneChanged()\n            }\n        }\n    }\n\n    private func timeZoneChanged() {\n        self.sleepTasks.values.forEach { $0.cancel() }\n    }\n\n    private func sleep(delay: TimeInterval) async {\n        let id = UUID().uuidString\n\n        let sleepTask = Task {\n            try await self.taskSleeper.sleep(timeInterval: delay)\n        }\n\n        sleepTasks[id] = sleepTask\n        try? await sleepTask.value\n        sleepTasks[id] = nil\n    }\n\n\n    private nonisolated func nextAvailability(window: ExecutionWindow) -> ExecutionWindowResult {\n        do {\n            return try onEvaluate(window, date.now)\n        } catch {\n            // We failed to process the window, use a long retry to prevent it from\n            // busy waiting\n            AirshipLogger.error(\"Failed to process execution window \\(error)\")\n            return .retry(60 * 60 * 24)\n        }\n    }\n\n    @MainActor\n    func process(window: ExecutionWindow) async {\n        while case .retry(let delay) = nextAvailability(window: window) {\n            if Task.isCancelled { return }\n            await sleep(delay: delay)\n            if Task.isCancelled { return }\n        }\n    }\n\n    @MainActor\n    func isActive(window: ExecutionWindow) -> Bool {\n        return nextAvailability(window: window) == .now\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/LegacyAutomationStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport CoreData\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nactor LegacyAutomationStore {\n    private let coreData: UACoreData?\n\n    init(appKey: String, inMemory: Bool = false) {\n        let modelURL = AirshipAutomationResources.bundle.url(\n            forResource: \"UAAutomation\",\n            withExtension:\"momd\"\n        )\n\n        self.coreData = if let modelURL = modelURL {\n            UACoreData(\n                name: \"UAAutomation\",\n                modelURL: modelURL,\n                inMemory: inMemory,\n                stores: [\"Automation-\\(appKey).sqlite\", \"In-app-automation-\\(appKey).sqlite\"]\n            )\n        } else {\n            nil\n        }\n    }\n\n    var legacyScheduleData: [LegacyScheduleData] {\n        get async throws {\n            return try await requireCoreData().performWithNullableResult(skipIfStoreNotCreated: true) { context in\n                let request: NSFetchRequest<UAScheduleData> = NSFetchRequest(entityName: \"UAScheduleData\")\n                request.includesPropertyValues = true\n\n                return try context.fetch(request).compactMap { entity in\n                    do {\n                        try entity.migrateData()\n                        return try entity.convert()\n                    } catch {\n                        AirshipLogger.error(\"Failed to convert schedule \\(entity) error \\(error)\")\n                    }\n                    return nil\n                }\n            } ?? []\n        }\n    }\n\n    func deleteAll() async throws {\n        try await self.requireCoreData().deleteStoresOnDisk()\n    }\n\n    private func requireCoreData() throws -> UACoreData {\n        guard let coreData = coreData else {\n            throw AirshipErrors.error(\"Failed to create core data.\")\n        }\n        return coreData\n    }\n}\n\nstruct LegacyScheduleData: Equatable, Sendable {\n    var scheduleData: AutomationScheduleData\n    var triggerDatas: [TriggerData]\n}\n\nfileprivate enum UAScheduleDelayAppState: Int {\n    case any\n    case foreground\n    case background\n}\n\nfileprivate enum UAScheduleState: Int {\n    case idle = 0\n    case timeDelayed = 5\n    case waitingScheduleConditions = 1\n    case preparingSchedule = 6\n    case executing = 2\n    case paused = 3\n    case finished = 4\n}\n\nfileprivate enum UAScheduleType: UInt {\n    case inAppMessage = 0\n    case actions = 1\n    case deferred = 2\n}\n\n@objc(UAScheduleData)\nfileprivate class UAScheduleData: NSManagedObject {\n    @NSManaged var identifier: String\n    @NSManaged var group: String?\n    @NSManaged var limit: NSNumber?\n    @NSManaged var triggeredCount: NSNumber?\n    @NSManaged var data: String\n    @NSManaged var metadata: String?\n    @NSManaged var dataVersion: NSNumber\n    @NSManaged var priority: NSNumber?\n    @NSManaged var triggers: Set<UAScheduleTriggerData>?\n    @NSManaged var start: Date?\n    @NSManaged var end: Date?\n    @NSManaged var delay: UAScheduleDelayData?\n    @NSManaged var executionState: NSNumber?\n    @NSManaged var executionStateChangeDate: Date?\n    @NSManaged var delayedExecutionDate: Date?\n    @NSManaged var editGracePeriod: NSNumber?\n    @NSManaged var interval: NSNumber?\n    @NSManaged var type: NSNumber?\n    @NSManaged var audience: String?\n    @NSManaged var campaigns: NSDictionary?\n    @NSManaged var reportingContext: NSDictionary?\n    @NSManaged var frequencyConstraintIDs: [String]?\n    @NSManaged var triggeredTime: Date?\n    @NSManaged var messageType: String?\n    @NSManaged var bypassHoldoutGroups: NSNumber?\n    @NSManaged var isNewUserEvaluationDate: Date?\n    @NSManaged var productId: String?\n}\n\n@objc(UAScheduleTriggerData)\nfileprivate class UAScheduleTriggerData: NSManagedObject {\n    @NSManaged var goal: NSNumber\n    @NSManaged var goalProgress: NSNumber?\n    @NSManaged var predicateData: Data?\n    @NSManaged var type: NSNumber\n    @NSManaged var schedule: UAScheduleData?\n    @NSManaged var delay: UAScheduleDelayData?\n    @NSManaged var start: Date?\n}\n\n@objc(UAScheduleDelayData)\nfileprivate class UAScheduleDelayData: NSManagedObject {\n    @NSManaged var seconds: NSNumber?\n    @NSManaged var screens: String?\n    @NSManaged var regionID: String?\n    @NSManaged var appState: NSNumber?\n    @NSManaged var schedule: UAScheduleData?\n    @NSManaged var cancellationTriggers: Set<UAScheduleTriggerData>?\n}\n\nfileprivate enum UAScheduleTriggerType: Int {\n    case appForeground\n    case appBackground\n    case regionEnter\n    case regionExit\n    case customEventCount\n    case customEventValue\n    case screen\n    case appInit\n    case activeSession\n    case version\n    case featureFlagInterracted\n}\n\n\nfileprivate extension UAScheduleDelayData {\n    func convert(\n        scheduleID: String\n    ) throws -> (delay: AutomationDelay, triggerData: [TriggerData]) {\n\n        var screens: [String]?\n        let json = try AirshipJSON.from(json: self.screens)\n        if json.isString, let string = json.unWrap() as? String {\n            screens = [string]\n        } else if json.isArray, let strings = json.unWrap() as? [String] {\n            screens = strings\n        }\n\n        var appState: AutomationAppState? = nil\n        if let rawValue = self.appState?.intValue, let parsed = UAScheduleDelayAppState(rawValue: rawValue) {\n            appState = switch(parsed) {\n            case .any: nil\n            case .background: .background\n            case .foreground: .foreground\n            }\n        }\n\n        let cancellationTriggerData = try self.cancellationTriggers?.map { data in\n            try data.convert(scheduleID: scheduleID, executionType: .delayCancellation)\n        } ?? []\n\n        let delay = AutomationDelay(\n            seconds: self.seconds?.doubleValue,\n            screens: screens,\n            regionID: self.regionID,\n            appState: appState,\n            cancellationTriggers: cancellationTriggerData.map { $0.trigger }\n        )\n\n        return (delay, cancellationTriggerData.map { $0.triggerData })\n    }\n}\n\nfileprivate extension UAScheduleTriggerData {\n    func convert(\n        scheduleID: String,\n        executionType:  TriggerExecutionType\n    ) throws -> (trigger: AutomationTrigger, triggerData: TriggerData) {\n\n        let decoder = JSONDecoder()\n\n        let predicate: JSONPredicate? = if let data = self.predicateData {\n            try decoder.decode(JSONPredicate.self, from: data)\n        } else {\n            nil\n        }\n\n        guard let legacyType = UAScheduleTriggerType(rawValue: self.type.intValue) else {\n            throw AirshipErrors.error(\"Invalid type \\(self.type)\")\n        }\n\n        let type: EventAutomationTriggerType = switch(legacyType) {\n        case .appForeground: .foreground\n        case .appBackground: .background\n        case .regionEnter: .regionEnter\n        case .regionExit: .regionExit\n        case .customEventCount: .customEventCount\n        case .customEventValue: .customEventValue\n        case .screen: .screen\n        case .appInit: .appInit\n        case .activeSession: .activeSession\n        case .version: .version\n        case .featureFlagInterracted: .featureFlagInteraction\n        }\n\n        var trigger = EventAutomationTrigger(\n            type: type,\n            goal: self.goal.doubleValue,\n            predicate: predicate\n        )\n        trigger.backfillIdentifier(executionType: executionType)\n        \n\n        let triggerData = TriggerData(\n            scheduleID: scheduleID,\n            triggerID: trigger.id,\n            count: self.goalProgress?.doubleValue ?? 0\n        )\n\n        return (.event(trigger), triggerData)\n    }\n}\n\nfileprivate extension UAScheduleData {\n\n    struct LegacyKeys {\n        static let displayType = \"display_type\"\n        static let display = \"display\"\n        static let audience = \"audience\"\n        static let source = \"source\"\n        static let duration = \"duration\"\n    }\n\n    func convert() throws -> LegacyScheduleData {\n        guard\n            let rawType = self.type?.uintValue,\n            let type = UAScheduleType(rawValue: rawType)\n        else {\n            throw AirshipErrors.error(\"Failed to convert message, invalid type: \\(String(describing: self.type))\")\n        }\n\n        guard\n            let data = self.data.data(using: .utf8)\n        else {\n            throw AirshipErrors.error(\"Unable to parse data\")\n        }\n\n        let decoder = JSONDecoder()\n\n        let scheduleData: AutomationSchedule.ScheduleData = switch(type) {\n        case .inAppMessage:\n                .inAppMessage(try decoder.decode(InAppMessage.self, from: data))\n        case .actions:\n                .actions(try decoder.decode(AirshipJSON.self, from: data))\n        case .deferred:\n                .deferred(try decoder.decode(DeferredAutomationData.self, from: data))\n        }\n\n        var audience: AutomationAudience?\n        if let data = self.audience?.data(using: .utf8) {\n            audience = try decoder.decode(AutomationAudience.self, from: data)\n        }\n\n        var editGracePeriodDays: UInt?\n        if let period = self.editGracePeriod?.doubleValue {\n            editGracePeriodDays = UInt(period / (24 * 60 * 60)) // convert to days\n        }\n\n        let executionTriggers = try self.triggers?.map { data in\n            try data.convert(scheduleID: self.identifier, executionType: .execution)\n        } ?? []\n\n        let delayData = try self.delay?.convert(scheduleID: self.identifier)\n\n        let schedule: AutomationSchedule = AutomationSchedule(\n            identifier: self.identifier,\n            data: scheduleData,\n            triggers: executionTriggers.map { $0.trigger },\n            created: self.isNewUserEvaluationDate,\n            group: self.group,\n            priority: self.priority?.intValue,\n            limit: self.limit?.uintValue,\n            start: self.start,\n            end: self.end,\n            audience: audience,\n            delay: delayData?.delay,\n            interval: self.interval?.doubleValue,\n            bypassHoldoutGroups: self.bypassHoldoutGroups?.boolValue,\n            editGracePeriodDays: editGracePeriodDays,\n            metadata: try AirshipJSON.from(json: self.metadata),\n            campaigns: self.campaigns == nil ? nil : try AirshipJSON.wrap(self.campaigns),\n            reportingContext: self.reportingContext == nil ? nil : try AirshipJSON.wrap(self.reportingContext),\n            productID: self.productId,\n            frequencyConstraintIDs: self.frequencyConstraintIDs,\n            messageType: self.messageType\n        )\n\n        var scheduleState: AutomationScheduleState = .idle\n        if let rawValue = self.executionState?.intValue, let parsed = UAScheduleState(rawValue: rawValue) {\n            scheduleState = switch(parsed) {\n            case .idle: .idle\n            case .timeDelayed: .prepared\n            case .waitingScheduleConditions: .prepared\n            case .preparingSchedule: .triggered\n            case .executing: .executing\n            case .paused: .paused\n            case .finished: .finished\n            }\n        }\n\n        var preparedInfo: PreparedScheduleInfo?\n        if scheduleState == .prepared || scheduleState == .executing {\n            preparedInfo = PreparedScheduleInfo(\n                scheduleID: schedule.identifier,\n                productID: schedule.productID,\n                campaigns: schedule.campaigns,\n                reportingContext: schedule.reportingContext,\n                triggerSessionID: UUID().uuidString,\n                priority: schedule.priority ?? 0\n            )\n        }\n\n        var triggerInfo: TriggeringInfo?\n        if scheduleState == .prepared || scheduleState == .executing || scheduleState == .executing {\n            triggerInfo = TriggeringInfo(\n                context: nil,\n                date: self.triggeredTime ?? self.executionStateChangeDate ?? Date.distantPast\n            )\n        }\n\n        let automationScheduleData = AutomationScheduleData(\n            schedule: schedule,\n            scheduleState: scheduleState,\n            lastScheduleModifiedDate: AirshipDate().now,\n            scheduleStateChangeDate: self.executionStateChangeDate ?? Date.distantPast,\n            executionCount: self.triggeredCount?.intValue ?? 0,\n            triggerInfo: triggerInfo,\n            preparedScheduleInfo: preparedInfo,\n            associatedData: nil,\n            triggerSessionID: UUID().uuidString\n        )\n\n        return LegacyScheduleData(\n            scheduleData: automationScheduleData,\n            triggerDatas: (delayData?.triggerData ?? []) + executionTriggers.map { $0.triggerData }\n        )\n    }\n\n\n    func migrateData() throws {\n        let version = self.dataVersion\n\n        if (version != 3) {\n            guard var json = AirshipJSONUtils.object(self.data) as? [String: Any] else {\n                return\n            }\n            switch(version) {\n            case 0:\n                try perform0To1DataMigration(json: &json)\n                fallthrough\n            case 1:\n                try perform1To2DataMigration(json: &json)\n                fallthrough\n            case 2:\n                try perform2To3DataMigration(json: &json)\n                break\n            default:\n                break\n            }\n\n            self.data = try AirshipJSONUtils.string(json, options: .fragmentsAllowed)\n        }\n\n    }\n\n    // migrate duration from milliseconds to seconds\n    private func perform0To1DataMigration(json: inout [String: Any]) throws {\n        guard \n            json[LegacyKeys.displayType] as? String == \"banner\",\n            var display = json[LegacyKeys.display] as? [String: Any],\n            let duration = display[LegacyKeys.duration] as? Double\n        else {\n            return\n        }\n\n        display[LegacyKeys.duration] = duration / 1000.0\n        json[LegacyKeys.display] = display\n    }\n\n    // some remote-data schedules had their source field set incorrectly to app-defined by faulty edit code\n    // this code migrates all app-defined sources to remote-data\n    private func perform1To2DataMigration(json: inout [String: Any]) throws {\n        if let source = json[LegacyKeys.source] as? String, source == InAppMessageSource.appDefined.rawValue {\n            json[LegacyKeys.source] = InAppMessageSource.remoteData.rawValue\n        }\n    }\n\n    // move scheduleData.message.audience to scheduleData.audience\n    // use message ID as schedule ID\n    // set the schedule type\n    private func perform2To3DataMigration(json: inout [String: Any]) throws {\n        if json[LegacyKeys.displayType] != nil && json[LegacyKeys.display] != nil {\n            self.type = NSNumber(value: UAScheduleType.inAppMessage.rawValue)\n\n            // Audience\n            if let audience = json[LegacyKeys.audience] {\n                self.audience = try AirshipJSON.wrap(audience).toString()\n            }\n\n            // If source is not app defined, set the group (message ID) as the ID\n            if let source = json[LegacyKeys.source] as? String, let group = self.group {\n                if source == InAppMessageSource.appDefined.rawValue {\n                    self.identifier = UUID().uuidString\n                } else {\n                    self.identifier = group\n                }\n            }\n        } else {\n            self.type = NSNumber(value: UAScheduleType.actions.rawValue)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/PreparedSchedule.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// A prepared schedule\nstruct PreparedSchedule: Sendable {\n    let info: PreparedScheduleInfo\n    let data: PreparedScheduleData\n    let frequencyChecker: (any FrequencyCheckerProtocol)?\n}\n\n\n/// Persisted info for a schedule that has been prepared for execution\nstruct PreparedScheduleInfo: Codable, Equatable {\n    var scheduleID: String\n    var productID: String?\n    var campaigns: AirshipJSON?\n    var contactID: String?\n    var experimentResult: ExperimentResult?\n    var reportingContext: AirshipJSON?\n    var triggerSessionID: String\n    var additionalAudienceCheckResult: Bool\n    var priority: Int\n\n    init(\n        scheduleID: String,\n        productID: String? = nil,\n        campaigns: AirshipJSON? = nil,\n        contactID: String? = nil,\n        experimentResult: ExperimentResult? = nil,\n        reportingContext: AirshipJSON? = nil,\n        triggerSessionID: String,\n        additionalAudienceCheckResult: Bool = true,\n        priority: Int\n    ) {\n        self.scheduleID = scheduleID\n        self.productID = productID\n        self.campaigns = campaigns\n        self.contactID = contactID\n        self.experimentResult = experimentResult\n        self.reportingContext = reportingContext\n        self.triggerSessionID = triggerSessionID\n        self.additionalAudienceCheckResult = additionalAudienceCheckResult\n        self.priority = priority\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.scheduleID = try container.decode(String.self, forKey: .scheduleID)\n        self.productID = try container.decodeIfPresent(String.self, forKey: .productID)\n        self.campaigns = try container.decodeIfPresent(AirshipJSON.self, forKey: .campaigns)\n        self.contactID = try container.decodeIfPresent(String.self, forKey: .contactID)\n        self.experimentResult = try container.decodeIfPresent(ExperimentResult.self, forKey: .experimentResult)\n        self.reportingContext = try container.decodeIfPresent(AirshipJSON.self, forKey: .reportingContext)\n        self.triggerSessionID = try container.decodeIfPresent(String.self, forKey: .triggerSessionID) ?? UUID().uuidString\n        self.additionalAudienceCheckResult = try container.decodeIfPresent(Bool.self, forKey: .additionalAudienceCheckResult) ?? true\n        self.priority = try container.decodeIfPresent(Int.self, forKey: .priority) ?? 0\n    }\n}\n\n/// Prepared schedule data\nenum PreparedScheduleData: Equatable {\n    case inAppMessage(PreparedInAppMessageData)\n    case actions(AirshipJSON)\n\n    public static func == (lhs: PreparedScheduleData, rhs: PreparedScheduleData) -> Bool {\n        switch lhs {\n        case  .actions(let lhsJson):\n            switch rhs {\n            case .actions(let rhsJson): return lhsJson == rhsJson\n            default: return false\n            }\n        case .inAppMessage(let lhsMessageData):\n            switch rhs {\n            case .inAppMessage(let rhsMessageData):\n                return rhsMessageData.message == lhsMessageData.message\n            default: return false\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/ScheduleExecuteResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Schedule execute result\nenum ScheduleExecuteResult: Sendable {\n    case cancel\n    case finished\n    case retry\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/SchedulePrepareResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Schedule prepare result\nenum SchedulePrepareResult: Sendable, CustomStringConvertible {\n    case prepared(PreparedSchedule)\n    case cancel\n    case invalidate\n    case skip\n    case penalize\n\n    var description: String {\n        switch(self) {\n        case .prepared: \"prepared\"\n        case .cancel: \"cancel\"\n        case .invalidate: \"invalidate\"\n        case .skip: \"skip\"\n        case .penalize: \"penalize\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/ScheduleReadyResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Schedule ready result\nenum ScheduleReadyResult: Sendable {\n    case ready\n    case invalidate\n    case notReady\n    case skip\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/TriggerProcessor/AutomationTriggerProcessor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol AutomationTriggerProcessorProtocol: Sendable {\n    @MainActor\n    func setPaused(_ paused: Bool)\n    \n    var triggerResults: AsyncStream<TriggerResult> { get async }\n\n    func processEvent(\n        _ event: AutomationEvent\n    ) async\n\n    func restoreSchedules(\n        _ datas: [AutomationScheduleData]\n    ) async throws\n\n    func updateSchedules(\n        _ datas: [AutomationScheduleData]\n    ) async\n\n    func updateScheduleState(\n        scheduleID: String,\n        state: AutomationScheduleState\n    ) async throws\n\n    /// Cancels/deletes all data for the given schedule ids\n    func cancel(scheduleIDs: [String]) async\n\n    /// Cancels/deletes all data for the given group\n    func cancel(group: String) async\n}\n\nfinal actor AutomationTriggerProcessor: AutomationTriggerProcessorProtocol {\n    let store: any TriggerStoreProtocol\n    private let date: any AirshipDateProtocol\n    private let eventsHistory: any AutomationEventsHistory\n    private let stream: AsyncStream<TriggerResult>\n    private let continuation: AsyncStream<TriggerResult>.Continuation\n    \n    @MainActor private var isPaused = false\n    \n    // scheduleID to [PreparedTriggers]\n    private var preparedTriggers: [String: [PreparedTrigger]] = [:]\n\n    /// scheduleID to group\n    private var scheduleGroups: [String: String] = [:]\n\n    private var appSessionState: TriggerableState?\n    \n    init(\n        store: any TriggerStoreProtocol,\n        history: any AutomationEventsHistory,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.store = store\n        self.date = date\n        self.eventsHistory = history\n        (self.stream, self.continuation) = AsyncStream<TriggerResult>.airshipMakeStreamWithContinuation()\n    }\n\n    @MainActor\n    func setPaused(_ paused: Bool) {\n        self.isPaused = paused\n    }\n\n    var triggerResults: AsyncStream<TriggerResult> {\n        return self.stream\n    }\n\n    // check triggers for events\n    func processEvent(_ event: AutomationEvent) async {\n        await ingest(event: event, triggers: preparedTriggers.values.flatMap(\\.self))\n    }\n    \n    private func ingest(event: AutomationEvent, triggers: [PreparedTrigger], isReplay: Bool = false) async {\n        if !isReplay {\n            //save current app state\n            self.trackStateChange(event: event)\n        }\n        \n        guard await self.isPaused == false else { return }\n        \n        var results = triggers.compactMap { item in\n            item.process(event: event)\n        }\n        \n        results.sort { left, right in\n            left.priority < right.priority\n        }\n        \n        results.forEach { result in\n            if let triggerResult = result.triggerResult {\n                self.continuation.yield(triggerResult)\n            }\n        }\n        \n        let triggerDatas = results.map { $0.triggerData }\n        do {\n            try await self.store.upsertTriggers(triggerDatas)\n        } catch {\n            AirshipLogger.error(\n                \"Failed to save tigger data \\(triggerDatas) error \\(error)\"\n            )\n        }\n    }\n    \n    /// Called once to update all schedules from the DB.\n    func restoreSchedules(_ datas: [AutomationScheduleData]) async throws {\n        await updateSchedules(datas)\n        let activeSchedules = Set(datas.map({ $0.schedule.identifier }))\n        try await self.store.deleteTriggers(excludingScheduleIDs: activeSchedules)\n    }\n\n    /// Called whenever the schedules are updated\n    func updateSchedules(_ datas: [AutomationScheduleData]) async {\n        /// Sort  by priority so wheen we restore the triggers we get events in order\n        let sorted = datas.sorted(\n            by: { l, r in\n                (l.schedule.priority ?? 0) < (r.schedule.priority ?? 0)\n            }\n        )\n        \n        var allNewTriggers: [PreparedTrigger] = []\n\n        for data in sorted {\n            let schedule = data.schedule\n\n            scheduleGroups[schedule.identifier] = schedule.group\n\n            var new: [PreparedTrigger] = []\n            let old = self.preparedTriggers[data.schedule.identifier] ?? []\n\n            for trigger in data.schedule.triggers {\n                let existing = old.first(\n                    where: { $0.trigger.id == trigger.id }\n                )\n\n                if let existing = existing {\n                    existing.update(\n                        trigger: trigger,\n                        startDate: data.schedule.start,\n                        endDate: data.schedule.end,\n                        priority: data.schedule.priority ?? 0\n                    )\n                    new.append(existing)\n                } else {\n                    AirshipLogger.debug(\n                        \"New execution trigger for schedule \\(schedule.identifier) id \\(trigger.id) type \\(trigger.type) — no existing match, count starts from store or zero\"\n                    )\n                    let prepared = await makePreparedTrigger(\n                        schedule: data.schedule,\n                        trigger: trigger,\n                        type: .execution\n                    )\n                    new.append(prepared)\n                    allNewTriggers.append(prepared)\n                }\n            }\n\n            for trigger in data.schedule.delay?.cancellationTriggers ?? [] {\n                let existing = old.first(\n                    where: { $0.trigger.id == trigger.id }\n                )\n\n                if let existing = existing {\n                    existing.update(\n                        trigger: trigger,\n                        startDate: data.schedule.start,\n                        endDate: data.schedule.end,\n                        priority: data.schedule.priority ?? 0\n                    )\n                    new.append(existing)\n                } else {\n                    AirshipLogger.debug(\n                        \"New cancellation trigger for schedule \\(schedule.identifier) id \\(trigger.id) type \\(trigger.type) — no existing match, count starts from store or zero\"\n                    )\n                    let prepared = await makePreparedTrigger(\n                        schedule: data.schedule,\n                        trigger: trigger,\n                        type: .delayCancellation\n                    )\n                    new.append(prepared)\n                    allNewTriggers.append(prepared)\n                }\n            }\n\n            self.preparedTriggers[schedule.identifier] = new\n\n            let newIDs = Set(new.map { $0.trigger.id })\n            let oldIDs = Set(old.map { $0.trigger.id })\n\n            do {\n                let stale = oldIDs.subtracting(newIDs)\n                if !stale.isEmpty {\n                    AirshipLogger.debug(\n                        \"Deleting \\(stale.count) stale trigger(s) for schedule \\(schedule.identifier): \\(stale.map { String($0.prefix(8)) })\"\n                    )\n                    try await self.store.deleteTriggers(scheduleID: schedule.identifier, triggerIDs: stale)\n                }\n\n            } catch {\n                AirshipLogger.error(\"Failed to delete trigger states error \\(error)\")\n            }\n\n            await self.updateScheduleState(\n                scheduleID: schedule.identifier,\n                state: data.scheduleState\n            )\n        }\n        \n        if !allNewTriggers.isEmpty {\n            let history = await self.eventsHistory.events\n            for event in history {\n                await self.ingest(event: event, triggers: allNewTriggers, isReplay: true)\n            }\n        }\n    }\n\n    /// delete trigger state\n    func cancel(scheduleIDs: [String]) async {\n        scheduleIDs.forEach { scheduleID in\n            self.preparedTriggers.removeValue(forKey: scheduleID)\n            self.scheduleGroups.removeValue(forKey: scheduleID)\n        }\n        \n        do {\n            try await self.store.deleteTriggers(scheduleIDs: scheduleIDs)\n        } catch {\n            AirshipLogger.error(\"Failed to delete trigger state \\(scheduleIDs) error \\(error)\")\n        }\n    }\n\n    /// delete trigger state\n    func cancel(group: String) async {\n        let scheduleIDs = self.scheduleGroups.filter { $0.value == group}.map { $0.key }\n        await cancel(scheduleIDs: scheduleIDs)\n    }\n\n    private func trackStateChange(event: AutomationEvent) {\n        guard case .stateChanged(let state) = event else {\n            return\n        }\n        self.appSessionState = state\n    }\n    \n    func updateScheduleState(scheduleID: String, state: AutomationScheduleState) async {\n        AirshipLogger.trace(\"Schedule state update: \\(scheduleID) -> \\(state)\")\n        switch state {\n        case .idle:\n            await self.updateActiveTriggerType(for: scheduleID, type: .execution)\n        case .triggered, .prepared:\n            await self.updateActiveTriggerType(for: scheduleID, type: .delayCancellation)\n        case .paused, .finished:\n            await self.updateActiveTriggerType(for: scheduleID, type: nil)\n        default: break\n        }\n    }\n    \n    private func updateActiveTriggerType(for scheduleID: String, type: TriggerExecutionType?) async {\n        guard let triggers = self.preparedTriggers[scheduleID] else { return }\n        guard let type = type else {\n            self.preparedTriggers[scheduleID]?.forEach { $0.disable() }\n            return\n        }\n\n        triggers.forEach {\n            if (type == $0.executionType) {\n                $0.activate()\n            } else {\n                $0.disable()\n            }\n        }\n\n        guard let state = self.appSessionState else { return }\n\n        let results = triggers.compactMap { trigger in\n            trigger.process(event: .stateChanged(state: state))\n        }\n\n        results.forEach { result in\n            if let triggerResult = result.triggerResult {\n                self.continuation.yield(triggerResult)\n            }\n        }\n\n        let triggerDatas = results.map { $0.triggerData }\n\n        do {\n            try await self.store.upsertTriggers(triggerDatas)\n        } catch {\n            AirshipLogger.error(\"Failed to save trigger data \\(triggerDatas) \\(error)\")\n        }\n    }\n\n    private func emit(result: TriggerResult) async {\n        guard await self.isPaused == false else { return }\n        self.continuation.yield(result)\n    }\n\n    private func makePreparedTrigger(\n        schedule: AutomationSchedule,\n        trigger: AutomationTrigger,\n        type: TriggerExecutionType\n    ) async -> PreparedTrigger {\n        var triggerData: TriggerData?\n        do {\n            triggerData = try await self.store.getTrigger(scheduleID: schedule.identifier, triggerID: trigger.id)\n        } catch {\n            AirshipLogger.error(\"Failed to load trigger state for \\(trigger) error \\(error)\")\n        }\n\n        if let loaded = triggerData {\n            AirshipLogger.debug(\n                \"Restored trigger data for schedule \\(schedule.identifier) trigger \\(trigger.id) type \\(trigger.type) count \\(loaded.count)/\\(trigger.goal)\"\n            )\n        } else {\n            AirshipLogger.debug(\n                \"No stored data for schedule \\(schedule.identifier) trigger \\(trigger.id) type \\(trigger.type) — starting at count 0\"\n            )\n        }\n\n        return PreparedTrigger(\n            scheduleID: schedule.identifier,\n            trigger: trigger,\n            type: type,\n            startDate: schedule.start,\n            endDate: schedule.end,\n            triggerData: triggerData,\n            priority: schedule.priority ?? 0,\n            date: self.date\n        )\n    }\n}\n\nenum TriggerExecutionType: String, Equatable, Hashable {\n    case execution\n    case delayCancellation = \"delay_cancellation\"\n}\n\nstruct TriggerResult: Sendable {\n    var scheduleID: String\n    var triggerExecutionType: TriggerExecutionType\n    var triggerInfo: TriggeringInfo\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/TriggerProcessor/PreparedTrigger.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n// This is only called from an actor `AutomationTriggerProcessor`\nfinal class PreparedTrigger {\n    struct EventProcessResult {\n        var triggerData: TriggerData\n        var triggerResult: TriggerResult?\n        var priority: Int\n    }\n\n    let date: any AirshipDateProtocol\n    let scheduleID: String\n    let executionType: TriggerExecutionType\n\n    private(set) var triggerData: TriggerData\n    private(set) var trigger: AutomationTrigger\n    private(set) var isActive: Bool = false\n    private(set) var startDate: Date?\n    private(set) var endDate: Date?\n    private(set) var priority: Int\n\n    init(\n        scheduleID: String,\n        trigger: AutomationTrigger,\n        type: TriggerExecutionType,\n        startDate: Date?,\n        endDate: Date?,\n        triggerData: TriggerData?,\n        priority: Int,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.scheduleID = scheduleID\n        self.executionType = type\n        self.date = date\n        self.trigger = trigger\n        self.startDate = startDate\n        self.endDate = endDate\n\n        self.triggerData = triggerData ?? TriggerData(\n            scheduleID: scheduleID,\n            triggerID: trigger.id\n        )\n\n        self.priority = priority\n        self.trigger.removeStaleChildData(data: &self.triggerData)\n    }\n\n    func process(event: AutomationEvent) -> EventProcessResult? {\n        guard self.isActive else {\n            AirshipLogger.trace(\"Trigger skipped (inactive): schedule \\(scheduleID) trigger \\(trigger.id) type \\(trigger.type) executionType \\(executionType)\")\n            return nil\n        }\n\n        guard self.isWithingDateRange() else {\n            AirshipLogger.trace(\"Trigger skipped (out of date range): schedule \\(scheduleID) trigger \\(trigger.id) type \\(trigger.type)\")\n            return nil\n        }\n\n        var currentData = self.triggerData\n        let match = self.trigger.matchEvent(event, data: &currentData, resetOnTrigger: true)\n\n        guard currentData != self.triggerData || match?.isTriggered == true else {\n            return nil\n        }\n\n        self.triggerData = currentData\n\n        if match?.isTriggered == true {\n            AirshipLogger.debug(\"Trigger fired: schedule \\(scheduleID) trigger \\(trigger.id) type \\(trigger.type)\")\n        } else {\n            AirshipLogger.trace(\"Trigger updated: schedule \\(scheduleID) trigger \\(trigger.id) type \\(trigger.type) count \\(currentData.count)/\\(trigger.goal)\")\n        }\n\n        return EventProcessResult(\n            triggerData: triggerData,\n            triggerResult: match?.isTriggered == true ? generateTriggerResult(eventData: event.eventData ?? .null) : nil,\n            priority: self.priority\n        )\n    }\n\n    func update(\n        trigger: AutomationTrigger,\n        startDate: Date?,\n        endDate: Date?,\n        priority: Int\n    ) {\n        self.trigger = trigger\n        self.startDate = startDate\n        self.endDate = endDate\n        self.priority = priority\n        self.trigger.removeStaleChildData(data: &triggerData)\n    }\n\n    func activate() {\n        guard !self.isActive else { return }\n\n        AirshipLogger.debug(\n            \"Trigger activated: schedule \\(scheduleID) trigger \\(trigger.id) type \\(trigger.type) executionType \\(executionType) count \\(triggerData.count)/\\(trigger.goal)\"\n        )\n\n        self.isActive = true\n\n        if self.executionType == .delayCancellation {\n            self.triggerData.resetCount()\n        }\n    }\n\n    func disable() {\n        guard self.isActive else { return }\n        AirshipLogger.debug(\n            \"Trigger disabled: schedule \\(scheduleID) trigger \\(trigger.id) type \\(trigger.type) executionType \\(executionType)\"\n        )\n        self.isActive = false\n    }\n\n    private func generateTriggerResult(eventData: AirshipJSON) -> TriggerResult {\n        return TriggerResult(\n            scheduleID: self.scheduleID,\n            triggerExecutionType: self.executionType,\n            triggerInfo: TriggeringInfo(\n                context: AirshipTriggerContext(\n                    type: trigger.type,\n                    goal: trigger.goal,\n                    event: eventData),\n                date: self.date.now\n            )\n        )\n    }\n\n    private func isWithingDateRange() -> Bool {\n        let now = self.date.now\n        if let start = self.startDate, start > now {\n            return false\n        }\n\n        if let end = self.endDate, end < now {\n            return false\n        }\n\n        return true\n    }\n}\n\nextension TriggerData {\n    func childData(triggerID: String) -> TriggerData {\n        guard let data = self.children[triggerID] else {\n            return TriggerData(scheduleID: self.scheduleID, triggerID: triggerID, count: 0)\n        }\n        return data\n    }\n}\n\nextension EventAutomationTrigger {\n    fileprivate func matchEvent(_ event: AutomationEvent, data: inout TriggerData) -> MatchResult? {\n        switch event {\n        case .stateChanged(let state):\n            return stateTriggerMatch(state: state, data: &data)\n        case .event(let type, let eventData, let value):\n            guard self.type == type else {\n                return nil\n            }\n            guard isPredicateMatching(value: eventData) else {\n                AirshipLogger.trace(\"Event trigger predicate no-match: trigger \\(self.id) type \\(self.type)\")\n                return nil\n            }\n\n            return evaluateResults(data: &data, increment: value)\n        }\n    }\n\n    private func stateTriggerMatch(state: TriggerableState, data: inout TriggerData) -> MatchResult? {\n        switch self.type {\n        case .version:\n            guard\n                let versionUpdated = state.versionUpdated,\n                versionUpdated != data.lastTriggerableState?.versionUpdated,\n                isPredicateMatching(\n                    value: [\n                        \"ios\": [\"version\": .string(versionUpdated)]\n                    ]\n                )\n            else {\n                return nil\n            }\n\n            data.lastTriggerableState = state\n            return evaluateResults(data: &data, increment: 1)\n        case .activeSession:\n            guard\n                let appSessionID = state.appSessionID,\n                    appSessionID != data.lastTriggerableState?.appSessionID\n            else {\n                return nil\n            }\n\n            data.lastTriggerableState = state\n            return evaluateResults(data: &data, increment: 1)\n        default:\n           return nil\n        }\n    }\n\n    private func isPredicateMatching(value: AirshipJSON?) -> Bool {\n        guard let predicate = self.predicate else { return true }\n        return predicate.evaluate(json: value ?? .null)\n    }\n\n    private func evaluateResults(\n        data: inout TriggerData,\n        increment: Double\n    ) -> MatchResult {\n        data.incrementCount(increment)\n        return MatchResult(\n            triggerID: self.id,\n            isTriggered: data.count >= self.goal\n        )\n    }\n}\n\nextension CompoundAutomationTrigger {\n    fileprivate func matchEvent(_ event: AutomationEvent, data: inout TriggerData) -> MatchResult? {\n\n        let triggeredChildren = triggeredChildrenCount(data: data)\n\n        var childResults = self.matchChildren(event: event, data: &data)\n\n        // Resend state event if children is triggered for chain triggers\n        if\n            self.type == .chain,\n            let state = data.lastTriggerableState,\n            !event.isStateEvent,\n            triggeredChildren != triggeredChildrenCount(data: data)  {\n\n            childResults = self.matchChildren(event: .stateChanged(state: state), data: &data)\n        } else if case .stateChanged(let state) = event {\n            // Remember state on compound trigger level in order to be able to re-send it\n            data.lastTriggerableState = state\n        }\n\n        switch self.type {\n        case .and, .chain:\n            let shouldIncrement = childResults.allSatisfy { result in\n                result.isTriggered\n            }\n\n            if (shouldIncrement) {\n                self.children.forEach { child in\n                    // Only reset the child if its not sticky\n                    if child.isSticky != true {\n                        var childData = data.childData(triggerID: child.trigger.id)\n                        childData.resetCount()\n                        data.children[child.trigger.id] = childData\n                    }\n                }\n                data.incrementCount(1.0)\n            }\n\n        case .or:\n            let shouldIncrement = childResults.contains(\n                where: { result in result.isTriggered }\n            )\n\n            if (shouldIncrement) {\n                self.children.forEach { child in\n                    var childData = data.childData(triggerID: child.trigger.id)\n\n                    // Reset the child if it reached the goal or if we are resetting it\n                    // on increment\n                    if (childData.count >= child.trigger.goal || child.resetOnIncrement == true) {\n                        childData.resetCount()\n                    }\n\n                    data.children[child.trigger.id] = childData\n                }\n                data.incrementCount(1.0)\n            }\n        }\n\n        let result = MatchResult(triggerID: self.id, isTriggered: data.count >= self.goal)\n        AirshipLogger.trace(\n            \"Compound trigger[\\(self.type)] id \\(self.id) count \\(data.count)/\\(self.goal) triggered \\(result.isTriggered) childResults \\(childResults.map { \"\\($0.triggerID.prefix(8)):\\($0.isTriggered)\" })\"\n        )\n        return result\n    }\n\n    private func matchChildren(\n        event: AutomationEvent,\n        data: inout TriggerData\n    ) -> [MatchResult] {\n        var evaluateRemaining = true\n        return children.enumerated().map { index, child in\n            var childData = data.childData(triggerID: child.trigger.id)\n\n            var matchResult: MatchResult?\n            if evaluateRemaining {\n                // Match the child without resetting it on trigger. We will process resets\n                // after we get all the child results\n                matchResult = child.trigger.matchEvent(event, data: &childData, resetOnTrigger: false)\n            }\n\n            let result = matchResult ?? MatchResult(\n                triggerID: child.trigger.id,\n                isTriggered: child.trigger.isTriggered(data: childData)\n            )\n\n            AirshipLogger.trace(\n                \"Compound child[\\(index)] id \\(child.trigger.id) type \\(child.trigger.type) count \\(childData.count)/\\(child.trigger.goal) triggered \\(result.isTriggered) evaluated \\(evaluateRemaining)\"\n            )\n\n            if self.type == .chain, evaluateRemaining, !result.isTriggered {\n                AirshipLogger.debug(\"Chain stopped at child[\\(index)] id \\(child.trigger.id) type \\(child.trigger.type) count \\(childData.count)/\\(child.trigger.goal)\")\n                evaluateRemaining = false\n            }\n\n            data.children[child.trigger.id] = childData\n            return result\n        }\n    }\n\n    func removeStaleChildData(data: inout TriggerData) {\n        guard data.children.isEmpty else { return }\n\n        var updatedData: [String: TriggerData] = [:]\n\n        self.children.forEach { child in\n            var childData = data.childData(triggerID: child.trigger.id)\n            child.trigger.removeStaleChildData(data: &childData)\n            updatedData[child.trigger.id] = childData\n        }\n\n        data.children = updatedData\n    }\n\n    private func triggeredChildrenCount(data: TriggerData) -> Int {\n        return children\n            .filter { child in\n                guard let state = data.children[child.trigger.id] else { return false }\n                return state.count >= child.trigger.goal\n            }.count\n    }\n}\n\nextension AutomationTrigger {\n\n    fileprivate func matchEvent(\n        _ event: AutomationEvent,\n        data: inout TriggerData,\n        resetOnTrigger: Bool\n    ) -> MatchResult? {\n\n        let result: MatchResult? = switch self {\n        case .compound(let compoundTrigger):\n            compoundTrigger.matchEvent(event, data: &data)\n        case .event(let eventTrigger):\n            eventTrigger.matchEvent(event, data: &data)\n        }\n\n        if resetOnTrigger, result?.isTriggered == true {\n            data.resetCount()\n        }\n\n        return result\n    }\n\n    func isTriggered(data: TriggerData) -> Bool {\n        return data.count >= self.goal\n    }\n\n\n    func removeStaleChildData(data: inout TriggerData) {\n        guard case .compound(let compoundTrigger) = self else {\n            return\n        }\n\n        compoundTrigger.removeStaleChildData(data: &data)\n    }\n}\n\nfileprivate struct MatchResult {\n   var triggerID: String\n   var isTriggered: Bool\n}\n\nfileprivate extension AutomationEvent {\n    var isStateEvent: Bool {\n        switch self {\n        case .stateChanged: return true\n        default: return false\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/TriggerProcessor/TriggerData.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct TriggerData: Sendable, Equatable, Codable {\n    var scheduleID: String\n    var triggerID: String\n\n    var count: Double\n    var children: [String: TriggerData]\n    var lastTriggerableState: TriggerableState?\n\n    init(\n        scheduleID: String,\n        triggerID: String,\n        count: Double = 0.0,\n        children: [String : TriggerData] = [:]\n    ) {\n        self.scheduleID = scheduleID\n        self.triggerID = triggerID\n        self.count = count\n        self.children = children\n    }\n} \n\nextension TriggerData {\n    mutating func incrementCount(_ value: Double) {\n        self.count = self.count + value\n    }\n\n    mutating func resetCount() {\n        self.count = 0\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/Engine/TriggeringInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct TriggeringInfo: Equatable, Sendable, Codable {\n    var context: AirshipTriggerContext?\n    var date: Date\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Automation/ExecutionWindow.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Defines when an automation is allowed to run (e.g. daily, weekly, or monthly time windows).\npublic struct ExecutionWindow: Sendable, Equatable, Codable {\n\n    let include: [Rule]?\n    let exclude: [Rule]?\n\n\n    init(include: [Rule]? = nil, exclude: [Rule]? = nil) throws {\n        self.include = include\n        self.exclude = exclude\n        try self.validate()\n    }\n\n    /// Creates an execution window from a decoder (e.g. JSON).\n    /// - Parameter decoder: The decoder to read from.\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.include = try container.decodeIfPresent([ExecutionWindow.Rule].self, forKey: .include)\n        self.exclude = try container.decodeIfPresent([ExecutionWindow.Rule].self, forKey: .exclude)\n        try self.validate()\n    }\n\n    fileprivate func validate() throws {\n        try include?.forEach { try $0.validate() }\n        try exclude?.forEach { try $0.validate() }\n    }\n\n    enum Rule: Sendable, Codable, Equatable {\n        case daily(timeRange: TimeRange, timeZone: TimeZone? = nil)\n        case weekly(daysOfWeek: [Int], timeRange: TimeRange? = nil,  timeZone: TimeZone? = nil)\n        case monthly(months: [Int]? = nil, daysOfMonth: [Int]? = nil, timeRange: TimeRange? = nil, timeZone: TimeZone? = nil)\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case timeRange = \"time_range\"\n            case daysOfWeek = \"days_of_week\"\n            case daysOfMonth = \"days_of_month\"\n            case timeZone = \"time_zone\"\n            case months = \"months\"\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let type = try container.decode(RuleType.self, forKey: .type)\n            switch(type) {\n            case .daily:\n                self = .daily(\n                    timeRange: try container.decode(TimeRange.self, forKey: .timeRange),\n                    timeZone: try container.decodeIfPresent(TimeZone.self, forKey: .timeZone)\n                )\n            case .weekly:\n                self = .weekly(\n                    daysOfWeek: try container.decode([Int].self, forKey: .daysOfWeek),\n                    timeRange: try container.decodeIfPresent(TimeRange.self, forKey: .timeRange),\n                    timeZone: try container.decodeIfPresent(TimeZone.self, forKey: .timeZone)\n                )\n            case .monthly:\n                self = .monthly(\n                    months: try container.decodeIfPresent([Int].self, forKey: .months),\n                    daysOfMonth: try container.decodeIfPresent([Int].self, forKey: .daysOfMonth),\n                    timeRange: try container.decodeIfPresent(TimeRange.self, forKey: .timeRange),\n                    timeZone: try container.decodeIfPresent(TimeZone.self, forKey: .timeZone)\n                )\n            }\n        }\n\n        func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n\n            switch(self) {\n            case .daily(timeRange: let timeRange, timeZone: let timeZone):\n                try container.encode(RuleType.daily, forKey: .type)\n                try container.encode(timeRange, forKey: .timeRange)\n                try container.encodeIfPresent(timeZone, forKey: .timeZone)\n            case .weekly(daysOfWeek: let daysOfWeek, timeRange: let timeRange, timeZone: let timeZone):\n                try container.encode(RuleType.weekly, forKey: .type)\n                try container.encodeIfPresent(daysOfWeek, forKey: .daysOfWeek)\n                try container.encodeIfPresent(timeRange, forKey: .timeRange)\n                try container.encodeIfPresent(timeZone, forKey: .timeZone)\n            case .monthly(months: let months, daysOfMonth: let daysOfMonth, timeRange: let timeRange, timeZone: let timeZone):\n                try container.encode(RuleType.monthly, forKey: .type)\n                try container.encodeIfPresent(months, forKey: .months)\n                try container.encodeIfPresent(daysOfMonth, forKey: .daysOfMonth)\n                try container.encodeIfPresent(timeRange, forKey: .timeRange)\n                try container.encodeIfPresent(timeZone, forKey: .timeZone)\n            }\n        }\n\n\n        fileprivate func validate() throws {\n            switch(self) {\n            case .daily(let timeRange, _):\n                try timeRange.validate()\n            case .weekly(daysOfWeek: let daysOfWeek, timeRange: let timeRange, timeZone: _):\n                guard !daysOfWeek.isEmpty else {\n                    throw AirshipErrors.error(\"Invalid daysOfWeek: \\(daysOfWeek), must contain at least 1 day of week\")\n                }\n                try daysOfWeek.forEach { dayOfWeek in\n                    guard dayOfWeek >= 1 && dayOfWeek <= 7 else {\n                        throw AirshipErrors.error(\"Invalid daysOfWeek: \\(daysOfWeek), all values must be [1-7]\")\n                    }\n                }\n                try timeRange?.validate()\n            case .monthly(months: let months, daysOfMonth: let daysOfMonth, timeRange: let timeRange, timeZone: _):\n                guard months?.isEmpty == false || daysOfMonth?.isEmpty == false else {\n                    throw AirshipErrors.error(\"monthly rule must define either months or days of month\")\n                }\n                try months?.forEach { month in\n                    guard month >= 1 && month <= 12 else {\n                        throw AirshipErrors.error(\"Invalid month: \\(months ?? []), all values must be [1-12]\")\n                    }\n                }\n\n                try daysOfMonth?.forEach { dayOfMonth in\n                    guard dayOfMonth >= 1 && dayOfMonth <= 31 else {\n                        throw AirshipErrors.error(\"Invalid days of month: \\(daysOfMonth ?? []), all values must be [1-31]\")\n                    }\n                }\n\n                try timeRange?.validate()\n            }\n        }\n    }\n\n\n    enum TimeZone: Sendable, Equatable, Codable{\n        case utc\n        case identifiers([String], secondsFromUTC: Int? = nil, onFailure: TimeZoneFailureMode = .error)\n        case local\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case identifiers\n            case secondsFromUTC = \"fallback_seconds_from_utc\"\n            case onFailure = \"on_failure\"\n\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let type = try container.decode(TimeZoneType.self, forKey: .type)\n            switch(type) {\n            case .local:\n                self = .local\n            case .utc:\n                self = .utc\n            case .identifiers:\n                self = .identifiers(\n                    try container.decode([String].self, forKey: .identifiers),\n                    secondsFromUTC: try container.decodeIfPresent(Int.self, forKey: .secondsFromUTC),\n                    onFailure: try container.decode(TimeZoneFailureMode.self, forKey: .onFailure)\n                )\n            }\n        }\n\n        func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            switch(self) {\n            case .local:\n                try container.encode(TimeZoneType.local, forKey: .type)\n            case .utc:\n                try container.encode(TimeZoneType.utc, forKey: .type)\n            case .identifiers(let identifiers, let secondsFromUTC, let failureMode):\n                try container.encode(TimeZoneType.identifiers, forKey: .type)\n                try container.encode(identifiers, forKey: .identifiers)\n                try container.encodeIfPresent(secondsFromUTC, forKey: .secondsFromUTC)\n                try container.encode(failureMode, forKey: .onFailure)\n            }\n        }\n    }\n\n    enum TimeZoneFailureMode: String, Sendable, Equatable, Codable {\n        case error = \"error\"\n        case skip = \"skip\"\n    }\n\n    struct TimeRange: Hashable, Equatable, Sendable, Codable {\n        var startHour: Int\n        var startMinute: Int\n        var endHour: Int\n        var endMinute: Int\n\n        enum CodingKeys: String, CodingKey {\n            case startHour = \"start_hour\"\n            case startMinute = \"start_minute\"\n            case endHour = \"end_hour\"\n            case endMinute = \"end_minute\"\n        }\n\n        init(startHour: Int, startMinute: Int = 0, endHour: Int, endMinute: Int = 0) {\n            self.startHour = startHour\n            self.startMinute = startMinute\n            self.endHour = endHour\n            self.endMinute = endMinute\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.startHour = try container.decode(Int.self, forKey: .startHour)\n            self.startMinute = try container.decode(Int.self, forKey: .startMinute)\n            self.endHour = try container.decode(Int.self, forKey: .endHour)\n            self.endMinute = try container.decode(Int.self, forKey: .endMinute)\n        }\n\n        fileprivate func validate() throws {\n            guard startHour >= 0 && startHour <= 23 else {\n                throw AirshipErrors.error(\"Invalid startHour: \\(startHour), must be [0-23]\")\n            }\n\n            guard startMinute >= 0 && startMinute <= 59 else {\n                throw AirshipErrors.error(\"Invalid startMinute: \\(startMinute), must be [0-59]\")\n            }\n\n            guard endHour >= 0 && endHour <= 23 else {\n                throw AirshipErrors.error(\"Invalid endHour: \\(endHour), must be [0-23]\")\n            }\n\n            guard endMinute >= 0 && endMinute <= 59 else {\n                throw AirshipErrors.error(\"Invalid endMinute: \\(endMinute), must be [0-59]\")\n            }\n        }\n    }\n\n    private enum RuleType: String, Sendable, Codable {\n        case daily = \"daily\"\n        case weekly = \"weekly\"\n        case monthly = \"monthly\"\n    }\n\n    private enum TimeZoneType: String, Sendable, Codable {\n        case utc = \"utc\"\n        case local = \"local\"\n        case identifiers = \"identifiers\"\n    }\n}\n\nenum ExecutionWindowResult: Equatable {\n    case now\n    case retry(TimeInterval)\n}\n\nextension ExecutionWindow {\n    func nextAvailability(date: Date, currentTimeZone: Foundation.TimeZone? = nil) throws -> ExecutionWindowResult {\n        let timeZone = currentTimeZone ?? Foundation.TimeZone.current\n\n        let excluded = try self.exclude?.compactMap {\n            try $0.resolve(date: date, currentTimeZone: timeZone)\n        }.filter {\n            $0.isWithin(date: date)\n        }.sorted { l, r in\n            /// Sort them with the longest exclude first\n            l.end > r.end\n        }.first\n\n        if let excluded {\n            return .retry(max(1.seconds, excluded.end.timeIntervalSince(date)))\n        }\n\n        let nextInclude = try include?.compactMap {\n            try $0.resolve(date: date, currentTimeZone: timeZone)\n        }.sorted { l, r in\n            // Sort with the next window first\n            l.start < r.start\n        }.first\n\n        guard let nextInclude, !nextInclude.isWithin(date: date) else {\n            return .now\n        }\n\n        return .retry(max(1.seconds, nextInclude.start.timeIntervalSince(date)))\n    }\n\n}\n\nfileprivate extension Int {\n    var hours: TimeInterval {\n        TimeInterval(self) * 60 * 60\n    }\n\n    var minutes: TimeInterval {\n        TimeInterval(self) * 60\n    }\n\n    var seconds: TimeInterval {\n        TimeInterval(self)\n    }\n}\n\nfileprivate extension ExecutionWindow.TimeRange {\n    var start: TimeInterval {\n        return startHour.hours + startMinute.minutes\n    }\n\n    var end: TimeInterval {\n        return endHour.hours + endMinute.minutes\n    }\n}\n\nfileprivate extension ExecutionWindow.TimeZone {\n\n    enum TimeZoneResult {\n        case resolved(Foundation.TimeZone)\n        case error(ExecutionWindow.TimeZoneFailureMode)\n    }\n\n    func resolve(currentTimeZone: TimeZone) -> TimeZoneResult {\n        switch(self) {\n        case .utc:\n            return .resolved(.gmt)\n\n        case .local:\n            return .resolved(currentTimeZone)\n\n        case .identifiers(let identifiers, let secondsFromUTC, let failureMode):\n            for identifier in identifiers {\n                if let timeZone = TimeZone(identifier: identifier) {\n                    return .resolved(timeZone)\n                }\n            }\n\n            if let secondsFromUTC, let timeZone = TimeZone(secondsFromGMT: secondsFromUTC) {\n                return .resolved(timeZone)\n            }\n\n            AirshipLogger.error(\"Failed to resolve time zone identifiers: \\(identifiers)\")\n            return .error(failureMode)\n        }\n    }\n}\n\nfileprivate extension ExecutionWindow.Rule {\n\n    private func calendar(timeZone: ExecutionWindow.TimeZone?, currentTimeZone: Foundation.TimeZone) throws -> AirshipCalendar? {\n        guard let timeZone else {\n            return AirshipCalendar(timeZone: currentTimeZone)\n        }\n\n        switch (timeZone.resolve(currentTimeZone: currentTimeZone)) {\n        case .resolved(let resolved):\n            return AirshipCalendar(timeZone: resolved)\n        case .error(let failureMode):\n            switch(failureMode) {\n            case .skip:\n                return nil\n            case .error:\n                throw AirshipErrors.error(\"Unable to resolve time zone: \\(timeZone)\")\n            }\n        }\n    }\n\n    func resolve(date: Date, currentTimeZone: Foundation.TimeZone) throws -> DateInterval? {\n        switch (self) {\n        case .daily(timeRange: let timeRange, timeZone: let timeZone):\n            guard let calendar = try calendar(\n                timeZone: timeZone,\n                currentTimeZone: currentTimeZone\n            ) else {\n                return nil\n            }\n            return calendar.dateInterval(date: date, timeRange: timeRange)\n\n        case .weekly(daysOfWeek: let daysOfWeek, timeRange: let timeRange, timeZone: let timeZone):\n            guard let calendar = try calendar(\n                timeZone: timeZone,\n                currentTimeZone: currentTimeZone\n            ) else {\n                return nil\n            }\n\n            guard let timeRange else {\n                let nextDate = calendar.nextDate(date: date, weekdays: daysOfWeek)\n                return calendar.remainingDay(date: nextDate)\n            }\n\n            var nextDate = calendar.nextDate(date: date, weekdays: daysOfWeek)\n\n            while true {\n                let timeInterval = calendar.dateInterval(date: nextDate, timeRange: timeRange)\n                let remainingDay = calendar.remainingDay(date: nextDate)\n\n                guard let result = timeInterval.intersection(with: remainingDay) else {\n                    nextDate = calendar.nextDate(\n                        date: calendar.startOfDay(date: date, dayOffset: 1),\n                        weekdays: daysOfWeek\n                    )\n                    continue\n                }\n\n                return result\n            }\n\n        case .monthly(months: let months, daysOfMonth: let daysOfMonth, timeRange: let timeRange, timeZone: let timeZone):\n            guard let calendar = try calendar(\n                timeZone: timeZone,\n                currentTimeZone: currentTimeZone\n            ) else {\n                return nil\n            }\n\n            guard let timeRange else {\n                let nextDate = calendar.nextDate(date: date, months: months, days: daysOfMonth)\n                return calendar.remainingDay(date: nextDate)\n            }\n\n            var nextDate = calendar.nextDate(date: date, months: months, days: daysOfMonth)\n\n            while true {\n                let timeInterval = calendar.dateInterval(date: nextDate, timeRange: timeRange)\n                let remainingDay = calendar.remainingDay(date: nextDate)\n\n                guard let result = timeInterval.intersection(with: remainingDay) else {\n                    nextDate = calendar.nextDate(\n                        date: calendar.startOfDay(date: date, dayOffset: 1),\n                        months: months,\n                        days: daysOfMonth\n                    )\n                    continue\n                }\n                return result\n            }\n        }\n    }\n}\n\n\nfileprivate struct AirshipCalendar : Hashable, Equatable, Sendable {\n\n    private let calendar: Calendar\n\n    init(timeZone: TimeZone) {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = timeZone\n        self.calendar = calendar\n    }\n\n    func startOfDay(date: Date, dayOffset: Int = 0) -> Date {\n        guard dayOffset != 0 else {\n            return calendar.startOfDay(for: date)\n        }\n\n        guard\n            let targetDate = calendar.date(byAdding: .day, value: dayOffset, to: date)\n        else {\n            // Fallback to using hours offset. Should be fine for most\n            // dates except for time zones with daylight savings on\n            // transition days.\n            return calendar.startOfDay(for: date + 24.hours)\n        }\n\n        return calendar.startOfDay(for: targetDate)\n    }\n\n    func endOfDay(date: Date, dayOffset: Int = 0) -> Date {\n        let day = startOfDay(date: date, dayOffset: dayOffset)\n        guard\n            let endOfDay = calendar.date(\n                bySettingHour: 23,\n                minute: 59,\n                second: 59,\n                of: day\n            )\n        else {\n            // Fallback to using hours offset. Should be fine for most\n            // dates except for time zones with daylight savings on\n            // transition days.\n            return day + 24.hours - 1.seconds\n        }\n\n        return endOfDay\n    }\n\n    private func date(date: Date, hour: Int, minute: Int) -> Date {\n        guard\n            let newDate = calendar.date(\n                bySettingHour: hour,\n                minute: minute,\n                second: 0,\n                of: date\n            )\n        else {\n            return startOfDay(date: date).advanced(by: hour.hours + minute.minutes)\n        }\n\n        return newDate\n    }\n\n    // Returns the date interval for the rest of the day\n    func remainingDay(date: Date) -> DateInterval {\n        return DateInterval(start: date, end: startOfDay(date: date, dayOffset: 1))\n    }\n\n    // Returns the date interval for the given date and timeRange. If the\n    // date is passed the time range, the DateInterval will be for the next day.\n    func dateInterval(date: Date, timeRange: ExecutionWindow.TimeRange) -> DateInterval {\n        guard timeRange.start != timeRange.end else {\n            let todayStart = self.date(\n                date: startOfDay(date: date),\n                hour: timeRange.startHour,\n                minute: timeRange.startMinute\n            )\n\n            if (todayStart == date) {\n                return DateInterval(start: todayStart, duration: 1)\n            } else {\n                let tomorrowStart = self.date(\n                    date: startOfDay(date: date, dayOffset: 1),\n                    hour: timeRange.startHour,\n                    minute: timeRange.startMinute\n                )\n                return DateInterval(start: tomorrowStart, duration: 1)\n            }\n        }\n\n        /// start: 23, end: 1\n\n        let yesterdayInterval = DateInterval(\n            start: self.date(\n                date: startOfDay(date: date, dayOffset: -1),\n                hour: timeRange.startHour,\n                minute: timeRange.startMinute\n            ),\n            end: self.date(\n                date: startOfDay(\n                    date: date,\n                    dayOffset: (timeRange.start > timeRange.end ? 0 : -1)\n                ),\n                hour: timeRange.endHour,\n                minute: timeRange.endMinute\n            )\n        )\n\n        if yesterdayInterval.isWithin(date: date) {\n            return yesterdayInterval\n        }\n\n        let todayInterval = DateInterval(\n            start: self.date(\n                date: startOfDay(date: date),\n                hour: timeRange.startHour,\n                minute: timeRange.startMinute\n            ),\n            end: self.date(\n                date: startOfDay(\n                    date: date,\n                    dayOffset: (timeRange.start > timeRange.end ? 1 : 0)\n                ),\n                hour: timeRange.endHour,\n                minute: timeRange.endMinute\n            )\n        )\n\n        if todayInterval.isWithin(date: date) || todayInterval.start >= date {\n            return todayInterval\n        }\n\n        return DateInterval(\n           start: self.date(\n               date: startOfDay(date: date, dayOffset: 1),\n               hour: timeRange.startHour,\n               minute: timeRange.startMinute\n           ),\n           end: self.date(\n               date: startOfDay(\n                   date: date,\n                   dayOffset: (timeRange.start > timeRange.end ? 2 : 1)\n               ),\n               hour: timeRange.endHour,\n               minute: timeRange.endMinute\n           )\n       )\n    }\n\n    // Returns the current date if it matches the weekdays,\n    // or the date of the start of the next requested weekday\n    func nextDate(date: Date, weekdays: [Int]) -> Date {\n        let currentWeekday = calendar.component(.weekday, from: date)\n        let sortedWeekdays = weekdays.sorted()\n        let targetWeekday = sortedWeekdays.first { $0 >= currentWeekday } ?? sortedWeekdays.first ?? currentWeekday\n\n        // Mod it with number of days in the week\n        let daysUntilNextSlot = if targetWeekday >= currentWeekday {\n            targetWeekday - currentWeekday\n        } else {\n            targetWeekday + (7 - currentWeekday)\n        }\n\n        return if (daysUntilNextSlot > 0) {\n            startOfDay(date: date, dayOffset: daysUntilNextSlot  )\n        } else {\n            date\n        }\n    }\n\n    func nextDate(date: Date, months: [Int]? = nil, days: [Int]?) -> Date {\n        guard months?.isEmpty == false || days?.isEmpty == false else {\n            return date\n        }\n\n        let currentDay = calendar.component(.day, from: date)\n        let currentMonth = calendar.component(.month, from: date)\n\n        let sortedMonths = months?.sorted()\n        let sortedDays = days?.sorted()\n\n        let targetMonth = sortedMonths?.first { $0 >= currentMonth } ?? sortedMonths?.first ?? currentMonth\n        var targetDay = sortedDays?.first(where: { $0 >= currentDay })\n\n        // Our target month is this month\n        if targetMonth == currentMonth {\n            if let targetDay {\n                return if targetDay == currentDay {\n                    date\n                } else {\n                    startOfDay(date: date, dayOffset: (targetDay - currentDay))\n                }\n            } else if sortedDays?.isEmpty != false {\n                return date\n            }\n        }\n\n        // Pick the earliest day\n        targetDay = sortedDays?.first ?? 1\n\n        guard let sortedMonths, !sortedMonths.isEmpty else {\n            return calendar.nextDate(\n                after: date,\n                matching: DateComponents(\n                    day: targetDay\n                ),\n                matchingPolicy: .strict\n            ) ?? Date.distantFuture\n        }\n\n        let results = sortedMonths.compactMap { month in\n            let next = calendar.nextDate(\n                after: date,\n                matching: DateComponents(\n                    month: month,\n                    day: targetDay\n                ),\n                matchingPolicy: .strict\n            )\n            return if let next {\n                startOfDay(date: next)\n            } else {\n                nil\n            }\n        }.sorted()\n\n        return results.first ?? Date.distantFuture\n    }\n}\n\nfileprivate extension DateInterval {\n    func isWithin(date: Date) -> Bool {\n        return contains(date) && self.end != date\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/AutomationSDKModule.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\n@objc(UAAutomationSDKModule)\npublic class AutomationSDKModule: NSObject, AirshipSDKModule {\n    public let components: [any AirshipComponent]\n    public let actionsManifest: (any ActionsManifest)? = AutomationActionManifest()\n    \n    init(components: [any AirshipComponent]) {\n        self.components = components\n    }\n    \n    public static func load(_ args: AirshiopModuleLoaderArgs) -> (any AirshipSDKModule)? {\n        /// Utils\n        let remoteDataAccess = AutomationRemoteDataAccess(remoteData: args.remoteData)\n        let assetManager = AssetCacheManager()\n        let displayCoordinatorManager = DisplayCoordinatorManager(dataStore: args.dataStore)\n        let frequencyLimits = FrequencyLimitManager(config: args.config)\n        let scheduleConditionsChangedNotifier = ScheduleConditionsChangedNotifier()\n        let eventRecorder = ThomasLayoutEventRecorder(airshipAnalytics: args.analytics, meteredUsage: args.meteredUsage)\n        let metrics = ApplicationMetrics(dataStore: args.dataStore, privacyManager: args.privacyManager)\n        \n        let automationStore = AutomationStore(config: args.config)\n        let history = DefaultAutomationEventsHistory()\n        \n        let analyticsFactory = InAppMessageAnalyticsFactory(\n            eventRecorder: eventRecorder,\n            displayHistoryStore: MessageDisplayHistoryStore(\n                storageGetter: { scheduleID in\n                    try await automationStore.getAssociatedData(scheduleID: scheduleID)\n                },\n                storageSetter: { scheduleID, history in\n                    try await automationStore.updateSchedule(scheduleID: scheduleID) { data in\n                        data.associatedData = try JSONEncoder().encode(history)\n                    }\n                }\n            ),\n            displayImpressionRuleProvider: DefaultInAppDisplayImpressionRuleProvider()\n        )\n        \n        /// Preperation\n        let actionPreparer = ActionAutomationPreparer()\n        let messagePreparer = InAppMessageAutomationPreparer(\n            assetManager: assetManager,\n            displayCoordinatorManager: displayCoordinatorManager,\n            analyticsFactory: analyticsFactory\n        )\n        let automationPreparer = AutomationPreparer(\n            actionPreparer: actionPreparer,\n            messagePreparer: messagePreparer,\n            deferredResolver: args.deferredResolver,\n            frequencyLimits: frequencyLimits,\n            audienceChecker: args.audienceChecker,\n            experiments: args.experimentsManager,\n            remoteDataAccess: remoteDataAccess,\n            config: args.config,\n            additionalAudienceResolver: AdditionalAudienceCheckerResolver(\n                config: args.config,\n                cache: args.cache\n            )\n        )\n        \n        \n        // Execution\n        let actionExecutor = ActionAutomationExecutor()\n        \n#if os(macOS)\n        let messageExecutor = InAppMessageAutomationExecutor(\n            assetManager: assetManager,\n            analyticsFactory: analyticsFactory,\n            scheduleConditionsChangedNotifier: scheduleConditionsChangedNotifier\n        )\n#else\n        let messageSceneManager = InAppMessageSceneManager(sceneManger: AirshipSceneManager.shared)\n        let messageExecutor = InAppMessageAutomationExecutor(\n            sceneManager: messageSceneManager,\n            assetManager: assetManager,\n            analyticsFactory: analyticsFactory,\n            scheduleConditionsChangedNotifier: scheduleConditionsChangedNotifier\n        )\n#endif\n        \n        let automationExecutor = AutomationExecutor(\n            actionExecutor: actionExecutor,\n            messageExecutor: messageExecutor,\n            remoteDataAccess: remoteDataAccess\n        )\n        \n        let feed = AutomationEventFeed(\n            applicationMetrics: metrics,\n            applicationStateTracker: AppStateTracker.shared,\n            analyticsFeed: args.analytics.eventFeed\n        )\n        feed.attach()\n        \n        // Engine\n        let engine = AutomationEngine(\n            store: automationStore,\n            executor: automationExecutor,\n            preparer: automationPreparer,\n            scheduleConditionsChangedNotifier: scheduleConditionsChangedNotifier,\n            eventFeed: feed,\n            triggersProcessor: AutomationTriggerProcessor(\n                store: automationStore,\n                history: history\n            ),\n            delayProcessor: AutomationDelayProcessor(analytics: args.analytics),\n            eventsHistory: history,\n        )\n        \n        let remoteDataSubscriber = AutomationRemoteDataSubscriber(\n            dataStore: args.dataStore,\n            remoteDataAccess: remoteDataAccess,\n            engine: engine,\n            frequencyLimitManager: frequencyLimits\n        )\n        \n        let inAppMessaging = DefaultInAppMessaging(\n            executor: messageExecutor,\n            preparer: messagePreparer\n        )\n        \n        let legacyInAppMessaging = DefaultLegacyInAppMessaging(\n            analytics: LegacyInAppAnalytics(recorder: eventRecorder),\n            dataStore: args.dataStore,\n            automationEngine: engine\n        )\n        \n        let inAppAutomation = DefaultInAppAutomation(\n            engine: engine,\n            inAppMessaging: inAppMessaging,\n            legacyInAppMessaging: legacyInAppMessaging,\n            remoteData: args.remoteData,\n            remoteDataSubscriber: remoteDataSubscriber,\n            dataStore: args.dataStore,\n            privacyManager: args.privacyManager,\n            config: args.config\n        )\n        \n        return AutomationSDKModule(\n            components: [\n                InAppAutomationComponent(inAppAutomation: inAppAutomation)\n            ]\n        )\n    }\n}\n\nfileprivate struct AutomationActionManifest : ActionsManifest {\n    var manifest: [[String] : () -> ActionEntry] = [\n        LandingPageAction.defaultNames: {\n            return ActionEntry(\n                action: LandingPageAction(),\n                predicate: LandingPageAction.defaultPredicate\n            )\n        }\n    ]\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppAutomation.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n\n/**\n * Provides a control interface for creating, canceling and executing in-app automations.\n */\npublic protocol InAppAutomation: AnyObject, Sendable {\n    /// In-App Messaging\n    var inAppMessaging: any InAppMessaging { get }\n\n    /// Legacy In-App Messaging\n    var legacyInAppMessaging: any LegacyInAppMessaging { get }\n\n    /// Paused state of in-app automation.\n    @MainActor\n    var isPaused: Bool { get set }\n\n    /// Creates the provided schedules or updates them if they already exist.\n    /// - Parameter schedules: The schedules to create or update.\n    func upsertSchedules(_ schedules: [AutomationSchedule]) async throws\n\n    /// Cancels an in-app automation via its schedule identifier.\n    /// - Parameter identifier: The schedule identifier to cancel.\n    func cancelSchedule(identifier: String) async throws\n\n    /// Cancels multiple in-app automations via their schedule identifiers.\n    /// - Parameter identifiers: The schedule identifiers to cancel.\n    func cancelSchedule(identifiers: [String]) async throws\n\n    /// Cancels multiple in-app automations via their group.\n    /// - Parameter group: The group to cancel.\n    func cancelSchedules(group: String) async throws\n\n    /// Gets the in-app automation with the provided schedule identifier.\n    /// - Parameter identifier: The schedule identifier.\n    /// - Returns: The in-app automation corresponding to the provided schedule identifier.\n    func getSchedule(identifier: String) async throws -> AutomationSchedule?\n\n    /// Gets the in-app automation with the provided group.\n    /// - Parameter identifier: The group to get.\n    /// - Returns: The in-app automation corresponding to the provided group.\n    func getSchedules(group: String) async throws -> [AutomationSchedule]\n    \n    /// Inapp Automation status updates. Possible values are upToDate, stale and outOfDate.\n    var statusUpdates: AsyncStream<InAppAutomationUpdateStatus> { get async }\n    \n    /// Current inApp Automation status. Possible values are upToDate, stale and outOfDate.\n    var status: InAppAutomationUpdateStatus { get async }\n    \n    /// Allows to wait for the refresh of the InApp Automation rules.\n    ///  - Parameters\n    ///     - maxTime: Timeout in seconds.\n    func waitRefresh(maxTime: TimeInterval?) async\n}\n\ninternal protocol InternalInAppAutomation: InAppAutomation {\n    func cancelSchedulesWith(type: AutomationSchedule.ScheduleType) async throws\n}\n\nfinal class DefaultInAppAutomation: InternalInAppAutomation, Sendable {\n\n    private let engine: any AutomationEngineProtocol\n    private let remoteDataSubscriber: any AutomationRemoteDataSubscriberProtocol\n    private let dataStore: PreferenceDataStore\n    private let privacyManager: any AirshipPrivacyManager\n    private let notificationCenter: AirshipNotificationCenter\n    private static let pausedStoreKey: String = \"UAInAppMessageManagerPaused\"\n    private let _legacyInAppMessaging: any InternalLegacyInAppMessaging\n    private let remoteData: any RemoteDataProtocol\n\n    /// In-App Messaging\n    let inAppMessaging: any InAppMessaging\n\n    /// Legacy In-App Messaging\n    var legacyInAppMessaging: any LegacyInAppMessaging {\n        return _legacyInAppMessaging\n    }\n\n    @MainActor\n    init(\n        engine: any AutomationEngineProtocol,\n        inAppMessaging: any InAppMessaging,\n        legacyInAppMessaging: any InternalLegacyInAppMessaging,\n        remoteData: any RemoteDataProtocol,\n        remoteDataSubscriber: any AutomationRemoteDataSubscriberProtocol,\n        dataStore: PreferenceDataStore,\n        privacyManager: any AirshipPrivacyManager,\n        config: RuntimeConfig,\n        notificationCenter: AirshipNotificationCenter = .shared\n    ) {\n        self.engine = engine\n        self.inAppMessaging = inAppMessaging\n        self._legacyInAppMessaging = legacyInAppMessaging\n        self.remoteDataSubscriber = remoteDataSubscriber\n        self.dataStore = dataStore\n        self.privacyManager = privacyManager\n        self.notificationCenter = notificationCenter\n        self.remoteData = remoteData\n\n        if (config.airshipConfig.autoPauseInAppAutomationOnLaunch) {\n            self.isPaused = true\n        }\n    }\n\n    /// Paused state of in-app automation.\n    @MainActor\n    public var isPaused: Bool {\n        get {\n            return self.dataStore.bool(forKey: Self.pausedStoreKey)\n        }\n        set {\n            self.dataStore.setBool(newValue, forKey: Self.pausedStoreKey)\n            self.engine.setExecutionPaused(newValue)\n        }\n    }\n\n    /// Creates the provided schedules or updates them if they already exist.\n    /// - Parameter schedules: The schedules to create or update.\n    func upsertSchedules(_ schedules: [AutomationSchedule]) async throws {\n        try await self.engine.upsertSchedules(schedules)\n    }\n\n    /// Cancels an in-app automation via its schedule identifier.\n    /// - Parameter identifier: The schedule identifier to cancel.\n    func cancelSchedule(identifier: String) async throws {\n        try await self.engine.cancelSchedules(identifiers: [identifier])\n    }\n\n    /// Cancels multiple in-app automations via their schedule identifiers.\n    /// - Parameter identifiers: The schedule identifiers to cancel.\n    func cancelSchedule(identifiers: [String]) async throws {\n        try await self.engine.cancelSchedules(identifiers: identifiers)\n    }\n\n    /// Cancels multiple in-app automations via their group.\n    /// - Parameter group: The group to cancel.\n    func cancelSchedules(group: String) async throws {\n        try await self.engine.cancelSchedules(group: group)\n    }\n\n    func cancelSchedulesWith(type: AutomationSchedule.ScheduleType) async throws {\n        try await self.engine.cancelSchedulesWith(type: type)\n    }\n\n    /// Gets the in-app automation with the provided schedule identifier.\n    /// - Parameter identifier: The schedule identifier.\n    /// - Returns: The in-app automation corresponding to the provided schedule identifier.\n    public func getSchedule(identifier: String) async throws -> AutomationSchedule? {\n        return try await self.engine.getSchedule(identifier: identifier)\n    }\n\n    /// Gets the in-app automation with the provided group.\n    /// - Parameter identifier: The group to get.\n    /// - Returns: The in-app automation corresponding to the provided group.\n    public func getSchedules(group: String) async throws -> [AutomationSchedule] {\n        return try await self.engine.getSchedules(group: group)\n    }\n\n    /// Inapp Automation status updates. Possible values are upToDate, stale and outOfDate.\n    public var statusUpdates: AsyncStream<InAppAutomationUpdateStatus> {\n        get async {\n            return await self.remoteData.statusUpdates(sources: [RemoteDataSource.app, RemoteDataSource.contact], map: { statuses in\n                if statuses.values.contains(.outOfDate) {\n                    return InAppAutomationUpdateStatus.outOfDate\n                } else if statuses.values.contains(.stale) {\n                    return InAppAutomationUpdateStatus.stale\n                } else {\n                    return InAppAutomationUpdateStatus.upToDate\n                }\n            })\n        }\n    }\n    \n    /// Current inApp Automation status. Possible values are upToDate, stale and outOfDate.\n    public var status: InAppAutomationUpdateStatus {\n        get async {\n            let statuses = await self.remoteData.statusUpdates(sources: [RemoteDataSource.app, RemoteDataSource.contact], map: { statuses in\n                if statuses.values.contains(.outOfDate) {\n                    return InAppAutomationUpdateStatus.outOfDate\n                } else if statuses.values.contains(.stale) {\n                    return InAppAutomationUpdateStatus.stale\n                } else {\n                    return InAppAutomationUpdateStatus.upToDate\n                }\n            })\n            \n            return await statuses.first {_ in true } ?? .upToDate\n        }\n    }\n    \n    /// Allows to wait for the refresh of the InApp Automation rules.\n    ///  - Parameters\n    ///     - maxTime: Timeout in seconds.\n    public func waitRefresh(maxTime: TimeInterval? = nil) async {\n        await self.remoteData.waitRefresh(source: RemoteDataSource.app, maxTime: maxTime)\n    }\n\n    @MainActor\n    private func privacyManagerUpdated() {\n        if self.privacyManager.isEnabled(.inAppAutomation) {\n            self.engine.setEnginePaused(false)\n            self.remoteDataSubscriber.subscribe()\n        } else {\n            self.engine.setEnginePaused(true)\n            self.remoteDataSubscriber.unsubscribe()\n        }\n    }\n}\n\nextension DefaultInAppAutomation {\n    @MainActor\n    func airshipReady() {\n        self.engine.setExecutionPaused(self.isPaused)\n\n        Task {\n            await self.engine.start()\n        }\n\n        self.notificationCenter.addObserver(forName: AirshipNotifications.PrivacyManagerUpdated.name) { [weak self] _ in\n            Task { @MainActor in\n                self?.privacyManagerUpdated()\n            }\n        }\n        self.privacyManagerUpdated()\n    }\n\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON // wrapped [AnyHashable: Any]\n    ) async -> UABackgroundFetchResult {\n        return await self._legacyInAppMessaging.receivedRemoteNotification(notification)\n    }\n\n#if !os(tvOS)\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        await self._legacyInAppMessaging.receivedNotificationResponse(response)\n    }\n#endif\n}\n\n\npublic extension Airship {\n    /// The shared `InAppAutomation` instance. `Airship.takeOff` must be called before accessing this instance.\n    static var inAppAutomation: any InAppAutomation {\n        return Airship.requireComponent(\n            ofType: InAppAutomationComponent.self\n        ).inAppAutomation\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppAutomationComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n@preconcurrency\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\n/// Actual airship component for InAppAutomation. Used to hide AirshipComponent methods.\nfinal class InAppAutomationComponent: AirshipComponent, AirshipPushableComponent {\n    let inAppAutomation: DefaultInAppAutomation\n\n    init(inAppAutomation: DefaultInAppAutomation) {\n        self.inAppAutomation = inAppAutomation\n    }\n\n    @MainActor\n    func airshipReady() {\n        self.inAppAutomation.airshipReady()\n    }\n\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON\n    ) async -> UABackgroundFetchResult {\n        return await self.inAppAutomation.receivedRemoteNotification(notification)\n    }\n\n#if !os(tvOS)\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        await self.inAppAutomation.receivedNotificationResponse(response)\n    }\n#endif\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppAutomationUpdateStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// In-app automation remote data status.\npublic enum InAppAutomationUpdateStatus: Sendable {\n    /// Remote data is current.\n    case upToDate\n    /// Remote data may be outdated; refresh in progress or deferred.\n    case stale\n    /// Remote data is known to be out of date.\n    case outOfDate\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Analytics/InAppDisplayImpressionRuleProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nenum InAppDisplayImpressionRule: Equatable, Sendable {\n    case once\n    case interval(TimeInterval)\n}\n\nprotocol InAppDisplayImpressionRuleProvider: Sendable {\n    func impressionRules(for message: InAppMessage) -> InAppDisplayImpressionRule\n}\n\nfinal class DefaultInAppDisplayImpressionRuleProvider: InAppDisplayImpressionRuleProvider  {\n    private static let defaultEmbeddedImpressionInterval: TimeInterval = 1800.0 // 30 mins\n\n    func impressionRules(for message: InAppMessage) -> InAppDisplayImpressionRule {\n        if (message.isEmbedded) {\n            return .interval(Self.defaultEmbeddedImpressionInterval)\n        } else {\n            return .once\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Analytics/InAppMessageAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct InAppCustomEventContext: Sendable, Encodable, Equatable {\n    var id: ThomasLayoutEventMessageID\n    var context: ThomasLayoutEventContext?\n}\n\nprotocol InAppMessageAnalyticsProtocol: ThomasLayoutMessageAnalyticsProtocol {\n    @MainActor\n    func makeCustomEventContext(layoutContext: ThomasLayoutContext?) -> InAppCustomEventContext?\n}\n\nfinal class LoggingInAppMessageAnalytics: InAppMessageAnalyticsProtocol {\n    func makeCustomEventContext(layoutContext: ThomasLayoutContext?) -> InAppCustomEventContext? {\n        return nil\n    }\n    \n    func recordEvent(_ event: any ThomasLayoutEvent, layoutContext: ThomasLayoutContext?) {\n        do {\n            let body = try event.data?.prettyString ?? \"nil\"\n            let context = try layoutContext?.prettyString ?? \"nil\"\n            AirshipLogger.debug(\n                \"Adding event \\(event.name.reportingName):\\n body: \\(body),\\n layoutContext: \\(context)\"\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to log event \\(event): \\(error)\")\n        }\n    }\n}\n\nfileprivate extension Encodable {\n    var prettyString: String? {\n        get throws {\n            let encoder = JSONEncoder()\n            encoder.outputFormatting = [.prettyPrinted]\n            return String(data: try encoder.encode(self), encoding: .utf8)\n        }\n    }\n}\n\n\nfinal class InAppMessageAnalytics: InAppMessageAnalyticsProtocol {\n    private let preparedScheduleInfo: PreparedScheduleInfo\n    private let messageID: ThomasLayoutEventMessageID\n    private let source: ThomasLayoutEventSource\n    private let renderedLocale: AirshipJSON?\n    private let eventRecorder: any ThomasLayoutEventRecorderProtocol\n    private let isReportingEnabled: Bool\n    private let date: any AirshipDateProtocol\n\n    private let historyStore: any MessageDisplayHistoryStoreProtocol\n    private let displayImpressionRule: InAppDisplayImpressionRule\n\n    private let displayHistory: AirshipMainActorValue<MessageDisplayHistory>\n    private let displayContext: AirshipMainActorValue<ThomasLayoutEventContext.Display>\n\n    init(\n        preparedScheduleInfo: PreparedScheduleInfo,\n        message: InAppMessage,\n        displayImpressionRule: InAppDisplayImpressionRule,\n        eventRecorder: any ThomasLayoutEventRecorderProtocol,\n        historyStore: any MessageDisplayHistoryStoreProtocol,\n        displayHistory: MessageDisplayHistory,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.preparedScheduleInfo = preparedScheduleInfo\n        self.messageID = Self.makeMessageID(\n            message: message,\n            scheduleID: preparedScheduleInfo.scheduleID,\n            campaigns: preparedScheduleInfo.campaigns\n        )\n        self.source = Self.makeEventSource(message: message)\n        self.renderedLocale = message.renderedLocale\n        self.eventRecorder = eventRecorder\n        self.isReportingEnabled = message.isReportingEnabled ?? true\n        self.displayImpressionRule = displayImpressionRule\n        self.historyStore = historyStore\n        self.date = date\n\n        self.displayHistory = AirshipMainActorValue(displayHistory)\n\n        self.displayContext = AirshipMainActorValue(\n            ThomasLayoutEventContext.Display(\n                triggerSessionID: preparedScheduleInfo.triggerSessionID,\n                isFirstDisplay: displayHistory.lastDisplay == nil,\n                isFirstDisplayTriggerSessionID: preparedScheduleInfo.triggerSessionID != displayHistory.lastDisplay?.triggerSessionID\n            )\n        )\n    }\n\n    @MainActor\n    func makeCustomEventContext(layoutContext: ThomasLayoutContext?) -> InAppCustomEventContext? {\n        return InAppCustomEventContext(\n            id: self.messageID,\n            context: ThomasLayoutEventContext.makeContext(\n                reportingContext: self.preparedScheduleInfo.reportingContext,\n                experimentsResult: self.preparedScheduleInfo.experimentResult,\n                layoutContext: layoutContext,\n                displayContext: self.displayContext.value\n            )\n        )\n    }\n\n    func recordEvent(\n        _ event: any ThomasLayoutEvent,\n        layoutContext: ThomasLayoutContext?\n    ) {\n        let now = self.date.now\n\n        if event is ThomasLayoutDisplayEvent {\n            if let lastDisplay = displayHistory.value.lastDisplay {\n                if self.preparedScheduleInfo.triggerSessionID == lastDisplay.triggerSessionID {\n                    self.displayContext.update { value in\n                        value.isFirstDisplay = false\n                        value.isFirstDisplayTriggerSessionID = false\n                    }\n                } else {\n                    self.displayContext.update { value in\n                        value.isFirstDisplay = false\n                    }\n                }\n            }\n\n            if (recordImpression(date: now)) {\n                self.displayHistory.update { value in\n                    value.lastImpression = MessageDisplayHistory.LastImpression(\n                        date: now,\n                        triggerSessionID: self.preparedScheduleInfo.triggerSessionID\n                    )\n                }\n            }\n\n\n            self.displayHistory.update { value in\n                value.lastDisplay = MessageDisplayHistory.LastDisplay(\n                    triggerSessionID: self.preparedScheduleInfo.triggerSessionID\n                )\n            }\n\n\n            self.historyStore.set(displayHistory.value, scheduleID: preparedScheduleInfo.scheduleID)\n        }\n\n\n        guard self.isReportingEnabled else { return }\n\n        let data = ThomasLayoutEventData(\n            event: event, \n            context: ThomasLayoutEventContext.makeContext(\n                reportingContext: self.preparedScheduleInfo.reportingContext,\n                experimentsResult: self.preparedScheduleInfo.experimentResult,\n                layoutContext: layoutContext,\n                displayContext: self.displayContext.value\n            ),\n            source: self.source,\n            messageID: self.messageID,\n            renderedLocale: self.renderedLocale\n        )\n\n        self.eventRecorder.recordEvent(inAppEventData: data)\n    }\n\n    @MainActor\n    var shouldRecordImpression: Bool {\n        guard\n            let lastImpression = displayHistory.value.lastImpression,\n            lastImpression.triggerSessionID == self.preparedScheduleInfo.triggerSessionID\n        else {\n            return true\n        }\n\n        switch (self.displayImpressionRule) {\n        case .interval(let interval):\n            return self.date.now.timeIntervalSince(lastImpression.date) >= interval\n        case .once:\n            return false\n        }\n    }\n\n    @MainActor\n    private func recordImpression(date: Date) -> Bool {\n        guard shouldRecordImpression else { return false }\n        guard let productID = self.preparedScheduleInfo.productID else { return false }\n\n        let event = AirshipMeteredUsageEvent(\n            eventID: UUID().uuidString,\n            entityID: self.messageID.identifier,\n            usageType: .inAppExperienceImpression,\n            product: productID,\n            reportingContext: self.preparedScheduleInfo.reportingContext,\n            timestamp: date,\n            contactID: self.preparedScheduleInfo.contactID\n        )\n        self.eventRecorder.recordImpressionEvent(event)\n\n        return true\n    }\n\n    private static func makeMessageID(\n        message: InAppMessage,\n        scheduleID: String,\n        campaigns: AirshipJSON?\n    ) -> ThomasLayoutEventMessageID {\n        switch (message.source ?? .remoteData) {\n        case .appDefined: return .appDefined(identifier: scheduleID)\n        case .remoteData: return .airship(identifier: scheduleID, campaigns: campaigns)\n        case .legacyPush: return .legacy(identifier: scheduleID)\n        }\n    }\n\n    private static func makeEventSource(\n        message: InAppMessage\n    ) -> ThomasLayoutEventSource {\n        switch (message.source ?? .remoteData) {\n        case .appDefined: return .appDefined\n        case .remoteData: return .airship\n        case .legacyPush: return .airship\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Analytics/InAppMessageAnalyticsFactory.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol InAppMessageAnalyticsFactoryProtocol: Sendable {\n    func makeAnalytics(\n        preparedScheduleInfo: PreparedScheduleInfo,\n        message: InAppMessage\n    ) async -> any InAppMessageAnalyticsProtocol\n}\n\nstruct InAppMessageAnalyticsFactory: InAppMessageAnalyticsFactoryProtocol {\n    private let eventRecorder: ThomasLayoutEventRecorder\n    private let displayHistoryStore: any MessageDisplayHistoryStoreProtocol\n    private let displayImpressionRuleProvider: any InAppDisplayImpressionRuleProvider\n\n    init(\n        eventRecorder: ThomasLayoutEventRecorder,\n        displayHistoryStore: any MessageDisplayHistoryStoreProtocol,\n        displayImpressionRuleProvider: any InAppDisplayImpressionRuleProvider\n    ) {\n        self.eventRecorder = eventRecorder\n        self.displayHistoryStore = displayHistoryStore\n        self.displayImpressionRuleProvider = displayImpressionRuleProvider\n    }\n\n    func makeAnalytics(\n        preparedScheduleInfo: PreparedScheduleInfo,\n        message: InAppMessage\n    ) async -> any InAppMessageAnalyticsProtocol {\n        return InAppMessageAnalytics(\n            preparedScheduleInfo: preparedScheduleInfo,\n            message: message,\n            displayImpressionRule: displayImpressionRuleProvider.impressionRules(\n                for: message\n            ),\n            eventRecorder: eventRecorder,\n            historyStore: displayHistoryStore,\n            displayHistory: await self.displayHistoryStore.get(\n                scheduleID: preparedScheduleInfo.scheduleID\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Assets/AirshipCachedAssets.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n /// Convenience struct representing an assets directory containing asset files\n /// with filenames derived from their remote URL using sha256.\npublic protocol AirshipCachedAssetsProtocol: Sendable {\n    /// Return URL at which to cache a given asset\n    /// - Parameters:\n    ///     - remoteURL: URL from which the cached data is fetched\n    /// - Returns: URL at which to cache a given asset\n    func cachedURL(remoteURL: URL) -> URL?\n\n    /// Checks if a URL is cached\n    /// - Parameters:\n    ///     - remoteURL: URL from which the cached data is fetched\n    /// - Returns: true if cached, otherwise false.\n    func isCached(remoteURL: URL) -> Bool\n}\n\nstruct EmptyAirshipCachedAssets: AirshipCachedAssetsProtocol {\n    func cachedURL(remoteURL: URL) -> URL? {\n        return nil\n    }\n    \n    func isCached(remoteURL: URL) -> Bool {\n        return false\n    }\n}\n\nstruct AirshipCachedAssets: AirshipCachedAssetsProtocol, Equatable {\n    static func == (lhs: AirshipCachedAssets, rhs: AirshipCachedAssets) -> Bool {\n        lhs.directory == rhs.directory\n    }\n\n    private let directory: URL\n\n    private let assetFileManager: any AssetFileManager\n\n    internal init(directory: URL, assetFileManager: any AssetFileManager = DefaultAssetFileManager()) {\n        self.directory = directory\n        self.assetFileManager = assetFileManager\n    }\n\n    private func getCachedAsset(from remoteURL: URL) -> URL {\n        /// Derive a unique and consistent asset filename from the remote URL using sha256\n        let filename: String = remoteURL.assetFilename\n\n        return directory.appendingPathComponent(filename, isDirectory: false)\n    }\n\n    func cachedURL(remoteURL: URL) -> URL? {\n        let cached: URL = getCachedAsset(from: remoteURL)\n\n        /// Ensure directory exists\n        guard assetFileManager.assetItemExists(at: directory) else {\n            return nil\n        }\n\n        return cached\n    }\n\n    func isCached(remoteURL: URL) -> Bool {\n        let cached: URL = getCachedAsset(from: remoteURL)\n\n        return assetFileManager.assetItemExists(at: cached)\n    }\n}\n\n\nfileprivate extension URL {\n    var assetFilename: String {\n        return AirshipUtils.sha256Hash(input: self.path)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Assets/AssetCacheManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Wrapper for the download tasks that is responsible for downloading assets\nprotocol AssetDownloader: Sendable {\n    /// Downloads the asset from a remote URL and returns its temporary local URL\n    func downloadAsset(remoteURL: URL) async throws -> URL\n}\n\n/// Wrapper for the filesystem that is responsible for asset-caching related file and directory operations\nprotocol AssetFileManager: Sendable {\n    /// Gets or creates the root directory\n    var rootDirectory: URL? { get }\n\n    /// Gets or creates cache directory based on the root directory with the provided identifier (usually a schedule ID) and returns its full cache URL\n    func ensureCacheDirectory(identifier: String) throws -> URL\n\n    /// Checks if asset file or directory exists at cache URL\n    func assetItemExists(at cacheURL: URL) -> Bool\n\n    /// Moves the asset from a temporary URL to its asset cache directory\n    func moveAsset(from tempURL: URL, to cacheURL: URL) throws\n\n    /// Clears all assets corresponding to the provided identifier\n    func clearAssets(cacheURL: URL) throws\n}\n\nprotocol AssetCacheManagerProtocol: Actor {\n    func cacheAssets(\n        identifier: String,\n        assets: [String]\n    ) async throws -> any AirshipCachedAssetsProtocol\n\n    func clearCache(identifier: String) async\n}\n\n\n/// Downloads and caches asset files in filesystem using cancelable thread-safe tasks.\nactor AssetCacheManager: AssetCacheManagerProtocol {\n    private let assetDownloader: any AssetDownloader\n    private let assetFileManager: any AssetFileManager\n\n    private var cacheRoot: URL?\n    private var taskMap: [String: Task<AirshipCachedAssets, any Error>] = [:]\n\n    private let downloadSemaphore: AirshipAsyncSemaphore = AirshipAsyncSemaphore(value: 6)\n\n    internal init(\n        assetDownloader: any AssetDownloader = DefaultAssetDownloader(),\n        assetFileManager: any AssetFileManager = DefaultAssetFileManager()\n    ) {\n        self.assetDownloader = assetDownloader\n        self.assetFileManager = assetFileManager\n\n        /// Set cache root for clearing operations\n        self.cacheRoot = assetFileManager.rootDirectory\n    }\n\n    /// Cache assets for a given identifer.\n    /// Downloads assets from remote paths and stores them in an identifier-named cache directory with consistent and unique file names\n    /// derived from their remote paths using sha256.\n    /// - Parameters:\n    ///   - identifier: Name of the directory within the root cache directory, usually an in-app message schedule ID\n    ///   - assets: An array of remote URL paths for the assets assoicated with the provided identifer\n    /// - Returns: AirshipCachesAssets instance\n    func cacheAssets(\n        identifier: String,\n        assets: [String]\n    ) async throws -> any AirshipCachedAssetsProtocol {\n        \n        if let running = taskMap[identifier] {\n            return try await running.result.get()\n        }\n        \n        let task: Task<AirshipCachedAssets, any Error> = Task {\n            let startTime = Date()\n\n            // Deduplicate URLs to prevent concurrent operations on the same asset\n            let uniqueAssets = Array(Set(assets))\n            let assetURLs = uniqueAssets.compactMap({ URL(string:$0) })\n\n            // Log if duplicate URLs were found\n            if assets.count != uniqueAssets.count {\n                AirshipLogger.debug(\"Found duplicate asset URLs for identifier \\(identifier): \\(assets.count) URLs reduced to \\(uniqueAssets.count) unique URLs\")\n            }\n\n            /// Create or get the directory for the assets corresponding to a specific identifier\n            let cacheDirectory = try assetFileManager.ensureCacheDirectory(identifier: identifier)\n\n            let cachedAssets = AirshipCachedAssets(directory: cacheDirectory, assetFileManager: assetFileManager)\n\n            try await withThrowingTaskGroup(of: Void.self) { [downloadSemaphore] group in\n                for asset in assetURLs {\n                    group.addTask {\n                        try await downloadSemaphore.withPermit {\n                            if Task.isCancelled || cachedAssets.isCached(remoteURL: asset) {\n                                return\n                            }\n\n                            let tempURL = try await self.assetDownloader.downloadAsset(remoteURL: asset)\n\n                            // Double-check after download in case another task cached it\n                            if cachedAssets.isCached(remoteURL: asset) {\n                                // Clean up temp file and return\n                                try? FileManager.default.removeItem(at: tempURL)\n                                AirshipLogger.trace(\"Asset was cached by another task during download, skipping: \\(asset)\")\n                                return\n                            }\n\n                            if let cacheURL = cachedAssets.cachedURL(remoteURL: asset) {\n                                try self.assetFileManager.moveAsset(from: tempURL, to: cacheURL)\n                            }\n                        }\n                    }\n                }\n\n                try await group.waitForAll()\n            }\n\n            let duration = Date().timeIntervalSince(startTime)\n\n            AirshipLogger.debug(\"In-app message \\(identifier): \\(assets.count) assets prepared in \\(duration) seconds\")\n\n\n            return cachedAssets\n        }\n\n        taskMap[identifier] = task\n\n        return try await task.result.get()\n    }\n\n\n    /// Clears the cache directory associated with the identifier\n    /// - Parameter identifier: Name of the directory within the root cache directory, usually an in-app message schedule ID\n    func clearCache(identifier: String) async {\n        taskMap[identifier]?.cancel()\n        taskMap.removeValue(forKey: identifier)\n\n        if let root = self.cacheRoot {\n            let cache = root.appendingPathComponent(identifier, isDirectory: true)\n\n            do {\n                try assetFileManager.clearAssets(cacheURL: cache)\n            } catch {\n                AirshipLogger.debug(\"Unable to clear asset cache for identifier: \\(identifier) with error:\\(error)\")\n            }\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Assets/DefaultAssetDownloader.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Data task wrapper used for testing the default asset downloader\nprotocol AssetDownloaderSession: Sendable {\n    func autoResumingDataTask(with url: URL, completion: @Sendable @escaping (Data?, URLResponse?, (any Error)?) -> Void) -> any AirshipCancellable\n}\n\nextension URLSession: AssetDownloaderSession {\n    func autoResumingDataTask(with url: URL, completion: @Sendable @escaping (Data?, URLResponse?, (any Error)?) -> Void) -> any AirshipCancellable {\n        \n        let task = self.dataTask(with: url, completionHandler: { data, response, error in\n            completion(data, response, error)\n        })\n\n        task.resume()\n\n        return CancellableValueHolder(value: task) { task in\n            task.cancel()\n        }\n    }\n}\n\nstruct DefaultAssetDownloader : AssetDownloader {\n    var session: any AssetDownloaderSession\n\n    init(session: any AssetDownloaderSession = URLSession.airshipSecureSession) {\n        self.session = session\n    }\n\n    func downloadAsset(remoteURL: URL) async throws -> URL {\n        let cancellable = CancellableValueHolder<any AirshipCancellable>() { cancellable in\n            cancellable.cancel()\n        }\n\n        return try await withTaskCancellationHandler {\n            try await withCheckedThrowingContinuation { continuation in\n                cancellable.value = session.autoResumingDataTask(with: remoteURL) { data, response, error in\n                    if let error = error {\n                        continuation.resume(throwing: error)\n                        return\n                    }\n\n                    guard let data = data else {\n                        continuation.resume(throwing: URLError(.badServerResponse))\n                        return\n                    }\n\n                    do {\n                        let tempDirectory = FileManager.default.temporaryDirectory\n                        let tempFileURL = tempDirectory.appendingPathComponent(UUID().uuidString + remoteURL.lastPathComponent)\n                        try data.write(to: tempFileURL)\n                        continuation.resume(returning: tempFileURL)\n                    } catch {\n                        continuation.resume(throwing: error)\n                    }\n                }\n            }\n        } onCancel: {\n            cancellable.cancel()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Assets/DefaultAssetFileManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct DefaultAssetFileManager: AssetFileManager {\n    private let rootPathComponent: String\n\n    init(rootPathComponent: String = \"com.urbanairship.iamassetcache\") {\n        self.rootPathComponent = rootPathComponent\n    }\n\n    var rootDirectory: URL? {\n       try? ensureCacheRootDirectory(rootPathComponent: rootPathComponent)\n    }\n\n    func ensureCacheDirectory(identifier: String) throws -> URL {\n        let url = try ensureCacheRootDirectory(rootPathComponent: rootPathComponent)\n\n        let cacheDirectory = url.appendingPathComponent(identifier, isDirectory: true)\n\n        return try ensureCacheDirectory(url: cacheDirectory)\n    }\n\n    func assetItemExists(at cacheURL: URL) -> Bool {\n        return FileManager.default.fileExists(atPath: cacheURL.path)\n    }\n\n    func moveAsset(from tempURL: URL, to cacheURL: URL) throws {\n        let fileManager = FileManager.default\n\n        do {\n            // Ensure parent directory exists\n            let parentDir = cacheURL.deletingLastPathComponent()\n            try fileManager.createDirectory(at: parentDir, withIntermediateDirectories: true, attributes: nil)\n\n            if fileManager.fileExists(atPath: cacheURL.path) {\n                // Use replaceItem for atomic replacement\n                _ = try fileManager.replaceItem(at: cacheURL,\n                                                withItemAt: tempURL,\n                                                backupItemName: nil,\n                                                options: [],\n                                                resultingItemURL: nil)\n            } else {\n                try fileManager.moveItem(at: tempURL, to: cacheURL)\n            }\n        } catch let error as NSError {\n            // Handle the specific case where file already exists\n            if error.domain == NSCocoaErrorDomain && error.code == NSFileWriteFileExistsError {\n                // File already exists - this is okay, just clean up temp file\n                try? fileManager.removeItem(at: tempURL)\n                AirshipLogger.trace(\"Asset already exists at cache URL, skipping move: \\(cacheURL)\")\n            } else {\n                throw AirshipErrors.error(\"Error moving asset to asset cache \\(error)\")\n            }\n        }\n    }\n\n    func clearAssets(cacheURL: URL) throws {\n        let fileManager = FileManager.default\n        try fileManager.removeItem(at: cacheURL)\n    }\n\n    // MARK: Helpers\n\n    private func ensureCacheRootDirectory(rootPathComponent: String) throws -> URL {\n        let fileManager = FileManager.default\n\n        guard let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first else {\n            throw AirshipErrors.error(\"Error creating asset cache root directory: user caches directory unavailable.\")\n        }\n\n        fileManager.urls(for: .cachesDirectory, in: .userDomainMask)\n\n        let cacheRootDirectory = cacheDirectory.appendingPathComponent(rootPathComponent, isDirectory: true)\n\n        return try ensureCacheDirectory(url: cacheRootDirectory)\n    }\n\n    private func ensureCacheDirectory(url:URL) throws -> URL {\n        let fileManager = FileManager.default\n\n        var isDirectory: ObjCBool = false\n        let fileExists = fileManager.fileExists(atPath: url.path, isDirectory: &isDirectory)\n\n        do {\n            if !fileExists {\n                try fileManager.createDirectory(at: url, withIntermediateDirectories: true)\n            } else if !isDirectory.boolValue {\n                AirshipLogger.debug(\"Path:\\(url) exists but is not a directory. Removing the file and creating the directory.\")\n                try fileManager.removeItem(at: url)\n                try fileManager.createDirectory(at: url, withIntermediateDirectories: true)\n            }\n\n            return url\n        } catch {\n            AirshipLogger.debug(\"Error creating directory at \\(url): \\(error)\")\n            throw error\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Adapter/AirshipLayoutDisplayAdapter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nfinal class AirshipLayoutDisplayAdapter: DisplayAdapter {\n\n    private let message: InAppMessage\n    private let priority: Int\n    private let assets: any AirshipCachedAssetsProtocol\n    private let actionRunner: (any InternalInAppActionRunner)?\n    private let networkChecker: any AirshipNetworkCheckerProtocol\n\n    @MainActor\n    var themeManager: InAppAutomationThemeManager {\n        return Airship.inAppAutomation.inAppMessaging.themeManager\n    }\n\n    init(\n        message: InAppMessage,\n        priority: Int,\n        assets: any AirshipCachedAssetsProtocol,\n        actionRunner: (any InternalInAppActionRunner)? = nil,\n        networkChecker: any AirshipNetworkCheckerProtocol = AirshipNetworkChecker.shared\n    ) throws {\n        self.message = message\n        self.priority = priority\n        self.assets = assets\n        self.actionRunner = actionRunner\n        self.networkChecker = networkChecker\n\n        if case .custom(_) = message.displayContent {\n            throw AirshipErrors.error(\"Invalid adapter for layout type\")\n        }\n    }\n\n    var isReady: Bool {\n        let urlInfos = message.urlInfos\n        let needsNetwork = urlInfos.contains { info in\n            switch(info) {\n            case .web(url: _, requireNetwork: let requireNetwork):\n                if (requireNetwork) {\n                    return true\n                }\n            case .video(url: _, requireNetwork: let requireNetwork):\n                if (requireNetwork) {\n                    return true\n                }\n            case .image(url: let url, prefetch: let prefetch):\n                if let url = URL(string: url), prefetch, !assets.isCached(remoteURL: url) {\n                    return true\n                }\n#if canImport(AirshipCore)\n            @unknown default:\n                return true\n#endif\n            }\n\n            return false\n        }\n\n        return needsNetwork ? networkChecker.isConnected : true\n    }\n\n    func waitForReady() async {\n        guard await !self.isReady else {\n            return\n        }\n\n        for await isConnected in await networkChecker.connectionUpdates {\n            if (isConnected) {\n                return\n            }\n        }\n    }\n\n    func display(\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult {\n        switch (message.displayContent) {\n        case .banner(let banner):\n            return try await displayBanner(\n                banner,\n                displayTarget: displayTarget,\n                analytics: analytics\n            )\n        case .modal(let modal):\n            return try await displayModal(\n                modal,\n                displayTarget: displayTarget,\n                analytics: analytics\n            )\n        case .fullscreen(let fullscreen):\n            return try await displayFullscreen(\n                fullscreen,\n                displayTarget: displayTarget,\n                analytics: analytics\n            )\n        case .html(let html):\n            return try await displayHTML(\n                html,\n                displayTarget: displayTarget,\n                analytics: analytics\n            )\n        case .airshipLayout(let layout):\n            return try await displayThomasLayout(\n                layout,\n                displayTarget: displayTarget,\n                analytics: analytics\n            )\n        case .custom(_):\n            // This should never happen - constructor will throw\n            return .finished\n        }\n    }\n\n    private func makeInAppExtensions() -> InAppMessageExtensions {\n#if !os(tvOS)\n        InAppMessageExtensions(\n            nativeBridgeExtension: InAppMessageNativeBridgeExtension(\n                message: message\n            ),\n            imageProvider: AssetCacheImageProvider(assets: assets),\n            actionRunner: actionRunner\n        )\n#else\n        InAppMessageExtensions(\n            imageProvider: AssetCacheImageProvider(assets: assets),\n            actionRunner: actionRunner\n        )\n#endif\n    }\n\n    @MainActor\n    private func displayBanner(\n        _ banner: InAppMessageDisplayContent.Banner,\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult {\n        return try await withCheckedThrowingContinuation { continuation in\n            let displayable = displayTarget.prepareDisplay(for: .banner)\n\n            let dismissViewController = {\n                displayable.dismiss()\n            }\n\n            let listener = InAppMessageDisplayListener(\n                analytics: analytics\n            ) { result in\n                // Dismiss the In app message banner view controller\n                continuation.resume(returning: result)\n            }\n\n            let theme = self.themeManager.makeBannerTheme(message: self.message)\n\n            let environment = InAppMessageEnvironment(\n                delegate: listener,\n                extensions: makeInAppExtensions()\n            )\n\n            do {\n                try displayable.display { windowInfo in\n                    let bannerConstraints = InAppMessageBannerConstraints(\n                        size: windowInfo.size\n                    )\n\n                    let rootView = InAppMessageBannerView(\n                        environment: environment,\n                        displayContent: banner,\n                        bannerConstraints: bannerConstraints,\n                        theme: theme,\n                        onDismiss: dismissViewController\n                    )\n\n                    return InAppMessageBannerViewController(\n                        rootView: rootView,\n                        placement: banner.placement,\n                        bannerConstraints: bannerConstraints\n                    )\n                }\n            } catch {\n                continuation.resume(\n                    throwing: AirshipErrors.error(\"Failed to find window to display in-app banner \\(error)\")\n                )\n            }\n        }\n    }\n\n    @MainActor\n    private func displayModal(\n        _ modal: InAppMessageDisplayContent.Modal,\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult {\n        return try await withCheckedThrowingContinuation { continuation in\n            let displayable = displayTarget.prepareDisplay(for: .modal)\n\n            let listener = InAppMessageDisplayListener(\n                analytics: analytics\n            ) { result in\n                displayable.dismiss()\n                continuation.resume(returning: result)\n            }\n\n            let theme = self.themeManager.makeModalTheme(message: self.message)\n\n            let environment = InAppMessageEnvironment(\n                delegate: listener,\n                extensions: makeInAppExtensions()\n            )\n\n            let rootView = InAppMessageRootView(inAppMessageEnvironment: environment) {\n                InAppMessageModalView(displayContent: modal, theme: theme)\n            }\n\n            do {\n                try displayable.display { _ in\n                    let viewController = InAppMessageHostingController(rootView: rootView)\n#if !os(macOS)\n                    viewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen\n#endif\n                    return viewController\n                }\n            } catch {\n                continuation.resume(\n                    throwing: AirshipErrors.error(\"Failed to find window to display in-app banner \\(error)\")\n                )\n            }\n        }\n    }\n\n    @MainActor\n    private func displayFullscreen(\n        _ fullscreen: InAppMessageDisplayContent.Fullscreen,\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult {\n        return try await withCheckedThrowingContinuation { continuation in\n            let displayable = displayTarget.prepareDisplay(for: .modal)\n\n            let listener = InAppMessageDisplayListener(\n                analytics: analytics\n            ) { result in\n                displayable.dismiss()\n                continuation.resume(returning: result)\n            }\n\n            let theme = self.themeManager.makeFullscreenTheme(message: self.message)\n\n            let environment = InAppMessageEnvironment(\n                delegate: listener,\n                extensions: makeInAppExtensions()\n            )\n\n            let rootView = InAppMessageRootView(inAppMessageEnvironment: environment) {\n                FullscreenView(displayContent: fullscreen, theme: theme)\n            }\n\n            do {\n                try displayable.display { _ in\n                    let viewController = InAppMessageHostingController(rootView: rootView)\n#if !os(macOS)\n                    viewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen\n#endif\n                    return viewController\n                }\n            } catch {\n                continuation.resume(\n                    throwing: AirshipErrors.error(\"Failed to find window to display in-app banner \\(error)\")\n                )\n            }\n        }\n    }\n\n    @MainActor\n    private func displayHTML(\n        _ html: InAppMessageDisplayContent.HTML,\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult {\n#if !os(tvOS)\n        return try await withCheckedThrowingContinuation { continuation in\n            let displayable = displayTarget.prepareDisplay(for: .modal)\n\n            let listener = InAppMessageDisplayListener(\n                analytics: analytics\n            ) { result in\n                displayable.dismiss()\n                continuation.resume(returning: result)\n            }\n\n            let theme = self.themeManager.makeHTMLTheme(message: self.message)\n\n            let environment = InAppMessageEnvironment(\n                delegate: listener,\n                extensions: makeInAppExtensions()\n            )\n\n            let rootView = InAppMessageRootView(inAppMessageEnvironment: environment) {\n                HTMLView(displayContent: html, theme: theme)\n            }\n\n            do {\n                try displayable.display { _ in\n                    let viewController = InAppMessageHostingController(rootView: rootView)\n#if !os(macOS)\n                    viewController.modalPresentationStyle = UIModalPresentationStyle.fullScreen\n#endif\n                    return viewController\n                }\n            } catch {\n                continuation.resume(\n                    throwing: AirshipErrors.error(\"Failed to find window to display in-app banner \\(error)\")\n                )\n            }\n        }\n#else\n        return .cancel\n#endif\n    }\n\n    @MainActor\n    private func displayThomasLayout(\n        _ layout: AirshipLayout,\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult {\n        return try await withCheckedThrowingContinuation { continuation in\n            let listener = ThomasDisplayListener(analytics: analytics) { result in\n                continuation.resume(returning: result.automationDisplayResult)\n            }\n\n#if !os(tvOS)\n            let extensions = ThomasExtensions(\n                nativeBridgeExtension: InAppMessageNativeBridgeExtension(\n                    message: message\n                ),\n                imageProvider: AssetCacheImageProvider(assets: assets),\n                actionRunner: actionRunner\n            )\n\n#else\n            let extensions = ThomasExtensions(\n                imageProvider: AssetCacheImageProvider(assets: assets),\n                actionRunner: actionRunner\n            )\n#endif\n            do {\n                try Thomas.display(\n                    layout: layout,\n                    displayTarget: displayTarget,\n                    extensions: extensions,\n                    delegate: listener,\n                    extras: message.extras,\n                    priority: priority\n                )\n            } catch {\n                continuation.resume(throwing: error)\n            }\n        }\n    }\n}\n\nfileprivate final class AssetCacheImageProvider : AirshipImageProvider {\n    let assets: any AirshipCachedAssetsProtocol\n    init(assets: any AirshipCachedAssetsProtocol) {\n        self.assets = assets\n    }\n    \n    func get(url: URL) -> AirshipImageData? {\n        guard \n            let url = assets.cachedURL(remoteURL: url),\n            let data = FileManager.default.contents(atPath: url.path),\n            let imageData = try? AirshipImageData(data: data)\n        else {\n            return nil\n        }\n        \n        return imageData\n    }\n}\n\nextension ThomasDisplayListener.DisplayResult {\n    var automationDisplayResult: DisplayResult {\n        return switch self {\n            case .finished: .finished\n            case .cancel: .cancel\n            @unknown default: .finished\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Adapter/CustomDisplayAdapter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\n/// Custom display adapter types\npublic enum CustomDisplayAdapterType: Sendable {\n    /// HTML adapter\n    case html\n    \n    /// Modal adapter\n    case modal\n\n    /// Fullscreen adapter\n    case fullscreen\n\n    /// Banner adapter\n    case banner\n\n    /// Custom adapter\n    case custom\n}\n\n/// Custom display adapter\npublic protocol CustomDisplayAdapter: Sendable {\n    /// Checks if the adapter is ready\n    /// Whether the adapter has finished loading and is ready to display.\n    @MainActor\n    var isReady: Bool { get }\n\n    /// Suspends until the adapter is ready to display (e.g. assets loaded).\n    @MainActor\n    func waitForReady() async\n\n#if !os(macOS)\n    /// Called to display the message\n    /// - Parameters:\n    ///     - scene: The window scene\n    /// - Returns a CustomDisplayResolution\n    @MainActor\n    func display(scene: UIWindowScene) async -> CustomDisplayResolution\n#else\n    /// Called to display the message\n    /// - Returns a CustomDisplayResolution\n    @MainActor\n    func display() async -> CustomDisplayResolution\n#endif\n}\n\n/// Resolution data\npublic enum CustomDisplayResolution {\n    /// Button tap\n    case buttonTap(InAppMessageButtonInfo)\n    /// Message tap\n    case messageTap\n    /// User dismissed\n    case userDismissed\n    /// Timed out\n    case timedOut\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Adapter/CustomDisplayAdapterWrapper.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Wraps a custom display adapter as a DisplayAdapter\nfinal class CustomDisplayAdapterWrapper: DisplayAdapter {\n    let adapter: any CustomDisplayAdapter\n\n    @MainActor\n    var isReady: Bool { return adapter.isReady }\n\n    func waitForReady() async {\n        await adapter.waitForReady()\n    }\n\n    init(\n        adapter: any CustomDisplayAdapter\n    ) {\n        self.adapter = adapter\n    }\n\n    @MainActor\n    func display(displayTarget: AirshipDisplayTarget, analytics: any InAppMessageAnalyticsProtocol) async throws -> DisplayResult {\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        let timer = ActiveTimer()\n        \n#if os(macOS)\n        timer.start()\n        let result = await self.adapter.display()\n#else\n        let scene = try displayTarget.sceneProvider()\n        timer.start()\n        let result = await self.adapter.display(scene: scene)\n#endif\n        timer.stop()\n        \n        switch(result) {\n        case .buttonTap(let buttonInfo):\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.buttonTap(\n                    identifier: buttonInfo.identifier,\n                    description: buttonInfo.label.text,\n                    displayTime: timer.time\n                ),\n                layoutContext: nil\n            )\n            \n            return buttonInfo.behavior == .cancel ? .cancel : .finished\n        case .messageTap:\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.messageTap(displayTime: timer.time),\n                layoutContext: nil\n            )\n        case .userDismissed:\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.userDismissed(displayTime: timer.time),\n                layoutContext: nil\n            )\n        case .timedOut:\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.timedOut(displayTime: timer.time),\n                layoutContext: nil\n            )\n        }\n        return .finished\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Adapter/DisplayAdapter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n// Internal display adapter\nprotocol DisplayAdapter: Sendable {\n    @MainActor\n    var isReady: Bool { get }\n\n    func waitForReady() async\n\n    @MainActor\n    func display(\n        displayTarget: AirshipDisplayTarget,\n        analytics: any InAppMessageAnalyticsProtocol\n    ) async throws -> DisplayResult\n}\n\nenum DisplayResult: Sendable, Equatable {\n    case cancel\n    case finished\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Adapter/DisplayAdapterFactory.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Arguments passed to display adapters when creating or displaying an in-app message.\npublic struct DisplayAdapterArgs: Sendable {\n    /// The in-app message\n    public var message: InAppMessage\n\n    /// The assets\n    public var assets: any AirshipCachedAssetsProtocol\n\n    /// The schedule priority\n    public var priority: Int\n\n    /// Action runner\n    public var actionRunner: any InAppActionRunner {\n        return _actionRunner\n    }\n    \n    var _actionRunner: any InternalInAppActionRunner\n}\n\nprotocol DisplayAdapterFactoryProtocol: Sendable {\n\n    @MainActor\n    func setAdapterFactoryBlock(\n        forType: CustomDisplayAdapterType,\n        factoryBlock: @Sendable @escaping (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?\n    )\n\n    @MainActor\n    func makeAdapter(\n        args: DisplayAdapterArgs\n    ) throws -> any DisplayAdapter\n}\n\nfinal class DisplayAdapterFactory: DisplayAdapterFactoryProtocol, Sendable {\n\n    @MainActor\n    var customAdapters: [CustomDisplayAdapterType: @Sendable (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?] = [:]\n\n    @MainActor\n    func setAdapterFactoryBlock(\n        forType type: CustomDisplayAdapterType,\n        factoryBlock: @Sendable @escaping (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?\n    ) {\n        customAdapters[type] = factoryBlock\n    }\n\n    @MainActor\n    func makeAdapter(\n        args: DisplayAdapterArgs\n    ) throws -> any DisplayAdapter {\n        switch (args.message.displayContent) {\n        case .banner(_):\n            if let custom = customAdapters[.banner]?(args) {\n                return CustomDisplayAdapterWrapper(adapter: custom)\n            }\n        case .fullscreen(_):\n            if let custom = customAdapters[.fullscreen]?(args) {\n                return CustomDisplayAdapterWrapper(adapter: custom)\n            }\n        case .modal(_):\n            if let custom = customAdapters[.modal]?(args) {\n                return CustomDisplayAdapterWrapper(adapter: custom)\n            }\n        case .html(_):\n            if let custom = customAdapters[.html]?(args) {\n                return CustomDisplayAdapterWrapper(adapter: custom)\n            }\n        case .custom(_):\n            if let custom = customAdapters[.custom]?(args) {\n                return CustomDisplayAdapterWrapper(adapter: custom)\n            } else {\n                throw AirshipErrors.error(\"No adapter for message: \\(args.message)\")\n            }\n        case .airshipLayout(_):\n            break\n        }\n\n        return try AirshipLayoutDisplayAdapter(\n            message: args.message,\n            priority: args.priority,\n            assets: args.assets,\n            actionRunner: args._actionRunner\n        )\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Adapter/InAppMessageDisplayListener.swift",
    "content": "import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nfinal class InAppMessageDisplayListener: InAppMessageViewDelegate {\n\n    private let analytics: any InAppMessageAnalyticsProtocol\n    private let timer: any AirshipTimerProtocol\n    private var onDismiss: (@MainActor @Sendable (DisplayResult) -> Void)?\n\n    init(\n        analytics: any InAppMessageAnalyticsProtocol,\n        timer: (any AirshipTimerProtocol)? = nil,\n        onDismiss: @MainActor @escaping @Sendable (DisplayResult) -> Void\n    ) {\n        self.analytics = analytics\n        self.onDismiss = onDismiss\n        self.timer = timer ?? ActiveTimer()\n    }\n\n    func onAppear() {\n        timer.start()\n\n        analytics.recordEvent(\n            ThomasLayoutDisplayEvent(),\n            layoutContext: nil\n        )\n    }\n\n    func onButtonDismissed(buttonInfo: InAppMessageButtonInfo) {\n        tryDismiss { time in\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.buttonTap(\n                    identifier: buttonInfo.identifier,\n                    description: buttonInfo.label.text,\n                    displayTime: time\n                ),\n                layoutContext: nil\n            )\n            return buttonInfo.behavior == .cancel ? .cancel : .finished\n        }\n    }\n\n    func onTimedOut() {\n        tryDismiss { time in\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.timedOut(displayTime: time),\n                layoutContext: nil\n            )\n            return .finished\n        }\n    }\n\n    func onUserDismissed() {\n        tryDismiss { time in\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.userDismissed(displayTime: time),\n                layoutContext: nil\n            )\n            return .finished\n        }\n    }\n\n    func onMessageTapDismissed() {\n        tryDismiss { time in\n            analytics.recordEvent(\n                ThomasLayoutResolutionEvent.messageTap(displayTime: time),\n                layoutContext: nil\n            )\n            return .finished\n        }\n    }\n\n    private func tryDismiss(dismissBlock: (TimeInterval) -> DisplayResult) {\n        guard let onDismiss = onDismiss else {\n            AirshipLogger.error(\"Dismissed already called!\")\n            return\n        }\n\n        self.timer.stop()\n        let result = dismissBlock(self.timer.time)\n        onDismiss(result)\n        self.onDismiss = nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Coordinators/DefaultDisplayCoordinator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Display coordinator that only allows a single message at a time to be displayed with an optional interval between\n/// displays.\n@MainActor\nfinal class DefaultDisplayCoordinator: DisplayCoordinator {\n\n    private enum LockState {\n        case unlocked\n        case locked\n        case unlocking\n    }\n\n    private var lockState: AirshipMainActorValue<LockState> = AirshipMainActorValue(.unlocked)\n    private let appStateTracker: any AppStateTrackerProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n    private var unlockTask: Task<Void, Never>?\n\n    public var displayInterval: TimeInterval {\n        didSet {\n            if self.lockState.value == .unlocking {\n                self.unlockTask?.cancel()\n                startUnlockTask()\n            }\n        }\n    }\n\n    init(\n        displayInterval: TimeInterval,\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        taskSleeper: any AirshipTaskSleeper = .shared\n    ) {\n        self.displayInterval = displayInterval\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n        self.taskSleeper = taskSleeper\n    }\n\n    var isReady: Bool {\n        return lockState.value == .unlocked && self.appStateTracker.state == .active\n    }\n\n    @MainActor\n    func messageWillDisplay(_ message: InAppMessage) {\n        self.lockState.set(.locked)\n    }\n\n    @MainActor\n    func messageFinishedDisplaying(_ message: InAppMessage) {\n        self.startUnlockTask()\n    }\n\n\n    @MainActor\n    private func startUnlockTask() {\n        guard \n            self.lockState.value != .unlocked\n        else {\n            return\n        }\n        \n        self.lockState.set(.unlocking)\n        self.unlockTask = Task { @MainActor in\n            try? await self.taskSleeper.sleep(timeInterval: self.displayInterval)\n            if (!Task.isCancelled) {\n                self.lockState.set(.unlocked)\n            }\n        }\n    }\n\n    func waitForReady() async {\n        while !isReady {\n            if Task.isCancelled {\n                return\n            }\n\n            for await state in self.lockState.updates {\n                if (state == .unlocked) {\n                    break\n                }\n            }\n\n            for await state in self.appStateTracker.stateUpdates {\n                if (state == .active) {\n                    break\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Coordinators/DisplayCoordinator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Controls the display of an In-App message\n@MainActor\nprotocol DisplayCoordinator: AnyObject, Sendable {\n    var isReady: Bool { get }\n    func messageWillDisplay(_ message: InAppMessage)\n    func messageFinishedDisplaying(_ message: InAppMessage)\n    func waitForReady() async\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Coordinators/DisplayCoordinatorManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol DisplayCoordinatorManagerProtocol: Sendable, AnyObject {\n    @MainActor\n    var displayInterval: TimeInterval { get set }\n    func displayCoordinator(message: InAppMessage) -> any DisplayCoordinator\n}\n\nfinal class DisplayCoordinatorManager: DisplayCoordinatorManagerProtocol {\n    private let immediateCoordinator: ImmediateDisplayCoordinator\n    private let defaultCoordinator: DefaultDisplayCoordinator\n    private let dataStore: PreferenceDataStore\n\n    private static let displayIntervalKey: String = \"UAInAppMessageManagerDisplayInterval\"\n\n    @MainActor\n    var displayInterval: TimeInterval {\n        get {\n            self.dataStore.double(forKey: Self.displayIntervalKey, defaultValue: 0.0)\n        }\n        set {\n            self.dataStore.setDouble(newValue, forKey: Self.displayIntervalKey)\n            self.defaultCoordinator.displayInterval = newValue\n        }\n    }\n\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        immediateCoordinator: ImmediateDisplayCoordinator? = nil,\n        defaultCoordinator: DefaultDisplayCoordinator? = nil\n    ) {\n        self.dataStore = dataStore\n        self.immediateCoordinator = immediateCoordinator ?? ImmediateDisplayCoordinator()\n        self.defaultCoordinator = defaultCoordinator ?? DefaultDisplayCoordinator(\n            displayInterval: dataStore.double(forKey: Self.displayIntervalKey, defaultValue: 0.0)\n        )\n    }\n\n    func displayCoordinator(message: InAppMessage) -> any DisplayCoordinator {\n        guard !message.isEmbedded else {\n            return immediateCoordinator\n        }\n        switch message.displayBehavior {\n        case .immediate: return immediateCoordinator\n        case .standard: return defaultCoordinator\n        case .none: return defaultCoordinator\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Display Coordinators/ImmediateDisplayCoordinator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\n/// A display coordinator that only requires the app to be active\n@MainActor\nfinal class ImmediateDisplayCoordinator: DisplayCoordinator {\n\n    private let appStateTracker: any AppStateTrackerProtocol\n\n    init(\n        appStateTracker: (any AppStateTrackerProtocol)? = nil\n    ) {\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n    }\n\n    var isReady: Bool {\n        return appStateTracker.state == .active\n    }\n\n    func messageWillDisplay(_ message: InAppMessage) {\n\n    }\n\n    func messageFinishedDisplaying(_ message: InAppMessage) {\n\n    }\n\n    func waitForReady() async {\n        for await update in appStateTracker.stateUpdates {\n            if Task.isCancelled {\n                break\n            }\n            if update == .active {\n                break\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppActionRunner.swift",
    "content": "#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\nimport Foundation\n\n/// Action runner for in-app experiences. Must be used in order to properly attribute custom events to the message.\npublic protocol InAppActionRunner: Sendable {\n    /// Runs an action.\n    /// - Parameters:\n    ///     - actionName: The action name.\n    ///     - arguments: The action arguments.\n    /// - Returns: Action result.\n    @MainActor\n    func run(actionName: String, arguments: ActionArguments) async -> ActionResult\n\n    /// Runs actions asynchronously.\n    /// - Parameters:\n    ///     - actions: The actions payload\n    @MainActor\n    func runAsync(actions: AirshipJSON)\n\n    /// Runs actions.\n    /// - Parameters:\n    ///     - actions: The actions payload\n    @MainActor\n    func run(actions: AirshipJSON) async\n}\n\nprotocol InternalInAppActionRunner: InAppActionRunner, ThomasActionRunner {\n\n}\n\nfinal class DefaultInAppActionRunner: InternalInAppActionRunner {\n    private let analytics: any InAppMessageAnalyticsProtocol\n    private let trackPermissionResults: Bool\n\n    init(analytics: any InAppMessageAnalyticsProtocol, trackPermissionResults: Bool) {\n        self.analytics = analytics\n        self.trackPermissionResults = trackPermissionResults\n    }\n\n    @MainActor\n    func extendMetadata(\n        _ metadata: inout [String: any Sendable],\n        layoutContext: ThomasLayoutContext? = nil\n    ) {\n        if trackPermissionResults {\n            let permissionReceiver: @Sendable (\n                AirshipPermission,\n                AirshipPermissionStatus,\n                AirshipPermissionStatus\n            ) async -> Void = { [analytics] permission, start, end in\n                await analytics.recordEvent(\n                    ThomasLayoutPermissionResultEvent(\n                        permission: permission,\n                        startingStatus: start,\n                        endingStatus: end\n                    ),\n                    layoutContext: layoutContext\n                )\n            }\n\n            metadata[PromptPermissionAction.resultReceiverMetadataKey] = permissionReceiver\n        }\n\n\n        metadata[AddCustomEventAction._inAppMetadata] = analytics.makeCustomEventContext(\n            layoutContext: layoutContext\n        )\n    }\n\n    @MainActor\n    public func run(actionName: String, arguments: ActionArguments) async -> ActionResult {\n        var mutated = arguments\n        self.extendMetadata(&mutated.metadata)\n        return await ActionRunner.run(actionName: actionName, arguments: mutated)\n    }\n\n    @MainActor\n    public func runAsync(actions: AirshipJSON) {\n        var metadata: [String: any Sendable] = [:]\n        self.extendMetadata(&metadata)\n\n        Task {\n            await self.run(actions: actions)\n        }\n    }\n\n    @MainActor\n    public func run(actions: AirshipJSON) async {\n        var metadata: [String: any Sendable] = [:]\n        self.extendMetadata(&metadata)\n\n        await ActionRunner.run(\n            actionsPayload: actions,\n            situation: .automation,\n            metadata: metadata\n        )\n    }\n\n    @MainActor\n    public func runAsync(actions: AirshipJSON, layoutContext: ThomasLayoutContext) {\n        var metadata: [String: any Sendable] = [:]\n        self.extendMetadata(&metadata, layoutContext: layoutContext)\n\n        Task {\n            await ActionRunner.run(\n                actionsPayload: actions,\n                situation: .automation,\n                metadata: metadata\n            )\n        }\n    }\n\n    @MainActor\n    public func run(actionName: String, arguments: ActionArguments, layoutContext: ThomasLayoutContext) async -> ActionResult {\n        var args = arguments\n        self.extendMetadata(&args.metadata, layoutContext: layoutContext)\n        return await ActionRunner.run(actionName: actionName, arguments: arguments)\n    }\n}\n\nprotocol InAppActionRunnerFactoryProtocol: Sendable {\n    func makeRunner(message: InAppMessage, analytics: any InAppMessageAnalyticsProtocol) -> any InternalInAppActionRunner\n}\n\n\nfinal class InAppActionRunnerFactory: InAppActionRunnerFactoryProtocol {\n    func makeRunner(message: InAppMessage, analytics: any InAppMessageAnalyticsProtocol) -> any InternalInAppActionRunner {\n        return DefaultInAppActionRunner(\n            analytics: analytics,\n            trackPermissionResults: message.isAirshipLayout\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessage.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\nenum InAppMessageSource: String, Codable, Equatable, Sendable {\n    case remoteData = \"remote-data\"\n    case appDefined = \"app-defined\"\n    case legacyPush = \"legacy-push\"\n}\n\n/// In-App Message\npublic struct InAppMessage: Codable, Equatable, Sendable {\n\n    /// Display behavior\n    public enum DisplayBehavior: String, Codable, Equatable, Sendable {\n        /// Immediate display, allows it to be displayed on top of other IAX\n        case immediate\n\n        /// Displays one at a time with display interval between displays\n        case standard = \"default\"\n    }\n\n    /// The name.\n    public var name: String\n\n    /// Display content\n    public var displayContent: InAppMessageDisplayContent {\n        get {\n            return displayContentWrapper.displayContent\n        }\n        set {\n            displayContentWrapper = DisplayContentWrapper(displayContent: newValue, json: nil)\n        }\n    }\n\n    // Workaround for iOS 26.0 encoding crash (FB#3472, June 2025).\n    // TODO: Test and remove in SDK 21 if iOS 26.x SDKs have fixed the encoding issue.\n    // The workaround avoids re-encoding AirshipLayout by caching the original JSON.\n    private var displayContentWrapper: DisplayContentWrapper\n\n    /// Source\n    var source: InAppMessageSource?\n\n    /// Any message extras.\n    public var extras: AirshipJSON?\n\n    /// Display actions.\n    public var actions: AirshipJSON?\n\n    /// If reporting is enabled or not for the message.\n    public var isReportingEnabled: Bool?\n\n    /// Display behavior\n    public var displayBehavior: DisplayBehavior?\n\n    /// Rendered locale\n    var renderedLocale: AirshipJSON?\n\n    enum CodingKeys: String, CodingKey {\n        case name\n        case extras = \"extra\"\n        case actions\n        case isReportingEnabled = \"reporting_enabled\"\n        case displayBehavior = \"display_behavior\"\n        case display\n        case layout\n        case displayType = \"display_type\"\n        case renderedLocale = \"rendered_locale\"\n        case source\n    }\n\n\n    /// In-app message constructor\n    /// - Parameters:\n    ///   - name: Name of the message\n    ///   - displayContent: Content model to be displayed in the message\n    ///   - extras: Extras payload as JSON\n    ///   - actions: Actions to be executed by the message as JSON\n    ///   - isReportingEnabled: Reporting enabled flag\n    ///   - displayBehavior: Display behavior\n    public init(\n        name: String,\n        displayContent: InAppMessageDisplayContent,\n        extras: AirshipJSON? = nil,\n        actions: AirshipJSON? = nil,\n        isReportingEnabled: Bool? = nil,\n        displayBehavior: DisplayBehavior? = nil\n    ) {\n        self.name = name\n        self.displayContentWrapper = DisplayContentWrapper(displayContent: displayContent, json: nil)\n        self.extras = extras\n        self.actions = actions\n        self.isReportingEnabled = isReportingEnabled\n        self.displayBehavior = displayBehavior\n        self.renderedLocale = nil\n        self.source = .appDefined\n    }\n\n\n    init(\n        name: String,\n        displayContent: InAppMessageDisplayContent,\n        displayContentJSON: AirshipJSON? = nil,\n        source: InAppMessageSource?,\n        extras: AirshipJSON? = nil,\n        actions: AirshipJSON? = nil,\n        isReportingEnabled: Bool? = nil,\n        displayBehavior: DisplayBehavior? = nil,\n        renderedLocale: AirshipJSON? = nil\n    ) {\n        self.name = name\n        self.displayContentWrapper = DisplayContentWrapper(displayContent: displayContent, json: displayContentJSON)\n        self.source = source\n        self.extras = extras\n        self.actions = actions\n        self.isReportingEnabled = isReportingEnabled\n        self.displayBehavior = displayBehavior\n        self.renderedLocale = renderedLocale\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        let name = try container.decodeIfPresent(String.self, forKey: .name) ?? \"\"\n        let source = try container.decodeIfPresent(InAppMessageSource.self, forKey: .source)\n        let extras = try container.decodeIfPresent(AirshipJSON.self, forKey: .extras)\n        let actions = try container.decodeIfPresent(AirshipJSON.self, forKey: .actions)\n        let isReportingEnabled = try container.decodeIfPresent(Bool.self, forKey: .isReportingEnabled)\n        let displayBehavior = try container.decodeIfPresent(DisplayBehavior.self, forKey: .displayBehavior)\n        let renderedLocale = try container.decodeIfPresent(AirshipJSON.self, forKey: .renderedLocale)\n\n        let displayType = try container.decode(DisplayType.self, forKey: .displayType)\n\n        var displayContent: InAppMessageDisplayContent!\n        var displayContentJSON: AirshipJSON?\n\n        switch (displayType) {\n        case .banner:\n            let banner = try container.decode(InAppMessageDisplayContent.Banner.self, forKey: .display)\n            displayContent = .banner(banner)\n        case .modal:\n            let modal = try container.decode(InAppMessageDisplayContent.Modal.self, forKey: .display)\n            displayContent = .modal(modal)\n        case .fullscreen:\n            let fullscreen = try container.decode(InAppMessageDisplayContent.Fullscreen.self, forKey: .display)\n            displayContent = .fullscreen(fullscreen)\n        case .custom:\n            let custom = try container.decode(AirshipJSON.self, forKey: .display)\n            displayContent = .custom(custom)\n        case .html:\n            let html = try container.decode(InAppMessageDisplayContent.HTML.self, forKey: .display)\n            displayContent = .html(html)\n        case .layout:\n            displayContentJSON = try container.decode(AirshipJSON.self, forKey: .display)\n            let wrapper = try container.decode(AirshipLayoutWrapper.self, forKey: .display)\n            displayContent = .airshipLayout(wrapper.layout)\n        }\n\n        self.init(\n            name: name,\n            displayContent: displayContent,\n            displayContentJSON: displayContentJSON,\n            source: source,\n            extras: extras,\n            actions: actions,\n            isReportingEnabled: isReportingEnabled,\n            displayBehavior: displayBehavior,\n            renderedLocale: renderedLocale\n        )\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.name, forKey: .name)\n        try container.encodeIfPresent(self.source, forKey: .source)\n        try container.encodeIfPresent(self.extras, forKey: .extras)\n        try container.encodeIfPresent(self.actions, forKey: .actions)\n        try container.encodeIfPresent(self.isReportingEnabled, forKey: .isReportingEnabled)\n        try container.encodeIfPresent(self.isReportingEnabled, forKey: .isReportingEnabled)\n        try container.encodeIfPresent(self.displayBehavior, forKey: .displayBehavior)\n        try container.encodeIfPresent(self.renderedLocale, forKey: .renderedLocale)\n\n        switch (self.displayContent) {\n        case .banner(let banner):\n            try container.encode(banner, forKey: .display)\n            try container.encode(DisplayType.banner, forKey: .displayType)\n        case .fullscreen(let fullscreen):\n            try container.encode(fullscreen, forKey: .display)\n            try container.encode(DisplayType.fullscreen, forKey: .displayType)\n        case .modal(let modal):\n            try container.encode(modal, forKey: .display)\n            try container.encode(DisplayType.modal, forKey: .displayType)\n        case .html(let html):\n            try container.encode(html, forKey: .display)\n            try container.encode(DisplayType.html, forKey: .displayType)\n        case .custom(let custom):\n            try container.encode(custom, forKey: .display)\n            try container.encode(DisplayType.custom, forKey: .displayType)\n        case .airshipLayout(let layout):\n            if let json = displayContentWrapper.json {\n                try container.encode(json, forKey: .display)\n            } else {\n                try container.encode(AirshipLayoutWrapper(layout: layout), forKey: .display)\n            }\n            try container.encode(DisplayType.layout, forKey: .displayType)\n        }\n    }\n\n    private enum DisplayType: String, Codable {\n        case banner\n        case modal\n        case fullscreen\n        case custom\n        case html\n        case layout\n    }\n}\n\nextension InAppMessage {\n    var urlInfos: [URLInfo] {\n        switch (self.displayContent) {\n        case .banner(let content):\n            return urlInfosForMedia(content.media)\n        case .fullscreen(let content):\n            return urlInfosForMedia(content.media)\n        case .modal(let content):\n            return urlInfosForMedia(content.media)\n        case .html(let html):\n            return [.web(url: html.url, requireNetwork: html.requiresConnectivity != false)]\n        case .custom(_):\n            return []\n        case .airshipLayout(let content):\n            return content.urlInfos\n        }\n    }\n\n    private func urlInfosForMedia(_ media: InAppMessageMediaInfo?) -> [URLInfo] {\n        guard let media = media else {\n            return []\n        }\n\n        switch (media.type) {\n        case .image: return [.image(url: media.url, prefetch: true)]\n        case .video: return [.video(url: media.url)]\n        case .youtube: return [.video(url: media.url)]\n        case .vimeo: return [.video(url: media.url)]\n        }\n    }\n\n    var isEmbedded: Bool {\n        guard case .airshipLayout(let data) = self.displayContent else {\n            return false\n        }\n\n        return data.isEmbedded\n    }\n\n\n    var isAirshipLayout: Bool {\n        guard case .airshipLayout(_) = self.displayContent else {\n            return false\n        }\n\n        return true\n    }\n}\n\nfileprivate struct AirshipLayoutWrapper: Codable {\n    var layout: AirshipLayout\n}\n\nfileprivate struct DisplayContentWrapper: Equatable {\n    var displayContent: InAppMessageDisplayContent\n    var json: AirshipJSON?\n\n    init(displayContent: InAppMessageDisplayContent, json: AirshipJSON?) {\n        self.displayContent = displayContent\n        self.json = json\n    }\n\n    static func ==(lhs: DisplayContentWrapper, rhs: DisplayContentWrapper) -> Bool {\n        return lhs.displayContent == rhs.displayContent\n    }\n}\n\n/// These are just for view testing purposes\nextension InAppMessage {\n    /// We return a window since we are implementing display\n    /// - Note: for internal use only.  :nodoc:\n    @MainActor\n    public func _display() async throws {\n        let adapter = try AirshipLayoutDisplayAdapter(message: self, priority: 0, assets: EmptyAirshipCachedAssets())\n#if os(macOS)\n        let displayTarget = AirshipDisplayTarget()\n#else\n        let displayTarget = AirshipDisplayTarget {\n            try AirshipSceneManager.shared.lastActiveScene\n        }\n#endif\n\n        _ = try await adapter.display(\n            displayTarget: displayTarget,\n            analytics: LoggingInAppMessageAnalytics()\n        )\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageAutomationExecutor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nfinal class InAppMessageAutomationExecutor: AutomationExecutorDelegate {\n    typealias ExecutionData = PreparedInAppMessageData\n\n    private let delegates: Delegates = Delegates()\n    private let assetManager: any AssetCacheManagerProtocol\n    private let analyticsFactory: any InAppMessageAnalyticsFactoryProtocol\n    private let scheduleConditionsChangedNotifier: ScheduleConditionsChangedNotifier\n\n#if os(macOS)\n    init(\n        assetManager: any AssetCacheManagerProtocol,\n        analyticsFactory: any InAppMessageAnalyticsFactoryProtocol,\n        scheduleConditionsChangedNotifier: ScheduleConditionsChangedNotifier\n    ) {\n        self.assetManager = assetManager\n        self.analyticsFactory = analyticsFactory\n        self.scheduleConditionsChangedNotifier = scheduleConditionsChangedNotifier\n    }\n#else\n    private let sceneManager: any InAppMessageSceneManagerProtocol\n\n\n    @MainActor\n    weak var sceneDelegate: (any InAppMessageSceneDelegate)? {\n        get {\n            return sceneManager.delegate\n        }\n        set {\n            sceneManager.delegate = newValue\n        }\n    }\n\n    init(\n        sceneManager: any InAppMessageSceneManagerProtocol,\n        assetManager: any AssetCacheManagerProtocol,\n        analyticsFactory: any InAppMessageAnalyticsFactoryProtocol,\n        scheduleConditionsChangedNotifier: ScheduleConditionsChangedNotifier\n    ) {\n        self.sceneManager = sceneManager\n        self.assetManager = assetManager\n        self.analyticsFactory = analyticsFactory\n        self.scheduleConditionsChangedNotifier = scheduleConditionsChangedNotifier\n    }\n#endif\n\n    @MainActor\n    weak var displayDelegate: (any InAppMessageDisplayDelegate)? {\n        get {\n            return delegates.displayDelegate\n        }\n        set {\n            delegates.displayDelegate = newValue\n        }\n    }\n\n    @MainActor\n    var onIsReadyToDisplay: (@MainActor @Sendable (InAppMessage, String) -> Bool)? {\n        get {\n            return delegates.onIsReadyToDisplay\n        }\n        set {\n            delegates.onIsReadyToDisplay = newValue\n        }\n    }\n    \n\n\n\n    func isReady(\n        data: PreparedInAppMessageData,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) -> ScheduleReadyResult {\n\n        guard data.displayAdapter.isReady else {\n            AirshipLogger.info(\"Schedule \\(preparedScheduleInfo.scheduleID) display adapter not ready\")\n            Task { [scheduleConditionsChangedNotifier] in\n                await data.displayAdapter.waitForReady()\n                scheduleConditionsChangedNotifier.notify()\n            }\n            return .notReady\n        }\n\n        guard data.displayCoordinator.isReady else {\n            AirshipLogger.info(\"Schedule \\(preparedScheduleInfo.scheduleID) display coordinator not ready\")\n            Task { [scheduleConditionsChangedNotifier] in\n                await data.displayCoordinator.waitForReady()\n                scheduleConditionsChangedNotifier.notify()\n            }\n            return .notReady\n        }\n\n        var isReady: Bool?\n        if let onDisplay = self.onIsReadyToDisplay {\n            isReady = onDisplay(\n                data.message,\n                preparedScheduleInfo.scheduleID\n            )\n        } else if let displayDelegate = self.displayDelegate {\n            isReady = displayDelegate.isMessageReadyToDisplay(\n                data.message,\n                scheduleID: preparedScheduleInfo.scheduleID\n            )\n        }\n        \n        guard isReady != false else {\n            AirshipLogger.info(\"Schedule \\(preparedScheduleInfo.scheduleID) InAppMessageDisplayDelegate not ready\")\n            return .notReady\n        }\n\n        return .ready\n    }\n\n    @MainActor\n    func execute(\n        data: PreparedInAppMessageData,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async throws -> ScheduleExecuteResult {\n        guard preparedScheduleInfo.additionalAudienceCheckResult else {\n            AirshipLogger.info(\"Schedule \\(preparedScheduleInfo.scheduleID) missed additional audience check\")\n            data.analytics.recordEvent(\n                ThomasLayoutResolutionEvent.audienceExcluded(),\n                layoutContext: nil\n            )\n            return .finished\n        }\n\n#if os(macOS)\n        let displayTarget = AirshipDisplayTarget()\n#else\n        let displayTarget = AirshipDisplayTarget {\n            try self.sceneManager.scene(forMessage: data.message).scene\n        }\n#endif\n\n\n        // Display\n        self.delegates.displayDelegate?.messageWillDisplay(\n             data.message,\n             scheduleID: preparedScheduleInfo.scheduleID\n        )\n        data.displayCoordinator.messageWillDisplay(data.message)\n\n        var result: ScheduleExecuteResult = .finished\n        \n        let experimentResult = preparedScheduleInfo.experimentResult\n        if let experimentResult = experimentResult, experimentResult.isMatch {\n            AirshipLogger.info(\"Schedule \\(preparedScheduleInfo.scheduleID) part of experiment\")\n            data.analytics.recordEvent(\n                ThomasLayoutResolutionEvent.control(experimentResult: experimentResult),\n                layoutContext: nil\n            )\n        } else {\n            do {\n                AirshipLogger.info(\"Displaying message \\(preparedScheduleInfo.scheduleID)\")\n\n                let displayResult = try await data.displayAdapter.display(displayTarget: displayTarget, analytics: data.analytics)\n                switch (displayResult) {\n                case .cancel:\n                    result = .cancel\n                case .finished:\n                    result = .finished\n                }\n\n                if let actions = data.message.actions  {\n                    data.actionRunner.runAsync(actions: actions)\n                }\n            } catch {\n                data.displayCoordinator.messageFinishedDisplaying(data.message)\n                AirshipLogger.error(\"Failed to display message \\(error)\")\n                result = .retry\n            }\n        }\n\n        // Finished\n        data.displayCoordinator.messageFinishedDisplaying(data.message)\n        self.delegates.displayDelegate?.messageFinishedDisplaying(\n            data.message,\n            scheduleID: preparedScheduleInfo.scheduleID\n       )\n\n        // Clean up assets\n        if (result != .retry) {\n            await self.assetManager.clearCache(identifier: preparedScheduleInfo.scheduleID)\n        }\n\n        return result\n    }\n\n    func interrupted(schedule: AutomationSchedule, preparedScheduleInfo: PreparedScheduleInfo) async -> InterruptedBehavior {\n        guard case .inAppMessage(let message) = schedule.data else {\n            return .finish\n        }\n\n        guard !message.isEmbedded else {\n            return .retry\n        }\n\n        let analytics = await self.analyticsFactory.makeAnalytics(\n            preparedScheduleInfo: preparedScheduleInfo,\n            message: message\n        )\n\n        analytics.recordEvent(\n            ThomasLayoutResolutionEvent.interrupted(),\n            layoutContext: nil\n        )\n\n        await self.assetManager.clearCache(identifier: preparedScheduleInfo.scheduleID)\n        return .finish\n    }\n\n    @MainActor\n    func notifyDisplayConditionsChanged() {\n        self.scheduleConditionsChangedNotifier.notify()\n    }\n\n    /// Delegates holder so I can keep the executor sendable\n    private final class Delegates: Sendable {\n        @MainActor\n        weak var displayDelegate: (any InAppMessageDisplayDelegate)?\n        \n        @MainActor\n        var onIsReadyToDisplay: (@MainActor @Sendable (InAppMessage, String) -> Bool)?\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageAutomationPreparer.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Any data needed by in-app message to handle displaying the message\nstruct PreparedInAppMessageData: Sendable {\n    var message: InAppMessage\n    var displayAdapter: any DisplayAdapter\n    var displayCoordinator: any DisplayCoordinator\n    var analytics: any InAppMessageAnalyticsProtocol\n    var actionRunner: any InAppActionRunner & ThomasActionRunner\n}\n\nfinal class InAppMessageAutomationPreparer: AutomationPreparerDelegate {\n    typealias PrepareDataIn = InAppMessage\n    typealias PrepareDataOut = PreparedInAppMessageData\n\n    private let displayCoordinatorManager: any DisplayCoordinatorManagerProtocol\n    private let displayAdapterFactory: any DisplayAdapterFactoryProtocol\n    private let assetManager: any AssetCacheManagerProtocol\n    private let analyticsFactory: any InAppMessageAnalyticsFactoryProtocol\n    private let actionRunnerFactory: any InAppActionRunnerFactoryProtocol\n\n    @MainActor\n    public var displayInterval: TimeInterval {\n        get {\n            return displayCoordinatorManager.displayInterval\n        }\n        set {\n            displayCoordinatorManager.displayInterval = newValue\n        }\n    }\n\n    init(\n        assetManager: any AssetCacheManagerProtocol,\n        displayCoordinatorManager: any DisplayCoordinatorManagerProtocol,\n        displayAdapterFactory: any DisplayAdapterFactoryProtocol = DisplayAdapterFactory(),\n        analyticsFactory: any InAppMessageAnalyticsFactoryProtocol,\n        actionRunnerFactory: any InAppActionRunnerFactoryProtocol = InAppActionRunnerFactory()\n    ) {\n        self.assetManager = assetManager\n        self.displayCoordinatorManager = displayCoordinatorManager\n        self.displayAdapterFactory = displayAdapterFactory\n        self.analyticsFactory = analyticsFactory\n        self.actionRunnerFactory = actionRunnerFactory\n    }\n\n    func prepare(\n        data: InAppMessage,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) async throws -> PreparedInAppMessageData {\n        let assets = try await self.prepareAssets(\n            message: data,\n            scheduleID: preparedScheduleInfo.scheduleID,\n            skip: preparedScheduleInfo.additionalAudienceCheckResult == false || preparedScheduleInfo.experimentResult?.isMatch == true\n        )\n\n        let displayCoordinator = self.displayCoordinatorManager.displayCoordinator(message: data)\n\n        let analytics = await self.analyticsFactory.makeAnalytics(\n            preparedScheduleInfo: preparedScheduleInfo,\n            message: data\n        )\n\n        let actionRunner = self.actionRunnerFactory.makeRunner(message: data, analytics: analytics)\n\n        let displayAdapter = try await self.displayAdapterFactory.makeAdapter(\n            args: DisplayAdapterArgs(\n                message: data,\n                assets: assets,\n                priority: preparedScheduleInfo.priority,\n                _actionRunner: actionRunner\n            )\n        )\n\n        return PreparedInAppMessageData(\n            message: data,\n            displayAdapter: displayAdapter,\n            displayCoordinator: displayCoordinator,\n            analytics: analytics,\n            actionRunner: actionRunner\n        )\n    }\n\n    func cancelled(scheduleID: String) async {\n        AirshipLogger.trace(\"Execution cancelled \\(scheduleID)\")\n        await self.assetManager.clearCache(identifier: scheduleID)\n    }\n\n    private func prepareAssets(message: InAppMessage, scheduleID: String, skip: Bool) async throws -> any AirshipCachedAssetsProtocol {\n        // - prepare assets\n        let imageURLs: [String] = if skip {\n            []\n        } else {\n            message.urlInfos\n                .compactMap { info in\n                    guard case .image(let url, let prefetch) = info, prefetch else {\n                        return nil\n                    }\n                    return url\n                }\n        }\n\n        AirshipLogger.trace(\"Preparing assets \\(scheduleID): \\(imageURLs)\")\n\n        return try await self.assetManager.cacheAssets(\n            identifier: scheduleID,\n            assets: imageURLs\n        )\n    }\n\n    @MainActor\n    func setAdapterFactoryBlock(\n        forType type: CustomDisplayAdapterType,\n        factoryBlock: @escaping @Sendable (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?\n    ) {\n        self.displayAdapterFactory.setAdapterFactoryBlock(\n            forType: type,\n            factoryBlock: { args in\n                factoryBlock(args)\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageColor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// In-App message color\npublic struct InAppMessageColor: Codable, Sendable, Equatable {\n    /// Raw hex string - #AARRGGBB\n    public let hexColorString: String\n\n    /// Parsed swiftUI color\n    public let color: Color\n\n    /// In-app message color initializer\n    /// - Parameter hexColorString: Color represented  by hex string of the format #AARRGGBB\n    public init(hexColorString: String) {\n        self.hexColorString = hexColorString\n        self.color = AirshipColor.resolveHexColor(hexColorString) ?? .clear\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let hexColorString = try decoder.singleValueContainer().decode(String.self)\n        self.init(hexColorString: hexColorString)\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n        try container.encode(self.hexColorString)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageDisplayContent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n\n/// In-App message display content\npublic enum InAppMessageDisplayContent: Sendable, Equatable {\n    /// Banner messages\n    case banner(Banner)\n\n    /// Fullscreen messages\n    case fullscreen(Fullscreen)\n\n    /// Modal messages\n    case modal(Modal)\n\n    /// Html messages\n    case html(HTML)\n\n    /// Custom messages\n    case custom(AirshipJSON)\n\n    /// Airship layout messages\n    case airshipLayout(AirshipLayout)\n\n    public func validate() -> Bool {\n        switch self {\n        case .banner(let layout):\n            return layout.validate()\n        case .fullscreen(let layout):\n            return layout.validate()\n        case .modal(let layout):\n            return layout.validate()\n        case .html(let layout):\n            return layout.validate()\n        case .custom(_):\n            /// Don't validate custom layouts\n            return true\n        case .airshipLayout(let layout):\n            return layout.validate()\n        }\n    }\n\n    /// Banner display content\n    public struct Banner: Codable, Sendable, Equatable {\n\n        /// Banner layout templates\n        public enum Template: String, Codable, Sendable {\n            /// Media left\n            case mediaLeft = \"media_left\"\n            /// Media right\n            case mediaRight = \"media_right\"\n        }\n\n        /// Banner placement\n        public enum Placement: String, Codable, Sendable, Equatable {\n            /// Top\n            case top\n            /// Bottom\n            case bottom\n        }\n\n        /// The heading\n        public var heading: InAppMessageTextInfo?\n\n        /// The body\n        public var body: InAppMessageTextInfo?\n\n        /// The media\n        public var media: InAppMessageMediaInfo?\n\n        /// The buttons\n        public var buttons: [InAppMessageButtonInfo]?\n\n        /// The button layout type\n        public var buttonLayoutType: InAppMessageButtonLayoutType?\n\n        /// The template\n        public var template: Template?\n\n        /// The  background color\n        public var backgroundColor: InAppMessageColor?\n\n        /// The dismiss button color\n        public var dismissButtonColor: InAppMessageColor?\n\n        /// The border radius\n        public var borderRadius: Double?\n\n        /// How long the banner displays\n        public var duration: TimeInterval?\n\n        /// Banner placement\n        public var placement: Placement?\n\n        /// Tap actions\n        public var actions: AirshipJSON?\n\n        enum CodingKeys: String, CodingKey {\n            case actions\n            case heading\n            case body\n            case media\n            case buttons\n            case buttonLayoutType = \"button_layout\"\n            case template\n            case backgroundColor = \"background_color\"\n            case dismissButtonColor = \"dismiss_button_color\"\n            case borderRadius = \"border_radius\"\n            case duration\n            case placement\n        }\n\n\n        /// Banner in-app message model initializer\n        /// - Parameters:\n        ///   - heading: Model defining the message heading\n        ///   - body: Model defining the message  body\n        ///   - media: Model defining the message  media\n        ///   - buttons: Message button models\n        ///   - buttonLayoutType: Button layout model\n        ///   - template: Layout template defining text position relative to media\n        ///   - backgroundColor: Background color\n        ///   - dismissButtonColor: Dismiss button color\n        ///   - borderRadius: Border radius of the message body\n        ///   - duration: Duration before message is dismissed\n        ///   - placement: Placement of message on its parent\n        ///   - actions: Actions to execute on message tap\n        public init(\n            heading: InAppMessageTextInfo? = nil,\n            body: InAppMessageTextInfo? = nil,\n            media: InAppMessageMediaInfo? = nil,\n            buttons: [InAppMessageButtonInfo]? = nil,\n            buttonLayoutType: InAppMessageButtonLayoutType? = nil,\n            template: Template? = nil,\n            backgroundColor: InAppMessageColor? = nil, \n            dismissButtonColor: InAppMessageColor? = nil,\n            borderRadius: Double? = nil,\n            duration: TimeInterval? = nil,\n            placement: Placement? = nil,\n            actions: AirshipJSON? = nil\n        ) {\n            self.heading = heading\n            self.body = body\n            self.media = media\n            self.buttons = buttons\n            self.buttonLayoutType = buttonLayoutType\n            self.template = template\n            self.backgroundColor = backgroundColor\n            self.dismissButtonColor = dismissButtonColor\n            self.borderRadius = borderRadius\n            self.duration = duration\n            self.placement = placement\n            self.actions = actions\n        }\n    }\n\n    /// Modal display content\n    public struct Modal: Codable, Sendable, Equatable {\n\n        /// Modal templates\n        public enum Template: String, Codable, Sendable {\n            /// Header, media, body\n            case headerMediaBody = \"header_media_body\"\n            /// Media, header, body\n            case mediaHeaderBody = \"media_header_body\"\n            /// Header, body, media\n            case headerBodyMedia = \"header_body_media\"\n        }\n\n        /// The heading\n        public var heading: InAppMessageTextInfo?\n\n        /// The body\n        public var body: InAppMessageTextInfo?\n\n        /// The media\n        public var media: InAppMessageMediaInfo?\n\n        /// The footer\n        public var footer: InAppMessageButtonInfo?\n\n        /// The buttons\n        public var buttons: [InAppMessageButtonInfo]?\n\n        /// The button layout type\n        public var buttonLayoutType: InAppMessageButtonLayoutType?\n\n        /// The template\n        public var template: Template?\n\n        /// The background color\n        public var backgroundColor: InAppMessageColor?\n\n        /// The dismiss button color\n        public var dismissButtonColor: InAppMessageColor?\n\n        /// The border radius\n        public var borderRadius: Double?\n\n        /// If the modal can be displayed as fullscreen on small devices\n        public var allowFullscreenDisplay: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case heading\n            case body\n            case media\n            case footer\n            case buttons\n            case buttonLayoutType = \"button_layout\"\n            case template\n            case backgroundColor = \"background_color\"\n            case dismissButtonColor = \"dismiss_button_color\"\n            case borderRadius = \"border_radius\"\n            case allowFullscreenDisplay = \"allow_fullscreen_display\"\n        }\n\n\n        /// Modal in-app message model initializer\n        /// - Parameters:\n        ///   - heading: Model defining the message heading\n        ///   - body: Model defining the message body\n        ///   - media: Model defining the message media\n        ///   - footer: Model defining a footer button\n        ///   - buttons: Message button models\n        ///   - buttonLayoutType: Layout for buttons\n        ///   - template: Layout template defining relative position of heading, body and media\n        ///   - dismissButtonColor: Dismiss button color\n        ///   - backgroundColor: Background color\n        ///   - borderRadius: Border radius of the message body\n        ///   - allowFullscreenDisplay: Flag determining if the message can be displayed as fullscreen on small devices\n        public init(\n            heading: InAppMessageTextInfo? = nil,\n            body: InAppMessageTextInfo? = nil,\n            media: InAppMessageMediaInfo? = nil,\n            footer: InAppMessageButtonInfo? = nil,\n            buttons: [InAppMessageButtonInfo],\n            buttonLayoutType: InAppMessageButtonLayoutType? = nil,\n            template: Template,\n            dismissButtonColor: InAppMessageColor? = nil,\n            backgroundColor: InAppMessageColor? = nil,\n            borderRadius: Double? = nil,\n            allowFullscreenDisplay: Bool? = nil\n        ) {\n            self.heading = heading\n            self.body = body\n            self.media = media\n            self.footer = footer\n            self.buttons = buttons\n            self.buttonLayoutType = buttonLayoutType\n            self.template = template\n            self.backgroundColor = backgroundColor\n            self.dismissButtonColor = dismissButtonColor\n            self.borderRadius = borderRadius\n            self.allowFullscreenDisplay = allowFullscreenDisplay\n        }\n    }\n\n    /// Fullscreen display content\n    public struct Fullscreen: Codable, Sendable, Equatable {\n\n        /// Fullscreen templates\n        public enum Template: String, Codable, Sendable {\n            /// Header, media, body\n            case headerMediaBody = \"header_media_body\"\n            /// Media, header, body\n            case mediaHeaderBody = \"media_header_body\"\n            /// Header, body, media\n            case headerBodyMedia = \"header_body_media\"\n        }\n\n        /// The heading\n        public var heading: InAppMessageTextInfo?\n\n        /// The body\n        public var body: InAppMessageTextInfo?\n\n        /// The media\n        public var media: InAppMessageMediaInfo?\n\n        /// The footer\n        public var footer: InAppMessageButtonInfo?\n\n        /// The buttons\n        public var buttons: [InAppMessageButtonInfo]?\n\n        /// The button layout type\n        public var buttonLayoutType: InAppMessageButtonLayoutType?\n\n        /// The template\n        public var template: Template?\n\n        /// The  background color\n        public var backgroundColor: InAppMessageColor?\n\n        /// The dismiss button color\n        public var dismissButtonColor: InAppMessageColor?\n\n        enum CodingKeys: String, CodingKey {\n            case heading\n            case body\n            case media\n            case footer\n            case buttons\n            case buttonLayoutType = \"button_layout\"\n            case template\n            case backgroundColor = \"background_color\"\n            case dismissButtonColor = \"dismiss_button_color\"\n        }\n\n\n        /// Full screen in-app message model initializer\n        /// - Parameters:\n        ///   - heading: Model defining the message heading\n        ///   - body: Model defining the message body\n        ///   - media: Model defining the message media\n        ///   - footer: Model defining a footer button\n        ///   - buttons: Message button models\n        ///   - buttonLayoutType: Layout for buttons\n        ///   - template: Layout template defining relative position of heading, body and media\n        ///   - dismissButtonColor: Dismiss button color\n        ///   - backgroundColor: Background color\n        public init(heading: InAppMessageTextInfo? = nil,\n             body: InAppMessageTextInfo? = nil,\n             media: InAppMessageMediaInfo? = nil,\n             footer: InAppMessageButtonInfo? = nil,\n             buttons: [InAppMessageButtonInfo],\n             buttonLayoutType: InAppMessageButtonLayoutType? = nil,\n             template: Template,\n             dismissButtonColor: InAppMessageColor? = nil,\n             backgroundColor: InAppMessageColor? = nil\n        ) {\n            self.heading = heading\n            self.body = body\n            self.media = media\n            self.footer = footer\n            self.buttons = buttons\n            self.buttonLayoutType = buttonLayoutType\n            self.template = template\n            self.backgroundColor = backgroundColor\n            self.dismissButtonColor = dismissButtonColor\n        }\n    }\n\n    /// HTML display content\n    public struct HTML: Codable, Sendable, Equatable {\n\n        /// The URL\n        public var url: String\n\n        /// The height of the manually sized HTML view\n        public var height: Double?\n\n        /// The width of the manually sized HTML view\n        public var width: Double?\n\n        /// Flag indicating if the HTML view should lock its aspect ratio when resizing to fit the screen\n        public var aspectLock: Bool?\n\n        /// Flag indicating if the content requires connectivity to display correctly\n        public var requiresConnectivity: Bool?\n\n        /// The dismiss button color\n        public var dismissButtonColor: InAppMessageColor?\n\n        /// The  background color\n        public var backgroundColor: InAppMessageColor?\n\n        /// The border radius\n        public var borderRadius: Double?\n\n        /// If the html can be displayed as fullscreen on small devices\n        public var allowFullscreen: Bool?\n\n        // If the html should always display as fullscreen\n        public var forceFullscreen: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case url\n            case height\n            case width\n            case aspectLock = \"aspect_lock\"\n            case requiresConnectivity = \"require_connectivity\"\n            case backgroundColor = \"background_color\"\n            case dismissButtonColor = \"dismiss_button_color\"\n            case borderRadius = \"border_radius\"\n            case allowFullscreen = \"allow_fullscreen_display\"\n            case forceFullscreen = \"force_fullscreen_display\"\n        }\n\n\n        /// HTML in-app message model initializer\n        /// - Parameters:\n        ///   - url: URL of content\n        ///   - height: Height of web view\n        ///   - width: Height of web view\n        ///   - aspectLock: Flag for locking aspect ratio\n        ///   - requiresConnectivity: Flag for determining if message can be displayed without connectivity\n        ///   - dismissButtonColor: Dismiss button color\n        ///   - backgroundColor: Background color\n        ///   - borderRadius: Border radius\n        ///   - allowFullscreen: Flag determining if the message can be displayed as fullscreen on small devices\n        public init(\n            url: String,\n            height: Double? = nil,\n            width: Double? = nil,\n            aspectLock: Bool? = nil,\n            requiresConnectivity: Bool? = nil,\n            dismissButtonColor: InAppMessageColor? = nil,\n            backgroundColor: InAppMessageColor? = nil,\n            borderRadius: Double? = nil,\n            allowFullscreen: Bool? = nil,\n            forceFullscreen: Bool? = nil\n        ) {\n            self.url = url\n            self.height = height\n            self.width = width\n            self.aspectLock = aspectLock\n            self.requiresConnectivity = requiresConnectivity\n            self.backgroundColor = backgroundColor\n            self.dismissButtonColor = dismissButtonColor\n            self.borderRadius = borderRadius\n            self.allowFullscreen = allowFullscreen\n            self.forceFullscreen = forceFullscreen\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageDisplayDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Message display delegate\npublic protocol InAppMessageDisplayDelegate: AnyObject, Sendable{\n    \n    /// Called to check if the message is ready to be displayed. This method will be called for\n    /// every message that is pending display whenever a display condition changes. Use `notifyDisplayConditionsChanged`\n    /// to notify whenever a condition changes to reevaluate the pending In-App messages.\n    ///\n    /// - Parameters:\n    ///     - message: The message\n    ///     - scheduleID: The schedule ID\n    /// - Returns: `true` if the message is ready to display, `false` otherwise.\n    @MainActor\n    func isMessageReadyToDisplay(_ message: InAppMessage, scheduleID: String) -> Bool\n\n    /// Called when a message will be displayed.\n    /// - Parameters:\n    ///     - message: The message\n    ///     - scheduleID: The schedule ID\n    @MainActor\n    func messageWillDisplay(_ message: InAppMessage, scheduleID: String)\n\n\n    /// Called when a message finished displaying\n    /// - Parameters:\n    ///     - message: The message\n    ///     - scheduleID: The schedule ID\n    @MainActor\n    func messageFinishedDisplaying(_ message: InAppMessage, scheduleID: String)\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageEnvironment.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nclass InAppMessageEnvironment: ObservableObject {\n    private let delegate: any InAppMessageViewDelegate\n\n    let imageLoader: AirshipImageLoader\n#if !os(tvOS)\n    let nativeBridgeExtension: (any NativeBridgeExtensionDelegate)?\n#endif\n    let actionRunner: (any InAppActionRunner)?\n\n\n    @Published var isDismissed = false\n\n    @MainActor\n    init(\n        delegate: any InAppMessageViewDelegate,\n        extensions: InAppMessageExtensions? = nil\n    ) {\n        self.delegate = delegate\n        self.imageLoader = AirshipImageLoader(imageProvider: extensions?.imageProvider)\n\n#if !os(tvOS)\n        self.nativeBridgeExtension = extensions?.nativeBridgeExtension\n#endif\n        self.actionRunner = extensions?.actionRunner\n    }\n\n    private func tryDismiss(callback: @escaping () -> Void) {\n        if !self.isDismissed {\n            withAnimation {\n                self.isDismissed = true\n            }\n            callback()\n        }\n    }\n\n    @MainActor\n    func onAppear() {\n        self.delegate.onAppear()\n    }\n\n    /// Called when a button dismisses the in-app message\n    /// - Parameters:\n    ///     - buttonInfo: The button info on the dismissing button.\n    @MainActor\n    func onButtonDismissed(buttonInfo: InAppMessageButtonInfo) {\n        tryDismiss {\n            self.delegate.onButtonDismissed(buttonInfo: buttonInfo)\n        }\n    }\n\n    func runActions(actions: AirshipJSON?) {\n        guard let actions = actions else { return }\n\n        Task {\n            await ActionRunner.run(actionsPayload: actions, situation: .automation, metadata: [:])\n        }\n    }\n\n    @MainActor\n    func runActions(_ actions: AirshipJSON?) {\n        guard let actions = actions else { return }\n\n        guard let runner = actionRunner else {\n            Task {\n                await ActionRunner.run(\n                    actionsPayload: actions,\n                    situation: .automation,\n                    metadata: [:]\n                )\n            }\n\n            return\n        }\n\n        runner.runAsync(actions: actions)\n    }\n\n    @MainActor\n    func runAction(_ actionName: String, arguments: ActionArguments) async -> ActionResult {\n        guard let runner = actionRunner else {\n            return await ActionRunner.run(actionName: actionName, arguments: arguments)\n        }\n\n        return await runner.run(\n            actionName: actionName,\n            arguments: arguments\n        )\n    }\n\n    /// Called when a message dismisses after the set timeout period\n    @MainActor\n    func onTimedOut() {\n        tryDismiss {\n            self.delegate.onTimedOut()\n        }\n    }\n\n    /// Called when a message dismisses with the close button or banner drawer handle\n    @MainActor\n    func onUserDismissed() {\n        tryDismiss {\n            self.delegate.onUserDismissed()\n        }\n    }\n\n    /// Called when a message is dismissed via a tap to the message body\n    @MainActor\n    func onMessageTapDismissed() {\n        tryDismiss {\n            self.delegate.onMessageTapDismissed()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageSceneDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(macOS)\n\nimport Foundation\npublic import UIKit\n\n/// Scene delegate\npublic protocol InAppMessageSceneDelegate: AnyObject {\n\n    /// Called to get the scene for a given message. If no scene is provided, the default scene will be used.\n    /// - Parameters:\n    ///     - message: The in-app message\n    /// - Returns: A UIWindowScene\n    @MainActor\n    func sceneForMessage(_ message: InAppMessage) -> UIWindowScene?\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageSceneManager.swift",
    "content": "/* Copyright Airship and Contributors */\n#if !os(macOS)\n\nimport Foundation\nimport UIKit\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol InAppMessageSceneManagerProtocol: AnyObject, Sendable {\n    @MainActor\n    var delegate: (any InAppMessageSceneDelegate)? { get set }\n\n    @MainActor\n    func scene(forMessage: InAppMessage) throws -> any WindowSceneHolder\n}\n\nfinal class InAppMessageSceneManager: InAppMessageSceneManagerProtocol, Sendable {\n\n    @MainActor\n    weak var delegate: (any InAppMessageSceneDelegate)?\n\n    private let sceneManger: any AirshipSceneManagerProtocol\n\n    init(sceneManger: any AirshipSceneManagerProtocol) {\n        self.sceneManger = sceneManger\n    }\n\n    @MainActor\n    func scene(forMessage message: InAppMessage) throws -> any WindowSceneHolder {\n        let scene = try self.delegate?.sceneForMessage(message) ?? sceneManger.lastActiveScene\n        return DefaultWindowSceneHolder(scene: scene)\n    }\n}\n\nprotocol WindowSceneHolder: Sendable {\n    @MainActor\n    var scene: UIWindowScene { get }\n}\n\n@MainActor\nstruct DefaultWindowSceneHolder: WindowSceneHolder {\n    var scene: UIWindowScene\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageValidation.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nextension InAppMessage {\n    func validate() -> Bool {\n        if !self.displayContent.validate() {\n            AirshipLogger.debug(\"Messages require valid display content\")\n            return false\n        }\n\n        return true\n    }\n}\n\nextension InAppMessageDisplayContent.Banner {\n    private static let maxButtons:Int = 2\n\n    func validate() -> Bool {\n        if (self.heading?.text ?? \"\").isEmpty && (self.body?.text ?? \"\").isEmpty {\n            AirshipLogger.debug(\"Banner must have either its body or heading defined.\")\n            return false\n        }\n\n        if self.buttons?.count ?? 0 > Self.maxButtons {\n            AirshipLogger.debug(\"Banner allows a maximum of \\(Self.maxButtons) buttons\")\n            return false\n        }\n\n        return true\n    }\n}\n\nextension InAppMessageDisplayContent.Modal {\n    func validate() -> Bool {\n        if (self.heading?.text ?? \"\").isEmpty && (self.body?.text ?? \"\").isEmpty {\n            AirshipLogger.debug(\"Modal display must have either its body or heading defined.\")\n            return false\n        }\n\n        return true\n    }\n}\n\nextension InAppMessageDisplayContent.Fullscreen {\n\n    func validate() -> Bool {\n        if (self.heading?.text ?? \"\").isEmpty && (self.body?.text ?? \"\").isEmpty {\n            AirshipLogger.debug(\"Full screen display must have either its body or heading defined.\")\n            return false\n        }\n\n        return true\n    }\n}\n\nextension InAppMessageDisplayContent.HTML {\n    func validate() -> Bool {\n        if self.url.isEmpty {\n            AirshipLogger.debug(\"HTML display must have a non-empty URL.\")\n            return false\n        }\n\n        return true\n    }\n}\n\nextension InAppMessageTextInfo {\n    func validate() -> Bool {\n        if (self.text.isEmpty) {\n            AirshipLogger.debug(\"In-app text infos require nonempty text\")\n            return false\n        }\n\n        return true\n    }\n}\n\nextension InAppMessageButtonInfo {\n    private static let minIdentifierLength:Int = 1\n    private static let maxIdentifierLength:Int = 100\n\n    func validate() -> Bool {\n        if self.label.text.isEmpty {\n            AirshipLogger.debug(\"In-app button infos require a nonempty label\")\n            return false\n        }\n\n        if identifier.count < Self.minIdentifierLength || identifier.count > Self.maxIdentifierLength {\n            AirshipLogger.debug(\"In-app button infos require an identifier between [\\(Self.minIdentifierLength), \\(Self.maxIdentifierLength)] characters\")\n            return false\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessageViewDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n\n/// Delegate for receiving callback pertaining to in-app message lifecycle state\npublic protocol InAppMessageViewDelegate {\n    /// Called whenever the view appears\n    @MainActor\n    func onAppear()\n\n    /// Called when a button dismisses the in-app message\n    /// - Parameters:\n    ///     - buttonInfo: The button info on the dismissing button.\n    @MainActor\n    func onButtonDismissed(buttonInfo: InAppMessageButtonInfo)\n\n    /// Called when a message dismisses after the set timeout period\n    @MainActor\n    func onTimedOut()\n\n    /// Called when a message dismisses with the close button or banner drawer handle\n    @MainActor\n    func onUserDismissed()\n\n    /// Called when a message is dismissed via a tap to the message body\n    @MainActor\n    func onMessageTapDismissed()\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/InAppMessaging.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// In-app messaging\npublic protocol InAppMessaging: AnyObject, Sendable {\n\n    /// Called when the Message  is requested to be displayed.\n    /// Return `true` if the message is ready to display,  `false`  otherwise.\n    @MainActor\n    var onIsReadyToDisplay: (@MainActor @Sendable (_ message: InAppMessage, _ scheduleID: String) -> Bool)? { get set }\n    \n    /// Theme manager\n    @MainActor\n    var themeManager: InAppAutomationThemeManager { get }\n\n    /// Display interval\n    @MainActor\n    var displayInterval: TimeInterval { get set }\n\n    /// Display delegate\n    @MainActor\n    var displayDelegate: (any InAppMessageDisplayDelegate)? { get set }\n\n#if !os(macOS)\n    /// Scene delegate\n    @MainActor\n    var sceneDelegate: (any InAppMessageSceneDelegate)? { get set }\n#endif\n\n    /// Sets a factory block for a custom display adapter.\n    /// If the factory block returns a nil adapter, the default adapter will be used.\n    ///\n    /// - Parameters:\n    ///     - forType: The type\n    ///     - factoryBlock: The factory block\n    @MainActor\n    func setCustomAdapter(\n        forType: CustomDisplayAdapterType,\n        factoryBlock: @escaping @Sendable (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?\n    )\n\n    /// Notifies In-App messages that the display conditions should be reevaluated.\n    /// This should only be called when state that was used to prevent a display with  `InAppMessageDisplayDelegate` changes.\n    @MainActor\n    func notifyDisplayConditionsChanged()\n}\n\nfinal class DefaultInAppMessaging: InAppMessaging {\n    \n    let executor: InAppMessageAutomationExecutor\n    let preparer: InAppMessageAutomationPreparer\n\n    @MainActor\n    let themeManager: InAppAutomationThemeManager = InAppAutomationThemeManager()\n    \n    @MainActor\n    var displayInterval: TimeInterval {\n        get {\n            return preparer.displayInterval\n        }\n        set {\n            preparer.displayInterval = newValue\n        }\n    }\n\n    @MainActor\n    var onIsReadyToDisplay: (@MainActor @Sendable (InAppMessage, String) -> Bool)? {\n        get {\n            return executor.onIsReadyToDisplay\n        }\n        set {\n            executor.onIsReadyToDisplay = newValue\n        }\n    }\n    \n    @MainActor\n    weak var displayDelegate: (any InAppMessageDisplayDelegate)? {\n        get {\n            return executor.displayDelegate\n        }\n        set {\n            executor.displayDelegate = newValue\n        }\n    }\n\n#if !os(macOS)\n    @MainActor\n    weak var sceneDelegate: (any InAppMessageSceneDelegate)? {\n        get {\n            return executor.sceneDelegate\n        }\n        set {\n            executor.sceneDelegate = newValue\n        }\n    }\n#endif\n\n    @MainActor\n    func setAdapterFactoryBlock(\n        forType type: CustomDisplayAdapterType,\n        factoryBlock: @escaping @Sendable (InAppMessage, any AirshipCachedAssetsProtocol) -> (any CustomDisplayAdapter)?\n    ) {\n        self.setCustomAdapter(forType: type) { args in\n            factoryBlock(args.message, args.assets)\n        }\n    }\n\n    @MainActor\n    func setCustomAdapter(\n        forType type: CustomDisplayAdapterType,\n        factoryBlock: @escaping @Sendable (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?\n    ) {\n        self.preparer.setAdapterFactoryBlock(forType: type, factoryBlock: factoryBlock)\n    }\n\n    @MainActor\n    init(\n        executor: InAppMessageAutomationExecutor,\n        preparer: InAppMessageAutomationPreparer\n    ) {\n        self.executor = executor\n        self.preparer = preparer\n    }\n\n    @MainActor\n    func notifyDisplayConditionsChanged() {\n        executor.notifyDisplayConditionsChanged()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Info/InAppMessageButtonInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Button info\npublic struct InAppMessageButtonInfo: Sendable, Codable, Equatable {\n    /// Button behavior\n    public enum Behavior: String, Sendable, Codable {\n        /// Dismisses the message when tapped\n        case dismiss\n\n        /// Dismisses and cancels the message when tapped\n        case cancel\n    }\n\n    /// Button identifier, used for reporting\n    public let identifier: String\n\n    /// Button label\n    public var label: InAppMessageTextInfo\n\n    /// Button actions\n    public var actions: AirshipJSON?\n\n    /// Button behavior\n    public var behavior: Behavior?\n\n    // Background color\n    public var backgroundColor: InAppMessageColor?\n\n    /// Border color\n    public var borderColor: InAppMessageColor?\n\n    /// Border radius in points\n    public var borderRadius: Double?\n\n    /// In-app message button model\n    /// - Parameters:\n    ///   - identifier: Button identifier\n    ///   - label: Text model for the button text\n    ///   - actions: Actions for the button to execute\n    ///   - behavior: Behavior of the button on tap\n    ///   - backgroundColor: Background color\n    ///   - borderColor: Border color\n    ///   - borderRadius: Border radius\n    public init(\n        identifier: String,\n        label: InAppMessageTextInfo,\n        actions: AirshipJSON? = nil,\n        behavior: Behavior? = nil,\n        backgroundColor: InAppMessageColor? = nil,\n        borderColor: InAppMessageColor? = nil,\n        borderRadius: Double? = nil\n    ) {\n        self.identifier = identifier\n        self.label = label\n        self.actions = actions\n        self.behavior = behavior\n        self.backgroundColor = backgroundColor\n        self.borderColor = borderColor\n        self.borderRadius = borderRadius\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case identifier = \"id\"\n        case label\n        case actions\n        case behavior\n        case backgroundColor = \"background_color\"\n        case borderColor = \"border_color\"\n        case borderRadius = \"border_radius\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Info/InAppMessageButtonLayoutType.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Button layout type\npublic enum InAppMessageButtonLayoutType: String, Codable, Sendable, Equatable {\n    /// Stacked vertically\n    case stacked\n\n    /// Joined horizontally\n    case joined\n\n    /// Separated horizontally\n    case separate\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Info/InAppMessageMediaInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Media info\npublic struct InAppMessageMediaInfo: Sendable, Codable, Equatable {\n\n    /// Media type\n    public enum MediaType: String, Sendable, Codable {\n\n        /// Youtube videos\n        case youtube\n        /// Vimeo videos\n        case vimeo\n        /// HTML video\n        case video\n        /// Image\n        case image\n    }\n\n    /// The media's URL\n    public var url: String\n\n    /// The media's type\n    public var type: MediaType\n\n    /// Content description\n    public var description: String?\n\n\n    /// In-app message media model\n    /// - Parameters:\n    ///   - url: URL from which to fetch the media\n    ///   - type: Media type\n    ///   - description: Content description for accessibility purposes\n    public init(\n        url: String,\n        type: MediaType,\n        description: String? = nil\n    ) {\n        self.url = url\n        self.type = type\n        self.description = description\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case url\n        case type\n        case description\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Info/InAppMessageTextInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// InAppMessage text info\npublic struct InAppMessageTextInfo: Sendable, Codable, Equatable {\n\n    /// Text styles\n    public enum Style: String, Sendable, Codable {\n        case bold = \"bold\"\n        case italic = \"italic\"\n        case underline = \"underline\"\n    }\n\n    /// Text alignment\n    public enum Alignment: String, Sendable, Codable {\n        case left = \"left\"\n        case center = \"center\"\n        case right = \"right\"\n    }\n\n    /// The display text\n    public var text: String\n\n    /// The text color\n    public var color: InAppMessageColor?\n\n    /// The font size\n    public var size: Double?\n\n    /// Font families\n    public var fontFamilies: [String]?\n\n    /// Alignment\n    public var alignment: Alignment?\n\n    /// Style\n    public var style: [Style]?\n\n\n    /// In-app message text model\n    /// - Parameters:\n    ///   - text: Text\n    ///   - color: Color\n    ///   - size: Size\n    ///   - fontFamilies: Font families\n    ///   - alignment: Text alignment inside its own frame\n    ///   - style: Text style\n    public init(\n        text: String,\n        color: InAppMessageColor? = nil,\n        size: Double? = nil, \n        fontFamilies: [String]? = nil,\n        alignment: Alignment? = nil,\n        style: [Style]? = nil\n    ) {\n        self.text = text\n        self.color = color\n        self.size = size\n        self.fontFamilies = fontFamilies\n        self.alignment = alignment\n        self.style = style\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case text\n        case color\n        case size\n        case fontFamilies = \"font_family\"\n        case alignment\n        case style\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Legacy/LegacyInAppAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\nprotocol LegacyInAppAnalyticsProtocol: Sendable {\n    func recordReplacedEvent(scheduleID: String, replacementID: String)\n    func recordDirectOpenEvent(scheduleID: String)\n}\n\nstruct LegacyInAppAnalytics : LegacyInAppAnalyticsProtocol {\n    let recorder: any ThomasLayoutEventRecorderProtocol\n\n    func recordReplacedEvent(scheduleID: String, replacementID: String) {\n        recorder.recordEvent(\n            inAppEventData: ThomasLayoutEventData(\n                event: LegacyResolutionEvent.replaced(replacementID: replacementID),\n                context: nil,\n                source: .airship,\n                messageID: .legacy(identifier: scheduleID),\n                renderedLocale: nil\n            )\n        )\n    }\n\n    func recordDirectOpenEvent(scheduleID: String) {\n        recorder.recordEvent(\n            inAppEventData: ThomasLayoutEventData(\n                event: LegacyResolutionEvent.directOpen(),\n                context: nil,\n                source: .airship,\n                messageID: .legacy(identifier: scheduleID),\n                renderedLocale: nil\n            )\n        )\n    }\n}\n\nstruct LegacyResolutionEvent : ThomasLayoutEvent {\n    let name = EventType.inAppResolution\n\n    let data: (any Encodable & Sendable)?\n\n    private init(data: (any Encodable & Sendable)?) {\n        self.data = data\n    }\n\n    static func replaced(replacementID: String) -> LegacyResolutionEvent {\n        return LegacyResolutionEvent(\n            data: LegacyResolutionBody(type: .replaced, replacementID: replacementID)\n        )\n    }\n\n    static func directOpen() -> LegacyResolutionEvent {\n        return LegacyResolutionEvent(\n            data: LegacyResolutionBody(type: .directOpen)\n        )\n    }\n\n    fileprivate enum LegacyResolutionType: String, Encodable {\n        case replaced\n        case directOpen = \"direct_open\"\n    }\n\n    fileprivate struct LegacyResolutionBody: Encodable {\n        var type: LegacyResolutionType\n        var replacementID: String?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case replacementID = \"replacement_id\"\n        }\n    }\n}\n\n\n\n\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Legacy/LegacyInAppMessage.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import UserNotifications\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/**\n * Model object representing in-app message data.\n */\npublic struct LegacyInAppMessage: Sendable, Equatable {\n\n    /**\n     * Enumeration of in-app message screen positions.\n     */\n    public enum Position: String, Sendable {\n        case top\n        case bottom\n    }\n\n    /**\n     * Enumeration of in-app message display types.\n     */\n    public enum DisplayType: String, Sendable {\n        case banner\n    }\n\n    ///---------------------------------------------------------------------------------------\n    /// @name Legacy In App Message Properties\n    ///---------------------------------------------------------------------------------------\n\n    /**\n     * The unique identifier for the message\n     */\n    public let identifier: String\n\n    ///---------------------------------------------------------------------------------------\n    /// @name Legacy In App Message Top Level Properties\n    ///---------------------------------------------------------------------------------------\n\n    /**\n     * The expiration date for the message.\n     * Unless otherwise specified, defaults to 30 days from construction.\n     */\n    public let expiry: Date\n\n    /**\n     * Optional key value extra.\n     */\n    public let extra: AirshipJSON?\n\n    ///---------------------------------------------------------------------------------------\n    /// @name Legacy In App Message Display Properties\n    ///---------------------------------------------------------------------------------------\n\n    /**\n     * The display type. Defaults to `LegacyInAppMessage.DisplayType.banner`\n     * when built with the default class constructor.\n     * When built from a payload with a missing or unidentified display type,\n     * the message will be nil.\n     */\n    public let displayType: DisplayType\n\n    /**\n     * The alert message.\n     */\n    public let alert: String\n\n    /**\n     * The screen position. Defaults to `LegacyInAppMessage.Position.bottom`.\n     */\n    public let position: Position\n\n    /**\n     * The amount of time to wait before automatically dismissing\n     * the message.\n     */\n    public let duration: TimeInterval\n\n    /**\n     * The primary color. hex\n     */\n    public let primaryColor: String?\n\n    /**\n     * The secondary color hex.\n     */\n    public let secondaryColor: String?\n\n    ///---------------------------------------------------------------------------------------\n    /// @name Legacy In App Message Actions Properties\n    ///---------------------------------------------------------------------------------------\n\n    /**\n     * The button group (category) associated with the message.\n     * This value will determine which buttons are present and their\n     * localized titles.\n     */\n    public let buttonGroup: String?\n\n    /**\n     * A dictionary mapping button group keys to dictionaries\n     * mapping action names to action arguments. The relevant\n     * action(s) will be run when the user taps the associated\n     * button.\n     */\n    public let buttonActions: [String: AirshipJSON]?\n\n    /**\n     * A dictionary mapping an action name to an action argument.\n     * The relevant action will be run when the user taps or \"clicks\"\n     * on the message.\n     */\n    public let onClick: AirshipJSON?\n\n    let campaigns: AirshipJSON?\n    let messageType: String?\n\n    /*\n     // Default values unless otherwise specified\n     self.displayType = UALegacyInAppMessageDisplayTypeBanner;\n     self.expiry = [NSDate dateWithTimeIntervalSinceNow:kUADefaultInAppMessageExpiryInterval];\n     self.position = UALegacyInAppMessagePositionBottom;\n     self.duration = kUADefaultInAppMessageDurationInterval;\n     */\n\n#if !os(tvOS)\n    /**\n     * An array of UNNotificationAction instances corresponding to the left-to-right order\n     * of interactive message buttons.\n     */\n    @MainActor\n    var notificationActions: [UNNotificationAction]? {\n        return self.buttonCategory?.actions\n    }\n\n    /**\n     * A UNNotificationCategory instance,\n     * corresponding to the button group of the message.\n     * If no matching category is found, this property will be nil.\n     */\n    @MainActor\n    public var buttonCategory: UNNotificationCategory? {\n        guard let group = buttonGroup else { return nil }\n\n        return Airship.push.combinedCategories.first(where: { $0.identifier == group })\n    }\n#endif\n\n    init?(\n        payload: [String: Any],\n        overrideId: String? = nil,\n        overrideOnClick: AirshipJSON? = nil,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        guard\n            let identifier = overrideId ?? (payload[ParseKey.identifier.rawValue] as? String),\n            let displayInfo = payload[ParseKey.display.rawValue] as? [String: Any],\n            let displayTypeRaw = displayInfo[ParseKey.Display.type.rawValue] as? String,\n            let displayType = DisplayType(rawValue: displayTypeRaw),\n            let alert = displayInfo[ParseKey.Display.alert.rawValue] as? String\n        else {\n            return nil\n        }\n\n\n        let wrapJson: (Any?) -> AirshipJSON? = { input in\n            guard let input = input else { return nil }\n\n            do {\n                return try AirshipJSON.wrap(input)\n            } catch {\n                AirshipLogger.warn(\"failed to wrap \\(String(describing: input)), \\(error)\")\n                return nil\n            }\n        }\n\n        self.identifier = identifier\n        self.campaigns = wrapJson(payload[ParseKey.campaigns.rawValue])\n        self.messageType = payload[ParseKey.messageType.rawValue] as? String\n\n        if\n            let rawDate = payload[ParseKey.expiry.rawValue] as? String,\n            let date = AirshipDateFormatter.date(fromISOString: rawDate)\n        {\n            self.expiry = date\n        } else {\n            self.expiry = date.now.advanced(by: Defaults.expiry)\n        }\n\n        self.extra = wrapJson(payload[ParseKey.extra.rawValue])\n        self.displayType = displayType\n\n        self.alert = alert\n        self.duration = displayInfo[ParseKey.Display.duration.rawValue] as? Double ?? Defaults.duration\n\n        if\n            let positionRaw = displayInfo[ParseKey.Display.position.rawValue] as? String,\n            let position = Position(rawValue: positionRaw) {\n            self.position = position\n        } else {\n            self.position = .bottom\n        }\n\n        self.primaryColor = displayInfo[ParseKey.Display.primaryColor.rawValue] as? String\n        self.secondaryColor = displayInfo[ParseKey.Display.secondaryColor.rawValue] as? String\n\n        if let actionsInfo = payload[ParseKey.actions.rawValue] as? [String: Any] {\n            self.buttonGroup = actionsInfo[ParseKey.Action.buttonGroup.rawValue] as? String\n            if let actions = actionsInfo[ParseKey.Action.buttonActions.rawValue] as? [String: Any] {\n                self.buttonActions = actions.reduce(into: [String: AirshipJSON]()) { partialResult, record in\n                    if let json = wrapJson(record.value) {\n                        partialResult[record.key] = json\n                    }\n                }\n            } else {\n                self.buttonActions = nil\n            }\n            self.onClick = overrideOnClick ?? wrapJson(actionsInfo[ParseKey.Action.onClick.rawValue])\n        } else {\n            self.buttonGroup = nil\n            self.buttonActions = nil\n            self.onClick = overrideOnClick\n        }\n    }\n\n    private enum ParseKey: String {\n        case identifier = \"identifier\"\n        case campaigns = \"campaigns\"\n        case messageType = \"message_type\"\n        case expiry = \"expiry\"\n        case extra = \"extra\"\n        case display = \"display\"\n        case actions = \"actions\"\n\n        enum Action: String {\n            case buttonGroup = \"button_group\"\n            case buttonActions = \"button_actions\"\n            case onClick = \"on_click\"\n        }\n\n        enum Display: String {\n            case type = \"type\"\n            case position = \"position\"\n            case alert = \"alert\"\n            case duration = \"duration\"\n            case primaryColor = \"primary_color\"\n            case secondaryColor = \"secondary_color\"\n        }\n    }\n\n    private enum Defaults {\n        static let expiry: TimeInterval = 60 * 60 * 24 * 30 // 30 days in seconds\n        static let duration: TimeInterval = 15 // seconds\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/Legacy/LegacyInAppMessaging.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic typealias MessageConvertor = @MainActor @Sendable (LegacyInAppMessage) -> AutomationSchedule?\npublic typealias MessageExtender = @Sendable (inout InAppMessage) -> Void\npublic typealias ScheduleExtender = @Sendable (inout AutomationSchedule) -> Void\n\n/// Legacy in-app messaging protocol \npublic protocol LegacyInAppMessaging: AnyObject, Sendable {\n    /// Optional message converter from a `LegacyInAppMessage` to an `AutomationSchedule`\n    @MainActor\n    var customMessageConverter: MessageConvertor? { get set }\n\n    /// Optional message extender.\n    @MainActor\n    var messageExtender: MessageExtender?  { get set }\n\n    /// Optional schedule extender.\n    @MainActor\n    var scheduleExtender: ScheduleExtender?  { get set }\n\n    /// Sets whether legacy messages will display immediately upon arrival, instead of waiting\n    /// until the following foreground. Defaults to `true`.\n    @MainActor\n    var displayASAPEnabled: Bool  { get set }\n}\n\nprotocol InternalLegacyInAppMessaging: LegacyInAppMessaging {\n#if !os(tvOS)\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async\n#endif\n\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON // wrapped [AnyHashable : Any]\n    ) async -> UABackgroundFetchResult\n}\n\nfinal class DefaultLegacyInAppMessaging: LegacyInAppMessaging, Sendable {\n\n    private let dataStore: PreferenceDataStore\n    private let analytics: any LegacyInAppAnalyticsProtocol\n    private let automationEngine: any AutomationEngineProtocol\n\n    @MainActor\n    public var customMessageConverter: MessageConvertor?\n\n    @MainActor\n    public var messageExtender: MessageExtender?\n\n    @MainActor\n    public var scheduleExtender: ScheduleExtender?\n\n    private let date: any AirshipDateProtocol\n\n    init(\n        analytics: any LegacyInAppAnalyticsProtocol,\n        dataStore: PreferenceDataStore,\n        automationEngine: any AutomationEngineProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.analytics = analytics\n        self.automationEngine = automationEngine\n        self.dataStore = dataStore\n        self.date = date\n\n        cleanUpOldData()\n    }\n\n    var pendingMessageID: String? {\n        get {\n            return dataStore.string(forKey: Keys.CurrentStorage.pendingMessageIds.rawValue)\n        }\n        set {\n            dataStore.setObject(newValue, forKey: Keys.CurrentStorage.pendingMessageIds.rawValue)\n        }\n    }\n\n    /**\n     * Sets whether legacy messages will display immediately upon arrival, instead of waiting\n     * until the following foreground. Defaults to `YES`.\n     */\n    @MainActor\n    var displayASAPEnabled: Bool = true\n\n    private func cleanUpOldData() {\n        self.dataStore.removeObject(forKey: Keys.LegacyStorage.pendingMessages.rawValue)\n        self.dataStore.removeObject(forKey: Keys.LegacyStorage.autoDisplayMessage.rawValue)\n        self.dataStore.removeObject(forKey: Keys.LegacyStorage.lastDisplayedMessageId.rawValue)\n    }\n\n    private func schedule(message: LegacyInAppMessage) async {\n        let generator = await customMessageConverter ?? generateScheduleFor\n\n        guard let schedule = await generator(message) else {\n            AirshipLogger.error(\"Failed to convert legacy in-app automation: \\(message)\")\n            return\n        }\n\n        if let pending = self.pendingMessageID {\n            if await self.scheduleExists(identifier: pending) {\n                AirshipLogger.debug(\"Pending in-app message replaced\")\n                self.analytics.recordReplacedEvent(\n                    scheduleID: pending,\n                    replacementID: schedule.identifier\n                )\n            }\n\n            await self.cancelSchedule(identifier: pending)\n        }\n\n        self.pendingMessageID = schedule.identifier\n\n        do {\n            try await self.automationEngine.upsertSchedules([schedule])\n            AirshipLogger.debug(\"LegacyInAppMessageManager - schedule is saved \\(schedule)\")\n        } catch {\n            AirshipLogger.error(\"Failed to schedule \\(schedule)\")\n        }\n    }\n\n    private func scheduleExists(identifier: String) async -> Bool {\n        do {\n            return try await automationEngine.getSchedule(identifier: identifier) != nil\n        } catch {\n            AirshipLogger.debug(\"Failed to query schedule \\(identifier), \\(error)\")\n            return true\n        }\n    }\n\n    private func cancelSchedule(identifier: String) async {\n        do {\n            return try await automationEngine.cancelSchedules(identifiers: [identifier])\n        } catch {\n            AirshipLogger.debug(\"Failed to cancel schedule \\(identifier), \\(error)\")\n        }\n    }\n\n    @MainActor\n    private func generateScheduleFor(message: LegacyInAppMessage) -> AutomationSchedule? {\n        let primaryColor = InAppMessageColor(hexColorString: message.primaryColor ?? Defaults.primaryColor)\n        let secondaryColor = InAppMessageColor(hexColorString: message.secondaryColor ?? Defaults.secondaryColor)\n#if !os(tvOS)\n        let buttons = message\n            .notificationActions?\n            .prefix(Defaults.notificationButtonsCount)\n            .map({ action in\n                return InAppMessageButtonInfo(\n                    identifier: action.identifier,\n                    label: InAppMessageTextInfo(\n                        text: action.title,\n                        color: primaryColor,\n                        alignment: .left\n                    ),\n                    actions: message.buttonActions?[action.identifier],\n                    backgroundColor: secondaryColor,\n                    borderRadius: Defaults.borderRadius\n                )\n            })\n#else\n        let buttons: [InAppMessageButtonInfo] = []\n#endif\n\n        let displayContent = InAppMessageDisplayContent.Banner(\n            body: InAppMessageTextInfo(text: message.alert, color: secondaryColor),\n            buttons: buttons,\n            buttonLayoutType: .separate,\n            backgroundColor: primaryColor,\n            dismissButtonColor: secondaryColor,\n            borderRadius: Defaults.borderRadius,\n            duration: message.duration,\n            placement: message.position.bannerPlacement,\n            actions: message.onClick\n        )\n\n        var inAppMessage = InAppMessage(\n            name: message.alert,\n            displayContent: .banner(displayContent),\n            source: .legacyPush,\n            extras: message.extra\n        )\n\n        self.messageExtender?(&inAppMessage)\n\n        // In terms of the scheduled message model, displayASAP means using an active session trigger.\n        // Otherwise the closest analog to the v1 behavior is the foreground trigger.\n        let trigger = self.displayASAPEnabled ?\n        AutomationTrigger.activeSession(count: 1) :\n        AutomationTrigger.foreground(count: 1)\n\n        var schedule = AutomationSchedule(\n            identifier: message.identifier,\n            data: .inAppMessage(inAppMessage),\n            triggers: [trigger],\n            created: date.now,\n            lastUpdated: date.now,\n            end: message.expiry,\n            campaigns: message.campaigns,\n            messageType: message.messageType\n        )\n        self.scheduleExtender?(&schedule)\n        return schedule\n    }\n}\n\nextension DefaultLegacyInAppMessaging: InternalLegacyInAppMessaging {\n\n#if !os(tvOS)\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        let userInfo = response.notification.request.content.userInfo\n\n        guard\n            userInfo.keys.contains(Keys.incomingMessageKey.rawValue),\n            let messageID = userInfo[\"_\"] as? String,\n            messageID == self.pendingMessageID\n        else {\n            return\n        }\n\n        self.pendingMessageID = nil\n\n        if await self.scheduleExists(identifier: messageID) {\n            AirshipLogger.debug(\"Pending in-app message replaced\")\n            self.analytics.recordDirectOpenEvent(scheduleID: messageID)\n        }\n\n        await self.cancelSchedule(identifier: messageID)\n    }\n#endif\n\n    func receivedRemoteNotification(_ notification: AirshipJSON) async -> UABackgroundFetchResult {\n        guard\n            let userInfo = notification.unWrap() as? [AnyHashable: Any],\n            let payload = userInfo[Keys.incomingMessageKey.rawValue] as? [String: Any] else {\n            return .noData\n        }\n\n        let overrideId = userInfo[\"_\"] as? String\n        let messageCenterAction: AirshipJSON?\n\n        if\n            let actionRaw = userInfo[Keys.messageCenterActionKey.rawValue] as? [String: Any],\n            let action = try? AirshipJSON.wrap(actionRaw) {\n            messageCenterAction = action\n        } else if let messageId = userInfo[Keys.messageCenterActionKey.rawValue] as? String {\n            messageCenterAction = .object([Keys.messageCenterActionKey.rawValue: .string(messageId)])\n        } else {\n            messageCenterAction = nil\n        }\n\n        let message = LegacyInAppMessage(\n            payload: payload,\n            overrideId: overrideId,\n            overrideOnClick: messageCenterAction,\n            date: self.date\n        )\n\n        if let message = message {\n            await schedule(message: message)\n        }\n        \n        return .noData\n    }\n\n    private enum Keys: String {\n        enum LegacyStorage: String {\n            // User defaults key for storing and retrieving pending messages\n            case pendingMessages = \"UAPendingInAppMessage\"\n\n            // User defaults key for storing and retrieving auto display enabled\n            case autoDisplayMessage = \"UAAutoDisplayInAppMessageDataStoreKey\"\n\n            // Legacy key for the last displayed message ID\n            case lastDisplayedMessageId = \"UALastDisplayedInAppMessageID\"\n        }\n\n        enum CurrentStorage: String {\n            // Data store key for storing and retrieving pending message IDs\n            case pendingMessageIds = \"UAPendingInAppMessageID\"\n        }\n\n        case incomingMessageKey = \"com.urbanairship.in_app\"\n        case messageCenterActionKey = \"_uamid\"\n    }\n\n    private enum Defaults {\n        static let primaryColor = \"#FFFFFF\"\n        static let secondaryColor = \"#1C1C1C\"\n        static let borderRadius = 2.0\n        static let notificationButtonsCount = 2\n    }\n}\n\nfileprivate extension LegacyInAppMessage.Position {\n    var bannerPlacement: InAppMessageDisplayContent.Banner.Placement {\n        switch self {\n        case .top:\n            return .top\n        case .bottom:\n            return .bottom\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/BeveledLoadingView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nstruct BeveledLoadingView: View {\n    let opacity = 0.7\n\n    var body: some View {\n        ZStack {\n            RoundedRectangle(cornerSize: CGSize(width: 16, height: 16), style: .continuous)\n                .frame(width: 100, height:100)\n                .opacity(0.7)\n            ProgressView()\n                .progressViewStyle(CircularProgressViewStyle(tint:Color.white.opacity(0.7)))\n                .scaleEffect(2)\n        }\n    }\n}\n\n#Preview {\n    BeveledLoadingView()\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/ButtonGroup.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprivate let defaultButtonMargin: CGFloat = 15\nprivate let defaultFooterMargin: CGFloat = 0\nprivate let buttonDefaultBorderWidth: CGFloat = 2\n\nstruct ViewHeightKey: PreferenceKey {\n    typealias Value = CGFloat\n    static let defaultValue = CGFloat.zero\n    static func reduce(value: inout Value, nextValue: () -> Value) {\n        value += nextValue()\n    }\n}\n\nstruct ButtonGroup: View {\n    /// Prevent cycling onPreferenceChange to set rest of the buttons' minHeight to the largest button's height\n    @State private var buttonMinHeight: CGFloat = 33\n    @State private var lastButtonHeight: CGFloat?\n    @EnvironmentObject var environment: InAppMessageEnvironment\n    @Binding private var isDisabled: Bool\n\n    let layout: InAppMessageButtonLayoutType\n    let buttons: [InAppMessageButtonInfo]\n    let theme: InAppMessageTheme.Button\n\n    init(\n        isDisabled: Binding<Bool>? = nil,\n        layout: InAppMessageButtonLayoutType,\n        buttons: [InAppMessageButtonInfo],\n        theme: InAppMessageTheme.Button\n    ) {\n        self._isDisabled = isDisabled ?? Binding.constant(false)\n        self.layout = layout\n        self.buttons = buttons\n        self.theme = theme\n    }\n\n    private func makeButtonView(buttonInfo: InAppMessageButtonInfo, roundedEdge: RoundedEdge = .all) -> some View {\n        return ButtonView(\n            buttonInfo: buttonInfo,\n            roundedEdge: roundedEdge,\n            relativeMinHeight: $buttonMinHeight,\n            minHeight: theme.height,\n            isDisabled: $isDisabled\n        )\n        .frame(minHeight:buttonMinHeight)\n        .environmentObject(environment)\n        .background(\n            GeometryReader {\n                Color.airshipTappableClear.preference(\n                    key: ViewHeightKey.self,\n                    value: $0.frame(in: .global).size.height\n                )\n            }.onPreferenceChange(ViewHeightKey.self) { value in\n                DispatchQueue.main.async {\n                    let buttonHeight = round(value)\n                    /// Prevent cycling by storing the last button height\n                    if self.lastButtonHeight ?? 0 != buttonHeight {\n                        /// Minium button height is the height of the largest button in the group\n                        self.buttonMinHeight = max(buttonMinHeight, buttonHeight)\n                        self.lastButtonHeight = buttonHeight\n                    }\n                }\n            }\n        )\n    }\n\n    var body: some View {\n        switch layout {\n        case .stacked:\n            VStack(spacing: theme.stackedSpacing) {\n                ForEach(buttons, id: \\.identifier)  { button in\n                    makeButtonView(buttonInfo: button)\n                }\n            }\n\n            .fixedSize(horizontal: false, vertical: true) /// Hug children in vertical axis\n        case .joined:\n            HStack(spacing: 0) {\n                ForEach(Array(buttons.enumerated()), id: \\.element.identifier) { index, button in\n                    if buttons.count > 1 {\n                        if index == 0 {\n                            // If first button of n buttons: only round leading edge\n                            makeButtonView(buttonInfo: button, roundedEdge: .leading)\n                        } else if index == buttons.count - 1 {\n                            // If last button of n buttons: only round trailing edge\n                            makeButtonView(buttonInfo: button, roundedEdge: .trailing)\n                        } else {\n                            // If middle button of n buttons: round trailing and leading edges\n                            makeButtonView(buttonInfo: button, roundedEdge: .none)\n                        }\n                    } else {\n                        // Round all button edges by default\n                        makeButtonView(buttonInfo: button)\n                    }\n                }\n            }.fixedSize(horizontal: false, vertical: true) /// Hug children in horizontal axis and veritcal axis\n        case .separate:\n            HStack(spacing: theme.separatedSpacing) {\n                ForEach(buttons, id: \\.identifier)  { button in\n                    makeButtonView(buttonInfo: button)\n                }\n            }.fixedSize(horizontal: false, vertical: true) /// Hug children in horizontal axis and veritcal axis\n        }\n    }\n}\n\nstruct ButtonView: View {\n    @EnvironmentObject var environment: InAppMessageEnvironment\n    @ScaledMetric var scaledPadding: CGFloat = 12\n\n    @Binding var isDisabled: Bool\n    let buttonInfo: InAppMessageButtonInfo\n    let roundedEdge: RoundedEdge\n    let minHeight: CGFloat\n\n    /// Min height of the button that can be dynamically set to size to the largest button in the group\n    /// This is so buttons normalize in height to match the button with the largest font size\n    @Binding private var relativeMinHeight:CGFloat\n\n    internal init(\n        buttonInfo: InAppMessageButtonInfo,\n        roundedEdge:RoundedEdge = .all,\n        relativeMinHeight: Binding<CGFloat>? = nil,\n        minHeight: CGFloat = 33,\n        isDisabled: Binding<Bool>? = nil\n    ) {\n        self.buttonInfo = buttonInfo\n        self.roundedEdge = roundedEdge\n        _relativeMinHeight = relativeMinHeight ?? Binding.constant(CGFloat(0))\n        self.minHeight = minHeight\n        _isDisabled = isDisabled ?? Binding.constant(false)\n    }\n\n\n    @ViewBuilder\n    var buttonLabel: some View {\n        TextView(\n            textInfo: buttonInfo.label,\n            textTheme: InAppMessageTheme.Text(\n                letterSpacing: 0,\n                lineSpacing: 0,\n                padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n            )\n        )\n    }\n\n    var body: some View {\n        Button(action: onTap) {\n            buttonLabel\n                .padding(scaledPadding)\n                .frame(maxWidth: .infinity, minHeight: max(relativeMinHeight, minHeight))\n                .background(buttonInfo.backgroundColor?.color)\n                .roundEdge(radius: buttonInfo.borderRadius ?? 0,\n                           edge: roundedEdge,\n                           borderColor: buttonInfo.borderColor?.color ?? .clear,\n                           borderWidth: 2)\n                #if os(macOS)\n                .fixedSize()\n                #endif\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)\n        #if os(tvOS)\n        .buttonStyle(.card)\n        #elseif os(macOS)\n        .buttonStyle(.plain)\n        #endif\n    }\n\n    private func onTap() {\n        if (!isDisabled) {\n            environment.onButtonDismissed(buttonInfo: self.buttonInfo)\n            environment.runActions(actions: self.buttonInfo.actions)\n        }\n    }\n}\n\nenum RoundedEdge {\n    case none\n    case leading\n    case trailing\n    case all\n}\n\nstruct RoundEdgeModifier: ViewModifier {\n    var radius: CGFloat\n    var edge: RoundedEdge\n    var borderColor: Color\n    var borderWidth: CGFloat\n\n    func body(content: Content) -> some View {\n        content\n            .clipShape(RoundedEdgeShape(radius: radius, edge: edge))\n            .overlay(RoundedEdgeShape(radius: radius, edge: edge)\n                .stroke(borderColor, lineWidth: borderWidth))\n    }\n}\n\nstruct RoundedEdgeShape: Shape {\n    var radius: CGFloat\n    var edge: RoundedEdge\n\n    func path(in rect: CGRect) -> Path {\n        var topLeading: CGFloat = 0\n        var bottomLeading: CGFloat = 0\n        var topTrailing: CGFloat = 0\n        var bottomTrailing: CGFloat = 0\n\n        switch edge {\n        case .leading:\n            topLeading = radius\n            bottomLeading = radius\n        case .trailing:\n            topTrailing = radius\n            bottomTrailing = radius\n        case .all:\n            topLeading = radius\n            bottomLeading = radius\n            topTrailing = radius\n            bottomTrailing = radius\n        case .none:\n            break\n        }\n\n        return UnevenRoundedRectangle(\n            topLeadingRadius: topLeading,\n            bottomLeadingRadius: bottomLeading,\n            bottomTrailingRadius: bottomTrailing,\n            topTrailingRadius: topTrailing\n        ).path(in: rect)\n    }\n}\n\nextension View {\n    func roundEdge(radius: CGFloat, edge: RoundedEdge, borderColor: Color = .clear, borderWidth: CGFloat = 0) -> some View {\n        self.modifier(RoundEdgeModifier(radius: radius, edge: edge, borderColor: borderColor, borderWidth: borderWidth))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/CloseButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nstruct CloseButton: View {\n    internal init(\n        dismissIconImage: Image,\n        dismissIconColor: Color,\n        width: CGFloat? = nil,\n        height: CGFloat? = nil,\n        onTap: @escaping () -> ()\n    ) {\n        self.dismissIconColor = dismissIconColor\n        self.dismissIconImage = dismissIconImage\n        self.width = width ?? 12\n        self.height = height ?? 12\n        self.onTap = onTap\n    }\n\n    let dismissIconColor: Color\n    let dismissIconImage: Image\n\n    let onTap: () -> ()\n\n    private let opacity: CGFloat = 0.25\n    private let defaultPadding: CGFloat = 24\n\n    private let height: CGFloat\n    private let width: CGFloat\n\n    private let tappableHeight: CGFloat = 44\n    private let tappableWidth: CGFloat = 44\n\n    /// Check bundle and system for resource name\n    /// If system image assume it's an icon and add a circular background\n    @ViewBuilder\n    private var dismissButtonImage: some View {\n        dismissIconImage\n            .foregroundColor(dismissIconColor)\n            .frame(width: width, height: height)\n            .padding(8)\n    }\n\n    var body: some View {\n        Button(action: onTap) {\n            dismissButtonImage\n                .frame(\n                    width: max(tappableWidth, width),\n                    height: max(tappableHeight, height)\n                )\n        }\n        .accessibilityLabel(\"Dismiss\")\n#if os(tvOS)\n        .buttonStyle(.card)\n#endif\n    }\n}\n\n#Preview {\n    CloseButton(dismissIconImage: Image(systemName: \"xmark\"),\n                dismissIconColor: .white,\n                onTap: {})\n    .background(Color.green)\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/FullscreenView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct FullscreenView: View, Sendable {\n    @EnvironmentObject var environment: InAppMessageEnvironment\n    let displayContent: InAppMessageDisplayContent.Fullscreen\n    let theme: InAppMessageTheme.Fullscreen\n\n\n    @ViewBuilder\n    private var headerView: some View {\n        if let heading = displayContent.heading {\n            TextView(textInfo: heading, textTheme: self.theme.header)\n                .applyAlignment(placement: displayContent.heading?.alignment ?? .left)\n                .accessibilityAddTraits(.isHeader)\n                .accessibilityAddTraits(.isStaticText)\n        }\n    }\n\n    @ViewBuilder\n    private var bodyView: some View {\n        if let body = displayContent.body {\n            TextView(textInfo: body, textTheme: self.theme.body)\n                .applyAlignment(placement: displayContent.body?.alignment ?? .left)\n                .accessibilityAddTraits(.isStaticText)\n        }\n    }\n\n    @ViewBuilder\n    private var mediaView: some View {\n        if let media = displayContent.media {\n            if shouldRemoveMediaTopPadding {\n                MediaView(mediaInfo: media, mediaTheme: theme.media)\n                    .padding(.top, -theme.padding.top)\n            } else {\n                MediaView(mediaInfo: media, mediaTheme: theme.media)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var buttonsView: some View {\n        if let buttons = displayContent.buttons, !buttons.isEmpty {\n            ButtonGroup(\n                layout: displayContent.buttonLayoutType ?? .stacked,\n                buttons: buttons,\n                theme: theme.buttons\n            )\n        }\n    }\n\n    @ViewBuilder\n    private var footerButton: some View {\n        if let footer = displayContent.footer {\n            ButtonView(buttonInfo: footer)\n        }\n    }\n\n\n    var shouldRemoveMediaTopPadding: Bool {\n        switch displayContent.template {\n        case .headerMediaBody:\n            displayContent.heading == nil\n        case .headerBodyMedia:\n            displayContent.heading == nil && displayContent.body == nil\n        case .mediaHeaderBody, .none:\n            true\n        }\n    }\n\n    var body: some View {\n            ScrollView {\n                VStack(spacing:24) {\n                    switch displayContent.template {\n                    case .headerMediaBody:\n                        headerView\n                        mediaView\n                        bodyView\n                    case .headerBodyMedia:\n                        headerView\n                        bodyView\n                        mediaView\n                    case .mediaHeaderBody, .none:\n                        mediaView\n                        headerView\n                        bodyView\n                    }\n                    buttonsView\n                    footerButton\n                }\n                .padding(theme.padding)\n                .background(Color.airshipTappableClear)\n            }\n            .addCloseButton(\n                dismissIconResource: theme.dismissIconResource,\n                dismissButtonColor: displayContent.dismissButtonColor?.color,\n                width: theme.dismissIconWidth,\n                height: theme.dismissIconHeight,\n                onUserDismissed: {\n                    environment.onUserDismissed()\n                }\n            )\n            .addBackground(\n                color: displayContent.backgroundColor?.color ?? Color.black\n            )\n            .onAppear {\n                self.environment.onAppear()\n            }\n    }\n}\n\n#Preview {\n    let headingText = InAppMessageTextInfo(text: \"this is header text\")\n    let bodyText = InAppMessageTextInfo(text: \"this is body text\")\n\n    let displayContent = InAppMessageDisplayContent.Fullscreen(heading: headingText, body:bodyText, buttons: [], template: .headerMediaBody)\n\n    return FullscreenView(displayContent: displayContent, theme: InAppMessageTheme.Fullscreen.defaultTheme)\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/HTMLView.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS)\n\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nimport Combine\n\nstruct HTMLView: View {\n\n#if !os(tvOS) && !os(watchOS)\n    @Environment(\\.verticalSizeClass) private var verticalSizeClass\n    @Environment(\\.horizontalSizeClass) private var horizontalSizeClass\n#endif\n\n    let displayContent: InAppMessageDisplayContent.HTML\n    let theme: InAppMessageTheme.HTML\n\n    #if os(iOS)\n    private var orientationChangePublisher = NotificationCenter.default\n        .publisher(for: UIDevice.orientationDidChangeNotification)\n        .makeConnectable()\n        .autoconnect()\n    #endif\n\n    @EnvironmentObject private var environment: InAppMessageEnvironment\n\n    init(displayContent: InAppMessageDisplayContent.HTML, theme: InAppMessageTheme.HTML) {\n        self.displayContent = displayContent\n        self.theme = theme\n    }\n\n    var body: some View {\n        let allowAspectLock = displayContent.width != nil && displayContent.height != nil && displayContent.aspectLock == true\n\n        InAppMessageWebView(displayContent: displayContent, accessibilityLabel: \"In-app web view\")\n            .airshipApplyIf(!theme.hideDismissIcon){\n                $0.addCloseButton(\n                    dismissIconResource: theme.dismissIconResource,\n                    dismissButtonColor: displayContent.dismissButtonColor?.color,\n                    width: theme.dismissIconWidth,\n                    height: theme.dismissIconHeight,\n                    onUserDismissed: {\n                        environment.onUserDismissed()\n                    }\n                )\n            }.airshipApplyIf(isModal && allowAspectLock) {\n                $0.cornerRadius(displayContent.borderRadius ?? 0)\n                    .aspectResize(\n                        width: displayContent.width,\n                        height: displayContent.height\n                    )\n                    .parentClampingResize(maxWidth: theme.maxWidth, maxHeight: theme.maxHeight)\n                    .padding(theme.padding)\n                    .addBackground(color: .airshipShadowColor)\n            }.airshipApplyIf(isModal && !allowAspectLock) {\n                $0.cornerRadius(displayContent.borderRadius ?? 0)\n                    .parentClampingResize(\n                        maxWidth: min(theme.maxWidth, (displayContent.width ?? .infinity)),\n                        maxHeight: min(theme.maxHeight, (displayContent.height ?? .infinity))\n                    )\n                    .padding(theme.padding)\n                    .addBackground(color: .airshipShadowColor)\n            }.airshipApplyIf(!isModal) {\n                /// Add system background color by default - clear color will be parsed by the display content if it's set\n                $0.addBackground(color: displayContent.backgroundColor?.color ?? AirshipColor.systemBackground)\n            }\n            .onAppear {\n                self.environment.onAppear()\n            }\n    }\n\n    var isModal: Bool {\n        guard displayContent.forceFullscreen != true else {\n            return false\n        }\n\n        guard displayContent.allowFullscreen == true else {\n            return true\n        }\n\n        #if os(tvOS)\n        return true\n        #elseif os(watchOS)\n        return false\n        #else\n        return verticalSizeClass == .regular && horizontalSizeClass == .regular\n        #endif\n    }            \n}\n\n#Preview {\n    let displayContent = InAppMessageDisplayContent.HTML(url: \"www.airship.com\")\n    return HTMLView(displayContent: displayContent, theme: InAppMessageTheme.HTML.defaultTheme)\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageBannerView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct InAppMessageBannerView: View {\n    @ObservedObject\n    var environment: InAppMessageEnvironment\n\n    /// Used to transmit self sizing info to UIKit host\n    @ObservedObject\n    var bannerConstraints: InAppMessageBannerConstraints\n\n    /// A state variable to prevent endless size refreshing\n    @State private var lastSize: CGSize?\n    @State private var isShowing: Bool = false\n    @State private var swipeOffset: CGFloat = 0\n    @State private var isButtonTapsDisabled: Bool = false\n\n    @StateObject var timer: AirshipObservableTimer\n\n    var theme: InAppMessageTheme.Banner\n    var onDismiss: () -> Void\n\n    private let displayContent: InAppMessageDisplayContent.Banner\n    private static let mediaMaxWidth: CGFloat = 120\n    private static let mediaMinHeight: CGFloat = 88\n    private static let mediaMaxHeight: CGFloat = 480\n    static let animationInOutDuration = 0.2\n\n    @ViewBuilder\n    private var dissmisActionButton: some View {\n        Button(\"ua_dismiss\".airshipLocalizedString(fallback: \"Dismiss\")) {\n            setShowing(state: false) {\n                onDismiss()\n            }\n        }\n        .accessibilityRemoveTraits(.isButton)\n    }\n\n    @ViewBuilder\n    private var headerView: some View {\n        if let heading = displayContent.heading {\n            TextView(textInfo: heading, textTheme: self.theme.header)\n                .fixedSize(horizontal: false, vertical: true)\n                .accessibilityAddTraits(.isHeader)\n                .accessibilityAddTraits(.isStaticText)\n                .applyAlignment(placement: heading.alignment ?? .left)\n                .accessibilityActions {\n                    dissmisActionButton\n                }\n\n        }\n    }\n\n    @ViewBuilder\n    private var bodyView: some View {\n        if let body = displayContent.body {\n            TextView(textInfo: body, textTheme: self.theme.body)\n                .fixedSize(horizontal: false, vertical: true)\n                .accessibilityAddTraits(.isStaticText)\n                .applyAlignment(placement: body.alignment ?? .left)\n                .accessibilityActions {\n                    dissmisActionButton\n                }\n        }\n    }\n\n    @ViewBuilder\n    private var mediaView: some View {\n        if let media = displayContent.media {\n            MediaView(\n                mediaInfo: media,\n                mediaTheme: self.theme.media\n            )\n            .padding(.horizontal, -theme.media.padding.leading)\n            .frame(\n                maxWidth: Self.mediaMaxWidth,\n                minHeight: Self.mediaMinHeight,\n                maxHeight: Self.mediaMaxHeight\n            )\n            .fixedSize(horizontal: false, vertical: true)\n        }\n\n    }\n\n    @ViewBuilder\n    private var buttonsView: some View {\n        if let buttons = displayContent.buttons, !buttons.isEmpty {\n            ButtonGroup(\n                isDisabled: $isButtonTapsDisabled,\n                layout: displayContent.buttonLayoutType ?? .stacked,\n                buttons: buttons,\n                theme: self.theme.buttons\n            )\n        }\n    }\n\n    #if os(iOS)\n    private var orientationChangePublisher = NotificationCenter.default\n        .publisher(for: UIDevice.orientationDidChangeNotification)\n        .makeConnectable()\n        .autoconnect()\n    #endif\n\n    init(\n        environment:InAppMessageEnvironment,\n        displayContent: InAppMessageDisplayContent.Banner,\n        bannerConstraints: InAppMessageBannerConstraints,\n        theme: InAppMessageTheme.Banner,\n        onDismiss: @escaping () -> Void\n    ) {\n        self.displayContent = displayContent\n        self.environment = environment\n        self.bannerConstraints = bannerConstraints\n        self.theme = theme\n        self.onDismiss = onDismiss\n        self._timer = StateObject(wrappedValue: AirshipObservableTimer(duration: displayContent.duration))\n    }\n\n    @ViewBuilder\n    private var contentBody: some View {\n        switch displayContent.template {\n        case .mediaLeft, .none:\n            HStack(alignment: .top, spacing: 16) {\n                mediaView\n                VStack(alignment: .center, spacing: 16) {\n                    headerView\n                    bodyView\n                }\n            }\n            .accessibilityActions {\n                dissmisActionButton\n            }\n        case .mediaRight:\n            HStack(alignment: .top, spacing: 16) {\n                VStack(alignment: .center, spacing: 16) {\n                    headerView\n                    bodyView\n                }\n                mediaView\n            }\n            .accessibilityActions {\n                dissmisActionButton\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var nub: some View {\n        let tabHeight: CGFloat = 4\n        let tabWidth: CGFloat = 36\n        let tabColor:Color = displayContent.dismissButtonColor?.color ?? Color.black.opacity(0.42)\n\n        Capsule()\n            .frame(width: tabWidth, height: tabHeight)\n            .foregroundColor(tabColor)\n            .accessibilityElement()\n            .padding(.horizontal, 40)\n            .padding(.vertical, 16)\n            .contentShape(Rectangle())\n            .airshipFocusableCompat()\n            .accessibilityLabel(\"ua_dismiss\".airshipLocalizedString(fallback: \"Dismiss\"))\n            .accessibilityAddTraits(.isButton)\n            .accessibilityAction {\n                onDismiss()\n            }\n\n    }\n    \n\n    @ViewBuilder\n    private var messageBody: some View {\n        let itemSpacing: CGFloat = 16\n\n        let messageContent = VStack(spacing: itemSpacing) {\n            contentBody\n            buttonsView\n        }\n\n        let body = VStack(spacing: 0) {\n            if (displayContent.placement == .top) {\n                messageContent\n                nub\n            } else {\n                nub\n                messageContent\n            }\n        }\n        .padding(.horizontal, itemSpacing)\n        .airshipGeometryGroupCompat()\n\n        if let actions = displayContent.actions {\n            Button(\n                action: {\n                    if (!self.isButtonTapsDisabled) {\n                        environment.onUserDismissed()\n                        environment.runActions(actions: actions)\n                    }\n                },\n                label: {\n                    body.background(Color.airshipTappableClear)\n                }\n            ).buttonStyle(\n                InAppMessageCustomOpacityButtonStyle(pressedOpacity: theme.tapOpacity)\n            )\n        } else {\n            body\n        }\n    }\n\n    private func setShowing(state:Bool, completion: (() -> Void)? = nil) {\n        withAnimation(Animation.easeInOut(duration: InAppMessageBannerView.animationInOutDuration)) {\n            self.isShowing = state\n        }\n\n        DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + InAppMessageBannerView.animationInOutDuration, execute: {\n            completion?()\n        })\n    }\n\n    private var banner: some View {\n        messageBody\n            .frame(maxWidth: theme.maxWidth)\n            .background(\n                GeometryReader(content: { contentMetrics -> Color in\n                    let size = contentMetrics.size\n                    DispatchQueue.main.async {\n                        if self.lastSize != size {\n                            self.bannerConstraints.size = size\n                            self.lastSize = size\n                        }\n                    }\n                    return Color.airshipTappableClear\n                })\n            )\n            .background(\n                (displayContent.backgroundColor?.color ?? Color.white)\n                    .cornerRadius(displayContent.borderRadius ?? 0)\n                    .edgesIgnoringSafeArea(displayContent.placement == .top ? .top : .bottom)\n                    .shadow(\n                        color: theme.shadow.color,\n                        radius: theme.shadow.radius,\n                        x: theme.shadow.xOffset,\n                        y: theme.shadow.yOffset\n                    )\n            )\n            .showing(isShowing: isShowing)\n            .padding(theme.padding)\n            .airshipApplyTransitioningPlacement(isTopPlacement: displayContent.placement == .top)\n            .offset(x: 0, y: swipeOffset)\n#if !os(tvOS)\n            .simultaneousGesture(swipeGesture)\n#endif\n            .accessibilityElement(children: .contain)\n            .accessibilityAction(.escape) {\n                setShowing(state: false) {\n                    onDismiss()\n                }\n            }\n            .onAppear {\n                setShowing(state: true)\n                timer.onAppear()\n                self.environment.onAppear()\n            }\n            .onDisappear {\n                self.timer.onDisappear()\n            }\n            .airshipOnChangeOf(swipeOffset) { value in\n                self.isButtonTapsDisabled = value != 0\n                self.timer.isPaused = value != 0\n            }\n            .airshipOnChangeOf(environment.isDismissed) { _ in\n                setShowing(state: false) {\n                    onDismiss()\n                }\n            }\n            .onReceive(timer.$isExpired) { expired in\n                if (expired) {\n                    self.environment.onUserDismissed()\n                }\n            }\n            .frame(width: self.width)\n    }\n\n    var width: CGFloat {\n#if os(visionOS)\n        return min(1280, theme.maxWidth)\n#elseif os(macOS)\n        // On macOS, we typically use the main window's width or\n        // a reasonable default if the window isn't attached yet.\n        let screenWidth = NSScreen.main?.frame.width ?? 1024\n        return min(screenWidth, theme.maxWidth)\n#else\n        min(UIScreen.main.bounds.size.width, theme.maxWidth)\n#endif\n    }\n\n    var body: some View {\n        InAppMessageRootView(inAppMessageEnvironment: environment) {\n            banner\n        }\n    }\n\n#if !os(tvOS)\n    private var swipeGesture: some Gesture {\n        let minSwipeDistance: CGFloat = if self.bannerConstraints.size.height > 0 {\n            min(100.0,  self.bannerConstraints.size.height * 0.5)\n        } else {\n            100.0\n        }\n\n        let placement = displayContent.placement ?? .bottom\n\n        return DragGesture(minimumDistance: 10)\n            .onChanged { gesture in\n                withAnimation(.interpolatingSpring(stiffness: 300, damping: 20)) {\n                    let offset = gesture.translation.height\n\n                    let upwardSwipeTopPlacement = (placement == .top && offset < 0)\n                    let downwardSwipeBottomPlacement = (placement == .bottom && offset > 0)\n\n                    if upwardSwipeTopPlacement || downwardSwipeBottomPlacement {\n                        self.swipeOffset = gesture.translation.height\n                    }\n                }\n            }\n            .onEnded { gesture in\n                withAnimation(.interpolatingSpring(stiffness: 300, damping: 20)) {\n                    let offset = gesture.translation.height\n                    swipeOffset = offset\n\n                    let upwardSwipeTopPlacement = (placement == .top && offset < -minSwipeDistance)\n                    let downwardSwipeBottomPlacement = (placement == .bottom && offset > minSwipeDistance)\n\n                    if upwardSwipeTopPlacement || downwardSwipeBottomPlacement {\n                        self.environment.onUserDismissed()\n                    } else {\n                        /// Return to origin and do nothing\n                        self.swipeOffset = 0\n                    }\n                }\n            }\n    }\n#endif\n}\n\nfileprivate struct InAppMessageCustomOpacityButtonStyle: ButtonStyle {\n    let pressedOpacity: Double\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label.opacity(configuration.isPressed ? pressedOpacity : 1.0)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageExtensions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Airship rendering engine extensions.\n/// - Note: for internal use only.  :nodoc:\nstruct InAppMessageExtensions {\n#if !os(tvOS)\n    let nativeBridgeExtension: (any NativeBridgeExtensionDelegate)?\n#endif\n    \n    let imageProvider: (any AirshipImageProvider)?\n    let actionRunner: (any InAppActionRunner)?\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageHostingController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n#if !os(watchOS)\n\nclass InAppMessageHostingController<Content> : AirshipNativeHostingController<Content> where Content : View {\n    var onDismiss: (() -> Void)?\n\n    override init(rootView: Content) {\n        super.init(rootView: rootView)\n#if os(macOS)\n        self.view.wantsLayer = true\n        self.view.layer?.backgroundColor = NSColor.clear.cgColor\n#else\n        self.view.backgroundColor = .clear\n#endif\n    }\n\n    required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n#if os(macOS)\n    override func viewDidDisappear() {\n        super.viewDidDisappear()\n        dismiss()\n    }\n\n    // macOS escape key handling is usually done via commands or\n    // overriding cancelOperation in the view hierarchy.\n    override func cancelOperation(_ sender: Any?) {\n        dismiss()\n    }\n#else\n    override func viewWillDisappear(_ animated: Bool) {\n        super.viewWillDisappear(animated)\n        dismiss()\n    }\n\n    override func accessibilityPerformEscape() -> Bool {\n        dismiss()\n        return true\n    }\n#endif\n\n    private func dismiss() {\n        self.onDismiss?()\n        onDismiss = nil\n    }\n\n\n#if !os(tvOS) && !os(macOS)\n    /// Just to be explicit about what we expect from these hosting controllers\n    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {\n        return .all\n    }\n\n    override var shouldAutorotate: Bool {\n        return true\n    }\n#endif\n}\n\n#endif\n\nimport Combine\n\nclass InAppMessageBannerViewController: InAppMessageHostingController<InAppMessageBannerView> {\n\n    private var centerXConstraint: NSLayoutConstraint?\n    private var topConstraint: NSLayoutConstraint?\n    private var bottomConstraint: NSLayoutConstraint?\n    private var heightConstraint: NSLayoutConstraint?\n    private var widthConstraint: NSLayoutConstraint?\n\n    private let bannerConstraints: InAppMessageBannerConstraints\n    private let placement: InAppMessageDisplayContent.Banner.Placement?\n\n    private var subscription: AnyCancellable?\n\n    init(\n         rootView: InAppMessageBannerView,\n         placement: InAppMessageDisplayContent.Banner.Placement?,\n         bannerConstraints: InAppMessageBannerConstraints\n    ) {\n        self.bannerConstraints = bannerConstraints\n        self.placement = placement\n        super.init(rootView: rootView)\n    }\n\n    required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n#if os(macOS)\n    override func viewDidAppear() {\n        super.viewDidAppear()\n\n        createBannerConstraints()\n        handleBannerConstraints(size: self.bannerConstraints.size)\n\n        subscription = bannerConstraints.$size.sink { [weak self] size in\n            self?.handleBannerConstraints(size: size)\n        }\n    }\n\n    override func viewWillDisappear() {\n        subscription?.cancel()\n        super.viewWillDisappear()\n    }\n\n    func createBannerConstraints() {\n        guard let contentView = view.window?.contentView else { return }\n        self.view.translatesAutoresizingMaskIntoConstraints = false\n        if let window = self.view.window {\n            centerXConstraint = self.view.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)\n            topConstraint = self.view.topAnchor.constraint(equalTo: contentView.topAnchor)\n            bottomConstraint = self.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)\n\n            heightConstraint = self.view.heightAnchor.constraint(equalToConstant: self.bannerConstraints.size.height)\n            widthConstraint = self.view.widthAnchor.constraint(equalToConstant: self.bannerConstraints.size.width)\n        }\n    }\n#else\n    override func viewDidAppear(_ animated: Bool) {\n        super.viewDidAppear(animated)\n\n        createBannerConstraints()\n        handleBannerConstraints(size: self.bannerConstraints.size)\n        self.view.layoutIfNeeded()\n\n        if UIAccessibility.isVoiceOverRunning {\n            DispatchQueue.main.asyncAfter(deadline: .now() + InAppMessageBannerView.animationInOutDuration) {\n                self.view.accessibilityViewIsModal = true\n                UIAccessibility.post(notification: .screenChanged, argument: self.view)\n            }\n        }\n\n        subscription = bannerConstraints.$size.sink { [weak self] size in\n            self?.handleBannerConstraints(size: size)\n            self?.view.layoutIfNeeded()\n        }\n    }\n\n\n    override func viewWillDisappear(_ animated: Bool) {\n        subscription?.cancel()\n        super.viewWillDisappear(animated)\n    }\n\n    func createBannerConstraints() {\n        self.view.translatesAutoresizingMaskIntoConstraints = false\n        if let window = self.view.window {\n            centerXConstraint = self.view.centerXAnchor.constraint(equalTo: window.centerXAnchor)\n            topConstraint = self.view.topAnchor.constraint(equalTo: window.topAnchor)\n            bottomConstraint = self.view.bottomAnchor.constraint(equalTo: window.bottomAnchor)\n\n            heightConstraint = self.view.heightAnchor.constraint(equalToConstant: self.bannerConstraints.size.height)\n            widthConstraint = self.view.widthAnchor.constraint(equalToConstant: self.bannerConstraints.size.width)\n        }\n    }\n#endif\n\n    func handleBannerConstraints(size: CGSize) {\n        // Ensure view is still in window hierarchy before updating constraints\n        guard self.view.window != nil else { return }\n\n        self.centerXConstraint?.isActive = true\n        self.heightConstraint?.isActive = true\n        self.widthConstraint?.isActive = true\n        self.widthConstraint?.constant = size.width\n\n        switch self.placement {\n        case .top:\n            self.topConstraint?.isActive = true\n            self.bottomConstraint?.isActive = false\n            self.heightConstraint?.constant = size.height + self.view.safeAreaInsets.top\n\n        default:\n            self.topConstraint?.isActive = false\n            self.bottomConstraint?.isActive = true\n            self.heightConstraint?.constant = size.height + self.view.safeAreaInsets.bottom\n        }\n    }\n}\n\n@MainActor\nclass InAppMessageBannerConstraints: ObservableObject {\n    @Published\n    var size: CGSize\n\n    init(size: CGSize) {\n        self.size = size\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageModalView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct InAppMessageModalView: View {\n    @EnvironmentObject var environment: InAppMessageEnvironment\n\n#if !os(tvOS) && !os(watchOS)\n    @Environment(\\.verticalSizeClass) private var verticalSizeClass\n    @Environment(\\.horizontalSizeClass) private var horizontalSizeClass\n#endif\n    \n    let displayContent: InAppMessageDisplayContent.Modal\n    let theme: InAppMessageTheme.Modal\n\n    @State\n    private var scrollViewContentSize: CGSize = .zero\n\n    @ViewBuilder\n    private var headerView: some View {\n        if let heading = displayContent.heading {\n            TextView(textInfo: heading, textTheme: theme.header)\n                .applyAlignment(placement: displayContent.heading?.alignment ?? .left)\n                .accessibilityAddTraits(.isHeader)\n                .accessibilityAddTraits(.isStaticText)\n        }\n    }\n\n    @ViewBuilder\n    private var bodyView: some View {\n        if let body = displayContent.body {\n            TextView(textInfo: body, textTheme: theme.body)\n                .applyAlignment(placement: displayContent.body?.alignment ?? .left)\n                .accessibilityAddTraits(.isStaticText)\n        }\n    }\n\n    @ViewBuilder\n    private var mediaView: some View {\n        if let media = displayContent.media {\n            if shouldRemoveMediaTopPadding {\n                MediaView(mediaInfo: media, mediaTheme: theme.media)\n                    .padding(.top, -theme.padding.top)\n            } else {\n                MediaView(mediaInfo: media, mediaTheme: theme.media)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var buttonsView: some View {\n        if let buttons = displayContent.buttons, !buttons.isEmpty {\n            ButtonGroup(\n                layout: displayContent.buttonLayoutType ?? .stacked,\n                buttons: buttons,\n                theme: theme.buttons\n            )\n        }\n    }\n\n    @ViewBuilder\n    private var footerButton: some View {\n        if let footer = displayContent.footer {\n            ButtonView(buttonInfo: footer)\n        }\n    }\n\n    #if os(iOS)\n    private var orientationChangePublisher = NotificationCenter.default\n        .publisher(for: UIDevice.orientationDidChangeNotification)\n        .makeConnectable()\n        .autoconnect()\n    #endif\n\n    init(displayContent: InAppMessageDisplayContent.Modal, theme: InAppMessageTheme.Modal) {\n        self.displayContent = displayContent\n        self.theme = theme\n    }\n\n    var shouldRemoveMediaTopPadding: Bool {\n        switch displayContent.template {\n        case .headerMediaBody:\n            displayContent.heading == nil\n        case .headerBodyMedia:\n            displayContent.heading == nil && displayContent.body == nil\n        case .mediaHeaderBody, .none:\n            true\n        }\n    }\n\n    @ViewBuilder\n    private var content: some View {\n        VStack(spacing: 24) {\n            ScrollView {\n                VStack(spacing: 24) {\n                    switch displayContent.template {\n                    case .headerMediaBody:\n                        headerView\n                        mediaView\n                        bodyView\n                    case .headerBodyMedia:\n                        headerView\n                        bodyView\n                        mediaView\n                    case .mediaHeaderBody, .none:\n                        if displayContent.media != nil {\n                            mediaView\n                        }\n                        headerView\n                        bodyView\n                    }\n                }\n                .padding(.leading, theme.padding.leading)\n                .padding(.trailing, theme.padding.trailing)\n                .padding(.top, theme.padding.top)\n                .background(\n                    GeometryReader { geo -> Color in\n                        DispatchQueue.main.async {\n                            if scrollViewContentSize != geo.size {\n                                if case .mediaHeaderBody = displayContent.template {\n                                    scrollViewContentSize = CGSize(width: geo.size.width, height: geo.size.height)\n                                } else {\n                                    scrollViewContentSize = geo.size\n                                }\n                            }\n                        }\n                        return Color.clear\n                    }\n                )\n            }\n            .airshipApplyIf(isModal) {\n                $0.frame(maxHeight: scrollViewContentSize.height)\n            }\n            VStack(spacing:24) {\n                buttonsView\n                footerButton\n            }\n            .padding(.leading, theme.padding.leading)\n            .padding(.trailing, theme.padding.trailing)\n            .padding(.bottom, theme.padding.bottom)\n        }\n    }\n\n    var body: some View {\n        content\n        .addCloseButton(\n            dismissIconResource: theme.dismissIconResource,\n            dismissButtonColor: displayContent.dismissButtonColor?.color,\n            width: theme.dismissIconWidth,\n            height: theme.dismissIconHeight,\n            onUserDismissed: {\n                environment.onUserDismissed()\n            }\n        )\n        .background(displayContent.backgroundColor?.color ?? Color.black)\n        .airshipApplyIf(isModal) {\n            $0.cornerRadius(displayContent.borderRadius ?? 0)\n            .parentClampingResize(maxWidth: theme.maxWidth, maxHeight: theme.maxHeight)\n            .padding(theme.padding)\n            .addBackground(color: .airshipShadowColor)\n        }\n        .airshipApplyIf(!isModal) {\n            $0.frame(maxWidth: .infinity, maxHeight: .infinity)\n        }\n        .onAppear {\n            self.environment.onAppear()\n        }\n    }\n\n    var isModal: Bool {\n        guard displayContent.allowFullscreenDisplay == true else {\n            return true\n        }\n\n        #if os(tvOS)\n        return true\n        #elseif os(watchOS)\n        return false\n        #else\n        return verticalSizeClass == .regular && horizontalSizeClass == .regular\n        #endif\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageNativeBridgeExtension.swift",
    "content": "/* Copyright Airship and Contributors */\n#if !os(tvOS)\nimport Foundation\npublic import WebKit\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Airship native bridge extension for an InAppMessage\npublic final class InAppMessageNativeBridgeExtension: NativeBridgeExtensionDelegate, Sendable {\n\n    private let message: InAppMessage\n\n    /// Airship native bridge extension initializer\n    /// - Parameter message: In-app message\n    public init(message: InAppMessage) {\n        self.message = message\n    }\n\n    public func actionsMetadata(\n        for command: JavaScriptCommand,\n        webView: WKWebView\n    ) -> [String: String] {\n        return [:]\n    }\n\n    public func extendJavaScriptEnvironment(\n        _ js: any JavaScriptEnvironmentProtocol,\n        webView: WKWebView\n    ) async {\n        let extras = message.extras?.unWrap() as? [String : AnyHashable]\n\n        js.add(\n            \"getMessageExtras\",\n            dictionary: extras ?? [:]\n        )\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageRootView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct InAppMessageRootView<Content: View>: View {\n    @ObservedObject var inAppMessageEnvironment: InAppMessageEnvironment\n\n    let content: () -> Content\n\n    init(\n        inAppMessageEnvironment: InAppMessageEnvironment,\n        @ViewBuilder content: @escaping () -> Content\n    ) {\n        self.inAppMessageEnvironment = inAppMessageEnvironment\n        self.content = content\n    }\n\n    @ViewBuilder\n    var body: some View {\n        content()\n            .environmentObject(inAppMessageEnvironment)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageViewUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nextension View {\n\n    @ViewBuilder\n    func addBackground(color: Color) -> some View {\n        ZStack {\n            color.ignoresSafeArea(.all).zIndex(0)\n            self.zIndex(1)\n        }\n    }\n\n    @ViewBuilder\n    func applyAlignment(\n        placement: InAppMessageTextInfo.Alignment\n    ) -> some View {\n        switch placement {\n        case .center:\n            HStack {\n                Spacer()\n                self\n                Spacer()\n            }\n        case .left:\n            HStack {\n                self\n                Spacer()\n            }\n        case .right:\n            HStack {\n                Spacer()\n                self\n            }\n        }\n    }\n\n    @ViewBuilder\n    func aspectResize(width:Double? = nil, height:Double? = nil) -> some View {\n        self.modifier(AspectResize(width:width, height:height))\n    }\n\n    @ViewBuilder\n    func parentClampingResize(maxWidth: CGFloat, maxHeight: CGFloat) -> some View {\n        self.modifier(ParentClampingResize(maxWidth: maxWidth, maxHeight: maxHeight))\n    }\n\n    @ViewBuilder\n    func addCloseButton(\n        dismissIconResource: String,\n        dismissButtonColor: Color?,\n        width: CGFloat? = nil,\n        height: CGFloat? = nil,\n        onUserDismissed: @escaping () -> Void\n    ) -> some View {\n        let dismissIconImage = InAppMessageTheme.dismissIcon(dismissIconResource)\n        let defaultDismissColor = Color.black\n\n        ZStack(alignment: .topTrailing) { // Align close button to the top trailing corner\n            self.zIndex(0)\n            CloseButton(\n                dismissIconImage: dismissIconImage,\n                dismissIconColor: dismissButtonColor ?? defaultDismissColor,\n                width: width,\n                height: height,\n                onTap: onUserDismissed\n            )\n            .zIndex(1)\n        }\n    }\n}\n\nstruct CenteredGeometryReader<Content: View>: View {\n    var content: (CGSize) -> Content\n\n    init(@ViewBuilder content: @escaping (CGSize) -> Content) {\n        self.content = content\n    }\n\n    var body: some View {\n        GeometryReader { geo in\n            let size = geo.size\n            content(size).position(\n                x: size.width / 2,\n                y: size.height / 2\n            )\n        }\n    }\n}\n\n/// Attempt to resize to specified size and clamp any size axis that exceeds parent size axis to said axis.\nstruct AspectResize: ViewModifier {\n    var width: Double?\n    var height: Double?\n\n    func body(content: Content) -> some View {\n        CenteredGeometryReader { size in\n            let parentWidth = size.width\n            let parentHeight = size.height\n\n            content.aspectRatio(\n                CGSize(width: width ?? parentWidth, height: height ?? parentHeight),\n                contentMode: .fit\n            )\n            .frame(maxWidth: parentWidth, maxHeight: parentHeight)\n        }\n    }\n}\n\n/// Attempt to resize to specified size and clamp any size axis that exceeds parent size axis to said axis.\nstruct ParentClampingResize: ViewModifier {\n    var maxWidth: CGFloat\n    var maxHeight: CGFloat\n\n    func body(content: Content) -> some View {\n        CenteredGeometryReader { parentSize in\n            let parentWidth = parentSize.width\n            let parentHeight = parentSize.height\n\n            content.frame(\n                maxWidth: min(parentWidth, maxWidth),\n                maxHeight: min(parentHeight, maxHeight)\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/InAppMessageWebView.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS)\nimport Foundation\nimport SwiftUI\nimport WebKit\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct InAppMessageWebView: View {\n    let displayContent: InAppMessageDisplayContent.HTML\n\n    @State var isWebViewLoading: Bool = false\n\n    let accessibilityLabel: String?\n\n    @EnvironmentObject var environment: InAppMessageEnvironment\n\n    var body: some View {\n\n        ZStack {\n            WKWebViewRepresentable(\n                url: self.displayContent.url,\n                nativeBridgeExtension: self.environment.nativeBridgeExtension,\n                isWebViewLoading: self.$isWebViewLoading,\n                accessibilityLabel: accessibilityLabel,\n                onRunActions: { name, value, _ in\n                    return await environment.runAction(name, arguments: value)\n                },\n                onDismiss: {\n                    environment.onUserDismissed()\n                }\n            )\n            .addBackground(\n                /// Add system background color by default - clear color will be parsed by the display content if it's set\n                color: displayContent.backgroundColor?.color ?? AirshipColor.systemBackground\n            )\n            .zIndex(0)\n\n            if self.isWebViewLoading {\n                BeveledLoadingView()\n                    .zIndex(1) /// Necessary to set z index for animation to work\n                    .transition(.opacity)\n            }\n        }\n    }\n}\n\nstruct WKWebViewRepresentable: AirshipNativeViewRepresentable {\n#if os(macOS)\n    typealias NSViewType = WKWebView\n    func makeNSView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateNSView(_ nsView: WKWebView, context: Context) {\n        updateView(nsView, context: context)\n    }\n#else\n    typealias UIViewType = WKWebView\n\n    func makeUIView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateUIView(_ uiView: WKWebView, context: Context) {\n        updateView(uiView, context: context)\n    }\n#endif\n\n    let url: String\n    let nativeBridgeExtension: (any NativeBridgeExtensionDelegate)?\n    @Binding var isWebViewLoading: Bool\n\n    let accessibilityLabel: String?\n    let onRunActions: @MainActor (String, ActionArguments, WKWebView) async -> ActionResult\n    let onDismiss: () -> Void\n\n    func makeWebView(context: Context) -> WKWebView {\n        let webView = WKWebView()\n\n#if os(macOS)\n        webView.setValue(false, forKey: \"drawsBackground\")\n        webView.setAccessibilityElement(true)\n        webView.setAccessibilityLabel(accessibilityLabel)\n#else\n        webView.isOpaque = false\n        webView.backgroundColor = .clear\n        webView.scrollView.backgroundColor = .clear\n        webView.isAccessibilityElement = true\n        webView.accessibilityLabel = accessibilityLabel\n#endif\n        webView.navigationDelegate = context.coordinator.nativeBridge\n\n        if #available(iOS 16.4, *) {\n            webView.isInspectable = Airship.isFlying && Airship.config.airshipConfig.isWebViewInspectionEnabled\n        }\n\n        if let url = URL(string: self.url) {\n            updateLoading(true)\n            webView.load(URLRequest(url: url))\n        }\n\n        return webView\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self, actionRunner: BlockNativeBridgeActionRunner(onRun: onRunActions))\n    }\n\n    func updateView(_ uiView: WKWebView, context: Context) {}\n\n    func updateLoading(_ isWebViewLoading: Bool) {\n        DispatchQueue.main.async {\n            withAnimation {\n                self.isWebViewLoading = isWebViewLoading\n            }\n        }\n    }\n\n    class Coordinator: NSObject, AirshipWKNavigationDelegate,\n                       JavaScriptCommandDelegate, NativeBridgeDelegate\n    {\n\n\n        private let parent: WKWebViewRepresentable\n        private let challengeResolver: ChallengeResolver\n        let nativeBridge: NativeBridge\n\n        init(_ parent: WKWebViewRepresentable, actionRunner: any NativeBridgeActionRunner, resolver: ChallengeResolver = .shared) {\n            self.parent = parent\n            self.nativeBridge = NativeBridge(actionRunner: actionRunner)\n            self.challengeResolver = resolver\n            \n            super.init()\n            \n            self.nativeBridge.nativeBridgeExtensionDelegate =\n            self.parent.nativeBridgeExtension\n            self.nativeBridge.forwardNavigationDelegate = self\n            self.nativeBridge.javaScriptCommandDelegate = self\n            self.nativeBridge.nativeBridgeDelegate = self\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            didFinish navigation: WKNavigation!\n        ) {\n            parent.updateLoading(false)\n        }\n\n        func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {\n            parent.updateLoading(true)\n            DispatchQueue.main.async {\n                webView.reload()\n            }\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            didFail navigation: WKNavigation!,\n            withError error: any Error\n        ) {\n            parent.updateLoading(true)\n            DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {\n                [weak webView] in\n                webView?.reload()\n            }\n        }\n        \n        func webView(\n            _ webView: WKWebView,\n            respondTo challenge: URLAuthenticationChallenge)\n        async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n            \n            return await challengeResolver.resolve(challenge)\n        }\n\n        func performCommand(_ command: JavaScriptCommand, webView: WKWebView) -> Bool {\n            return false\n        }\n\n        nonisolated func close() {\n            DispatchQueue.main.async {\n                self.parent.onDismiss()\n            }\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/MediaView.swift",
    "content": "/* Copyright Airship and Contributors */\n\n\nimport SwiftUI\n\n#if canImport(WebKit)\nimport WebKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct MediaView: View {\n    @EnvironmentObject\n    var environment: InAppMessageEnvironment\n\n    var mediaInfo: InAppMessageMediaInfo\n    var mediaTheme: InAppMessageTheme.Media\n\n    var body: some View {\n        switch mediaInfo.type {\n        case .image:\n            mediaImageView\n        default:\n#if canImport(WebKit)\n            webView\n#else\n            EmptyView()\n#endif\n        }\n    }\n\n    @ViewBuilder\n    private var mediaImageView: some View {\n        AirshipAsyncImage(\n            url: mediaInfo.url,\n            imageLoader: environment.imageLoader,\n            image: { image, imageSize in\n                 image\n                    .resizable()\n                    .scaledToFit()\n            },\n            placeholder: {\n                ProgressView()\n            }\n        )\n        .padding(mediaTheme.padding)\n\n    }\n\n#if canImport(WebKit)\n    private var webView: some View {\n        InAppMessageMediaWebView(mediaInfo: mediaInfo)\n            .aspectRatio(16.0/9.0, contentMode: .fill)\n            .frame(maxWidth: .infinity)\n            .padding(mediaTheme.padding)\n    }\n#endif\n\n}\n\n#if canImport(WebKit)\nstruct InAppMessageMediaWebView: AirshipNativeViewRepresentable {\n#if os(macOS)\n    typealias NSViewType = WKWebView\n    func makeNSView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateNSView(_ nsView: WKWebView, context: Context) {\n        updateView(nsView, context: context)\n    }\n#else\n    typealias UIViewType = WKWebView\n\n    func makeUIView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateUIView(_ uiView: WKWebView, context: Context) {\n        updateView(uiView, context: context)\n    }\n#endif\n\n    let mediaInfo: InAppMessageMediaInfo\n\n    private var baseURL: URL? {\n        let bundleIdentifier = Bundle.main.bundleIdentifier ?? \"com.airship.sdk\"\n        return URL(string: \"https://\\(bundleIdentifier)\")\n    }\n\n    func makeWebView(context: Context) -> WKWebView {\n        let config = WKWebViewConfiguration()\n\n\n#if os(macOS)\n        let webView = WKWebView(frame: .zero, configuration: config)\n        webView.setAccessibilityElement(true)\n        webView.layer?.backgroundColor = .clear\n        webView.setValue(false, forKey: \"drawsBackground\") // For transparency\n#else\n        config.allowsInlineMediaPlayback = true\n        config.allowsPictureInPictureMediaPlayback = true\n        let webView = WKWebView(frame: .zero, configuration: config)\n        webView.isAccessibilityElement = true\n        webView.scrollView.isScrollEnabled = false\n        webView.isOpaque = false\n        webView.backgroundColor = .clear\n        webView.scrollView.backgroundColor = .clear\n        webView.scrollView.contentInsetAdjustmentBehavior = .never\n#endif\n\n        webView.navigationDelegate = context.coordinator\n\n\n        if #available(iOS 16.4, *) {\n            webView.isInspectable = Airship.isFlying && Airship.config.airshipConfig.isWebViewInspectionEnabled\n        }\n\n        return webView\n    }\n\n    func updateView(_ uiView: WKWebView, context: Context) {\n        switch mediaInfo.type {\n        case .video:\n            let htmlString = \"<body style=\\\"margin:0\\\"><video playsinline controls height=\\\"100%\\\" width=\\\"100%\\\" src=\\\"\\(mediaInfo.url)\\\"></video></body>\"\n            uiView.loadHTMLString(htmlString, baseURL: baseURL)\n        case .youtube:\n            guard var urlComponents = URLComponents(string: mediaInfo.url) else { return }\n            urlComponents.query = \"playsinline=1\"\n            if let url = urlComponents.url {\n                uiView.load(URLRequest(url: url))\n            }\n        case .vimeo:\n            guard var urlComponents = URLComponents(string: mediaInfo.url) else { return }\n            urlComponents.query = \"playsinline=1\"\n            if let url = urlComponents.url {\n                uiView.load(URLRequest(url: url))\n            }\n        case .image:\n            break // Do nothing for images\n        }\n    }\n\n    func makeCoordinator() -> Coordinator {\n        return Coordinator()\n    }\n\n    class Coordinator: NSObject, WKNavigationDelegate {\n        let challengeResolver: ChallengeResolver\n\n        init(resolver: ChallengeResolver = .shared) {\n            self.challengeResolver = resolver\n        }\n\n        func webView(_ webView: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n            return await challengeResolver.resolve(challenge)\n        }\n    }\n}\n#endif\n\nstruct MediaInfo {\n    let url: String\n    let type: InAppMediaType\n}\n\nenum InAppMediaType {\n    case video, youtube, image, vimeo\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/TextView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct TextView: View {\n    let textInfo: InAppMessageTextInfo\n    let textTheme: InAppMessageTheme.Text\n\n    var body: some View {\n        Text(textInfo.text)\n            .foregroundColor(textInfo.color?.color ?? .primary)\n            .multilineTextAlignment(alignment(for: textInfo.alignment))\n            .applyTextStyling(textInfo: textInfo)\n            .applyTextTheme(textTheme)\n    }\n\n    private func alignment(for alignment: InAppMessageTextInfo.Alignment?) -> TextAlignment {\n        switch alignment {\n        case .left:\n            return .leading\n        case .center:\n            return .center\n        case .right:\n            return .trailing\n        case .none:\n            return .center\n        }\n    }\n}\n\nextension View {\n    func applyTextStyling(textInfo: InAppMessageTextInfo) -> some View {\n        return self.modifier(TextStyleViewModifier(textInfo: textInfo))\n    }\n}\n\nstruct TextStyleViewModifier: ViewModifier {\n    @Environment(\\.sizeCategory) var sizeCategory\n    let textInfo: InAppMessageTextInfo\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        content.font(\n            AirshipFont.resolveFont(\n                size: textInfo.size ?? 14,\n                families: textInfo.fontFamilies,\n                isItalic: textInfo.style?.contains(.italic) ?? false,\n                isBold: textInfo.style?.contains(.bold) ?? false\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageTheme.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// In-app message themes\npublic struct InAppMessageTheme {\n    static func decode<T>(\n        _ type: T.Type,\n        plistName: String,\n        bundle: Bundle? = Bundle.main\n    ) throws -> T where T : Decodable {\n        guard\n            let bundle,\n            let url = bundle.url(forResource: plistName, withExtension: \"plist\"),\n            let data = try? Data(contentsOf: url)\n        else {\n            throw AirshipErrors.error(\"Unable to locate theme override \\(plistName) from \\(String(describing: bundle))\")\n        }\n\n        return try PropertyListDecoder().decode(type, from: data)\n    }\n\n    static func decodeIfExists<T>(\n        _ type: T.Type,\n        plistName: String,\n        bundle: Bundle? = Bundle.main\n    ) throws -> T? where T : Decodable {\n        guard\n            let bundle,\n            let url = bundle.url(forResource: plistName, withExtension: \"plist\"),\n            let data = try? Data(contentsOf: url)\n        else {\n            return nil\n        }\n\n        return try PropertyListDecoder().decode(type, from: data)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeAdditionalPadding.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n\nextension InAppMessageTheme {\n    struct AdditionalPadding: Decodable {\n        var top: CGFloat?\n        var leading: CGFloat?\n        var trailing: CGFloat?\n        var bottom: CGFloat?\n    }\n}\n\n\nextension EdgeInsets {\n    mutating func add(_ additionalPadding: InAppMessageTheme.AdditionalPadding?) {\n        guard let additionalPadding else { return }\n        self.top =  self.top + (additionalPadding.top ?? 0)\n        self.leading = self.leading + (additionalPadding.leading ?? 0)\n        self.trailing = self.trailing + (additionalPadding.trailing ?? 0)\n        self.bottom = self.bottom + (additionalPadding.bottom ?? 0)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeBanner.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic extension InAppMessageTheme {\n\n\n    /// Banner in-app message theme\n    struct Banner: Equatable, Sendable {\n\n        /// Max width\n        public var maxWidth: CGFloat\n\n        /// Padding\n        public var padding: EdgeInsets\n\n        /// Tap opacity when the banner is tappable\n        public var tapOpacity: CGFloat\n\n        /// Shadow theme\n        public var shadow: InAppMessageTheme.Shadow\n\n        /// Header theme\n        public var header: InAppMessageTheme.Text\n\n        /// Body theme\n        public var body: InAppMessageTheme.Text\n\n        // Media theme\n        public var media: InAppMessageTheme.Media\n\n        /// Button theme\n        public var buttons:  InAppMessageTheme.Button\n\n        /// Default plist file for overrides\n        public static let defaultPlistName: String = \"UAInAppMessageBannerStyle\"\n\n        /// Applies a style from a plist to the theme.\n        /// - Parameters:\n        ///     - plistName: The name of the plist\n        ///     - bundle: The plist bundle.\n        public mutating func applyPlist(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decode(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n\n        mutating func applyPlistIfExists(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decodeIfExists(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides = overrides else { return }\n            self.padding.add(overrides.additionalPadding)\n            self.maxWidth = overrides.maxWidth ?? self.maxWidth\n            self.tapOpacity = overrides.tapOpacity ?? tapOpacity\n            self.shadow.applyOverrides(overrides.shadowTheme)\n            self.header.applyOverrides(overrides.headerTheme)\n            self.body.applyOverrides(overrides.bodyTheme)\n            self.media.applyOverrides(overrides.mediaTheme)\n            self.buttons.applyOverrides(overrides.buttonTheme)\n        }\n\n        struct Overrides: Decodable {\n            var additionalPadding: InAppMessageTheme.AdditionalPadding?\n            var maxWidth: CGFloat?\n            var tapOpacity: CGFloat?\n            var shadowTheme: InAppMessageTheme.Shadow.Overrides?\n            var headerTheme: InAppMessageTheme.Text.Overrides?\n            var bodyTheme: InAppMessageTheme.Text.Overrides?\n            var mediaTheme: InAppMessageTheme.Media.Overrides?\n            var buttonTheme: InAppMessageTheme.Button.Overrides?\n\n            enum CodingKeys: String, CodingKey {\n                case additionalPadding = \"additionalPadding\"\n                case maxWidth = \"maxWidth\"\n                case tapOpacity = \"tapOpacity\"\n                case shadowTheme = \"shadowStyle\"\n                case headerTheme = \"headerStyle\"\n                case bodyTheme = \"bodyStyle\"\n                case mediaTheme = \"mediaStyle\"\n                case buttonTheme = \"buttonStyle\"\n            }\n        }\n\n        static let defaultTheme: InAppMessageTheme.Banner = {\n            // Default\n            var theme = InAppMessageTheme.Banner(\n                maxWidth: 480,\n                padding: EdgeInsets(top: 0, leading: 24, bottom: 0, trailing: 24),\n                tapOpacity: 0.7,\n                shadow: InAppMessageTheme.Shadow(\n                    radius: 5,\n                    xOffset: 0,\n                    yOffset: 0,\n                    color: Color.black.opacity(0.33)\n                ),\n                header: InAppMessageTheme.Text(\n                    letterSpacing: 0,\n                    lineSpacing: 0,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                body: InAppMessageTheme.Text(\n                    letterSpacing: 0,\n                    lineSpacing: 0,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                media: InAppMessageTheme.Media(\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                buttons: InAppMessageTheme.Button(\n                    height: 33,\n                    stackedSpacing: 24,\n                    separatedSpacing: 16,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                )\n            )\n\n            /// Overrides\n            do {\n                try theme.applyPlistIfExists(plistName: \"UAInAppMessageBannerStyle\")\n            } catch {\n                AirshipLogger.error(\"Unable to apply theme overrides \\(error)\")\n            }\n            return theme\n        }()\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic extension InAppMessageTheme {\n    static func dismissIcon(_ dismissIconResource: String?) -> Image {\n        if let name = dismissIconResource, let customImage = AirshipNativeImage(named: name) {\n            return Image(airshipNativeImage: customImage)\n        }\n        if let name = dismissIconResource, let systemImage = AirshipNativeImage.airshipSystemImage(name: name) {\n            return Image(airshipNativeImage: systemImage)\n        }\n        return Image(systemName: \"xmark\")\n    }\n\n    /// Button in-app message theme\n    struct Button: Equatable, Sendable {\n        /// Button height\n        public var height: Double\n\n        /// Button spacing when stacked\n        public var stackedSpacing: Double\n\n        /// Button spacing when separated\n        public var separatedSpacing: Double\n\n        /// Padding\n        public var padding: EdgeInsets\n\n        public init(\n            height: Double,\n            stackedSpacing: Double,\n            separatedSpacing: Double,\n            padding: EdgeInsets = EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n        ) {\n            self.height = height\n            self.stackedSpacing = stackedSpacing\n            self.separatedSpacing = separatedSpacing\n            self.padding = padding\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides else { return }\n            self.height = overrides.buttonHeight ?? self.height\n            self.stackedSpacing = overrides.stackedButtonSpacing ?? self.stackedSpacing\n            self.separatedSpacing = overrides.separatedButtonSpacing ?? self.separatedSpacing\n            self.padding.add(overrides.additionalPadding)\n        }\n\n        struct Overrides: Decodable {\n            var buttonHeight: Double?\n            var stackedButtonSpacing: Double?\n            var separatedButtonSpacing: Double?\n            var additionalPadding: InAppMessageTheme.AdditionalPadding?\n\n            enum CodingKeys: String, CodingKey {\n                case buttonHeight\n                case stackedButtonSpacing\n                case separatedButtonSpacing\n                case additionalPadding\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeFullscreen.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic extension InAppMessageTheme {\n\n    /// Fullscreen in-app message theme\n    struct Fullscreen: Equatable, Sendable {\n\n        /// Padding\n        public var padding: EdgeInsets\n\n        /// Header theme\n        public var header: InAppMessageTheme.Text\n\n        /// Body theme\n        public var body: InAppMessageTheme.Text\n\n        // Media theme\n        public var media: InAppMessageTheme.Media\n\n        /// Button theme\n        public var buttons: InAppMessageTheme.Button\n\n        /// Dismiss icon resource name\n        public var dismissIconResource: String\n\n        /// Dismiss icon width\n        public var dismissIconWidth: CGFloat\n\n        /// Dismiss icon height\n        public var dismissIconHeight: CGFloat\n\n        /// Applies a style from a plist to the theme.\n        /// - Parameters:\n        ///     - plistName: The name of the plist\n        ///     - bundle: The plist bundle.\n        public mutating func applyPlist(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decode(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyPlistIfExists(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decodeIfExists(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides = overrides else { return }\n            self.padding.add(overrides.additionalPadding)\n            self.header.applyOverrides(overrides.headerTheme)\n            self.body.applyOverrides(overrides.bodyTheme)\n            self.media.applyOverrides(overrides.mediaTheme)\n            self.buttons.applyOverrides(overrides.buttonTheme)\n            self.dismissIconResource = overrides.dismissIconResource ?? self.dismissIconResource\n            self.dismissIconWidth = overrides.dismissIconWidth ?? self.dismissIconWidth\n            self.dismissIconHeight = overrides.dismissIconHeight ?? self.dismissIconHeight\n        }\n\n        struct Overrides: Decodable {\n            var additionalPadding: InAppMessageTheme.AdditionalPadding?\n            var headerTheme: InAppMessageTheme.Text.Overrides?\n            var bodyTheme: InAppMessageTheme.Text.Overrides?\n            var mediaTheme: InAppMessageTheme.Media.Overrides?\n            var buttonTheme: InAppMessageTheme.Button.Overrides?\n            var dismissIconResource: String?\n            var dismissIconWidth: CGFloat?\n            var dismissIconHeight: CGFloat?\n\n            enum CodingKeys: String, CodingKey {\n                case additionalPadding = \"additionalPadding\"\n                case headerTheme = \"headerStyle\"\n                case bodyTheme = \"bodyStyle\"\n                case mediaTheme = \"mediaStyle\"\n                case buttonTheme = \"buttonStyle\"\n                case dismissIconResource = \"dismissIconResource\"\n                case dismissIconWidth = \"dismissIconWidth\"\n                case dismissIconHeight = \"dismissIconHeight\"\n            }\n        }\n\n        static let defaultTheme: InAppMessageTheme.Fullscreen = {\n            // Default\n            var theme = InAppMessageTheme.Fullscreen(\n                padding: EdgeInsets(top: 24, leading: 24, bottom: 24, trailing: 24),\n                header: InAppMessageTheme.Text(\n                    letterSpacing: 0,\n                    lineSpacing: 0,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                body: InAppMessageTheme.Text(\n                    letterSpacing: 0,\n                    lineSpacing: 0,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                media: InAppMessageTheme.Media(\n                    padding: EdgeInsets(top: 0, leading: -24, bottom: 0, trailing: -24)\n                ),\n                buttons: InAppMessageTheme.Button(\n                    height: 33,\n                    stackedSpacing: 24,\n                    separatedSpacing: 16,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                dismissIconResource: \"ua_airship_dismiss\",\n                dismissIconWidth: 12,\n                dismissIconHeight: 12\n            )\n\n            /// Overrides\n            do {\n                try theme.applyPlistIfExists(plistName: \"UAInAppMessageFullScreenStyle\")\n            } catch {\n                AirshipLogger.error(\"Unable to apply theme overrides \\(error)\")\n            }\n            return theme\n        }()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeHTML.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\npublic extension InAppMessageTheme {\n    /// Html message theme\n    struct HTML: Equatable, Sendable {\n\n        /// Max width in points\n        public var maxWidth: CGFloat\n\n        /// Max height in points\n        public var maxHeight: CGFloat\n\n        /// If the dismiss icon should be hidden or not. Defaults to `false`\n        public var hideDismissIcon: Bool = false\n\n        /// Additional padding\n        public var padding: EdgeInsets\n\n        /// Dismiss icon resource name\n        public var dismissIconResource: String\n\n        /// Dismiss icon width\n        public var dismissIconWidth: CGFloat\n\n        /// Dismiss icon height\n        public var dismissIconHeight: CGFloat\n\n        /// Applies a style from a plist to the theme.\n        /// - Parameters:\n        ///     - plistName: The name of the plist\n        ///     - bundle: The plist bundle.\n        public mutating func applyPlist(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decode(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyPlistIfExists(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decodeIfExists(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides = overrides else { return }\n            self.hideDismissIcon = overrides.hideDismissIcon ?? self.hideDismissIcon\n            self.padding.add(overrides.additionalPadding)\n            self.dismissIconResource = overrides.dismissIconResource ?? self.dismissIconResource\n            self.dismissIconWidth = overrides.dismissIconWidth ?? self.dismissIconWidth\n            self.dismissIconHeight = overrides.dismissIconHeight ?? self.dismissIconHeight\n            self.maxWidth = overrides.maxWidth ?? self.maxWidth\n            self.maxHeight = overrides.maxHeight ?? self.maxHeight\n        }\n\n        struct Overrides: Decodable {\n            var hideDismissIcon: Bool?\n            var additionalPadding: InAppMessageTheme.AdditionalPadding?\n            var dismissIconResource: String?\n            var dismissIconWidth: CGFloat?\n            var dismissIconHeight: CGFloat?\n            var maxWidth: CGFloat?\n            var maxHeight: CGFloat?\n        }\n\n        static let defaultTheme: InAppMessageTheme.HTML = {\n            // Default\n            var theme = InAppMessageTheme.HTML(\n                maxWidth: 420,\n                maxHeight: 720,\n                padding: EdgeInsets(top: 48, leading: 24, bottom: 48, trailing: 24),\n                dismissIconResource: \"ua_airship_dismiss\",\n                dismissIconWidth: 12,\n                dismissIconHeight: 12\n            )\n\n            /// Overrides\n            do {\n                try theme.applyPlistIfExists(plistName: \"UAInAppMessageHTMLStyle\")\n            } catch {\n                AirshipLogger.error(\"Unable to apply theme overrides \\(error)\")\n            }\n            return theme\n        }()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Theme manager for in-app messages.\n@MainActor\npublic final class InAppAutomationThemeManager: Sendable {\n\n    /// Sets the html theme extender block\n    public var htmlThemeExtender: (@MainActor (InAppMessage, inout InAppMessageTheme.HTML) -> Void)?\n\n    /// Sets the modal theme extender block\n    public var modalThemeExtender: (@MainActor (InAppMessage, inout InAppMessageTheme.Modal) -> Void)?\n\n    /// Sets the fullscreen theme extender block\n    public var fullscreenThemeExtender: (@MainActor (InAppMessage, inout InAppMessageTheme.Fullscreen) -> Void)?\n\n    /// Sets the banner theme extender block\n    public var bannerThemeExtender: (@MainActor (InAppMessage, inout InAppMessageTheme.Banner) -> Void)?\n\n\n    func makeHTMLTheme(message: InAppMessage) -> InAppMessageTheme.HTML {\n        var theme = InAppMessageTheme.HTML.defaultTheme\n        htmlThemeExtender?(message, &theme)\n        return theme\n    }\n\n    func makeModalTheme(message: InAppMessage) -> InAppMessageTheme.Modal {\n        var theme = InAppMessageTheme.Modal.defaultTheme\n        modalThemeExtender?(message, &theme)\n        return theme\n    }\n\n    func makeFullscreenTheme(message: InAppMessage) -> InAppMessageTheme.Fullscreen {\n        var theme = InAppMessageTheme.Fullscreen.defaultTheme\n        fullscreenThemeExtender?(message, &theme)\n        return theme\n    }\n\n    func makeBannerTheme(message: InAppMessage) -> InAppMessageTheme.Banner {\n        var theme = InAppMessageTheme.Banner.defaultTheme\n        bannerThemeExtender?(message, &theme)\n        return theme\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeMedia.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\npublic extension InAppMessageTheme {\n\n    /// Media in-app message theme\n    struct Media: Equatable, Sendable {\n\n        /// Padding\n        public var padding: EdgeInsets\n\n        public init(padding: EdgeInsets) {\n            self.padding = padding\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides else { return }\n            self.padding.add(overrides.additionalPadding)\n        }\n\n        struct Overrides: Decodable {\n            var additionalPadding: AdditionalPadding?\n\n            enum CodingKeys: String, CodingKey {\n                case additionalPadding\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeModal.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic extension InAppMessageTheme {\n\n    /// Modal in-app message theme\n    struct Modal: Equatable, Sendable {\n\n        /// Max width\n        public var maxWidth: CGFloat\n\n        /// Max height\n        public var maxHeight: CGFloat\n\n        /// Padding\n        public var padding: EdgeInsets\n\n        /// Header theme\n        public var header: InAppMessageTheme.Text\n\n        /// Body theme\n        public var body: InAppMessageTheme.Text\n\n        // Media theme\n        public var media: InAppMessageTheme.Media\n\n        /// Button theme\n        public var buttons:  InAppMessageTheme.Button\n\n        /// Dismiss icon resource name\n        public var dismissIconResource:  String\n\n        /// Dismiss icon width\n        public var dismissIconWidth: CGFloat\n\n        /// Dismiss icon height\n        public var dismissIconHeight: CGFloat\n\n        /// Applies a style from a plist to the theme.\n        /// - Parameters:\n        ///     - plistName: The name of the plist\n        ///     - bundle: The plist bundle.\n        public mutating func applyPlist(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decode(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyPlistIfExists(plistName: String, bundle: Bundle? = Bundle.main) throws {\n            let overrides = try InAppMessageTheme.decodeIfExists(\n                Overrides.self,\n                plistName: plistName,\n                bundle: bundle\n            )\n            self.applyOverrides(overrides)\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides = overrides else { return }\n            self.maxWidth = overrides.maxWidth ?? self.maxWidth\n            self.maxHeight = overrides.maxHeight ?? self.maxHeight\n            self.padding.add(overrides.additionalPadding)\n            self.header.applyOverrides(overrides.headerTheme)\n            self.body.applyOverrides(overrides.bodyTheme)\n            self.media.applyOverrides(overrides.mediaTheme)\n            self.buttons.applyOverrides(overrides.buttonTheme)\n            self.dismissIconResource = overrides.dismissIconResource ?? self.dismissIconResource\n            self.dismissIconWidth = overrides.dismissIconWidth ?? self.dismissIconWidth\n            self.dismissIconHeight = overrides.dismissIconHeight ?? self.dismissIconHeight\n        }\n\n        struct Overrides: Decodable {\n            var additionalPadding: InAppMessageTheme.AdditionalPadding?\n            var headerTheme: InAppMessageTheme.Text.Overrides?\n            var bodyTheme: InAppMessageTheme.Text.Overrides?\n            var mediaTheme: InAppMessageTheme.Media.Overrides?\n            var buttonTheme: InAppMessageTheme.Button.Overrides?\n            var dismissIconResource: String?\n            var dismissIconWidth: CGFloat?\n            var dismissIconHeight: CGFloat?\n            var maxWidth: CGFloat?\n            var maxHeight: CGFloat?\n\n            enum CodingKeys: String, CodingKey {\n                case additionalPadding = \"additionalPadding\"\n                case headerTheme = \"headerStyle\"\n                case bodyTheme = \"bodyStyle\"\n                case mediaTheme = \"mediaStyle\"\n                case buttonTheme = \"buttonStyle\"\n                case dismissIconResource = \"dismissIconResource\"\n                case dismissIconWidth = \"dismissIconWidth\"\n                case dismissIconHeight = \"dismissIconHeight\"\n                case maxWidth = \"maxWidth\"\n                case maxHeight = \"maxHeight\"\n            }\n        }\n\n        static let defaultTheme: InAppMessageTheme.Modal = {\n            // Default\n            var theme = InAppMessageTheme.Modal(\n                maxWidth: 420,\n                maxHeight: 720,\n                padding: EdgeInsets(top: 48, leading: 24, bottom: 48, trailing: 24),\n                header: InAppMessageTheme.Text(\n                    letterSpacing: 0,\n                    lineSpacing: 0,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                body: InAppMessageTheme.Text(\n                    letterSpacing: 0,\n                    lineSpacing: 0,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                media: InAppMessageTheme.Media(\n                    padding: EdgeInsets(top: 0, leading: -24, bottom: 0, trailing: -24)\n                ),\n                buttons: InAppMessageTheme.Button(\n                    height: 33,\n                    stackedSpacing: 24,\n                    separatedSpacing: 16,\n                    padding: EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)\n                ),\n                dismissIconResource: \"ua_airship_dismiss\",\n                dismissIconWidth: 12,\n                dismissIconHeight: 12\n            )\n\n            /// Overrides\n            do {\n                try theme.applyPlistIfExists(plistName: \"UAInAppMessageModalStyle\")\n            } catch {\n                AirshipLogger.error(\"Unable to apply theme overrides \\(error)\")\n            }\n            return theme\n        }()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeShadow.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n/// For color utils\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\npublic extension InAppMessageTheme {\n\n    /// Shadow  in-app message theme\n    struct Shadow: Equatable, Sendable {\n\n        /// Shadow radius\n        public var radius: CGFloat\n\n        /// X offset\n        public var xOffset: CGFloat\n\n        /// Y offset\n        public var yOffset: CGFloat\n\n        /// Shadow color\n        public var color: Color\n\n        public init(radius: CGFloat,  xOffset: CGFloat, yOffset: CGFloat, color: Color) {\n            self.radius = radius\n            self.xOffset = xOffset\n            self.yOffset = yOffset\n            self.color = color\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides else { return }\n            self.radius = overrides.radius ?? self.radius\n            self.xOffset = overrides.xOffset ?? self.xOffset\n            self.yOffset = overrides.yOffset ?? self.yOffset\n            self.color = overrides.color.flatMap { $0.airshipToColor() } ?? self.color\n        }\n\n        struct Overrides: Decodable {\n            var radius: CGFloat?\n            var xOffset: CGFloat?\n            var yOffset: CGFloat?\n            var color: String?\n\n            init(radius: CGFloat? = nil, xOffset: CGFloat? = nil, yOffset: CGFloat? = nil, color: String? = nil) {\n                self.radius = radius\n                self.xOffset = xOffset\n                self.yOffset = yOffset\n                self.color = color\n            }\n\n            enum CodingKeys: String, CodingKey {\n                case radius = \"radius\"\n                case xOffset = \"xOffset\"\n                case yOffset = \"yOffset\"\n                case color = \"colorHex\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/InAppMessageThemeText.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\npublic extension InAppMessageTheme {\n\n    /// Text in-app message theme\n    struct Text: Equatable, Sendable {\n        \n        /// Letter spacing\n        public var letterSpacing: CGFloat\n        \n        /// Line spacing\n        public var lineSpacing: CGFloat\n\n        /// Text view padding\n        public var padding: EdgeInsets\n\n        public init(letterSpacing: Double, lineSpacing: Double, padding: EdgeInsets) {\n            self.letterSpacing = letterSpacing\n            self.lineSpacing = lineSpacing\n            self.padding = padding\n        }\n\n        mutating func applyOverrides(_ overrides: Overrides?) {\n            guard let overrides else { return }\n            self.letterSpacing = overrides.letterSpacing ?? self.letterSpacing\n            self.lineSpacing = overrides.lineSpacing ?? self.lineSpacing\n            self.padding.add(overrides.additionalPadding)\n        }\n\n        struct Overrides: Decodable {\n            var letterSpacing: CGFloat?\n            var lineSpacing: CGFloat?\n            var additionalPadding: InAppMessageTheme.AdditionalPadding?\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/InAppMessage/View/Theme/ThemeExtensions.swift",
    "content": "/* Copyright Airship and Contributors */\n\n\nimport Foundation\nimport SwiftUI\n\nextension View {\n    @ViewBuilder\n    func applyTextTheme(_ textTheme: InAppMessageTheme.Text) -> some View {\n        self.padding(textTheme.padding)\n            .lineSpacing(textTheme.lineSpacing)\n            .kerning(textTheme.letterSpacing)\n        \n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Limits/FrequencyChecker.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Protocol for checking and incrementing frequency limits (e.g. for in-app message display caps).\npublic protocol FrequencyCheckerProtocol: Sendable {\n    /// Whether the frequency limit has been exceeded.\n    @MainActor\n    var isOverLimit: Bool { get }\n    /// Checks the limit and, if not over limit, increments the count. Call from main actor.\n    /// - Returns: `true` if the increment was applied (under limit), `false` if over limit.\n    @MainActor\n    func checkAndIncrement() -> Bool\n}\n\nfinal class FrequencyChecker: FrequencyCheckerProtocol {\n    private let isOverLimitBlock: @Sendable @MainActor () -> Bool\n    private let checkAndIncrementBlock: @Sendable @MainActor () -> Bool\n\n    var isOverLimit: Bool {\n        return isOverLimitBlock()\n    }\n\n    init(\n        isOverLimitBlock: @escaping  @Sendable @MainActor () -> Bool,\n        checkAndIncrementBlock: @escaping  @Sendable @MainActor () -> Bool\n    ) {\n        self.isOverLimitBlock = isOverLimitBlock\n        self.checkAndIncrementBlock = checkAndIncrementBlock\n    }\n\n    @MainActor\n    func checkAndIncrement() -> Bool {\n        return checkAndIncrementBlock()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Limits/FrequencyConstraint.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents a constraint on occurrences within a given time period.\nstruct FrequencyConstraint: Equatable, Hashable, Sendable, Decodable {\n\n    var identifier: String\n\n    var range: TimeInterval\n\n    var count: UInt\n    \n\n    fileprivate enum CodingKeys: String, CodingKey {\n        case identifier = \"id\"\n        case range = \"range\"\n        case boundary = \"boundary\"\n        case period = \"period\"\n    }\n\n    fileprivate enum Period: String, Decodable {\n        case seconds\n        case minutes\n        case hours\n        case days\n        case weeks\n        case months\n        case years\n\n        func toTimeInterval(_ value: Double) -> TimeInterval {\n            switch (self) {\n            case .seconds:\n                return value\n            case .minutes:\n                return value * 60\n            case .hours:\n                return value * 60 * 60\n            case .days:\n                return value * 60 * 60 * 24\n            case .weeks:\n                return value * 60 * 60 * 24 * 6\n            case .months:\n                return value * 60 * 60 * 24 * 30\n            case .years:\n                return value * 60 * 60 * 24 * 365\n            }\n        }\n    }\n\n    init(identifier: String, range: TimeInterval, count: UInt) {\n        self.identifier = identifier\n        self.range = range\n        self.count = count\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let identifier = try container.decode(String.self, forKey: .identifier)\n        let periodRange = try container.decode(Double.self, forKey: .range)\n        let period = try container.decode(Period.self, forKey: .period)\n        let boundary = try container.decode(UInt.self, forKey: .boundary)\n\n        self.init(\n            identifier: identifier,\n            range: period.toTimeInterval(periodRange),\n            count: boundary\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Limits/FrequencyLimitManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Manager protocol for keeping track of frequency limits and occurrence counts.\nprotocol FrequencyLimitManagerProtocol: Sendable {\n\n    /// Gets a frequency checker corresponding to the passed in constraints identifiers\n    /// - Parameter constraintIDs: Constraint identifiers\n    /// - Returns: The frequency checker instance\n    @MainActor\n    func getFrequencyChecker(\n        constraintIDs: [String]?\n    ) async throws -> any FrequencyCheckerProtocol\n\n    func setConstraints(_ constraints: [FrequencyConstraint]) async throws\n}\n\n/// Manager for keeping track of frequency limits and occurrence counts.\nfinal class FrequencyLimitManager: FrequencyLimitManagerProtocol, Sendable {\n    private let frequencyLimitStore: FrequencyLimitStore\n    private let date: any AirshipDateProtocol\n    private let storeQueue: AirshipSerialQueue\n\n    private let emptyChecker = FrequencyChecker(\n        isOverLimitBlock: { false },\n        checkAndIncrementBlock: { true }\n    )\n\n    @MainActor\n    private var fetchedConstraints: [String: ConstraintInfo] = [:]\n\n    @MainActor\n    private var pendingOccurrences: [Occurrence] = []\n\n    init(\n        dataStore: FrequencyLimitStore,\n        date: any AirshipDateProtocol = AirshipDate(),\n        storeQueue: AirshipSerialQueue = AirshipSerialQueue()\n    ) {\n        self.frequencyLimitStore = dataStore\n        self.date = date\n        self.storeQueue = storeQueue\n    }\n    \n    convenience init(config: RuntimeConfig) {\n        self.init(dataStore: FrequencyLimitStore(config: config))\n    }\n\n    @MainActor\n    func getFrequencyChecker(\n        constraintIDs: [String]?\n    ) async throws -> any FrequencyCheckerProtocol {\n        guard let constraintIDs = constraintIDs, !constraintIDs.isEmpty else {\n            return emptyChecker\n        }\n\n        return try await storeQueue.run {\n            await self.writePending()\n\n            let fetched = await self.fetchedConstraints.keys\n            let need = Set(constraintIDs).subtracting(fetched)\n\n            if !need.isEmpty {\n                let constraintInfos = try await self.frequencyLimitStore.fetchConstraints(\n                    Array(need)\n                )\n\n                if (constraintInfos.count != need.count) {\n                    let missing = need.subtracting(constraintInfos.map { $0.constraint.identifier } )\n                    throw AirshipErrors.error(\"Requested constraints \\(constraintIDs) missing: \\(missing)\")\n                }\n\n                await self.updateFetchedConstraintInfos(constraintInfos)\n            }\n\n            return FrequencyChecker(\n                isOverLimitBlock: { [weak self] in\n                    return self?.isOverLimit(constraintIDs: constraintIDs) ?? true\n                },\n                checkAndIncrementBlock: { [weak self] in\n                    return self?.checkAndIncrement(constraintIDs: constraintIDs) ?? false\n                }\n            )\n        }\n    }\n\n    func setConstraints(data: Data) async throws {\n        let constraints = try JSONDecoder().decode(\n            [FrequencyConstraint].self,\n            from: data\n        )\n        try await setConstraints(constraints)\n    }\n\n    func setConstraints(_ constraints: [FrequencyConstraint]) async throws {\n        try await self.storeQueue.run {\n            await self.writePending()\n\n            let existing = Set(\n                try await self.frequencyLimitStore.fetchConstraints()\n                    .map { $0.constraint }\n            )\n\n            let incomingIDs = Set(constraints.map { $0.identifier })\n\n            let upsert = constraints.filter { constraint in\n                !existing.contains(constraint)\n            }\n\n            let delete = existing\n                .filter { constraint in\n                    if (!incomingIDs.contains(constraint.identifier)) {\n                        return true\n                    }\n\n                    return constraints.contains { incoming in\n                        constraint.identifier == incoming.identifier &&\n                        constraint.range != incoming.range\n                    }\n                }\n                .map { $0.identifier }\n\n            try await self.frequencyLimitStore.deleteConstraints(delete)\n            await self.removeFetchedConstraints(delete)\n\n            for upsert in upsert {\n                try await self.frequencyLimitStore.upsertConstraint(upsert)\n                await self.updateFetchedConstraint(upsert)\n            }\n        }\n    }\n\n    @MainActor\n    private func isOverLimit(constraintIDs: [String]) -> Bool {\n        return constraintIDs.contains(\n            where: { constraintID in\n                guard let constraintInfo = self.fetchedConstraints[constraintID] else {\n                    return false\n                }\n\n                let constraint = constraintInfo.constraint\n                let occurrences = constraintInfo.occurrences.sorted { l, r in\n                    l.timestamp <= r.timestamp\n                }\n\n                guard occurrences.count >= constraint.count else { return false }\n\n                let timeStamp = occurrences[occurrences.count - Int(constraint.count)].timestamp\n                let timeSinceOccurrence = self.date.now.timeIntervalSince(timeStamp)\n                return timeSinceOccurrence <= constraint.range\n            }\n        )\n    }\n\n    @MainActor\n    private func checkAndIncrement(constraintIDs: [String]) -> Bool {\n        guard !isOverLimit(constraintIDs: constraintIDs) else { return false }\n\n        let now = self.date.now\n\n        constraintIDs.forEach { constraintID in\n            let occurrence = Occurrence(constraintID: constraintID, timestamp: now)\n            self.fetchedConstraints[constraintID]?.occurrences.append(occurrence)\n            self.pendingOccurrences.append(occurrence)\n        }\n\n        // Queue up a task to write pending\n        Task {\n            await self.storeQueue.runSafe {\n                await self.writePending()\n            }\n        }\n\n        return true\n    }\n\n    @MainActor\n    private func updateFetchedConstraint(_ constraint: FrequencyConstraint) {\n        self.fetchedConstraints[constraint.identifier]?.constraint = constraint\n    }\n\n    @MainActor\n    private func removeFetchedConstraints(_ constraintIDs: [String]) {\n        constraintIDs.forEach { constraintID in\n            self.fetchedConstraints[constraintID] = nil\n        }\n    }\n\n    @MainActor\n    private func updateFetchedConstraintInfos(_ constraintInfos: [ConstraintInfo]) {\n        constraintInfos.forEach { info in\n            self.fetchedConstraints[info.constraint.identifier] = info\n        }\n    }\n\n    func writePending() async {\n        let pending = await popPendingOccurrences()\n        do {\n            try await self.frequencyLimitStore.saveOccurrences(pending)\n        } catch {\n            AirshipLogger.error(\"Failed to write pending: \\(pending) \\(error)\")\n            await appendPendingOccurrences(pending)\n        }\n    }\n\n    @MainActor\n    private func appendPendingOccurrences(_ pending: [Occurrence]) {\n        self.pendingOccurrences.append(contentsOf: pending)\n    }\n\n    @MainActor\n    private func popPendingOccurrences() -> [Occurrence] {\n        let pending = self.pendingOccurrences\n        self.pendingOccurrences = []\n        return pending\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Limits/FrequencyLimitStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nenum FrequencyLimitStoreError: Error, Sendable {\n    case coreDataUnavailable\n    case coreDataError\n}\n\nactor FrequencyLimitStore {\n\n    private let coreData: UACoreData?\n\n    init(\n        appKey: String,\n        inMemory: Bool\n    ) {\n        let bundle = AirshipAutomationResources.bundle\n        if let modelURL = bundle.url(forResource: \"UAFrequencyLimits\", withExtension:\"momd\") {\n            self.coreData = UACoreData(\n                name: \"UAFrequencyLimits\",\n                modelURL: modelURL,\n                inMemory: inMemory,\n                stores: [\"Frequency-limits-\\(appKey).sqlite\"]\n            )\n        } else {\n            self.coreData = nil\n        }\n    }\n\n    init(\n        config: RuntimeConfig\n    ) {\n        self.init(\n            appKey: config.appCredentials.appKey,\n            inMemory: false\n        )\n    }\n\n    init(\n        coreData: UACoreData\n    ) {\n        self.coreData = coreData\n    }\n\n    // MARK: -\n    // MARK: Public Data Access\n\n    func fetchConstraints(\n        _ constraintIDs: [String]? = nil\n    ) async throws -> [ConstraintInfo] {\n        guard let coreData = self.coreData else {\n            throw FrequencyLimitStoreError.coreDataUnavailable\n        }\n\n        AirshipLogger.trace(\n            \"Fetching frequency limit constraints\"\n        )\n\n        return try await coreData.performWithResult { context in\n            let result = try self.fetchConstraintsData(\n                forIDs: constraintIDs,\n                context: context\n            )\n\n            return result.map { data in\n                return self.makeInfo(data: data)\n            }\n        }\n    }\n\n    func deleteConstraints(\n        _ constraintIDs: [String]\n    ) async throws {\n\n        guard let coreData = self.coreData else {\n            throw FrequencyLimitStoreError.coreDataUnavailable\n        }\n\n        AirshipLogger.trace(\n            \"Deleting constraint IDs : \\(constraintIDs)\"\n        )\n\n        try await coreData.perform { context in\n            let constraints = try self.fetchConstraintsData(\n                forIDs: constraintIDs,\n                context: context)\n            constraints.forEach { constraint in\n                context.delete(constraint)\n            }\n        }\n    }\n\n\n    func saveOccurrences(\n        _ occurrences: [Occurrence]\n    ) async throws {\n\n        guard let coreData = self.coreData else {\n            throw FrequencyLimitStoreError.coreDataUnavailable\n        }\n\n        AirshipLogger.trace(\"Saving occurrences \\(occurrences)\")\n\n        let map: [String: [Occurrence]] = Dictionary(grouping: occurrences, by: { $0.constraintID })\n\n        try await coreData.perform { context in\n\n            try map.forEach { constraintID, occurrences in\n                let constraintsData = try self.fetchConstraintsData(\n                    forIDs: [constraintID],\n                    context: context\n                )\n\n                if let constraintData = constraintsData.first {\n                    try occurrences.forEach { occurrence in\n                        let occurrenceData = try self.makeOccurrenceData(context: context)\n                        occurrenceData.timestamp = occurrence.timestamp\n                        constraintData.occurrence.insert(occurrenceData)\n                    }\n                }\n            }\n        }\n\n    }\n\n    func upsertConstraint(\n        _ constraint: FrequencyConstraint\n    ) async throws {\n        guard let coreData = self.coreData else {\n            throw FrequencyLimitStoreError.coreDataUnavailable\n        }\n\n        AirshipLogger.trace(\n            \"Update constraint : \\(constraint.identifier)\"\n        )\n\n        try await coreData.perform { context in\n\n            let result = try self.fetchConstraintsData(\n                forIDs: [constraint.identifier],\n                context: context\n            )\n\n            let data = try (result.first ?? self.makeConstraintData(context: context))\n            data.identifier = constraint.identifier\n            data.count = constraint.count\n            data.range = constraint.range\n        }\n    }\n\n    // MARK: -\n    // MARK: Helpers\n\n    fileprivate nonisolated func fetchConstraintsData(\n        forIDs constraintIDs: [String]? = nil,\n        context: NSManagedObjectContext\n    ) throws -> [FrequencyConstraintData] {\n\n        let request: NSFetchRequest<FrequencyConstraintData> = FrequencyConstraintData.fetchRequest()\n        request.includesPropertyValues = true\n\n        if let constraintIDs = constraintIDs {\n            request.predicate = NSPredicate(format: \"identifier IN %@\", constraintIDs)\n        }\n\n        return try context.fetch(request)\n    }\n\n    fileprivate nonisolated func makeConstraintData(\n        context: NSManagedObjectContext\n    ) throws -> FrequencyConstraintData {\n        guard let data = NSEntityDescription.insertNewObject(\n            forEntityName: FrequencyConstraintData.frequencyConstraintDataEntity,\n            into:context) as? FrequencyConstraintData\n        else {\n            throw FrequencyLimitStoreError.coreDataError\n        }\n\n        return data\n    }\n\n    fileprivate nonisolated func makeOccurrenceData(\n        context:NSManagedObjectContext\n    ) throws -> OccurrenceData {\n\n        guard\n            let data = NSEntityDescription.insertNewObject(\n                forEntityName: OccurrenceData.occurrenceDataEntity,\n                into:context\n            ) as? OccurrenceData\n        else {\n            throw FrequencyLimitStoreError.coreDataError\n        }\n\n        return data\n    }\n\n    fileprivate nonisolated func makeInfo(data: FrequencyConstraintData) -> ConstraintInfo {\n        return ConstraintInfo(\n            constraint: FrequencyConstraint(\n                identifier: data.identifier,\n                range: data.range,\n                count: data.count\n            ),\n            occurrences: data.occurrence.map({ occurrenceData in\n                Occurrence(\n                    constraintID: data.identifier,\n                    timestamp: occurrenceData.timestamp\n                )\n            })\n        )\n    }\n}\n\nstruct ConstraintInfo: Hashable, Equatable, Sendable {\n    var constraint: FrequencyConstraint\n    var occurrences: [Occurrence]\n}\n\n\n/// Represents a constraint on occurrences within a given time period.\n/// \n@objc(UAFrequencyConstraintData)\nfileprivate class FrequencyConstraintData: NSManagedObject {\n\n    static let frequencyConstraintDataEntity = \"UAFrequencyConstraintData\"\n\n    @nonobjc class func fetchRequest<T>() -> NSFetchRequest<T> {\n        return NSFetchRequest<T>(entityName: FrequencyConstraintData.frequencyConstraintDataEntity)\n    }\n\n    /// The constraint identifier.\n    @NSManaged var identifier: String\n\n    /// The time range.\n    @NSManaged var range: TimeInterval\n\n    /// The number of allowed occurrences.\n    @NSManaged var count: UInt\n\n    /// The occurrences\n    @NSManaged var occurrence: Set<OccurrenceData>\n}\n\n@objc(UAOccurrenceData)\nfileprivate class OccurrenceData: NSManagedObject {\n\n    static let occurrenceDataEntity = \"UAOccurrenceData\"\n\n    @nonobjc class func fetchRequest<T>() -> NSFetchRequest<T> {\n        return NSFetchRequest<T>(entityName: OccurrenceData.occurrenceDataEntity)\n    }\n\n    /// The timestamp\n    @NSManaged var timestamp: Date\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Limits/Occurrence.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct Occurrence: Sendable, Equatable, Hashable {\n    let constraintID: String\n    let timestamp: Date\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/RemoteData/AutomationRemoteDataAccess.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Remote data access for automation\nprotocol AutomationRemoteDataAccessProtocol: Sendable {\n    var publisher: AnyPublisher<InAppRemoteData, Never> { get }\n    func isCurrent(schedule: AutomationSchedule) async -> Bool\n    func requiresUpdate(schedule: AutomationSchedule) async -> Bool\n    func waitFullRefresh(schedule: AutomationSchedule) async\n    func bestEffortRefresh(schedule: AutomationSchedule) async -> Bool\n    func notifyOutdated(schedule: AutomationSchedule) async\n    func contactID(forSchedule schedule: AutomationSchedule) -> String?\n    func source(forSchedule schedule: AutomationSchedule) -> RemoteDataSource?\n}\n\nfinal class AutomationRemoteDataAccess: AutomationRemoteDataAccessProtocol {\n    private let remoteData: any RemoteDataProtocol\n    private let network: any AirshipNetworkCheckerProtocol\n\n    private static let remoteDataTypes = [\"in_app_messages\"]\n\n    init(\n        remoteData: any RemoteDataProtocol,\n        network: any AirshipNetworkCheckerProtocol = AirshipNetworkChecker()\n    ) {\n        self.remoteData = remoteData\n        self.network = network\n    }\n\n    var publisher: AnyPublisher<InAppRemoteData, Never> {\n        return remoteData.publisher(types: Self.remoteDataTypes)\n            .map { payloads in\n                InAppRemoteData.fromPayloads(payloads)\n            }\n            .eraseToAnyPublisher()\n    }\n\n    func isCurrent(schedule: AutomationSchedule) async -> Bool {\n        guard isRemoteSchedule(schedule) else {\n            return true\n        }\n        \n        guard let remoteDataInfo = remoteDataInfo(forSchedule: schedule) else {\n            return false\n        }\n\n        return await self.remoteData.isCurrent(remoteDataInfo: remoteDataInfo)\n    }\n\n    func requiresUpdate(schedule: AutomationSchedule) async -> Bool {\n        guard isRemoteSchedule(schedule) else {\n            return false\n        }\n\n        guard \n            let remoteDataInfo = remoteDataInfo(forSchedule: schedule),\n            await self.remoteData.isCurrent(remoteDataInfo: remoteDataInfo)\n        else {\n            return true\n        }\n\n        let source = remoteDataInfo.source\n        switch(await remoteData.status(source: source)) {\n        case .outOfDate:\n            return true\n        case .stale:\n            return false\n        case .upToDate:\n            return false\n#if canImport(AirshipCore)\n        @unknown default:\n            return false\n#endif\n        }\n    }\n\n    func waitFullRefresh(schedule: AutomationSchedule) async {\n        guard isRemoteSchedule(schedule) else {\n            return\n        }\n\n        let source = remoteDataInfo(forSchedule: schedule)?.source ?? .app\n        await self.remoteData.waitRefresh(source: source)\n    }\n\n    func bestEffortRefresh(schedule: AutomationSchedule) async -> Bool {\n        guard isRemoteSchedule(schedule) else {\n            return true\n        }\n\n        guard \n            let remoteDataInfo = remoteDataInfo(forSchedule: schedule),\n            await remoteData.isCurrent(remoteDataInfo: remoteDataInfo)\n        else {\n            return false\n        }\n\n        let source = remoteDataInfo.source\n        if await self.remoteData.status(source: source) == .upToDate {\n            return true\n        }\n\n        // if we are connected wait for refresh attempt\n        if (await network.isConnected) {\n            await remoteData.waitRefreshAttempt(source: source)\n        }\n\n        return await remoteData.isCurrent(remoteDataInfo: remoteDataInfo)\n    }\n\n    func notifyOutdated(schedule: AutomationSchedule) async {\n        if let remoteDataInfo = remoteDataInfo(forSchedule: schedule) {\n            await self.remoteData.notifyOutdated(remoteDataInfo: remoteDataInfo)\n        }\n    }\n\n    func contactID(forSchedule schedule: AutomationSchedule) -> String? {\n        return remoteDataInfo(forSchedule: schedule)?.contactID\n    }\n\n    func source(forSchedule schedule: AutomationSchedule) -> RemoteDataSource? {\n        guard self.isRemoteSchedule(schedule) else {\n            return nil\n        }\n        return remoteDataInfo(forSchedule: schedule)?.source ?? .app\n    }\n\n    private func isRemoteSchedule(_ schedule: AutomationSchedule) -> Bool {\n        if case .object(let map) = schedule.metadata {\n            if map[InAppRemoteData.remoteInfoMetadataKey] != nil {\n                return true\n            }\n\n            if map[InAppRemoteData.legacyRemoteInfoMetadataKey] != nil {\n                return true\n            }\n        }\n\n        // legacy way\n        if case .inAppMessage(let message) = schedule.data {\n            return message.source == .remoteData\n        }\n\n        return false\n    }\n\n    private func remoteDataInfo(forSchedule schedule: AutomationSchedule) -> RemoteDataInfo? {\n        guard case .object(let map) = schedule.metadata else {\n            return nil\n        }\n\n        guard let remoteInfoJson = map[InAppRemoteData.remoteInfoMetadataKey] else {\n            return nil\n        }\n\n\n        do {\n            if let json = remoteInfoJson.string {\n                // 17.x and older\n                let object = try AirshipJSON.from(json: json)\n                return try object.decode()\n            } else {\n                return try remoteInfoJson.decode()\n            }\n        } catch {\n            AirshipLogger.trace(\"Failed to parse remote info from schedule \\(schedule) \\(error)\")\n        }\n\n        return nil\n    }\n}\n\nstruct InAppRemoteData: Sendable {\n    static let legacyRemoteInfoMetadataKey: String = \"com.urbanairship.iaa.REMOTE_DATA_METADATA\"\n    static let remoteInfoMetadataKey: String = \"com.urbanairship.iaa.REMOTE_DATA_INFO\";\n\n    struct Data: Decodable, Equatable {\n        var schedules: [AutomationSchedule]\n        var constraints: [FrequencyConstraint]?\n        var failedSchedules: [FailedScheduleRecord]\n\n        enum CodingKeys: String, CodingKey {\n            case schedules = \"in_app_messages\"\n            case constraints = \"frequency_constraints\"\n        }\n        \n        init(\n            schedules: [AutomationSchedule],\n            constraints: [FrequencyConstraint]?,\n            failedSchedules: [FailedScheduleRecord] = []\n        ) {\n            self.schedules = schedules\n            self.constraints = constraints\n            self.failedSchedules = failedSchedules\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            \n            var schedules: [AutomationSchedule] = []\n            var failedScheduleRecords: [FailedScheduleRecord] = []\n            \n            let decodedSchedules = try container\n                .decode([ScheduleDecodeResult].self, forKey: .schedules)\n            \n            for parsed in decodedSchedules {\n                switch parsed {\n                case .succeed(let result):\n                    schedules.append(result)\n                case .failed(let schedule, let error):\n                    AirshipLogger.error(\"Failed to parse schedule \\(error)\")\n                    if let schedule = schedule {\n                        failedScheduleRecords.append(\n                            FailedScheduleRecord(\n                                identifier: schedule.identifier,\n                                createdDate: schedule.created ?? Date(),\n                                minSDKVersion: schedule.minSDKVersion\n                            )\n                        )\n                    }\n                }\n            }\n            \n            self.schedules = schedules\n            self.failedSchedules = failedScheduleRecords\n            self.constraints = try container.decodeIfPresent([FrequencyConstraint].self, forKey: .constraints)\n        }\n    }\n\n    struct Payload {\n        var data: Data\n        var timestamp: Date\n        var remoteDataInfo: RemoteDataInfo?\n    }\n\n    var payloads: [RemoteDataSource: Payload]\n    \n    /// Returns all schedule record that failed to parse across all payloads with their failure timestamps\n    var failedSchedules: [FailedScheduleRecord] {\n        payloads.values.flatMap { payload in\n            payload.data.failedSchedules\n        }\n    }\n\n    static func parsePayload(_ payload: RemoteDataPayload?) -> Payload? {\n        guard let payload = payload else { return nil }\n        do {\n            let metadata = try AirshipJSON.wrap(\n                [\n                    legacyRemoteInfoMetadataKey: \"\",\n                    remoteInfoMetadataKey: payload.remoteDataInfo\n                ] as [String: AnyHashable]\n            )\n\n            var data: Data = try payload.data.decode()\n            data.schedules.indices.forEach { i in\n                data.schedules[i].metadata = metadata\n\n                if case .inAppMessage(var message) = data.schedules[i].data {\n                    message.source = .remoteData\n                    data.schedules[i].data = .inAppMessage(message)\n                }\n\n                data.schedules[i].triggers.indices.forEach { j in\n                    let trigger = data.schedules[i].triggers[j]\n                    if (trigger.shouldBackFillIdentifier) {\n                        data.schedules[i].triggers[j] = trigger.backfilledIdentifier(executionType: .execution)\n                    }\n                }\n\n                if var delay = data.schedules[i].delay, var triggers = delay.cancellationTriggers {\n                    triggers.indices.forEach { j in\n                        let trigger = triggers[j]\n                        if (trigger.shouldBackFillIdentifier) {\n                            triggers[j] = trigger.backfilledIdentifier(executionType: .delayCancellation)\n                        }\n                    }\n                    delay.cancellationTriggers = triggers\n                    data.schedules[i].delay = delay\n                }\n            }\n\n            return Payload(\n                data: data,\n                timestamp:payload.timestamp,\n                remoteDataInfo: payload.remoteDataInfo\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to parse app remote-data response. \\(error)\")\n        }\n\n        return nil\n    }\n\n    static func fromPayloads(_ payloads: [RemoteDataPayload]) -> InAppRemoteData {\n        var parsed: [RemoteDataSource: Payload] = [:]\n        payloads.forEach { payload in\n            parsed[payload.remoteDataInfo?.source ?? .app] = parsePayload(payload)\n        }\n        return InAppRemoteData(payloads: parsed)\n    }\n}\n\n/// A struct to extract just the ID, created date, and min SDK version from a schedule when full parsing fails\nfileprivate struct PartialSchedule: Decodable {\n    let identifier: String\n    let created: Date?\n    let minSDKVersion: String?\n    \n    enum CodingKeys: String, CodingKey {\n        case identifier = \"id\"\n        case created\n        case minSDKVersion = \"min_sdk_version\"\n    }\n    \n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.identifier = try container.decode(String.self, forKey: .identifier)\n        self.minSDKVersion = try container.decodeIfPresent(String.self, forKey: .minSDKVersion)\n        \n        if let createdString = try? container.decodeIfPresent(String.self, forKey: .created) {\n            self.created = AirshipDateFormatter.date(fromISOString: createdString)\n        } else {\n            self.created = nil\n        }\n    }\n}\n\n\nfileprivate enum ScheduleDecodeResult: Decodable {\n\n    case succeed(AutomationSchedule)\n    case failed(PartialSchedule?, any Error)\n\n    init(from decoder: any Decoder) throws {\n        do {\n            let schedule = try AutomationSchedule(from: decoder)\n            self = .succeed(schedule)\n        } catch {\n            // Try to at least extract the ID and created date for tracking purposes\n            if let container = try? decoder.singleValueContainer(),\n               let partial = try? container.decode(PartialSchedule.self) {\n                self = .failed(partial, error)\n            } else {\n                self = .failed(nil, error)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/RemoteData/AutomationRemoteDataSubscriber.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@preconcurrency\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Protocol for subscribing to remote data updates and syncing automation schedules.\nprotocol AutomationRemoteDataSubscriberProtocol: Sendable {\n    /// Starts listening for remote data updates and processing automation schedules.\n    @MainActor\n    func subscribe()\n\n    /// Stops listening for remote data updates and cancels any in-flight processing.\n    @MainActor\n    func unsubscribe()\n}\n\n/// Subscribes to in-app remote data, applies frequency constraints, and syncs schedules with the automation engine.\nfinal class AutomationRemoteDataSubscriber: AutomationRemoteDataSubscriberProtocol, Sendable {\n    private let sourceInfoStore: AutomationSourceInfoStore\n    private let remoteDataAccess: any AutomationRemoteDataAccessProtocol\n    private let engine: any AutomationEngineProtocol\n    private let frequencyLimitManager: any FrequencyLimitManagerProtocol\n    private let airshipSDKVersion: String\n\n    @MainActor\n    private var processTask: Task<Void, Never>?\n\n    private var updateStream: AsyncStream<InAppRemoteData> {\n        AsyncStream { continuation in\n            let cancellable = self.remoteDataAccess.publisher.sink { continuation.yield($0) }\n            continuation.onTermination = { _ in\n                cancellable.cancel()\n            }\n        }\n    }\n\n    /// Creates a remote data subscriber.\n    /// - Parameters:\n    ///   - dataStore: Store for preference and source info.\n    ///   - remoteDataAccess: Source of in-app remote data updates.\n    ///   - engine: Engine used to upsert and stop automation schedules.\n    ///   - frequencyLimitManager: Manager for frequency constraints from remote data.\n    ///   - airshipSDKVersion: SDK version used for source info (defaults to current).\n    init(\n        dataStore: PreferenceDataStore,\n        remoteDataAccess: any AutomationRemoteDataAccessProtocol,\n        engine: any AutomationEngineProtocol,\n        frequencyLimitManager: any FrequencyLimitManagerProtocol,\n        airshipSDKVersion: String = AirshipVersion.version\n    ) {\n        self.sourceInfoStore = AutomationSourceInfoStore(dataStore: dataStore)\n        self.remoteDataAccess = remoteDataAccess\n        self.engine = engine\n        self.frequencyLimitManager = frequencyLimitManager\n        self.airshipSDKVersion = airshipSDKVersion\n    }\n\n    /// Starts subscribing to remote data and processing automation updates.\n    @MainActor\n    func subscribe() {\n        if processTask != nil {\n            return\n        }\n\n        let stream = self.updateStream\n        self.processTask = Task { [weak self] in\n            for await update in stream {\n                guard !Task.isCancelled else {\n                    return\n                }\n\n                await self?.processConstraints(update)\n                await self?.processAutomations(update)\n            }\n        }\n    }\n\n    /// Stops the subscription and cancels any ongoing processing task.\n    @MainActor\n    func unsubscribe() {\n        processTask?.cancel()\n        processTask = nil\n    }\n\n    private func processAutomations(_ data: InAppRemoteData) async {\n        let currentSchedules: [AutomationSchedule]\n        do {\n            currentSchedules = try await engine.schedules\n        } catch {\n            AirshipLogger.error(\"Unable to process automations. Failed to query current schedules with error \\(error)\")\n            return\n        }\n\n        for source in RemoteDataSource.allCases {\n            let schedules = currentSchedules.filter { schedule in\n                self.remoteDataAccess.source(forSchedule: schedule) == source\n            }\n\n            do {\n                try await self.syncAutomations(\n                    payload: data.payloads[source],\n                    source: source,\n                    currentSchedules: schedules\n                )\n            } catch {\n                AirshipLogger.error(\"Failed to process \\(source) automations \\(error)\")\n            }\n        }\n    }\n\n    private func syncAutomations(\n        payload: InAppRemoteData.Payload?,\n        source: RemoteDataSource,\n        currentSchedules: [AutomationSchedule]\n    ) async throws {\n\n        let currentScheduleIDs = Set(currentSchedules.map { $0.identifier })\n\n        guard let payload = payload else {\n            if !currentSchedules.isEmpty {\n                try await engine.stopSchedules(\n                    identifiers: Array(currentScheduleIDs)\n                )\n            }\n            return\n        }\n\n        let contactID = payload.remoteDataInfo?.contactID\n        let lastSourceInfo = self.sourceInfoStore.getSourceInfo(\n            source: source,\n            contactID: contactID\n        )\n\n        let failureResolution = resolveFailedSchedules(\n            lastSourceInfo: lastSourceInfo,\n            currentFailures: payload.data.failedSchedules\n        )\n\n        let currentSourceInfo = AutomationSourceInfo(\n            remoteDataInfo: payload.remoteDataInfo,\n            payloadTimestamp: payload.timestamp,\n            airshipSDKVersion: airshipSDKVersion,\n            failedSchedules: failureResolution.tracked\n        )\n\n        guard lastSourceInfo != currentSourceInfo else {\n            return\n        }\n\n        let payloadScheduleIDs = Set(payload.data.schedules.map { $0.identifier })\n\n        let schedulesToStop = currentSchedules.filter { !payloadScheduleIDs.contains($0.identifier) }\n        if !schedulesToStop.isEmpty {\n            try await engine.stopSchedules(\n                identifiers: schedulesToStop.map { $0.identifier }\n            )\n        }\n\n        let schedulesToUpsert = payload.data.schedules.filter { schedule in\n            if currentScheduleIDs.contains(schedule.identifier) {\n                return true\n            }\n\n            if failureResolution.recovered.contains(schedule.identifier) {\n                return true\n            }\n\n            return AutomationSchedule.isNewSchedule(\n                created: schedule.created,\n                minSDKVersion: schedule.minSDKVersion,\n                sinceDate: lastSourceInfo?.payloadTimestamp ?? .distantPast,\n                lastSDKVersion: lastSourceInfo?.airshipSDKVersion\n            )\n        }\n\n        if !schedulesToUpsert.isEmpty {\n            try await engine.upsertSchedules(schedulesToUpsert)\n        }\n\n        self.sourceInfoStore.setSourceInfo(\n            currentSourceInfo,\n            source: source,\n            contactID: contactID\n        )\n    }\n\n    private func resolveFailedSchedules(\n        lastSourceInfo: AutomationSourceInfo?,\n        currentFailures: [FailedScheduleRecord]\n    ) -> (tracked: [FailedScheduleRecord], recovered: Set<String>) {\n        let previouslyFailed = Set(lastSourceInfo?.failedSchedules?.map { $0.identifier } ?? [])\n        let currentlyFailed = Set(currentFailures.map { $0.identifier })\n        let recovered = previouslyFailed.subtracting(currentlyFailed)\n\n        let stillFailing = lastSourceInfo?.failedSchedules?.filter {\n            currentlyFailed.contains($0.identifier)\n        } ?? []\n\n        let stillFailingIDs = Set(stillFailing.map { $0.identifier })\n        let lastPayloadTimestamp = lastSourceInfo?.payloadTimestamp ?? .distantPast\n\n        let newlyFailed = currentFailures\n            .filter { !stillFailingIDs.contains($0.identifier) }\n            .filter {\n                AutomationSchedule.isNewSchedule(\n                    created: $0.createdDate,\n                    minSDKVersion: $0.minSDKVersion,\n                    sinceDate: lastPayloadTimestamp,\n                    lastSDKVersion: lastSourceInfo?.airshipSDKVersion\n                )\n            }\n\n        return (tracked: stillFailing + newlyFailed, recovered: recovered)\n    }\n\n    private func processConstraints(_ data: InAppRemoteData) async {\n        let constraints = RemoteDataSource.allCases\n            .compactMap { source in\n                data.payloads[source]?.data.constraints\n            }\n            .reduce([], +)\n\n        do {\n            try await frequencyLimitManager.setConstraints(constraints)\n        } catch {\n            AirshipLogger.error(\"Failed to process constraints \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/RemoteData/AutomationSourceInfoStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Stores information about a remote-data source used for scheduling\nfinal class AutomationSourceInfoStore: Sendable {\n    let dataStore: PreferenceDataStore\n\n    init(dataStore: PreferenceDataStore) {\n        self.dataStore = dataStore\n    }\n\n    private static let sourceInfoKeyPrefix: String = \"AutomationSourceInfo\"\n\n    func getSourceInfo(source: RemoteDataSource, contactID: String?) -> AutomationSourceInfo? {\n        let key = makeInfoKey(source: source, contactID: contactID)\n\n        if let info: AutomationSourceInfo = self.dataStore.safeCodable(forKey: key) {\n            return info\n        }\n\n        return self.recoverSourceInfo(source: source, contactID: contactID)\n    }\n\n    func setSourceInfo(\n        _ sourceInfo: AutomationSourceInfo,\n        source: RemoteDataSource,\n        contactID: String?\n    )  {\n        let key = makeInfoKey(source: source, contactID: contactID)\n        self.dataStore.setSafeCodable(sourceInfo, forKey: key)\n    }\n\n    private func makeInfoKey(source: RemoteDataSource, contactID: String?) -> String {\n        return if source == .contact {\n            \"\\(Self.sourceInfoKeyPrefix).\\(source).\\(contactID ?? \"\")\"\n        } else {\n            \"\\(Self.sourceInfoKeyPrefix).\\(source)\"\n        }\n    }\n\n    private func recoverSourceInfo(source: RemoteDataSource, contactID: String?) -> AutomationSourceInfo? {\n        let key = makeInfoKey(source: source, contactID: contactID)\n\n        switch (source) {\n        case .app:\n            let lastSDKVersion = self.dataStore.string(forKey: LegacyAppKeys.lastSDKVersion)\n            let lastPayloadTimestamp = self.dataStore.object(forKey: LegacyAppKeys.lastPayloadTimestamp)\n\n            defer {\n                self.dataStore.removeObject(forKey: LegacyAppKeys.lastMetadata)\n                self.dataStore.removeObject(forKey: LegacyAppKeys.lastPayloadTimestamp)\n                self.dataStore.removeObject(forKey: LegacyAppKeys.lastRemoteDataInfo)\n                self.dataStore.removeObject(forKey: LegacyAppKeys.lastSDKVersion)\n            }\n\n            guard let lastPayloadTimestamp = lastPayloadTimestamp as? Date else {\n                return nil\n            }\n\n            let sourceInfo = AutomationSourceInfo(\n                remoteDataInfo: nil,\n                payloadTimestamp: lastPayloadTimestamp,\n                airshipSDKVersion: lastSDKVersion\n            )\n            self.dataStore.setSafeCodable(sourceInfo, forKey: key)\n            return sourceInfo\n\n        case .contact:\n            let lastSDKVersion = self.dataStore.string(forKey: LegacyContactKeys.lastSDKVersion(contactID))\n            let lastPayloadTimestamp = self.dataStore.object(forKey: LegacyContactKeys.lastPayloadTimestamp(contactID))\n\n            defer {\n                self.dataStore.removeObject(forKey: LegacyContactKeys.lastPayloadTimestamp(contactID))\n                self.dataStore.removeObject(forKey: LegacyContactKeys.lastRemoteDataInfo(contactID))\n                self.dataStore.removeObject(forKey: LegacyContactKeys.lastSDKVersion(contactID))\n            }\n\n            guard let lastPayloadTimestamp = lastPayloadTimestamp as? Date else {\n                return nil\n            }\n\n            let sourceInfo = AutomationSourceInfo(\n                remoteDataInfo: nil,\n                payloadTimestamp: lastPayloadTimestamp,\n                airshipSDKVersion: lastSDKVersion\n            )\n            self.dataStore.setSafeCodable(sourceInfo, forKey: key)\n            return sourceInfo\n#if canImport(AirshipCore)\n        @unknown default:\n            return nil\n#endif\n        }\n    }\n}\n\n/// Represents a schedule that failed to parse, with enough info to evaluate newness on retry.\nstruct FailedScheduleRecord: Sendable, Codable, Equatable {\n    let identifier: String\n    let createdDate: Date\n    let minSDKVersion: String?\n}\n\nstruct AutomationSourceInfo: Sendable, Codable, Equatable {\n    let remoteDataInfo: RemoteDataInfo?\n    let payloadTimestamp: Date\n    let airshipSDKVersion: String?\n    \n    /// Schedules that failed to parse, carried forward across syncs until\n    /// they either parse successfully or are removed from remote data.\n    var failedSchedules: [FailedScheduleRecord]?\n}\n\nfileprivate struct LegacyAppKeys {\n    static let lastPayloadTimestamp = \"UAInAppRemoteDataClient.LastPayloadTimeStamp\"\n    static let lastSDKVersion = \"UAInAppRemoteDataClient.LastSDKVersion\"\n    static let lastRemoteDataInfo = \"UAInAppRemoteDataClient.LastRemoteDataInfo\"\n    static let lastMetadata = \"UAInAppRemoteDataClient.LastPayloadMetadata\"\n}\n\nfileprivate struct LegacyContactKeys {\n    private static let lastPayloadTimestampPrefix = \"UAInAppRemoteDataClient.LastPayloadTimeStamp.Contact\"\n    private static let lastSDKVersionPrefix = \"UAInAppRemoteDataClient.LastSDKVersion.Contact\"\n    private static let lastRemoteDataInfoPrefix = \"UAInAppRemoteDataClient.LastRemoteDataInfo.Contact\"\n\n    static func lastPayloadTimestamp(_ contactID: String?) -> String {\n        return \"\\(lastPayloadTimestampPrefix)\\(contactID ?? \"\")\"\n    }\n\n    static func lastSDKVersion(_ contactID: String?) -> String {\n        return \"\\(lastSDKVersionPrefix)\\(contactID ?? \"\")\"\n    }\n\n    static func lastRemoteDataInfo(_ contactID: String?) -> String {\n        return \"\\(lastRemoteDataInfoPrefix)\\(contactID ?? \"\")\"\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/RemoteData/DeferredScheduleResult.swift",
    "content": "import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct DeferredScheduleResult: Sendable, Codable, Equatable {\n    var isAudienceMatch: Bool\n    var message: InAppMessage?\n    var actions: AirshipJSON?\n\n    enum CodingKeys: String, CodingKey {\n        case isAudienceMatch = \"audience_match\"\n        case message\n        case actions\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Utils/ActiveTimer.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nfinal class ActiveTimer: AirshipTimerProtocol {\n\n    private var isStarted: Bool = false\n    private var isActive: Bool\n    private var elapsedTime: TimeInterval = 0\n    private var startDate: Date? = nil\n    private let notificationCenter: AirshipNotificationCenter\n    private let dateFetcher: any AirshipDateProtocol\n    \n    init(\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) { \n        \n        self.notificationCenter = notificationCenter\n        self.dateFetcher = date\n        \n        let stateTracker = appStateTracker ?? AppStateTracker.shared\n        self.isActive = stateTracker.state == .active\n        \n        notificationCenter.addObserver(self, selector: #selector(onApplicationBecomeActive),\n                                       name: AppStateTracker.didBecomeActiveNotification)\n        \n        notificationCenter.addObserver(self, selector: #selector(onApplicationWillResignActive),\n                                       name: AppStateTracker.willResignActiveNotification)\n    }\n    \n    deinit {\n        self.notificationCenter.removeObserver(self)\n    }\n    \n    func start() {\n        guard !self.isStarted else { return }\n        \n        if self.isActive {\n            self.startDate = dateFetcher.now\n        }\n        \n        self.isStarted = true\n    }\n    \n    func stop() {\n        guard self.isStarted else { return }\n        \n        self.elapsedTime += currentSessionTime()\n        self.startDate = nil\n        \n        self.isStarted = false\n    }\n    \n    private func currentSessionTime() -> TimeInterval {\n        guard let date = self.startDate else { return 0 }\n        return self.dateFetcher.now.timeIntervalSince(date)\n    }\n    \n    @objc\n    private func onApplicationBecomeActive() {\n        self.isActive = true\n        if self.isStarted, self.startDate == nil {\n            self.startDate = dateFetcher.now\n        }\n    }\n    \n    @objc\n    private func onApplicationWillResignActive() {\n        self.isActive = false\n        stop()\n    }\n    \n    var time: TimeInterval {\n        return self.elapsedTime + currentSessionTime()\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Utils/AirshipAsyncSemaphore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nactor AirshipAsyncSemaphore {\n    private var value: Int\n    private var waiters: [CheckedContinuation<Void, Never>] = []\n\n    init(value: Int) {\n        self.value = value\n    }\n\n    func withPermit<T: Sendable>(block: @Sendable () async throws -> T) async throws -> T {\n        await self.wait()\n        defer {\n            signal()\n        }\n        return try await block()\n    }\n\n    private func wait() async {\n        if value > 0 {\n            value -= 1\n            return\n        }\n\n        await withCheckedContinuation { cont in\n            waiters.append(cont)\n        }\n    }\n\n    private func signal() {\n        if let first = waiters.first {\n            waiters.removeFirst()\n            first.resume()\n        } else {\n            value += 1\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Utils/AutomationActionRunner.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\n/// Action runner\nprotocol AutomationActionRunnerProtocol: Sendable {\n    func runActions(_ actions: AirshipJSON, situation: ActionSituation, metadata: [String: any Sendable]) async\n}\n\n/// Default action runner\nstruct AutomationActionRunner: AutomationActionRunnerProtocol {\n    func runActions(_ actions: AirshipJSON, situation: ActionSituation, metadata: [String: any Sendable]) async {\n        await ActionRunner.run(actionsPayload: actions, situation: situation, metadata: metadata)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Utils/RetryingQueue.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\n/// A concurrent queue that automatically retries an operation with a backoff.\n///\n/// This queue manages a set of operations, ensuring that they are executed\n/// according to priority and concurrency limits. The queue's primary feature is its\n/// strict priority model: a higher-priority task will always be processed before\n/// a lower-priority task, even if that means a completed low-priority task must\n/// wait to return its result.\n///\n/// Operations can be retried with an exponential backoff. Concurrency is limited\n/// by `maxConcurrentOperations`, and backpressure is applied via `maxPendingResults`\n/// to prevent too many completed operations from awaiting their turn to return.\n///\n/// NOTE: For internal use only. :nodoc:\nactor RetryingQueue<T: Sendable> {\n\n    /// Work state that persists across retries for a single operation.\n    actor State {\n        private var state: [String: any Sendable] = [:]\n\n        /// Sets a value for a given key, allowing state to be preserved across retries.\n        /// - Parameters:\n        ///     - value: The value to set.\n        ///     - key: The key.\n        func setValue(_ value: (any Sendable)?, key: String) {\n            self.state[key] = value\n        }\n\n        /// Gets the state for the given key.\n        /// - Parameters:\n        ///     - key: The key.\n        /// - Returns: The value if it exists, cast to the expected type.\n        func value<R: Sendable>(key: String) -> R? {\n            return self.state[key] as? R\n        }\n    }\n\n    /// The result of an operation block.\n    enum Result: Sendable {\n        /// A successful result.\n        /// - Parameters:\n        ///     - result: The value to return from the `run` method.\n        ///     - ignoreReturnOrder: If `true`, the result is returned immediately. If `false`,\n        ///     it waits for its turn based on priority.\n        case success(result: T, ignoreReturnOrder: Bool = false)\n\n        /// Indicates the operation should be retried after a specific delay.\n        /// The next retry will use an exponential backoff based on this value.\n        /// - Parameters:\n        ///     - retryAfter: The minimum amount of time to wait before retrying.\n        case retryAfter(TimeInterval)\n\n        /// Indicates the operation should be retried using the default exponential backoff.\n        case retry\n    }\n\n    /// The internal status of a queued operation.\n    private enum Status: Equatable {\n        /// The operation is in the queue, waiting for its turn to run.\n        case pendingRun\n        /// The operation is currently executing.\n        case running\n        /// The operation has finished and is waiting for its turn to return the result.\n        case pendingReturn\n        /// The operation failed and is waiting for the retry delay to pass.\n        case retrying\n    }\n\n    /// Internal state for tracking each operation.\n    private struct OperationState {\n        /// The priority of the operation. Lower numbers are higher priority.\n        let priority: Int\n        /// A unique ID for the operation.\n        var id: UInt\n        /// The current status of the operation.\n        var status: Status = .pendingRun\n        /// A continuation used to suspend and resume the operation's task.\n        private var continuation: CheckedContinuation<Void, Never>? = nil\n\n        init(id: UInt, priority: Int) {\n            self.id = id\n            self.priority = priority\n        }\n\n        /// Resumes the operation's suspended task.\n        mutating func continueWork() {\n            self.continuation?.resume()\n            self.continuation = nil\n        }\n\n        /// Sets the continuation to allow the operation's task to be suspended.\n        mutating func setContinuation(\n            _ continuation: CheckedContinuation<Void, Never>\n        ) {\n            self.continuation = continuation\n        }\n    }\n\n    /// Max number of operations to run simultaneously.\n    private var maxConcurrentOperations: UInt\n\n    /// The target maximum number of completed results waiting to be returned. This provides\n    /// backpressure against the queue. This limit may be bypassed to allow a\n    /// high-priority task to run, preventing a potential deadlock.\n    private var maxPendingResults: UInt\n\n    /// The initial delay for exponential backoff retries.\n    private var initialBackOff: TimeInterval\n\n    /// The maximum delay for exponential backoff retries.\n    private var maxBackOff: TimeInterval\n\n    /// A dictionary holding the state for all current operations.\n    private var operationState: [UInt: OperationState] = [:]\n\n    /// A counter to generate unique operation IDs.\n    private var nextID: UInt = 1\n\n    private let taskSleeper: any AirshipTaskSleeper\n\n    /// Queue id for logging\n    private let id: String\n\n    init(\n        id: String,\n        config: RemoteConfig.RetryingQueueConfig? = nil,\n        taskSleeper: any AirshipTaskSleeper = .shared\n    ) {\n        self.id = id\n        self.maxConcurrentOperations = config?.maxConcurrentOperations ?? 3\n        self.maxPendingResults = config?.maxPendingResults ?? 2\n        self.initialBackOff = config?.initialBackoff ?? 15\n        self.maxBackOff = config?.maxBackOff ?? 60\n        self.taskSleeper = taskSleeper\n    }\n\n    init(\n        id: String,\n        maxConcurrentOperations: UInt = 3,\n        maxPendingResults: UInt = 2,\n        initialBackOff: TimeInterval = 15,\n        maxBackOff: TimeInterval = 60,\n        taskSleeper: any AirshipTaskSleeper = .shared\n    ) {\n        self.id = id\n        self.maxConcurrentOperations = max(1, maxConcurrentOperations)\n        self.maxPendingResults = max(1, maxPendingResults)\n        self.initialBackOff = max(1, initialBackOff)\n        self.maxBackOff = max(initialBackOff, maxBackOff)\n        self.taskSleeper = taskSleeper\n    }\n\n    /// Adds and runs an operation on the queue.\n    ///\n    /// This method returns only when the operation completes successfully. If the\n    /// operation fails, it will be automatically retried according to the backoff configuration.\n    /// The `async` task calling this method will be suspended until the operation can\n    /// start and will remain suspended until it can return its final result.\n    ///\n    /// - Parameters:\n    ///     - name: The name of the operation, used for logging.\n    ///     - priority: The priority of the operation. Lower numbers are higher priority.\n    ///     - operation: The operation block to execute. It receives a `State` object\n    ///     to persist data across retries and must return a `Result`.\n    /// - Returns: The successful result value of the operation.\n    func run(\n        name: String,\n        priority: Int = 0,\n        operation: @escaping @Sendable (State) async throws -> Result\n    ) async  -> T {\n        let state = State()\n        var nextBackOff = initialBackOff\n\n        let operationID = addOperation(priority: priority)\n        AirshipLogger.trace(\"Queue \\(self.id) added: \\(name) (priority: \\(priority), id: \\(operationID))\")\n\n        while(true) {\n            AirshipLogger.trace(\"Queue \\(self.id) waiting to start: \\(name) (id: \\(operationID))\")\n            await waitForStart(operationID: operationID)\n\n            AirshipLogger.trace(\"Queue \\(self.id) starting task for: \\(name) (id: \\(operationID))\")\n            let task: Task<Result, any Error> = Task {\n                AirshipLogger.trace(\"Queue \\(self.id) running operation for: \\(name) (id: \\(operationID))\")\n                return try await operation(state)\n            }\n\n            var result: Result\n            do {\n                result = try await task.value\n                AirshipLogger.trace(\"Queue \\(self.id) task finished for: \\(name) (id: \\(operationID))\")\n            } catch {\n                AirshipLogger.trace(\"Queue \\(self.id) task failed for: \\(name) (id: \\(operationID)). Error: \\(error). Will retry.\")\n                result = .retry\n            }\n\n            switch(result) {\n            case .success(let result, let ignoreReturnOrder):\n                AirshipLogger.trace(\"Queue \\(self.id) waiting to return success for: \\(name) (id: \\(operationID)). Ignore order: \\(ignoreReturnOrder)\")\n                await waitForReturn(operationID: operationID, ignoreReturnOrder: ignoreReturnOrder)\n                AirshipLogger.trace(\"Queue \\(self.id) returning success for: \\(name) (id: \\(operationID))\")\n                return result\n\n            case .retryAfter(let retryAfter):\n                AirshipLogger.trace(\"Queue \\(self.id) will retry after delay: \\(name) (id: \\(operationID)), delay: \\(retryAfter)s\")\n                await waitForRetry(operationID: operationID, retryAfter: retryAfter)\n                AirshipLogger.trace(\"Queue \\(self.id) resuming after wait for: \\(name) (id: \\(operationID))\")\n                nextBackOff = min(maxBackOff, max(initialBackOff, retryAfter * 2))\n\n            case .retry:\n                AirshipLogger.trace(\"Queue \\(self.id) will retry with backoff: \\(name) (id: \\(operationID)), backoff: \\(nextBackOff)s\")\n                await waitForRetry(operationID: operationID, retryAfter: nextBackOff)\n                AirshipLogger.trace(\"Queue \\(self.id) resuming after wait for: \\(name) (id: \\(operationID))\")\n                nextBackOff = min(maxBackOff, nextBackOff * 2)\n            }\n        }\n    }\n\n    /// Adds a new operation to the queue and returns its ID.\n    private func addOperation(priority: Int) -> UInt {\n        let state = OperationState(id: nextID, priority: priority)\n        nextID += 1\n        self.operationState[state.id] = state\n        return state.id\n    }\n\n    /// Handles the retry logic for a failed operation.\n    /// The operation is moved to the `.retrying` state, other tasks are unblocked,\n    /// and this task sleeps for the specified interval before re-queuing itself.\n    private func waitForRetry(operationID: UInt, retryAfter: TimeInterval) async {\n        self.operationState[operationID]?.status = .retrying\n\n        // Unblock the next operation in the return queue, since this one is no longer returning.\n        if let next = nextReturnID() {\n            self.operationState[next]?.continueWork()\n        }\n\n        // Unblock the next operation waiting to start, since this one is no longer running.\n        if let next = nextPendingOperationID() {\n            self.operationState[next]?.continueWork()\n        }\n\n        try? await self.taskSleeper.sleep(timeInterval: retryAfter)\n\n        if (retryAfter <= 0) {\n            await Task.yield()\n        }\n\n        // Re-queue the operation to be run again.\n        self.operationState[operationID]?.status = .pendingRun\n    }\n\n    /// Suspends the current task until it is its turn to start running.\n    private func waitForStart(operationID: UInt) async  {\n        if (self.nextPendingOperationID() != operationID) {\n            await withCheckedContinuation { continuation in\n                self.setContinuation(continuation, operationID: operationID)\n            }\n        }\n        self.operationState[operationID]?.status = .running\n    }\n\n    /// Suspends the current task until it is its turn to return the result.\n    /// After returning, it cleans up and signals other waiting tasks.\n    private func waitForReturn(operationID: UInt, ignoreReturnOrder: Bool) async {\n        self.operationState[operationID]?.status = .pendingReturn\n\n        // Unblock the next pending operation, as this one is no longer running.\n        if let next = nextPendingOperationID() {\n            self.operationState[next]?.continueWork()\n        }\n\n        // If strict order is required, wait until this is the highest-priority finished task.\n        if (!ignoreReturnOrder && self.nextReturnID() != operationID) {\n            await withCheckedContinuation { continuation in\n                self.setContinuation(continuation, operationID: operationID)\n            }\n        }\n\n        // Operation is complete, remove its state.\n        self.operationState.removeValue(forKey: operationID)\n\n        // Unblock the next operation in the return queue.\n        if let next = nextReturnID() {\n            self.operationState[next]?.continueWork()\n        }\n\n        // Unblock the next pending operation, as the pending results count has decreased.\n        if let next = nextPendingOperationID() {\n            self.operationState[next]?.continueWork()\n        }\n    }\n\n    /// Determines the ID of the next operation that should be started.\n    ///\n    /// This function contains the core scheduling and deadlock-prevention logic.\n    /// - Returns: The ID of the next operation to run, or `nil` if no operation can be started.\n    private func nextPendingOperationID() -> UInt? {\n        let running = self.operationState.values.filter { $0.status == .running }\n        if (running.count >= self.maxConcurrentOperations) {\n            return nil\n        }\n\n        // DEADLOCK PREVENTION: If the highest-priority item in the entire queue is\n        // waiting to run, it might be blocked by a lower-priority item that is\n        // `.pendingReturn`, which in turn is waiting for the high-priority item.\n        // To break this circular dependency, we allow the high-priority item to\n        // bypass the `maxPendingResults` check and run immediately.\n        if let id = nextReturnID(), self.operationState[id]?.status == .pendingRun {\n            return id\n        }\n\n        // BACKPRESSURE: If the deadlock condition isn't met, enforce the limit\n        // on the number of completed operations waiting to return.\n        let returning = self.operationState.values.filter { $0.status == .pendingReturn }\n        if (returning.count >= self.maxPendingResults) {\n            return nil\n        }\n\n        // STANDARD SELECTION: Return the highest-priority task that is pending to run.\n        return self.operationState.values\n            .filter { $0.status == .pendingRun }\n            .sorted { $0.priority < $1.priority }\n            .first?.id\n    }\n\n    /// Determines the ID of the operation that has the highest priority overall.\n    ///\n    /// This is used to enforce strict priority ordering. A lower-priority item that has\n    /// finished (`.pendingReturn`) will be forced to wait if a higher-priority item\n    /// exists, even if it's only just been added (`.pendingRun`).\n    ///\n    /// - Returns: The ID of the highest-priority active task.\n    private func nextReturnID() -> UInt? {\n        return self.operationState.values\n            .filter { $0.status != .retrying }\n            .sorted { $0.priority < $1.priority }\n            .first?.id\n    }\n\n    /// Associates a continuation with an operation, allowing its task to be suspended.\n    private func setContinuation(\n        _ continuation: CheckedContinuation<Void, Never>,\n        operationID: UInt\n    ) {\n        self.operationState[operationID]?.setContinuation(continuation)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Source/Utils/ScheduleConditionsChangedNotifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol ScheduleConditionsChangedNotifierProtocol: Sendable {\n    @MainActor\n    func notify()\n\n    @MainActor\n    func wait() async\n}\n\n\n/// NOTE: For internal use only. :nodoc:\n@MainActor\nfinal class ScheduleConditionsChangedNotifier: Sendable, ScheduleConditionsChangedNotifierProtocol {\n    private var waiting: [CheckedContinuation<Void, Never>] = []\n\n    @MainActor\n    func notify() {\n        waiting.forEach { continuation in\n            continuation.resume()\n        }\n        waiting.removeAll()\n    }\n\n    @MainActor\n    func wait() async {\n        return await withCheckedContinuation { continuation in\n            waiting.append(continuation)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Action Automation/ActionAutomationExecutorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nclass ActionAutomationExecutorTest: XCTestCase {\n\n    private let actionRunner: TestActionRunner = TestActionRunner()\n    private var executor: ActionAutomationExecutor!\n\n    private let preparedScheduleInfo = PreparedScheduleInfo(scheduleID: \"some id\", triggerSessionID: UUID().uuidString, priority: 0)\n    private let actions = try! AirshipJSON.wrap([\"some-action\": \"some-value\"])\n\n    override func setUp() {\n        self.executor = ActionAutomationExecutor(actionRunner: actionRunner)\n    }\n\n    func testExecute() async throws {\n        let result = await self.executor.execute(data: actions, preparedScheduleInfo: preparedScheduleInfo)\n\n        XCTAssertEqual(self.actionRunner.actions, actions)\n        XCTAssertEqual(self.actionRunner.situation, .automation)\n        XCTAssertTrue(self.actionRunner.metadata!.isEmpty)\n        XCTAssertEqual(result, .finished)\n    }\n\n    func testIsReady() async throws {\n        let result = await self.executor.isReady(data: actions, preparedScheduleInfo: preparedScheduleInfo)\n        XCTAssertEqual(result, .ready)\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Action Automation/ActionAutomationPreparerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class ActionPreparerTest: XCTestCase {\n\n    private let preparer: ActionAutomationPreparer = ActionAutomationPreparer()\n    private let actions = try! AirshipJSON.wrap([\"some-action\": \"some-value\"])\n    private let preparedScheduleInfo = PreparedScheduleInfo(scheduleID: \"some id\", triggerSessionID: UUID().uuidString, priority: 0)\n\n    func testPrepare() async throws {\n        let result = try await self.preparer.prepare(data: actions, preparedScheduleInfo: preparedScheduleInfo)\n        XCTAssertEqual(actions, result)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Actions/CancelSchedulesActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class CancelSchedulesActionTest: XCTestCase {\n    \n    let automation = TestAutomationEngine()\n    var action: CancelSchedulesAction!\n    \n    override func setUp() async throws {\n        let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        let config = RuntimeConfig.testConfig()\n        \n        let inAppAutomation = await DefaultInAppAutomation(\n            engine: automation,\n            inAppMessaging: TestInAppMessaging(),\n            legacyInAppMessaging: TestLegacyInAppMessaging(),\n            remoteData: TestRemoteData(),\n            remoteDataSubscriber: TestRemoteDataSubscriber(),\n            dataStore: dataStore,\n            privacyManager: TestPrivacyManager(\n                dataStore: dataStore,\n                config: config,\n                defaultEnabledFeatures: .all),\n            config: config)\n        \n        action = CancelSchedulesAction(overrideAutomation: inAppAutomation)\n    }\n    \n    func testAcceptsArguments() async throws {\n        let valid: [ActionSituation] = [\n            .foregroundPush, .backgroundPush, .manualInvocation, .webViewInvocation, .automation\n        ]\n        \n        let rejected: [ActionSituation] = [\n            .launchedFromPush, .foregroundInteractiveButton, .backgroundInteractiveButton\n        ]\n        \n        for situation in valid {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejected {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n    \n    func testArguments() async throws {\n        //should accept all\n        var args = ActionArguments(value: try AirshipJSON.wrap(\"all\"), situation: .automation)\n        var result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n        \n        //should fail other strings\n        args = ActionArguments(value: try AirshipJSON.wrap(\"invalid\"), situation: .automation)\n        await assertThrowsAsync { _ = try await action.perform(arguments: args) }\n        \n        //should accept dictionaries with groups\n        args = ActionArguments(value: try AirshipJSON.wrap([\"groups\": \"test\"]), situation: .automation)\n        result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n        \n        //should accept dictionaries with groups array\n        args = ActionArguments(value: try AirshipJSON.wrap([\"groups\": [\"test\"]]), situation: .automation)\n        result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n        \n        //should accept dictionaries with ids\n        args = ActionArguments(value: try AirshipJSON.wrap([\"ids\": \"test\"]), situation: .automation)\n        result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n        \n        //should accept dictionaries with ids array\n        args = ActionArguments(value: try AirshipJSON.wrap([\"ids\": [\"test\"]]), situation: .automation)\n        result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n        \n        //should accept dictionaries with ids and groups\n        args = ActionArguments(value: try AirshipJSON.wrap([\"ids\": [\"test\"], \"groups\": \"test1\"]), situation: .automation)\n        result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n        \n        //should fail if neither groups nor ids key found\n        args = ActionArguments(value: try AirshipJSON.wrap([\"key\": \"invalid\"]), situation: .automation)\n        await assertThrowsAsync { _ = try await action.perform(arguments: args) }\n    }\n    \n    func assertThrowsAsync(_ block: () async throws -> Void) async {\n        do {\n            try await block()\n            XCTFail()\n        } catch { }\n    }\n    \n    func testCancellAll() async throws {\n        await automation.setSchedules([\n            AutomationSchedule(identifier: \"action1\", data: .actions(.null), triggers: []),\n            AutomationSchedule(identifier: \"action2\", data: .actions(.null), triggers: []),\n            AutomationSchedule(identifier: \"message\", data: .inAppMessage(InAppMessage(name: \"test\", displayContent: .custom(.null))), triggers: [])\n        ])\n        \n        var count = await automation.schedules.count\n        XCTAssertEqual(3, count)\n        \n        _ = try await action.perform(arguments: ActionArguments(value: AirshipJSON.string(\"all\")))\n        count = await automation.schedules.count\n        XCTAssertEqual(1, count)\n        let schedule = await automation.schedules.first\n        XCTAssertEqual(\"message\", schedule?.identifier)\n    }\n    \n    func testCancelGroups() async throws {\n        await automation.setSchedules([\n            AutomationSchedule(identifier: \"group1\", triggers: [], data: .actions(.null), group: \"group-1\"),\n            AutomationSchedule(identifier: \"group2\", triggers: [], data: .actions(.null), group: \"group-2\"),\n            AutomationSchedule(identifier: \"group3\", triggers: [], data: .actions(.null), group: \"group-3\"),\n        ])\n        \n        let count = await automation.schedules.count\n        XCTAssertEqual(3, count)\n        \n        _ = try await action.perform(\n            arguments: ActionArguments(\n                value: [\"groups\": \"group-1\"]))\n        \n        var scheduleIds = await automation.schedules.map({ $0.identifier })\n        XCTAssertEqual([\"group2\", \"group3\"], scheduleIds)\n        \n        _ = try await action.perform(\n            arguments: ActionArguments(\n                value: [\"groups\": [\"group-2\", \"group-3\"]]))\n        \n        scheduleIds = await automation.schedules.map({ $0.identifier })\n        XCTAssert(scheduleIds.isEmpty)\n    }\n    \n    func testCancelWithIds() async throws {\n        await automation.setSchedules([\n            AutomationSchedule(identifier: \"id-1\", triggers: [], data: .actions(.null)),\n            AutomationSchedule(identifier: \"id-2\", triggers: [], data: .actions(.null)),\n            AutomationSchedule(identifier: \"id-3\", triggers: [], data: .actions(.null)),\n        ])\n        \n        let count = await automation.schedules.count\n        XCTAssertEqual(3, count)\n        \n        _ = try await action.perform(\n            arguments: ActionArguments(\n                value: [\"ids\": \"id-1\"]))\n        \n        var scheduleIds = await automation.schedules.map({ $0.identifier })\n        XCTAssertEqual([\"id-2\", \"id-3\"], scheduleIds)\n        \n        _ = try await action.perform(\n            arguments: ActionArguments(\n                value: [\"ids\": [\"id-2\", \"id-3\"]]))\n        \n        scheduleIds = await automation.schedules.map({ $0.identifier })\n        XCTAssert(scheduleIds.isEmpty)\n    }\n    \n    func testBothGroupsAndIds() async throws {\n        await automation.setSchedules([\n            AutomationSchedule(identifier: \"id-1\", triggers: [], data: .actions(.null)),\n            AutomationSchedule(identifier: \"id-2\", triggers: [], data: .actions(.null), group: \"group\")\n        ])\n        \n        let count = await automation.schedules.count\n        XCTAssertEqual(2, count)\n        \n        _ = try await action.perform(\n            arguments: ActionArguments(\n                value: [\"ids\": \"id-1\", \"groups\": \"group\"]))\n        \n        let scheduleIds = await automation.schedules.map({ $0.identifier })\n        XCTAssert(scheduleIds.isEmpty)\n    }\n}\n\nfinal class TestInAppMessaging: InAppMessaging, @unchecked Sendable {\n    \n    @MainActor\n    var onIsReadyToDisplay: (@MainActor @Sendable (AirshipAutomation.InAppMessage, String) -> Bool)?\n    \n    @MainActor\n    var themeManager: InAppAutomationThemeManager = InAppAutomationThemeManager()\n\n    var displayInterval: TimeInterval = 0.0\n    \n    var displayDelegate: InAppMessageDisplayDelegate?\n    \n    var sceneDelegate: InAppMessageSceneDelegate?\n    \n    func setAdapterFactoryBlock(\n        forType: CustomDisplayAdapterType,\n        factoryBlock: @escaping @Sendable (InAppMessage, AirshipCachedAssetsProtocol) -> CustomDisplayAdapter?\n    ) {\n\n    }\n\n    func setCustomAdapter(\n        forType: CustomDisplayAdapterType,\n        factoryBlock: @escaping @Sendable (DisplayAdapterArgs) -> CustomDisplayAdapter?\n    ) {\n\n    }\n\n    func notifyDisplayConditionsChanged() {\n        \n    }\n}\n\nfinal class TestLegacyInAppMessaging: InternalLegacyInAppMessaging, @unchecked Sendable {\n    \n    init(customMessageConverter: AirshipAutomation.MessageConvertor? = nil, messageExtender: AirshipAutomation.MessageExtender? = nil, scheduleExtender: AirshipAutomation.ScheduleExtender? = nil, displayASAPEnabled: Bool = true) {\n        self.customMessageConverter = customMessageConverter\n        self.messageExtender = messageExtender\n        self.scheduleExtender = scheduleExtender\n        self.displayASAPEnabled = displayASAPEnabled\n    }\n    \n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        \n    }\n    \n    func receivedRemoteNotification(_ notification: AirshipJSON) async -> UABackgroundFetchResult {\n        return .noData\n    }\n    \n    var customMessageConverter: AirshipAutomation.MessageConvertor?\n    \n    var messageExtender: AirshipAutomation.MessageExtender?\n    \n    var scheduleExtender: AirshipAutomation.ScheduleExtender?\n    \n    var displayASAPEnabled: Bool\n}\n\nfinal class TestRemoteDataSubscriber: AutomationRemoteDataSubscriberProtocol {\n    func subscribe() {\n        \n    }\n    \n    func unsubscribe() {\n        \n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Actions/LandingPageActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class LandingPageActionTest: XCTestCase {\n\n    func testAcceptsArguments() async throws {\n        let action = LandingPageAction()\n\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n\n        for situation in validSituations {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testSimpleURLArg() async throws {\n        let urlChecked = expectation(description: \"url checked\")\n        let scheduled = expectation(description: \"scheduled\")\n\n        let expectedMessage = InAppMessage(\n            name: \"Landing Page https://some-url\",\n            displayContent: .html(\n                .init(\n                    url: \"https://some-url\",\n                    requiresConnectivity: false,\n                    borderRadius: 10\n                )\n            ),\n            isReportingEnabled: false,\n            displayBehavior: .immediate\n        )\n\n        let action = LandingPageAction(\n            borderRadius: 10,\n            scheduleExtender: nil,\n            allowListChecker: { url in\n                XCTAssertEqual(\"https://some-url\", url.absoluteString)\n                urlChecked.fulfill()\n                return true\n            },\n            scheduler: { schedule in\n                XCTAssertEqual(schedule.data, .inAppMessage(expectedMessage))\n                XCTAssertEqual(schedule.triggers.count, 1)\n                XCTAssertEqual(schedule.triggers[0].type, EventAutomationTriggerType.activeSession.rawValue)\n                XCTAssertEqual(schedule.triggers[0].goal, 1.0)\n                XCTAssertTrue(schedule.bypassHoldoutGroups!)\n                XCTAssertEqual(schedule.productID, \"landing_page\")\n                XCTAssertEqual(schedule.queue, \"landing_page\")\n                XCTAssertEqual(schedule.priority, Int.min)\n                scheduled.fulfill()\n            }\n        )\n\n        let args = ActionArguments(value: \"https://some-url\", situation: .manualInvocation)\n\n        let result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n\n        await self.fulfillment(of: [urlChecked, scheduled])\n    }\n\n    func testDictionaryArgs() async throws {\n        let urlChecked = expectation(description: \"url checked\")\n        let scheduled = expectation(description: \"scheduled\")\n\n        let expectedMessage = InAppMessage(\n            name: \"Landing Page https://some-url\",\n            displayContent: .html(\n                .init(\n                    url: \"https://some-url\",\n                    height: 20.0,\n                    width: 10.0,\n                    aspectLock: true,\n                    requiresConnectivity: false,\n                    borderRadius: 10\n                )\n            ),\n            isReportingEnabled: false,\n            displayBehavior: .immediate\n        )\n\n        let action = LandingPageAction(\n            borderRadius: 10,\n            scheduleExtender: nil,\n            allowListChecker: { url in\n                XCTAssertEqual(\"https://some-url\", url.absoluteString)\n                urlChecked.fulfill()\n                return true\n            },\n            scheduler: { schedule in\n                XCTAssertEqual(schedule.data, .inAppMessage(expectedMessage))\n                XCTAssertEqual(schedule.triggers.count, 1)\n                XCTAssertEqual(schedule.triggers[0].type, EventAutomationTriggerType.activeSession.rawValue)\n                XCTAssertEqual(schedule.triggers[0].goal, 1.0)\n                XCTAssertTrue(schedule.bypassHoldoutGroups!)\n                XCTAssertEqual(schedule.productID, \"landing_page\")\n                XCTAssertEqual(schedule.queue, \"landing_page\")\n                XCTAssertEqual(schedule.priority, Int.min)\n                scheduled.fulfill()\n            }\n        )\n\n        let argsJSON = \"\"\"\n        {\n            \"url\": \"https://some-url\",\n            \"width\": 10.0,\n            \"height\": 20.0,\n            \"aspect_lock\": true\n        }\n        \"\"\"\n\n        let args = ActionArguments(value: try AirshipJSON.from(json: argsJSON), situation: .manualInvocation)\n\n        let result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n\n        await self.fulfillment(of: [urlChecked, scheduled])\n    }\n\n    func testAppendSchema() async throws {\n        let urlChecked = expectation(description: \"url checked\")\n        let scheduled = expectation(description: \"scheduled\")\n\n        let expectedMessage = InAppMessage(\n            name: \"Landing Page https://some-url\",\n            displayContent: .html(\n                .init(\n                    url: \"https://some-url\",\n                    requiresConnectivity: false,\n                    borderRadius: 10\n                )\n            ),\n            isReportingEnabled: false,\n            displayBehavior: .immediate\n        )\n\n        let action = LandingPageAction(\n            borderRadius: 10,\n            scheduleExtender: nil,\n            allowListChecker: { url in\n                XCTAssertEqual(\"https://some-url\", url.absoluteString)\n                urlChecked.fulfill()\n                return true\n            },\n            scheduler: { schedule in\n                XCTAssertEqual(schedule.data, .inAppMessage(expectedMessage))\n                XCTAssertEqual(schedule.triggers.count, 1)\n                XCTAssertEqual(schedule.triggers[0].type, EventAutomationTriggerType.activeSession.rawValue)\n                XCTAssertEqual(schedule.triggers[0].goal, 1.0)\n                XCTAssertTrue(schedule.bypassHoldoutGroups!)\n                XCTAssertEqual(schedule.productID, \"landing_page\")\n                XCTAssertEqual(schedule.priority, Int.min)\n                scheduled.fulfill()\n            }\n        )\n\n        let args = ActionArguments(value: \"some-url\", situation: .manualInvocation)\n        let result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n\n        await self.fulfillment(of: [urlChecked, scheduled])\n    }\n\n    func testExtendSchedule() async throws {\n        let urlChecked = expectation(description: \"url checked\")\n        let scheduled = expectation(description: \"scheduled\")\n\n        let expectedMessage = InAppMessage(\n            name: \"Landing Page https://some-url\",\n            displayContent: .html(\n                .init(\n                    url: \"https://some-url\",\n                    requiresConnectivity: false,\n                    borderRadius: 20\n                )\n            ),\n            isReportingEnabled: false,\n            displayBehavior: .immediate\n        )\n\n        let action = LandingPageAction(\n            borderRadius: 10,\n            scheduleExtender: { args, schedule in\n                schedule.group = \"some-group\"\n                guard case .inAppMessage(var message) = schedule.data else { return }\n                guard case .html(var html) = message.displayContent else { return }\n                html.borderRadius = 20.0\n\n                message.displayContent = .html(html)\n                schedule.data = .inAppMessage(message)\n            },\n            allowListChecker: { url in\n                XCTAssertEqual(\"https://some-url\", url.absoluteString)\n                urlChecked.fulfill()\n                return true\n            },\n            scheduler: { schedule in\n                XCTAssertEqual(schedule.data, .inAppMessage(expectedMessage))\n                XCTAssertEqual(schedule.triggers.count, 1)\n                XCTAssertEqual(schedule.triggers[0].type, EventAutomationTriggerType.activeSession.rawValue)\n                XCTAssertEqual(schedule.triggers[0].goal, 1.0)\n                XCTAssertTrue(schedule.bypassHoldoutGroups!)\n                XCTAssertEqual(schedule.productID, \"landing_page\")\n                XCTAssertEqual(schedule.priority, Int.min)\n                scheduled.fulfill()\n            }\n        )\n\n        let args = ActionArguments(value: \"some-url\", situation: .manualInvocation)\n        let result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n\n        await self.fulfillment(of: [urlChecked, scheduled])\n    }\n\n    func testRejectsURL() async throws {\n        let expectation = expectation(description: \"url checked\")\n        let action = LandingPageAction(\n            borderRadius: 2,\n            scheduleExtender: nil,\n            allowListChecker: { url in\n                XCTAssertEqual(\"https://some-url\", url.absoluteString)\n                expectation.fulfill()\n                return false\n            },\n            scheduler: { schedule in\n                XCTFail(\"Should skip scheduling\")\n            }\n        )\n\n        let args = ActionArguments(value: \"https://some-url\", situation: .manualInvocation)\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"should throw\")\n        } catch {}\n\n        await self.fulfillment(of: [expectation])\n    }\n\n    func testReportingEnabled() async throws {\n        let pushMetadata: AirshipJSON = [\"_\": \"some-send-ID\"]\n\n        let scheduled = expectation(description: \"scheduled\")\n\n        let expectedMessage = InAppMessage(\n            name: \"Landing Page https://some-url\",\n            displayContent: .html(\n                .init(\n                    url: \"https://some-url\",\n                    requiresConnectivity: false,\n                    borderRadius: 10\n                )\n            ),\n            isReportingEnabled: true,\n            displayBehavior: .immediate\n        )\n\n        let action = LandingPageAction(\n            borderRadius: 10,\n            scheduleExtender: nil,\n            allowListChecker: { url in\n                return true\n            },\n            scheduler: { schedule in\n                XCTAssertEqual(schedule.data, .inAppMessage(expectedMessage))\n                XCTAssertEqual(schedule.triggers.count, 1)\n                XCTAssertEqual(schedule.triggers[0].type, EventAutomationTriggerType.activeSession.rawValue)\n                XCTAssertEqual(schedule.triggers[0].goal, 1.0)\n                XCTAssertTrue(schedule.bypassHoldoutGroups!)\n                XCTAssertEqual(schedule.productID, \"landing_page\")\n                XCTAssertEqual(schedule.priority, Int.min)\n                XCTAssertEqual(schedule.identifier, \"some-send-ID\")\n                scheduled.fulfill()\n            }\n        )\n\n        let args = ActionArguments(\n            value: \"https://some-url\",\n            situation: .manualInvocation,\n            metadata: [ActionArguments.pushPayloadJSONMetadataKey: pushMetadata]\n        )\n\n        let result = try await action.perform(arguments: args)\n        XCTAssertNil(result)\n\n        await self.fulfillment(of: [scheduled])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Actions/ScheduleActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class ScheduleActionTest: XCTestCase {\n    \n    let automation = TestAutomationEngine()\n    var action: ScheduleAction!\n    \n    override func setUp() async throws {\n        let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        let config = RuntimeConfig.testConfig()\n        \n        let inAppAutomation = await DefaultInAppAutomation(\n            engine: automation,\n            inAppMessaging: TestInAppMessaging(),\n            legacyInAppMessaging: TestLegacyInAppMessaging(),\n            remoteData: TestRemoteData(),\n            remoteDataSubscriber: TestRemoteDataSubscriber(),\n            dataStore: dataStore,\n            privacyManager: TestPrivacyManager(\n                dataStore: dataStore,\n                config: config,\n                defaultEnabledFeatures: .all),\n            config: config)\n        \n        action = ScheduleAction(overrideAutomation: inAppAutomation)\n    }\n    \n    func testAcceptsArguments() async throws {\n        let valid: [ActionSituation] = [\n            .foregroundPush, .backgroundPush, .manualInvocation, .webViewInvocation, .automation\n        ]\n        \n        let rejected: [ActionSituation] = [\n            .launchedFromPush, .foregroundInteractiveButton, .backgroundInteractiveButton\n        ]\n        \n        for situation in valid {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejected {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n    \n    func testSchedule() async throws {\n        let start = Date(timeIntervalSince1970: 1709138610)\n        let end = Date(timeIntervalSince1970: 1709138610).advanced(by: 1)\n        let json: AirshipJSON = [\n            \"id\": \"test-id\",\n            \"type\": \"actions\",\n            \"group\": \"test-group\",\n            \"limit\": 1,\n            \"actions\": [\"action-name\": \"action-value\"],\n            \"end\": .string(AirshipDateFormatter.string(fromDate: end, format: .iso)),\n            \"start\": .string(AirshipDateFormatter.string(fromDate: start, format: .iso)),\n            \"triggers\": [\n                [\n                    \"type\": \"foreground\",\n                    \"goal\": 2\n                ]\n            ]\n        ]\n        \n        var count = await automation.schedules.count\n        XCTAssertEqual(0, count)\n        \n        let scheduleId = try await action.perform(arguments: ActionArguments(value: json))\n        XCTAssertEqual(\"test-id\", scheduleId?.string)\n        \n        count = await automation.schedules.count\n        XCTAssertEqual(1, count)\n        \n        let schedule = await automation.schedules.first\n        \n        XCTAssertEqual(\"test-id\", schedule?.identifier)\n        XCTAssertEqual(\"test-group\", schedule?.group)\n        XCTAssertEqual(1, schedule?.limit)\n        XCTAssertEqual(end, schedule?.end)\n        XCTAssertEqual(start, schedule?.start)\n        XCTAssertEqual(1, schedule?.triggers.count)\n        XCTAssertEqual(EventAutomationTriggerType.foreground.rawValue, schedule?.triggers.first?.type)\n        XCTAssertEqual(2, schedule?.triggers.first?.goal)\n        \n        let actionJson: AirshipJSON\n        switch schedule?.data {\n        case .actions(let json): actionJson = json\n        default: actionJson = .null\n        }\n        \n        XCTAssertEqual(AirshipJSON.object([\"action-name\": \"action-value\"]), actionJson)\n    }\n    \n    func testScheduleThrowsOnInvalidSource() async throws {\n        do {\n            _ = try await action.perform(arguments: ActionArguments(value: [:]))\n            XCTFail()\n        } catch { }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/ApplicationMetricsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class ApplicationMetricsTest: XCTestCase {\n\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(\n        notificationCenter: NotificationCenter()\n    )\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var privacyManager: TestPrivacyManager!\n    private var metrics: ApplicationMetrics!\n\n    override func setUp() async throws {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: .testConfig(),\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n\n        self.metrics = ApplicationMetrics(\n            dataStore: self.dataStore,\n            privacyManager: self.privacyManager,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"1.0.0\"\n        )\n    }\n\n\n    func testAppVersionUpdated() throws {\n        // Fresh install\n        XCTAssertFalse(self.metrics.isAppVersionUpdated)\n\n        // No change\n        self.metrics = ApplicationMetrics(\n            dataStore: self.dataStore,\n            privacyManager: self.privacyManager,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"1.0.0\"\n        )\n        XCTAssertFalse(self.metrics.isAppVersionUpdated)\n\n        // Update\n        self.metrics = ApplicationMetrics(\n            dataStore: self.dataStore,\n            privacyManager: self.privacyManager,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"2.0.0\"\n        )\n\n\n        XCTAssertTrue(self.metrics.isAppVersionUpdated)\n    }\n\n    func testOptedOut() {\n        // Update\n        self.metrics = ApplicationMetrics(\n            dataStore: self.dataStore,\n            privacyManager: self.privacyManager,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"2.0.0\"\n        )\n\n        XCTAssertTrue(self.metrics.isAppVersionUpdated)\n\n        self.privacyManager.enabledFeatures = [.analytics, .push]\n        XCTAssertTrue(self.metrics.isAppVersionUpdated)\n\n        self.privacyManager.enabledFeatures = [.inAppAutomation, .push]\n        XCTAssertTrue(self.metrics.isAppVersionUpdated)\n\n        self.privacyManager.enabledFeatures = .push\n        XCTAssertFalse(self.metrics.isAppVersionUpdated)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/AudienceCheck/AdditionalAudienceCheckerResolverTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nclass AdditionalAudienceCheckerResolverTest: XCTestCase {\n    \n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let date = UATestDate(dateOverride: Date())\n    private let apiClient = TestAudienceApiClient()\n    private var cache: AirshipCache!\n    \n    private var resolver: AdditionalAudienceCheckerResolver!\n    private var deviceInfoProvider: TestDeviceInfoProvider = TestDeviceInfoProvider()\n\n    private let defaultAudienceConfig = RemoteConfig.AdditionalAudienceCheckConfig(\n        isEnabled: true,\n        context: \"remote config context\",\n        url: \"https://test.config\")\n    \n    override func setUp() async throws {\n        cache = TestAirshipCoreDataCache.makeCache(date: date)\n    }\n    \n    func testHappyPath() async throws {\n        makeResolver(config: defaultAudienceConfig)\n        \n        deviceInfoProvider.stableContactInfo = StableContactInfo(contactID: \"existing-contact-id\", namedUserID: \"some user id\")\n        deviceInfoProvider.channelID = \"channel-id\"\n\n        apiClient.onResponse = { request in\n            XCTAssertEqual(\"channel-id\", request.channelID)\n            XCTAssertEqual(\"existing-contact-id\", request.contactID)\n            XCTAssertEqual(\"some user id\", request.namedUserID)\n            XCTAssertEqual(AirshipJSON.string(\"default context\"), request.context)\n            XCTAssertEqual(\"https://test.config\", request.url.absoluteString)\n            \n            return AirshipHTTPResponse.make(\n                result: AdditionalAudienceCheckResult(isMatched: true, cacheTTL: 10),\n                statusCode: 200,\n                headers: [:])\n        }\n        \n        let cacheKey = \"https://test.config:\\\"default context\\\":existing-contact-id:channel-id\"\n        \n        var cached: AdditionalAudienceCheckResult? = await cache.getCachedValue(key: cacheKey)\n        XCTAssertNil(cached)\n        \n        let result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: nil\n            )\n        )\n\n        cached = await cache.getCachedValue(key: cacheKey)\n        XCTAssertEqual(true, cached?.isMatched)\n        XCTAssertEqual(10, cached?.cacheTTL)\n        XCTAssert(result)\n    }\n    \n    func testResolverReturnsTrueOnNoConfigOrDisabled() async throws {\n        makeResolver(config: nil)\n        \n        var result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: nil\n            )\n        )\n\n        XCTAssert(result)\n        \n        makeResolver(config: .init(isEnabled: false, context: .null, url: \"test\"))\n        result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: nil\n            )\n        )\n\n        XCTAssert(result)\n    }\n    \n    func testResolverThrowsOnNoUrlProvided() async throws {\n        date.offset = 0\n        makeResolver(config: defaultAudienceConfig)\n        apiClient.onResponse = { _ in\n            return AirshipHTTPResponse.make(\n                result: AdditionalAudienceCheckResult(isMatched: true, cacheTTL: 1),\n                statusCode: 200,\n                headers: [:])\n        }\n        \n        var result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: nil))\n        \n        XCTAssert(result)\n        \n        date.offset = 2\n        makeResolver(config: .init(isEnabled: true, context: .null, url: nil))\n        result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: \"https://test.url\"))\n        \n        XCTAssert(result)\n        \n        date.offset += 2\n        do {\n            result = try await resolver.resolve(\n                deviceInfoProvider: deviceInfoProvider,\n                additionalAudienceCheckOverrides: .init(\n                    bypass: false,\n                    context: \"default context\",\n                    url: nil))\n            XCTFail()\n        } catch {\n            \n        }\n    }\n    \n    func testOverridesBypass() async throws {\n        makeResolver(config: defaultAudienceConfig)\n        apiClient.onResponse = { _ in\n            AirshipHTTPResponse.make(result: nil, statusCode: 400, headers: [:])\n        }\n        \n        let result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: true,\n                context: .null,\n                url: nil))\n        \n        XCTAssert(result)\n    }\n    \n    func testContextDefaultsToConfig() async throws {\n        makeResolver(config: defaultAudienceConfig)\n        \n        apiClient.onResponse = { request in\n            XCTAssertEqual(AirshipJSON.string(\"remote config context\"), request.context)\n            \n            return AirshipHTTPResponse.make(\n                result: AdditionalAudienceCheckResult(isMatched: true, cacheTTL: 10),\n                statusCode: 200,\n                headers: [:])\n        }\n        \n        let result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: true,\n                context: nil,\n                url: nil\n            )\n        )\n\n        XCTAssert(result)\n    }\n    \n    func testReturnsCachedIfAvailable() async throws {\n        makeResolver(config: defaultAudienceConfig)\n        \n        deviceInfoProvider.stableContactInfo = StableContactInfo(contactID: \"existing-contact-id\", namedUserID: \"some user id\")\n        deviceInfoProvider.channelID = \"channel-id\"\n\n        apiClient.onResponse = { request in\n            return AirshipHTTPResponse.make(\n                result: nil,\n                statusCode: 400,\n                headers: [:])\n        }\n        \n        let cacheKey = \"https://test.config:\\\"default context\\\":existing-contact-id:channel-id\"\n        \n        await cache.setCachedValue(AdditionalAudienceCheckResult(isMatched: true, cacheTTL: 10), key: cacheKey, ttl: 10)\n        \n        let result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: nil\n            )\n        )\n\n        XCTAssert(result)\n    }\n    \n    func testIsNotCachedOnError() async throws {\n        makeResolver(config: defaultAudienceConfig)\n        \n        deviceInfoProvider.stableContactInfo = StableContactInfo(contactID: \"existing-contact-id\", namedUserID: \"some user id\")\n        deviceInfoProvider.channelID = \"channel-id\"\n\n        apiClient.onResponse = { request in\n            return AirshipHTTPResponse.make(\n                result: nil,\n                statusCode: 400,\n                headers: [:])\n        }\n        \n        let cacheKey = \"https://test.config:\\\"default context\\\":existing-contact-id:channel-id\"\n        \n        var cached: AdditionalAudienceCheckResult? = await cache.getCachedValue(key: cacheKey)\n        XCTAssertNil(cached)\n        \n        let result = try await resolver.resolve(\n            deviceInfoProvider: deviceInfoProvider,\n            additionalAudienceCheckOverrides: .init(\n                bypass: false,\n                context: \"default context\",\n                url: nil\n            )\n        )\n\n        XCTAssertFalse(result)\n        \n        cached = await cache.getCachedValue(key: cacheKey)\n        XCTAssertNil(cached)\n    }\n    \n    func testThrowsOnServerError() async throws {\n        makeResolver(config: defaultAudienceConfig)\n        \n        apiClient.onResponse = { request in\n            return AirshipHTTPResponse.make(\n                result: nil,\n                statusCode: 500,\n                headers: [:])\n        }\n        \n        do {\n            _ = try await resolver.resolve(\n                deviceInfoProvider: deviceInfoProvider,\n                additionalAudienceCheckOverrides: .init(\n                    bypass: false,\n                    context: \"default context\",\n                    url: nil\n                )\n            )\n            XCTFail()\n        } catch {}\n    }\n    \n    private func makeResolver(\n        config: RemoteConfig.AdditionalAudienceCheckConfig?\n    ) {\n        resolver = AdditionalAudienceCheckerResolver(\n            cache: cache,\n            apiClient: apiClient,\n            date: date,\n            configProvider: { config }\n        )\n    }\n    \n}\n\n\nfinal class TestAudienceApiClient: AdditionalAudienceCheckerAPIClientProtocol, @unchecked Sendable {\n\n    var onResponse: ((AdditionalAudienceCheckResult.Request) -> AirshipHTTPResponse<AdditionalAudienceCheckResult>)? = nil\n    \n    func resolve(info: AdditionalAudienceCheckResult.Request) async throws -> AirshipHTTPResponse<AdditionalAudienceCheckResult> {\n        guard let handler = onResponse else {\n            return AirshipHTTPResponse<AdditionalAudienceCheckResult>.make(result: nil, statusCode: 200, headers: [:])\n        }\n        \n        return handler(info)\n    }\n}\n            \n            \n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/AutomationScheduleDataTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\n\nfinal class AutomationScheduleDataTest: XCTestCase {\n    \n\n    private let date: Date = Date()\n    private let triggerInfo: TriggeringInfo = TriggeringInfo(\n        context: nil, \n        date: Date()\n    )\n\n    private let preparedScheduleInfo = PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0)\n\n    private var data: AutomationScheduleData!\n\n    override func setUp() async throws {\n        self.data = AutomationScheduleData(\n            schedule: AutomationSchedule(\n                identifier: \"neat\",\n                triggers: [],\n                data: .actions(.string(\"actions\"))\n            ),\n            scheduleState: .idle,\n            lastScheduleModifiedDate: self.date,\n            scheduleStateChangeDate: self.date,\n            executionCount: 0,\n            triggerSessionID: UUID().uuidString\n        )\n    }\n\n    func testIsInState() throws {\n        XCTAssertTrue(data.isInState([.idle]))\n        XCTAssertFalse(data.isInState([]))\n        XCTAssertFalse(data.isInState([.executing]))\n        XCTAssertFalse(data.isInState([.executing, .finished, .prepared, .paused]))\n        XCTAssertTrue(data.isInState([.idle, .executing, .finished, .prepared, .paused]))\n    }\n\n    func testIsActive() throws {\n        // no start or end\n        XCTAssertTrue(data.isActive(date: self.date))\n\n        // starts in the future\n        self.data.schedule.start = self.date + 1\n        XCTAssertFalse(data.isActive(date: self.date))\n\n        // starts now\n        self.data.schedule.start = self.date\n        XCTAssertTrue(data.isActive(date: self.date))\n\n        // ends in the past\n        self.data.schedule.end = self.date - 1\n        XCTAssertFalse(data.isActive(date: self.date))\n\n        // ends now\n        self.data.schedule.end = self.date\n        XCTAssertFalse(data.isActive(date: self.date))\n\n        // ends in the future\n        self.data.schedule.end = self.date + 1\n        XCTAssertTrue(data.isActive(date: self.date))\n    }\n\n    func testIsExpired() throws {\n        // no end set\n        XCTAssertFalse(data.isExpired(date: self.date))\n\n        // ends in the past\n        self.data.schedule.end = self.date - 1\n        XCTAssertTrue(data.isExpired(date: self.date))\n\n        // ends now\n        self.data.schedule.end = self.date\n        XCTAssertTrue(data.isExpired(date: self.date))\n\n        // ends in the future\n        self.data.schedule.end = self.date + 1\n        XCTAssertFalse(data.isExpired(date: self.date))\n    }\n\n    func testOverLimitNotSetDefaultsTo1() throws {\n        self.data.schedule.limit = nil\n\n        self.data.executionCount = 0\n        XCTAssertFalse(data.isOverLimit)\n\n        self.data.executionCount = 1\n        XCTAssertTrue(data.isOverLimit)\n    }\n\n    func testOverLimitUnlimited() throws {\n        self.data.schedule.limit = 0\n\n        self.data.executionCount = 0\n        XCTAssertFalse(data.isOverLimit)\n\n        self.data.executionCount = 1\n        XCTAssertFalse(data.isOverLimit)\n\n        self.data.executionCount = 100\n        XCTAssertFalse(data.isOverLimit)\n    }\n\n    func testOverLimit() throws {\n        self.data.schedule.limit = 10\n\n        self.data.executionCount = 0\n        XCTAssertFalse(data.isOverLimit)\n\n        self.data.executionCount = 9\n        XCTAssertFalse(data.isOverLimit)\n\n        self.data.executionCount = 10\n        XCTAssertTrue(data.isOverLimit)\n\n        self.data.executionCount = 11\n        XCTAssertTrue(data.isOverLimit)\n    }\n\n    func testFinished() {\n        self.data.triggerInfo = self.triggerInfo\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n        self.data.finished(date: self.date + 100)\n\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertNil(self.data.triggerInfo)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testIdle() {\n        self.data.scheduleState = .finished\n        self.data.triggerInfo = self.triggerInfo\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n        self.data.idle(date: self.date + 100)\n\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertNil(self.data.triggerInfo)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPaused() {\n        self.data.triggerInfo = self.triggerInfo\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n        self.data.paused(date: self.date + 100)\n\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertNil(self.data.triggerInfo)\n        XCTAssertEqual(self.data.scheduleState, .paused)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testUpdateStateFinishesOverLimit() {\n        self.data.scheduleState = .idle\n        self.data.executionCount = 1\n        self.data.schedule.limit = 1\n\n        self.data.updateState(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testUpdateStateExpired() {\n        self.data.scheduleState = .idle\n        self.data.schedule.end = self.date\n\n        self.data.updateState(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testUpdateFinishedToIdle() {\n        self.data.scheduleState = .finished\n\n        self.data.updateState(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testUpdateStateFinished() {\n        self.data.scheduleState = .idle\n\n        self.data.updateState(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date)\n    }\n\n    func testPrepareCancelledPenalize() {\n        self.data.schedule.limit = 2\n        self.data.scheduleState = .triggered\n\n        self.data.prepareCancelled(date: self.date + 100, penalize: true)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.executionCount, 1)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n\n    }\n\n    func testPrepareCancelled() {\n        self.data.scheduleState = .triggered\n\n        self.data.prepareCancelled(date: self.date + 100, penalize: false)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.executionCount, 0)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPrepareCancelledOverLimit() {\n        self.data.scheduleState = .triggered\n\n        self.data.prepareCancelled(date: self.date + 100, penalize: true)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 1)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPrepareCancelledExpired() {\n        self.data.schedule.limit = 2\n        self.data.scheduleState = .triggered\n        self.data.schedule.end = self.date\n\n        self.data.prepareCancelled(date: self.date + 100, penalize: true)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 1)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPrepareInterrupted() {\n        self.data.scheduleState = .prepared\n\n        self.data.prepareInterrupted(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .triggered)\n        XCTAssertEqual(self.data.executionCount, 0)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testTriggeredScheduleInterrupted() {\n        self.data.scheduleState = .triggered\n\n        self.data.prepareInterrupted(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .triggered)\n        XCTAssertEqual(self.data.executionCount, 0)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date)\n    }\n\n    func testPrepareInterruptedOverLimit() {\n        self.data.schedule.limit = 1\n        self.data.executionCount = 1\n        self.data.scheduleState = .triggered\n\n        self.data.prepareInterrupted(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPrepareInterruptedExpired() {\n        self.data.scheduleState = .triggered\n        self.data.schedule.end = self.date\n\n        self.data.prepareInterrupted(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 0)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionCancelled() {\n        self.data.scheduleState = .prepared\n\n        self.data.executionCancelled(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.executionCount, 0)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionCancelledOverLimit() {\n        self.data.schedule.limit = 1\n        self.data.executionCount = 1\n        self.data.scheduleState = .prepared\n\n        self.data.executionCancelled(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionCancelledExpired() {\n        self.data.scheduleState = .prepared\n        self.data.schedule.end = self.date\n\n        self.data.executionCancelled(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPrepared() {\n        self.data.scheduleState = .triggered\n\n        self.data.prepared(info: self.preparedScheduleInfo, date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .prepared)\n        XCTAssertEqual(self.data.preparedScheduleInfo, self.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPreparedOverLimit() {\n        self.data.schedule.limit = 1\n        self.data.executionCount = 1\n        self.data.scheduleState = .triggered\n\n        self.data.prepared(info: self.preparedScheduleInfo, date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testPreparedExpired() {\n        self.data.schedule.end = self.date\n        self.data.scheduleState = .triggered\n\n        self.data.prepared(info: self.preparedScheduleInfo, date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionSkipped() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.scheduleState = .prepared\n\n        self.data.executionSkipped(date: self.date + 100)\n        XCTAssertEqual(self.data.executionCount, 1)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionSkippedOverLimit() {\n        self.data.schedule.limit = 1\n        self.data.executionCount = 1\n        self.data.scheduleState = .prepared\n\n        self.data.executionSkipped(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionSkippedExpired() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.schedule.end = self.date\n        self.data.scheduleState = .prepared\n\n        self.data.executionSkipped(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInvalidated() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.scheduleState = .prepared\n\n        self.data.executionInvalidated(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .triggered)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInvalidatedOverLimit() {\n        self.data.schedule.limit = 1\n        self.data.executionCount = 1\n        self.data.scheduleState = .prepared\n\n        self.data.executionInvalidated(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInvalidatedExpired() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.schedule.end = self.date\n        self.data.scheduleState = .prepared\n\n        self.data.executionInvalidated(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecuting() {\n        self.data.executionCount = 1\n        self.data.scheduleState = .prepared\n\n        self.data.executing(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .executing)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInterrupted() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.scheduleState = .executing\n\n        self.data.executionInterrupted(date: self.date + 100, retry: false)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInterruptedRetry() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.schedule.interval = 10.0\n        self.data.scheduleState = .executing\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n        self.data.schedule.end = self.date\n\n        self.data.executionInterrupted(date: self.date + 100, retry: true)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 1)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInterruptedOverLimit() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.schedule.interval = 10.0\n        self.data.scheduleState = .executing\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n\n        self.data.executionInterrupted(date: self.date + 100, retry: false)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInterruptedExpired() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.schedule.interval = 10.0\n        self.data.scheduleState = .executing\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n        self.data.schedule.end = self.date\n\n        self.data.executionInterrupted(date: self.date + 100, retry: true)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 1)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testExecutionInterruptedInterval() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.scheduleState = .executing\n        self.data.schedule.interval = 10.0\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n\n        self.data.executionInterrupted(date: self.date + 100, retry: false)\n        XCTAssertEqual(self.data.scheduleState, .paused)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n\n    func testFinishedExecuting() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.scheduleState = .executing\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n\n        self.data.finishedExecuting(date: self.date + 100)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleState, .idle)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testFinishedExecutingOverLimit() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.schedule.interval = 10.0\n        self.data.scheduleState = .executing\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n\n        self.data.finishedExecuting(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testFinishedExecutingExpired() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.schedule.interval = 10.0\n        self.data.scheduleState = .executing\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n        self.data.schedule.end = self.date\n\n        self.data.finishedExecuting(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testFinishedExecutingInterval() {\n        self.data.schedule.limit = 3\n        self.data.executionCount = 1\n        self.data.scheduleState = .executing\n        self.data.schedule.interval = 10.0\n        self.data.preparedScheduleInfo = self.preparedScheduleInfo\n\n        self.data.finishedExecuting(date: self.date + 100)\n        XCTAssertEqual(self.data.scheduleState, .paused)\n        XCTAssertEqual(self.data.executionCount, 2)\n        XCTAssertNil(self.data.preparedScheduleInfo)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testShouldDelete() {\n        XCTAssertFalse(self.data.shouldDelete(date: self.date))\n\n\n        self.data.scheduleState = .finished\n        XCTAssertTrue(self.data.shouldDelete(date: self.date))\n\n        self.data.schedule.editGracePeriodDays = 10\n        XCTAssertFalse(self.data.shouldDelete(date: self.date))\n        XCTAssertFalse(self.data.shouldDelete(date: self.date + 10 * 60 * 60 * 24 - 1))\n        XCTAssertTrue(self.data.shouldDelete(date: self.date + 10 * 60 * 60 * 24))\n    }\n\n    func testTriggered() {\n        let previousTriggerSessionID = self.data.triggerSessionID\n\n        let context = AirshipTriggerContext(type: \"some-type\", goal: 10.0, event: \"event\")\n        \n        self.data.triggered(triggerInfo: TriggeringInfo(context: context, date: self.date), date: self.date + 100)\n        XCTAssertEqual(self.data.triggerInfo?.context, context)\n        XCTAssertEqual(self.data.triggerInfo?.date, self.date)\n        XCTAssertEqual(self.data.scheduleState, .triggered)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n        XCTAssertNotEqual(self.data.triggerSessionID, previousTriggerSessionID)\n    }\n\n    func testTriggeredOverLimit() {\n        self.data.schedule.limit = 1\n        self.data.executionCount = 1\n\n        let context = AirshipTriggerContext(type: \"some-type\", goal: 10.0, event: \"event\")\n        self.data.triggered(triggerInfo: TriggeringInfo(context: context, date: self.date), date: self.date + 100)\n\n        XCTAssertNil(self.data.triggerInfo)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n\n    func testTriggeredExpired() {\n        self.data.schedule.limit = 2\n        self.data.executionCount = 1\n        self.data.schedule.end = self.date\n\n        let context = AirshipTriggerContext(type: \"some-type\", goal: 10.0, event: \"event\")\n        self.data.triggered(triggerInfo: TriggeringInfo(context: context, date: self.date), date: self.date + 100)\n\n        XCTAssertNil(self.data.triggerInfo)\n        XCTAssertEqual(self.data.scheduleState, .finished)\n        XCTAssertEqual(self.data.scheduleStateChangeDate, self.date + 100)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/AutomationScheduleTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nclass AutomationScheduleTests: XCTestCase {\n\n    func testParseActions() throws {\n        let jsonString = \"\"\"\n           {\n               \"id\": \"test_schedule\",\n               \"triggers\": [\n                   {\n                       \"type\": \"custom_event_count\",\n                       \"goal\": 1,\n                       \"id\": \"json-id\"\n                   }\n               ],\n               \"group\": \"test_group\",\n               \"priority\": 2,\n               \"limit\": 5,\n               \"start\": \"2023-12-20T00:00:00Z\",\n               \"end\": \"2023-12-21T00:00:00Z\",\n               \"audience\": {},\n               \"delay\": {},\n               \"interval\": 3600,\n               \"type\": \"actions\",\n               \"actions\": {\n                   \"foo\": \"bar\",\n               },\n               \"bypass_holdout_groups\": true,\n               \"edit_grace_period\": 7,\n               \"metadata\": {},\n               \"frequency_constraint_ids\": [\"constraint1\", \"constraint2\"],\n               \"message_type\": \"test_type\",\n               \"last_updated\": \"2023-12-20T12:30:00Z\",\n               \"created\": \"2023-12-20T12:00:00Z\",\n               \"additional_audience_check_overrides\": {\n                   \"bypass\": true,\n                   \"context\": \"json-context\",\n                   \"url\": \"https://result.url\"\n               }\n           }\n           \"\"\"\n\n        let expectedSchedule = AutomationSchedule(\n            identifier: \"test_schedule\",\n            data: .actions(try AirshipJSON.wrap([\"foo\": \"bar\"])),\n            triggers: [.event(EventAutomationTrigger(id: \"json-id\", type: .customEventCount, goal: 1.0))],\n            created: Date(timeIntervalSince1970: 1703073600),\n            lastUpdated: Date(timeIntervalSince1970: 1703075400),\n            group: \"test_group\",\n            priority: 2,\n            limit: 5,\n            start: Date(timeIntervalSince1970: 1703030400),\n            end: Date(timeIntervalSince1970: 1703116800),\n            audience: AutomationAudience(audienceSelector: DeviceAudienceSelector()),\n            delay: AutomationDelay(),\n            interval: 3600,\n            bypassHoldoutGroups: true,\n            editGracePeriodDays: 7,\n            metadata: [:],\n            frequencyConstraintIDs: [\"constraint1\", \"constraint2\"],\n            messageType: \"test_type\",\n            additionalAudienceCheckOverrides: .init(bypass: true, context: \"json-context\", url: \"https://result.url\")\n        )\n\n        try verify(json: jsonString, expected: expectedSchedule)\n    }\n\n    func testParseDeferred() throws {\n        let jsonString = \"\"\"\n           {\n               \"id\": \"test_schedule\",\n               \"triggers\": [\n                   {\n                       \"type\": \"custom_event_count\",\n                       \"goal\": 1,\n                       \"id\": \"json-id\"\n                   }\n               ],\n               \"group\": \"test_group\",\n               \"priority\": 2,\n               \"limit\": 5,\n               \"start\": \"2023-12-20T00:00:00Z\",\n               \"end\": \"2023-12-21T00:00:00Z\",\n               \"audience\": {\n                 \"new_user\": true,\n                 \"miss_behavior\": \"cancel\"\n               },\n               \"delay\": {},\n               \"interval\": 3600,\n               \"type\": \"deferred\",\n               \"deferred\": {\n                   \"url\": \"some:url\",\n                   \"retry_on_timeout\": true,\n                   \"type\": \"in_app_message\"\n               },\n               \"bypass_holdout_groups\": true,\n               \"edit_grace_period\": 7,\n               \"metadata\": {},\n               \"frequency_constraint_ids\": [\"constraint1\", \"constraint2\"],\n               \"message_type\": \"test_type\",\n               \"last_updated\": \"2023-12-20T12:30:00Z\",\n               \"created\": \"2023-12-20T12:00:00Z\"\n           }\n           \"\"\"\n\n\n        let expectedSchedule = AutomationSchedule(\n            identifier: \"test_schedule\",\n            data: .deferred(DeferredAutomationData(url: URL(string:\"some:url\")!, retryOnTimeOut: true, type: .inAppMessage)),\n            triggers: [.event(EventAutomationTrigger(id: \"json-id\", type: .customEventCount, goal: 1.0))],\n            created: Date(timeIntervalSince1970: 1703073600),\n            lastUpdated: Date(timeIntervalSince1970: 1703075400),\n            group: \"test_group\",\n            priority: 2,\n            limit: 5,\n            start: Date(timeIntervalSince1970: 1703030400),\n            end: Date(timeIntervalSince1970: 1703116800),\n            audience: AutomationAudience(audienceSelector: DeviceAudienceSelector(newUser: true), missBehavior: .cancel),\n            delay: AutomationDelay(),\n            interval: 3600,\n            bypassHoldoutGroups: true,\n            editGracePeriodDays: 7,\n            metadata: [:],\n            frequencyConstraintIDs: [\"constraint1\", \"constraint2\"],\n            messageType: \"test_type\"\n        )\n\n        try verify(json: jsonString, expected: expectedSchedule)\n    }\n\n    func testParseInAppMessage() throws {\n        let jsonString = \"\"\"\n           {\n               \"id\": \"test_schedule\",\n               \"triggers\": [\n                   {\n                       \"type\": \"custom_event_count\",\n                       \"goal\": 1,\n                       \"id\": \"json-id\"\n                   }\n               ],\n               \"group\": \"test_group\",\n               \"priority\": 2,\n               \"limit\": 5,\n               \"start\": \"2023-12-20T00:00:00Z\",\n               \"end\": \"2023-12-21T00:00:00Z\",\n               \"audience\": {},\n               \"delay\": {},\n               \"interval\": 3600,\n               \"type\": \"in_app_message\",\n               \"message\": {\n                   \"source\": \"app-defined\",\n                   \"display\" : {\n                       \"cool\": \"story\"\n                   },\n                   \"display_type\" : \"custom\",\n                   \"name\" : \"woot\"\n               },\n               \"bypass_holdout_groups\": true,\n               \"edit_grace_period\": 7,\n               \"metadata\": {},\n               \"frequency_constraint_ids\": [\"constraint1\", \"constraint2\"],\n               \"message_type\": \"test_type\",\n               \"last_updated\": \"2023-12-20T12:30:00Z\",\n               \"created\": \"2023-12-20T12:00:00Z\"\n           }\n           \"\"\"\n\n\n        let message = InAppMessage(\n            name: \"woot\",\n            displayContent: .custom(\n                [\"cool\": \"story\"]\n            ),\n            source: .appDefined\n        )\n\n        let expectedSchedule = AutomationSchedule(\n            identifier: \"test_schedule\",\n            data: .inAppMessage(message),\n            triggers: [.event(EventAutomationTrigger(id: \"json-id\", type: .customEventCount, goal: 1.0))],\n            created: Date(timeIntervalSince1970: 1703073600),\n            lastUpdated: Date(timeIntervalSince1970: 1703075400),\n            group: \"test_group\",\n            priority: 2,\n            limit: 5,\n            start: Date(timeIntervalSince1970: 1703030400),\n            end: Date(timeIntervalSince1970: 1703116800),\n            audience: AutomationAudience(audienceSelector: DeviceAudienceSelector(), missBehavior: nil),\n            delay: AutomationDelay(),\n            interval: 3600,\n            bypassHoldoutGroups: true,\n            editGracePeriodDays: 7,\n            metadata: [:],\n            frequencyConstraintIDs: [\"constraint1\", \"constraint2\"],\n            messageType: \"test_type\"\n        )\n\n        try verify(json: jsonString, expected: expectedSchedule)\n    }\n    \n    func testParseInAppMessageCompoundAudience() throws {\n        let jsonString = \"\"\"\n           {\n             \"id\": \"test_schedule\",\n             \"triggers\": [\n               {\n                 \"type\": \"custom_event_count\",\n                 \"goal\": 1,\n                 \"id\": \"json-id\"\n               }\n             ],\n             \"group\": \"test_group\",\n             \"priority\": 2,\n             \"limit\": 5,\n             \"start\": \"2023-12-20T00:00:00Z\",\n             \"end\": \"2023-12-21T00:00:00Z\",\n             \"compound_audience\": {\n               \"selector\": {\n                 \"type\": \"atomic\",\n                 \"audience\": {\n                   \"new_user\": true\n                 }\n               },\n               \"miss_behavior\": \"skip\"\n             },\n             \"delay\": {},\n             \"interval\": 3600,\n             \"type\": \"in_app_message\",\n             \"message\": {\n               \"source\": \"app-defined\",\n               \"display\": {\n                 \"cool\": \"story\"\n               },\n               \"display_type\": \"custom\",\n               \"name\": \"woot\"\n             },\n             \"bypass_holdout_groups\": true,\n             \"edit_grace_period\": 7,\n             \"metadata\": {},\n             \"frequency_constraint_ids\": [\n               \"constraint1\",\n               \"constraint2\"\n             ],\n             \"message_type\": \"test_type\",\n             \"last_updated\": \"2023-12-20T12:30:00Z\",\n             \"created\": \"2023-12-20T12:00:00Z\"\n           }\n           \"\"\"\n\n\n        let message = InAppMessage(\n            name: \"woot\",\n            displayContent: .custom(\n                [\"cool\": \"story\"]\n            ),\n            source: .appDefined\n        )\n\n        let expectedSchedule = AutomationSchedule(\n            identifier: \"test_schedule\",\n            data: .inAppMessage(message),\n            triggers: [.event(EventAutomationTrigger(id: \"json-id\", type: .customEventCount, goal: 1.0))],\n            created: Date(timeIntervalSince1970: 1703073600),\n            lastUpdated: Date(timeIntervalSince1970: 1703075400),\n            group: \"test_group\",\n            priority: 2,\n            limit: 5,\n            start: Date(timeIntervalSince1970: 1703030400),\n            end: Date(timeIntervalSince1970: 1703116800),\n            audience: nil,\n            compoundAudience: AutomationCompoundAudience(\n                selector: .atomic(.init(newUser: true)),\n                missBehavior: .skip\n            ),\n            delay: AutomationDelay(),\n            interval: 3600,\n            bypassHoldoutGroups: true,\n            editGracePeriodDays: 7,\n            metadata: [:],\n            frequencyConstraintIDs: [\"constraint1\", \"constraint2\"],\n            messageType: \"test_type\"\n        )\n\n        try verify(json: jsonString, expected: expectedSchedule)\n    }\n\n    func verify(json: String, expected: AutomationSchedule) throws {\n        let decoder = JSONDecoder()\n        let encoder = JSONEncoder()\n\n        let fromJSON = try decoder.decode(AutomationSchedule.self, from: json.data(using: .utf8)!)\n        XCTAssertEqual(fromJSON, expected)\n\n        let roundTrip = try decoder.decode(AutomationSchedule.self, from: try encoder.encode(fromJSON))\n        XCTAssertEqual(roundTrip, fromJSON)\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationDelayProcessorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\n\nfinal class AutomationDelayProcessorTest: XCTestCase {\n\n    private let analytics: TestAnalytics = TestAnalytics()\n    private let stateTracker: TestAppStateTracker = TestAppStateTracker()\n    private let date: UATestDate = UATestDate()\n    private let taskSleeper: TestTaskSleeper = TestTaskSleeper()\n\n    private var processor: AutomationDelayProcessor!\n    private var executionWindowProcessor: TestExecutionWindowProcessor!\n\n    override func setUp() async throws {\n        self.executionWindowProcessor = TestExecutionWindowProcessor()\n        self.date.dateOverride = Date()\n        self.processor = await AutomationDelayProcessor(\n            analytics: analytics,\n            appStateTracker: stateTracker,\n            taskSleeper: taskSleeper,\n            date: date,\n            executionWindowProcessor: executionWindowProcessor\n        )\n    }\n\n    @MainActor\n    func testProcess() async throws {\n        let executionWindow = try ExecutionWindow(\n            include: [ .weekly(daysOfWeek: [1]) ]\n        )\n        let delay = AutomationDelay(\n            seconds: 100.0,\n            screens: [\"screen1\", \"screen2\"],\n            regionID: \"region1\",\n            appState: .foreground,\n            executionWindow: executionWindow\n        )\n\n        let finished = AirshipMainActorValue<Bool>(false)\n        let started = expectation(description: \"delay started\")\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            started.fulfill()\n            await processor!.process(delay: delay, triggerDate: now)\n            finished.set(true)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [started])\n\n        XCTAssertFalse(finished.value)\n\n        self.analytics.setScreen(\"screen1\")\n        self.analytics.setRegions(Set([\"region1\"]))\n        self.analytics.setRegions(Set([\"region1\"]))\n        self.stateTracker.currentState = .active\n        self.executionWindowProcessor.onIsActive = { window in\n            XCTAssertEqual(window, executionWindow)\n            return true\n        }\n\n        await self.fulfillment(of: [ended])\n        XCTAssertTrue(finished.value)\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [100.0])\n        let processed = await self.executionWindowProcessor.getProcessed()\n        XCTAssertEqual(processed, [executionWindow])\n    }\n\n    @MainActor\n    func testPreprocess() async throws {\n        let executionWindow = try ExecutionWindow(\n            include: [ .weekly(daysOfWeek: [1]) ]\n        )\n\n        let delay = AutomationDelay(\n            seconds: 100.0,\n            screens: [\"screen1\", \"screen2\"],\n            regionID: \"region1\",\n            appState: .foreground,\n            executionWindow: executionWindow\n        )\n\n        let finished = AirshipMainActorValue<Bool>(false)\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            try! await processor!.preprocess(delay: delay, triggerDate: now)\n            finished.set(true)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [ended])\n        XCTAssertTrue(finished.value)\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [70.0])\n\n        let processed = await self.executionWindowProcessor.getProcessed()\n        XCTAssertEqual(processed, [executionWindow])\n    }\n\n    @MainActor\n    func testTaskSleep() async throws {\n        let delay = AutomationDelay(\n            seconds: 100.0\n        )\n\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            await processor!.process(delay: delay, triggerDate: now)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [ended])\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [100.0])\n    }\n\n    @MainActor\n    func testRemainingSleep() async throws {\n        let delay = AutomationDelay(\n            seconds: 100.0\n        )\n\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            await processor!.process(delay: delay, triggerDate: now - 50.0)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [ended])\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [50.0])\n    }\n\n    @MainActor\n    func testSkipSleep() async throws {\n        let delay = AutomationDelay(\n            seconds: 100.0\n        )\n\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            await processor!.process(delay: delay, triggerDate: now - 100.0)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [ended])\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [])\n    }\n\n    @MainActor\n    func testEmptyDelay() async throws {\n        let delay = AutomationDelay()\n\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            await processor!.process(delay: delay, triggerDate: now - 100.0)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [ended])\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [])\n\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n    }\n\n    @MainActor\n    func testNilDelay() async throws {\n        let ended = expectation(description: \"delay processed\")\n\n        let now = date.now\n        Task { @MainActor [processor] in\n            await processor!.process(delay: nil, triggerDate: now - 100.0)\n            ended.fulfill()\n        }\n\n        await self.fulfillment(of: [ended])\n\n        let sleeps = self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [])\n\n        XCTAssertTrue(self.processor.areConditionsMet(delay: nil))\n    }\n\n\n    @MainActor\n    func testScreenConditions() async throws {\n        let delay = AutomationDelay(\n            screens: [\"screen1\", \"screen2\"]\n        )\n\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n\n        self.analytics.setScreen(\"screen1\")\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n\n        self.analytics.setScreen(\"screen3\")\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n    }\n\n    @MainActor\n    func testRegionCondition() async throws {\n        let delay = AutomationDelay(\n            regionID: \"foo\"\n        )\n\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n\n        self.analytics.setRegions(Set([\"foo\", \"baz\"]))\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n\n        self.analytics.setRegions(Set([\"bar\", \"baz\"]))\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n    }\n\n\n    @MainActor\n    func testForegroundAppState() async throws {\n        let delay = AutomationDelay(\n            appState: .foreground\n        )\n\n        self.stateTracker.currentState = .background\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n\n        self.stateTracker.currentState = .inactive\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n\n        self.stateTracker.currentState = .active\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n    }\n\n    @MainActor\n    func testBackgroundAppState() async throws {\n        let delay = AutomationDelay(\n            appState: .background\n        )\n\n        self.stateTracker.currentState = .background\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n\n        self.stateTracker.currentState = .inactive\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n\n        self.stateTracker.currentState = .active\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n    }\n\n    @MainActor\n    func testExecutionWindow() async throws {\n        let executionWindow = try ExecutionWindow(\n            include: [ .weekly(daysOfWeek: [1]) ]\n        )\n\n        let delay = AutomationDelay(\n            executionWindow: executionWindow\n        )\n\n        self.executionWindowProcessor.onIsActive = { _ in\n            return false\n        }\n        XCTAssertFalse(self.processor.areConditionsMet(delay: delay))\n\n        self.executionWindowProcessor.onIsActive = { _ in\n            return true\n        }\n        XCTAssertTrue(self.processor.areConditionsMet(delay: delay))\n    }\n}\n\n\nfileprivate actor TestExecutionWindowProcessor: ExecutionWindowProcessorProtocol {\n\n    private var processed: [ExecutionWindow] = []\n\n    @MainActor\n    var onIsActive: ((ExecutionWindow) -> Bool)?\n\n    func process(window: ExecutionWindow) async throws {\n        processed.append(window)\n    }\n\n    func getProcessed() -> [ExecutionWindow] {\n        return processed\n    }\n\n    @MainActor\n    func isActive(window: ExecutionWindow) -> Bool {\n        return onIsActive?(window) ?? false\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationEngineTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\n@MainActor\nfinal class TestScheduleConditionsChangedNotifier: ScheduleConditionsChangedNotifierProtocol {\n    var onNotify: (() -> Void)?\n    var onWait: (() -> Void)?\n\n    func notify() {\n        onNotify?()\n    }\n    \n    func wait() async {\n        onWait?()\n    }\n}\n\nfinal class AutomationEngineTest: XCTestCase {\n    \n    private var engine: AutomationEngine!\n    private var dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var automationStore: AutomationStore!\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(\n        notificationCenter: NotificationCenter()\n    )\n    private let actionPreparer: TestPreparerDelegate<AirshipJSON, AirshipJSON> = TestPreparerDelegate()\n    private let messagePreparer: TestPreparerDelegate<InAppMessage, PreparedInAppMessageData> = TestPreparerDelegate()\n    private let remoteDataAccess: TestRemoteDataAccess = TestRemoteDataAccess()\n    private var privacyManager: TestPrivacyManager!\n    private let deferredResolver: TestDeferredResolver = TestDeferredResolver()\n    private let experiments: TestExperimentDataProvider = TestExperimentDataProvider()\n    private let frequencyLimits: TestFrequencyLimitManager = TestFrequencyLimitManager()\n    private let audienceChecker: TestAudienceChecker = TestAudienceChecker()\n    private var preparer: AutomationPreparer!\n    private var eventFeed: AutomationEventFeed!\n    private var executor: AutomationExecutor!\n    private var messageExecutor: InAppMessageAutomationExecutor!\n    private var delayProcessor: AutomationDelayProcessor!\n    private var metrics: ApplicationMetrics!\n    private let runtimeConfig: RuntimeConfig = .testConfig()\n\n    private var scheduleConditionsChangedNotifier: TestScheduleConditionsChangedNotifier!\n\n    @MainActor\n    override func setUp() async throws {\n        let history = DefaultAutomationEventsHistory(clock: UATestDate())\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: runtimeConfig,\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n\n        self.automationStore = AutomationStore(appKey: UUID().uuidString, inMemory: true)\n        self.preparer = AutomationPreparer(\n            actionPreparer: actionPreparer,\n            messagePreparer: messagePreparer,\n            deferredResolver: deferredResolver,\n            frequencyLimits: frequencyLimits,\n            audienceChecker: audienceChecker,\n            experiments: experiments,\n            remoteDataAccess: remoteDataAccess,\n            config: self.runtimeConfig,\n            additionalAudienceResolver: TestAdditionalAudienceResolver()\n        )\n        \n        let actionExecutor = ActionAutomationExecutor()\n        let messageExecutor = TestInAppMessageAutomationExecutor()\n        let executor = AutomationExecutor(actionExecutor: actionExecutor, messageExecutor: messageExecutor, remoteDataAccess: remoteDataAccess)\n        let triggersProcessor = AutomationTriggerProcessor(store: automationStore, history: history)\n        \n        self.metrics = ApplicationMetrics(\n            dataStore: dataStore,\n            privacyManager: privacyManager,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"1.0.0\"\n        )\n\n        let analyticsFeed = AirshipAnalyticsFeed() { true }\n        self.scheduleConditionsChangedNotifier = TestScheduleConditionsChangedNotifier()\n        eventFeed = AutomationEventFeed(applicationMetrics: metrics, applicationStateTracker: AppStateTracker.shared, analyticsFeed: analyticsFeed)\n        let analytics = TestAnalytics()\n        let delayProcessor = AutomationDelayProcessor(analytics: analytics)\n        \n        self.engine = AutomationEngine(\n            store: self.automationStore,\n            executor: executor,\n            preparer: self.preparer,\n            scheduleConditionsChangedNotifier: scheduleConditionsChangedNotifier,\n            eventFeed: eventFeed,\n            triggersProcessor: triggersProcessor,\n            delayProcessor: delayProcessor,\n            eventsHistory: history\n        )\n    }\n    \n    override func tearDown() async throws {\n        await self.engine.stop()\n    }\n    \n    func testStart() async throws {\n        await self.engine.start()\n        let startTask = await self.engine.startTask\n        let listenTask = await self.engine.listenerTask\n        \n        XCTAssertNotNil(startTask)\n        XCTAssertNotNil(listenTask)\n    }\n    \n    func testStop() async throws {\n        await self.engine.stop()\n        let startTask = await self.engine.startTask\n        let listenTask = await self.engine.listenerTask\n        XCTAssertNil(startTask)\n        XCTAssertNil(listenTask)\n    }\n    \n    @MainActor\n    func testSetEnginePaused() async throws {\n        self.engine.setEnginePaused(true)\n        XCTAssertTrue(self.engine.isEnginePaused.value)\n    }\n    \n    @MainActor\n    func testSetExecutionPaused() async throws {\n        let onNotifyExpectation = expectation(description: \"Schedule conditions changed notifiers should be notified when pause state changes.\")\n\n        self.scheduleConditionsChangedNotifier.onNotify = {\n            onNotifyExpectation.fulfill()\n        }\n\n        self.engine.setExecutionPaused(true)\n        XCTAssertTrue(self.engine.isExecutionPaused.value)\n\n\n        self.engine.setExecutionPaused(false)\n        XCTAssertFalse(self.engine.isExecutionPaused.value)\n\n        await fulfillment(of: [onNotifyExpectation], timeout: 1)\n    }\n    \n    func testStopSchedules() async throws {\n        try await self.engine.upsertSchedules([AutomationSchedule(identifier: \"test\", triggers: [], data: .inAppMessage(\n            InAppMessage(\n                name: \"test\",\n                displayContent: .custom(.string(\"test\"))\n            )))])\n        var schedule = try await self.engine.getSchedule(identifier: \"test\")\n        XCTAssertNotNil(schedule)\n        try await self.engine.stopSchedules(identifiers: [\"test\"])\n        schedule = try await self.engine.getSchedule(identifier: \"test\")\n        XCTAssertNil(schedule)\n    }\n    \n    func testUpsertSchedules() async throws {\n        var schedule = try await self.engine.getSchedule(identifier: \"test\")\n        XCTAssertNil(schedule)\n\n        var storedSchedule = try await self.automationStore.getSchedule(scheduleID: \"test\")\n        XCTAssertNil(storedSchedule)\n\n        let beforeDate = AirshipDate().now\n\n        try await self.engine.upsertSchedules([AutomationSchedule(identifier: \"test\", triggers: [], data: .inAppMessage(\n            InAppMessage(\n                name: \"test\",\n                displayContent: .custom(.string(\"test\"))\n            )))])\n        schedule = try await self.engine.getSchedule(identifier: \"test\")\n\n        storedSchedule = try await self.automationStore.getSchedule(scheduleID: \"test\")\n\n        XCTAssertGreaterThan(storedSchedule!.lastScheduleModifiedDate, beforeDate)\n        XCTAssertNotNil(schedule)\n    }\n    \n    func testCancelSchedule() async throws {\n        try await self.engine.upsertSchedules([AutomationSchedule(identifier: \"test\", triggers: [], data: .inAppMessage(\n            InAppMessage(\n                name: \"test\",\n                displayContent: .custom(.string(\"test\"))\n            )))])\n        try await self.engine.cancelSchedules(identifiers: [\"test\"])\n        let schedule = try await self.engine.getSchedule(identifier: \"test\")\n        XCTAssertNil(schedule)\n    }\n}\n\nactor TestAdditionalAudienceResolver: AdditionalAudienceCheckerResolverProtocol {\n    struct ResolveRequest {\n        let channelID: String\n        let contactID: String?\n        let overrides: AdditionalAudienceCheckOverrides?\n    }\n    \n    var recordedReqeusts: [ResolveRequest] = []\n    public func setResult(_ result: Bool) {\n        returnResult = result\n    }\n    private var returnResult = true\n\n    func resolve(\n        deviceInfoProvider: AudienceDeviceInfoProvider,\n        additionalAudienceCheckOverrides: AdditionalAudienceCheckOverrides?\n    ) async throws -> Bool {\n        recordedReqeusts.append(\n            ResolveRequest(\n                channelID: try await deviceInfoProvider.channelID,\n                contactID: await deviceInfoProvider.stableContactInfo.contactID,\n                overrides: additionalAudienceCheckOverrides\n            )\n        )\n        return returnResult\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationEventFeedTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\n@testable import AirshipCore\n\nfinal class AutomationEventFeedTest: XCTestCase, @unchecked Sendable {\n    private let date = UATestDate(offset: 0, dateOverride: Date())\n    private let datastore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var subject: AutomationEventFeed!\n    private let analyticsFeed: AirshipAnalyticsFeed = AirshipAnalyticsFeed() { true }\n    private let stateTracker: TestAppStateTracker = TestAppStateTracker()\n\n    var iterator: AsyncStream<AutomationEvent>.Iterator!\n    \n    override func setUp() async throws {\n        let metrics = TestApplicationMetrics()\n        metrics.versionUpdated = true\n        \n        subject = await AutomationEventFeed(applicationMetrics: metrics, applicationStateTracker: stateTracker, analyticsFeed: analyticsFeed)\n\n        iterator = subject.feed.makeAsyncIterator()\n    }\n    \n    func testFirstAttachProducesInitAndVersionUpdated() async throws {\n        await subject.attach()\n\n        let events = await takeNext(count: 2)\n        \n        let state = TriggerableState(versionUpdated: \"test\")\n        \n        XCTAssertEqual([AutomationEvent.event(type: .appInit), AutomationEvent.stateChanged(state: state)], events)\n    }\n    \n    func testSubsequentAttachEmitsNoEvents() async throws {\n        await subject.attach()\n        var events = await takeNext(count: 3)\n\n        await subject.attach()\n        events = await takeNext()\n        XCTAssert(events.isEmpty)\n        \n        await subject.detach().attach()\n        events = await takeNext()\n        \n        XCTAssert(events.isEmpty)\n    }\n    \n    @MainActor\n    func testSupportedEvents() async throws {\n        subject.attach()\n        await takeNext(count: 3)\n\n        stateTracker.currentState = .active\n        var events = await takeNext(count: 2)\n        XCTAssertEqual(AutomationEvent.event(type: .foreground), events.first)\n        verifyStateChange(event: events.last!, foreground: true, versionUpdated: \"test\")\n\n        stateTracker.currentState = .background\n        events = await takeNext(count: 2)\n        XCTAssertEqual(AutomationEvent.event(type: .background), events.first)\n        verifyStateChange(event: events.last!, foreground: false, versionUpdated: \"test\")\n\n        let trackScreenName = \"test-screen\"\n        await analyticsFeed.notifyEvent(.screen(screen: trackScreenName))\n        var event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .screen, data: .string(trackScreenName)), event)\n        \n        await analyticsFeed.notifyEvent(.analytics(eventType: .regionEnter, body: \"some region data\"))\n        event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .regionEnter, data: \"some region data\"), event)\n\n        await analyticsFeed.notifyEvent(.analytics(eventType: .regionExit, body: \"some region data\"))\n        event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .regionExit, data: \"some region data\"), event)\n\n        await analyticsFeed.notifyEvent(.analytics(eventType: .customEvent, body: \"some data\", value: 100))\n        event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .customEventCount, data: \"some data\", value: 1), event)\n        event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .customEventValue, data: \"some data\", value: 100), event)\n        \n\n        await analyticsFeed.notifyEvent(.analytics(eventType: .featureFlagInteraction, body: \"some data\"))\n        event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .featureFlagInteraction, data: \"some data\"), event)\n    }\n    \n    func testAnalyticFeedEvents() async throws {\n        await subject.attach()\n        await takeNext(count: 3)\n        \n        let eventMap: [EventType: [EventAutomationTriggerType]] = [\n            .customEvent: [.customEventCount, .customEventValue],\n            .regionExit: [.regionExit],\n            .regionEnter: [.regionEnter],\n            .featureFlagInteraction: [.featureFlagInteraction],\n            .inAppDisplay: [.inAppDisplay],\n            .inAppResolution: [.inAppResolution],\n            .inAppButtonTap: [.inAppButtonTap],\n            .inAppPermissionResult: [.inAppPermissionResult],\n            .inAppFormDisplay: [.inAppFormDisplay],\n            .inAppFormResult: [.inAppFormResult],\n            .inAppGesture: [.inAppGesture],\n            .inAppPagerCompleted: [.inAppPagerCompleted],\n            .inAppPagerSummary: [.inAppPagerSummary],\n            .inAppPageSwipe: [.inAppPageSwipe],\n            .inAppPageView: [.inAppPageView],\n            .inAppPageAction: [.inAppPageAction]\n        ]\n        \n        for eventType in EventType.allCases {\n            guard let expected = eventMap[eventType] else { continue }\n            \n            let data = AirshipJSON.string(UUID().uuidString)\n            await analyticsFeed.notifyEvent(.analytics(eventType: eventType, body: data))\n            \n            for expectedTriggerType in expected {\n                let event = await takeNext().first\n                XCTAssertEqual(AutomationEvent.event(type: expectedTriggerType, data: data, value: 1.0), event)\n            }\n        }\n    }\n    \n    func testScreenEvent() async throws {\n        await subject.attach()\n        await takeNext(count: 3)\n        \n        await analyticsFeed.notifyEvent(.screen(screen: \"foo\"))\n        let event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .screen, data: \"foo\", value: 1.0), event)\n    }\n    \n    func testCustomEventValues() async throws {\n        await subject.attach()\n        await takeNext(count: 3)\n        \n        await analyticsFeed.notifyEvent(.analytics(eventType: .customEvent, body: .null, value: 10))\n        \n        var event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .customEventCount, data: .null, value: 1.0), event)\n        \n        event = await takeNext().first\n        XCTAssertEqual(AutomationEvent.event(type: .customEventValue, data: .null, value: 10.0), event)\n    }\n    \n    func testNoEventsIfNotAttached() async throws {\n        var events = await takeNext()\n        XCTAssert(events.isEmpty)\n        \n        await self.analyticsFeed.notifyEvent(.screen(screen: \"foo\"))\n        events = await takeNext()\n        XCTAssert(events.isEmpty)\n    }\n    \n    func testNoEventsAfterDetach() async throws {\n        await self.subject.attach()\n        var events = await takeNext(count: 3)\n        XCTAssert(events.count > 0)\n        \n        await subject.detach()\n\n        await self.analyticsFeed.notifyEvent(.screen(screen: \"foo\"))\n        events = await takeNext()\n        XCTAssert(events.isEmpty)\n    }\n\n    func verifyStateChange(event: AutomationEvent, foreground: Bool, versionUpdated: String?, line: UInt = #line) {\n        guard case .stateChanged(let state) = event else {\n            XCTFail(\"invalid event\", line: line)\n            return\n        }\n\n        if (foreground) {\n            XCTAssertNotNil(state.appSessionID)\n        } else {\n            XCTAssertNil(state.appSessionID)\n        }\n        XCTAssertEqual(versionUpdated, state.versionUpdated)\n    }\n\n    @discardableResult\n    private func takeNext(count: UInt = 1, timeout: Int = 1) async -> [AutomationEvent] {\n        \n        let collectTask = Task {\n            var result: [AutomationEvent] = []\n            var iterator = self.subject.feed.makeAsyncIterator()\n            while result.count < count, !Task.isCancelled {\n                if let next = await iterator.next() {\n                    result.append(next)\n                }\n            }\n            \n            return result\n        }\n        \n        let cancel = DispatchWorkItem {\n            collectTask.cancel()\n        }\n        \n        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(timeout), execute: cancel)\n        \n        let result = await collectTask.result.get()\n        cancel.cancel()\n        return result\n    }\n}\n\n\n\nclass TestApplicationMetrics: ApplicationMetricsProtocol, @unchecked Sendable {\n    var currentAppVersion: String? = \"test\"\n\n\n    var versionUpdated = false\n\n    var isAppVersionUpdated: Bool { return versionUpdated }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationEventsHistoryTest.swift",
    "content": "// Copyright Urban Airship and Contributors\n\n\nimport Foundation\nimport Testing\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nstruct AutomationEventsHistoryTest {\n    let clock = UATestDate(dateOverride: Date())\n    \n    private func makeSubject() -> DefaultAutomationEventsHistory {\n        return DefaultAutomationEventsHistory(\n            clock: clock\n        )\n    }\n\n    @Test\n    func testAddAndRetrieveEvent() async {\n        let subject = makeSubject()\n        await subject.add(.event(type: .foreground))\n\n        let events = await subject.events\n        #expect(events.count == 1)\n        #expect(events.first == AutomationEvent.event(type: .foreground, data: nil, value: 1.0))\n    }\n\n    @Test\n    func testPrunesEventsOlderThanMaxDuration() async {\n        let subject = makeSubject()\n\n        // Add an event at the current time\n        await subject.add(.event(type: .foreground))\n\n        // Advance time beyond the max duration (30 seconds in implementation)\n        clock.advance(by: 31)\n\n        let events = await subject.events\n        #expect(events.count == 0, \"Events older than maxDuration should be pruned\")\n    }\n\n    @Test\n    func testKeepsOnlyMostRecentMaxEvents() async {\n        let subject = makeSubject()\n        \n        // Add 110 events; DefaultAutomationEventsHistory keeps last 100\n        let total = 110\n        for i in 0..<total {\n            await subject.add(.event(type: .customEventValue, data: nil, value: Double(i)))\n        }\n\n        let events = await subject.events\n        #expect(events.count == 100, \"Expected to keep only the last 100 events\")\n\n        // The first kept event should correspond to index 10 (110 - 100)\n        let firstEventValue: Double? = if case .event(_, _, let value) = events.first {\n            value\n        } else {\n            nil\n        }\n        #expect(firstEventValue == 10)\n        \n        let lastEventValue: Double? = if case .event(_, _, let value) = events.last {\n            value\n        } else {\n            nil\n        }\n        \n        #expect(lastEventValue == 109)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationExecutorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class AutomationExecutorTest: XCTestCase {\n\n    private let actionExecutor: TestExecutorDelegate<AirshipJSON> = TestExecutorDelegate()\n    private let messageExecutor: TestExecutorDelegate<PreparedInAppMessageData> = TestExecutorDelegate()\n    private let remoteDataAccess: TestRemoteDataAccess = TestRemoteDataAccess()\n    private let messageAnalyitics: TestInAppMessageAnalytics = TestInAppMessageAnalytics()\n\n    private var executor: AutomationExecutor!\n\n    private var preparedMessageData: PreparedInAppMessageData!\n\n    @MainActor\n    override func setUp() async throws {\n        self.preparedMessageData = PreparedInAppMessageData(\n            message: InAppMessage(\n                name: \"some name\",\n                displayContent: .custom(.string(\"custom\"))\n            ),\n            displayAdapter: TestDisplayAdapter(),\n            displayCoordinator: TestDisplayCoordinator(),\n            analytics: messageAnalyitics,\n            actionRunner: TestInAppActionRunner()\n        )\n\n        self.executor = AutomationExecutor(\n            actionExecutor: actionExecutor,\n            messageExecutor: messageExecutor,\n            remoteDataAccess: remoteDataAccess\n        )\n    }\n\n    func testMessageIsReady() async throws {\n        let messageSchedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .inAppMessage(self.preparedMessageData),\n            frequencyChecker: nil\n        )\n\n        for readyResult in ScheduleReadyResult.allResults {\n            self.messageExecutor.isReadyCalled = false\n            self.messageExecutor.isReadyBlock = { data, info in\n                XCTAssertEqual(.inAppMessage(data), messageSchedule.data)\n                XCTAssertEqual(info, messageSchedule.info)\n                return readyResult\n            }\n\n            let result = await self.executor.isReady(\n                preparedSchedule: messageSchedule\n            )\n\n            XCTAssertEqual(readyResult, result)\n            XCTAssertTrue(messageExecutor.isReadyCalled)\n        }\n    }\n\n    func testActionIsReady() async throws {\n        let actionSchedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .actions(AirshipJSON.string(\"neat\")),\n            frequencyChecker: nil\n        )\n\n        for readyResult in ScheduleReadyResult.allResults {\n            self.actionExecutor.isReadyCalled = false\n\n            self.actionExecutor.isReadyBlock = { data, info in\n                XCTAssertEqual(.actions(data), actionSchedule.data)\n                XCTAssertEqual(info, actionSchedule.info)\n                return readyResult\n            }\n\n            let result = await self.executor.isReady(\n                preparedSchedule: actionSchedule\n            )\n\n            XCTAssertEqual(readyResult, result)\n            XCTAssertTrue(actionExecutor.isReadyCalled)\n        }\n    }\n\n    func testFrequencyChekerNotCheckedIfDelegateNotReady() async throws {\n        let frequencyChecker = TestFrequencyChecker()\n\n        let schedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .actions(AirshipJSON.string(\"neat\")),\n            frequencyChecker: frequencyChecker\n        )\n\n        self.actionExecutor.isReadyBlock = { _, _ in\n            return .notReady\n        }\n\n        frequencyChecker.checkAndIncrementBlock = {\n            return false\n        }\n\n        let result = await self.executor.isReady(\n            preparedSchedule: schedule\n        )\n\n        XCTAssertEqual(result, .notReady)\n        XCTAssertFalse(frequencyChecker.checkAndIncrementCalled)\n    }\n\n    func testFrequencyCheckerCheckFailed() async throws {\n        let frequencyChecker = TestFrequencyChecker()\n\n        let schedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .actions(AirshipJSON.string(\"neat\")),\n            frequencyChecker: frequencyChecker\n        )\n\n        self.actionExecutor.isReadyBlock = { _, _ in\n            return .ready\n        }\n        \n        frequencyChecker.checkAndIncrementBlock = {\n            return false\n        }\n\n        let result = await self.executor.isReady(\n            preparedSchedule: schedule\n        )\n\n        XCTAssertEqual(result, .skip)\n        XCTAssertTrue(frequencyChecker.checkAndIncrementCalled)\n    }\n\n    func testFrequencyCheckerCheckSuccess() async throws {\n        let frequencyChecker = TestFrequencyChecker()\n\n        let schedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .actions(AirshipJSON.string(\"neat\")),\n            frequencyChecker: frequencyChecker\n        )\n\n        frequencyChecker.checkAndIncrementBlock = {\n            return true\n        }\n\n        self.actionExecutor.isReadyBlock = { _, _ in\n            return .ready\n        }\n\n        let result = await self.executor.isReady(\n            preparedSchedule: schedule\n        )\n\n        XCTAssertEqual(result, .ready)\n        XCTAssertTrue(frequencyChecker.checkAndIncrementCalled)\n        XCTAssertTrue(actionExecutor.isReadyCalled)\n    }\n\n    func testIsValid() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: \"some schedule\",\n            triggers: [],\n            data: .actions(AirshipJSON.null)\n        )\n\n        self.remoteDataAccess.isCurrentBlock = { schedule in\n            XCTAssertEqual(schedule, automationSchedule)\n            return true\n        }\n\n        let result = await self.executor.isValid(schedule: automationSchedule)\n        XCTAssertTrue(result)\n    }\n\n    func testIsValidFals() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: \"some schedule\",\n            triggers: [],\n            data: .actions(AirshipJSON.null)\n        )\n\n        self.remoteDataAccess.isCurrentBlock = { schedule in\n            XCTAssertEqual(schedule, automationSchedule)\n            return false\n        }\n\n        let result = await self.executor.isValid(schedule: automationSchedule)\n        XCTAssertFalse(result)\n    }\n\n    func testExecuteActions() async throws {\n        let actionSchedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .actions(AirshipJSON.string(\"neat\")),\n            frequencyChecker: nil\n        )\n\n        self.actionExecutor.executeBlock = { data, info in\n            XCTAssertEqual(.actions(data), actionSchedule.data)\n            XCTAssertEqual(info, actionSchedule.info)\n            return .finished\n        }\n\n        let result = await self.executor.execute(preparedSchedule: actionSchedule)\n\n        XCTAssertTrue(actionExecutor.executeCalled)\n        XCTAssertEqual(result, .finished)\n    }\n\n    func testExecuteMessage() async throws {\n        let messageSchedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .inAppMessage(self.preparedMessageData),\n            frequencyChecker: nil\n        )\n\n        self.messageExecutor.executeBlock = { data, info in\n            XCTAssertEqual(.inAppMessage(data), messageSchedule.data)\n            XCTAssertEqual(info, messageSchedule.info)\n            return .finished\n        }\n\n        let result = await self.executor.execute(preparedSchedule: messageSchedule)\n        XCTAssertTrue(messageExecutor.executeCalled)\n        XCTAssertEqual(result, .finished)\n    }\n\n    func testExecuteDelegateThrows() async throws {\n        let messageSchedule = PreparedSchedule(\n            info: PreparedScheduleInfo(scheduleID: UUID().uuidString, triggerSessionID: UUID().uuidString, priority: 0),\n            data: .inAppMessage(self.preparedMessageData),\n            frequencyChecker: nil\n        )\n\n        self.messageExecutor.executeBlock = { data, info in\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        let result = await self.executor.execute(preparedSchedule: messageSchedule)\n        XCTAssertTrue(messageExecutor.executeCalled)\n        XCTAssertEqual(result, .retry)\n    }\n\n\n    func testInterruptedAction() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: \"some schedule\",\n            triggers: [],\n            data: .actions(AirshipJSON.string(\"neat\"))\n        )\n\n        let preparedScheduleInfo = PreparedScheduleInfo(scheduleID: \"some schedule\", triggerSessionID: UUID().uuidString, priority: 0)\n\n        self.actionExecutor.interruptedBlock = { info in\n            XCTAssertEqual(info, preparedScheduleInfo)\n            return .retry\n        }\n\n        let result = await self.executor.interrupted(\n            schedule: automationSchedule,\n            preparedScheduleInfo: preparedScheduleInfo\n        )\n\n        XCTAssertTrue(self.actionExecutor.interruptCalled)\n        XCTAssertEqual(result, .retry)\n    }\n\n    func testInterruptedMessage() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: \"some schedule\",\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            )\n        )\n\n        let preparedScheduleInfo = PreparedScheduleInfo(scheduleID: \"some schedule\",  triggerSessionID: UUID().uuidString, priority: 0)\n\n        self.messageExecutor.interruptedBlock = { info in\n            XCTAssertEqual(info, preparedScheduleInfo)\n            return .finish\n        }\n\n        let result = await self.executor.interrupted(\n            schedule: automationSchedule,\n            preparedScheduleInfo: preparedScheduleInfo\n        )\n\n        XCTAssertTrue(self.messageExecutor.interruptCalled)\n        XCTAssertEqual(result, .finish)\n    }\n}\n\n\nfileprivate final class TestExecutorDelegate<T: Sendable>: AutomationExecutorDelegate, @unchecked Sendable {\n\n    \n    typealias ExecutionData = T\n\n    var isReadyCalled: Bool = false\n    var isReadyBlock: (@Sendable (T, PreparedScheduleInfo) -> ScheduleReadyResult)?\n\n    var executeCalled: Bool = false\n    var executeBlock: (@Sendable (T, PreparedScheduleInfo) async throws -> ScheduleExecuteResult)?\n\n    var interruptCalled: Bool = false\n    var interruptedBlock: (@Sendable (PreparedScheduleInfo) async -> InterruptedBehavior)?\n\n    @MainActor\n    func isReady(\n        data: T,\n        preparedScheduleInfo: PreparedScheduleInfo\n    ) -> ScheduleReadyResult {\n        isReadyCalled = true\n        return self.isReadyBlock!(data, preparedScheduleInfo)\n    }\n\n    @MainActor\n    func execute(data: T, preparedScheduleInfo: PreparedScheduleInfo) async throws -> ScheduleExecuteResult {\n        executeCalled = true\n        return try await self.executeBlock!(data, preparedScheduleInfo)\n    }\n\n\n    func interrupted(schedule: AutomationSchedule, preparedScheduleInfo: PreparedScheduleInfo) async -> InterruptedBehavior {\n        interruptCalled = true\n        return await self.interruptedBlock!(preparedScheduleInfo)\n    }\n}\n\nextension ScheduleReadyResult {\n    static var allResults: [ScheduleReadyResult] {\n        return [.ready, .notReady, .invalidate, .skip]\n    }\n}\n\n\nfinal class TestInAppActionRunner: InternalInAppActionRunner, @unchecked Sendable {\n\n    var singleActions: [(String, ActionArguments, ThomasLayoutContext?)] = []\n    var actionPayloads: [(AirshipJSON, ThomasLayoutContext?)] = []\n\n    func runAsync(actions: AirshipJSON, layoutContext: ThomasLayoutContext) {\n        actionPayloads.append((actions, layoutContext))\n    }\n\n    func run(actionName: String, arguments: ActionArguments, layoutContext: ThomasLayoutContext) async -> ActionResult {\n        singleActions.append((actionName, arguments, layoutContext))\n        return .error(AirshipErrors.error(\"not implemented\"))\n    }\n\n    func run(actionName: String, arguments: ActionArguments) async -> ActionResult {\n        singleActions.append((actionName, arguments, nil))\n        return .error(AirshipErrors.error(\"not implemented\"))\n    }\n\n    func runAsync(actions: AirshipJSON) {\n        actionPayloads.append((actions, nil))\n    }\n\n    func run(actions: AirshipJSON) async {\n        actionPayloads.append((actions, nil))\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationPreparerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class AutomationPreparerTest: XCTestCase {\n\n    private let actionPreparer: TestPreparerDelegate<AirshipJSON, AirshipJSON> = TestPreparerDelegate()\n    private let messagePreparer: TestPreparerDelegate<InAppMessage, PreparedInAppMessageData> = TestPreparerDelegate()\n    private let remoteDataAccess: TestRemoteDataAccess = TestRemoteDataAccess()\n    private let deferredResolver: TestDeferredResolver = TestDeferredResolver()\n    private let experiments: TestExperimentDataProvider = TestExperimentDataProvider()\n    private let frequencyLimits: TestFrequencyLimitManager = TestFrequencyLimitManager()\n    private let audienceChecker: TestAudienceChecker = TestAudienceChecker()\n    private var preparer: AutomationPreparer!\n    private let deviceInfoProvider = TestDeviceInfoProvider()\n    private let audienceAdditionalResolver = TestAdditionalAudienceResolver()\n\n    private let triggerContext = AirshipTriggerContext(type: \"some type\", goal: 10, event: .null)\n\n    private var preparedMessageData: PreparedInAppMessageData!\n    private let runtimeConfig: RuntimeConfig = .testConfig()\n\n    @MainActor\n    override func setUp() async throws {\n        self.preparedMessageData = PreparedInAppMessageData(\n            message: InAppMessage(\n                name: \"some name\",\n                displayContent: .custom(.string(\"custom\"))\n            ),\n            displayAdapter: TestDisplayAdapter(),\n            displayCoordinator: TestDisplayCoordinator(),\n            analytics: TestInAppMessageAnalytics(),\n            actionRunner: TestInAppActionRunner()\n        )\n\n        self.preparer = AutomationPreparer(\n            actionPreparer: actionPreparer,\n            messagePreparer: messagePreparer,\n            deferredResolver: deferredResolver,\n            frequencyLimits: frequencyLimits,\n            audienceChecker: audienceChecker,\n            experiments: experiments,\n            remoteDataAccess: remoteDataAccess,\n            config: self.runtimeConfig,\n            deviceInfoProviderFactory: { [provider = self.deviceInfoProvider] contactID in\n                provider.stableContactInfo = StableContactInfo(contactID: contactID ?? UUID().uuidString)\n                return provider\n            },\n            additionalAudienceResolver: audienceAdditionalResolver\n        )\n    \n    }\n\n    func testRequiresUpdate() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            )\n        )\n\n        self.remoteDataAccess.requiresUpdateBlock = { schedule in\n            XCTAssertEqual(automationSchedule, schedule)\n            return true\n        }\n\n        self.remoteDataAccess.waitFullRefreshBlock = { schedule in\n            XCTAssertEqual(automationSchedule, schedule)\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n        \n        XCTAssertTrue(prepareResult.isInvalidate)\n    }\n\n    func testBestEfforRefreshNotCurrent() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            )\n        )\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { schedule in\n            XCTAssertEqual(automationSchedule, schedule)\n            return false\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(prepareResult.isInvalidate)\n    }\n\n    func testAudienceMismatchSkip() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .skip\n            ),\n            compoundAudience: .init(\n                selector: .atomic(.init(newUser: true)),\n                missBehavior: .cancel)\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return nil\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { audience, created, _ in\n            XCTAssertEqual(\n                audience,\n                CompoundDeviceAudienceSelector.combine(\n                    compoundSelector: automationSchedule.compoundAudience!.selector,\n                    deviceSelector: automationSchedule.audience!.audienceSelector\n                )\n            )\n            XCTAssertEqual(created, automationSchedule.created)\n            return .miss\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        /// Uses compound selector if available\n        XCTAssertTrue(prepareResult.isCancelled)\n    }\n    \n    func testAudienceMismatchPenalize() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            )\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return nil\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { audience, created, _ in\n            XCTAssertEqual(audience, .atomic(automationSchedule.audience!.audienceSelector))\n            XCTAssertEqual(created, automationSchedule.created)\n            return .miss\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(prepareResult.isPenalize)\n    }\n    \n    func testAudienceCheckFirst() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .skip\n            ),\n            compoundAudience: .init(\n                selector: .atomic(.init(newUser: true)),\n                missBehavior: .cancel\n            )\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return nil\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { audience, created, _ in\n            XCTAssertEqual(\n                audience,\n                CompoundDeviceAudienceSelector.combine(\n                    compoundSelector: automationSchedule.compoundAudience!.selector,\n                    deviceSelector: automationSchedule.audience!.audienceSelector\n                )\n            )\n            XCTAssertEqual(created, automationSchedule.created)\n            return .miss\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(prepareResult.isCancelled)\n    }\n    \n    func testCompoundAudienceCheck() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: nil,\n            compoundAudience: .init(\n                selector: .atomic(.init(newUser: true)),\n                missBehavior: .cancel)\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return nil\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { audience, created, provider in\n            XCTAssertEqual(audience, automationSchedule.compoundAudience?.selector)\n            XCTAssertEqual(created, automationSchedule.created)\n            return .miss\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(prepareResult.isCancelled)\n    }\n\n\n    func testAudienceMismatchCancel() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .cancel\n            )\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return nil\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { audience, created, _ in\n            XCTAssertEqual(audience, .atomic(automationSchedule.audience!.audienceSelector))\n            XCTAssertEqual(created, automationSchedule.created)\n            return .miss\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(prepareResult.isCancelled)\n    }\n\n    func testContactIDAudienceChecks() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            )\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, provider in\n            let contactID = await provider.stableContactInfo.contactID\n            XCTAssertEqual(\"contact ID\", contactID)\n            return .miss\n        }\n\n        let _ = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n    }\n\n    func testPrepareMessage() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _,  _, _ in\n            return .match\n        }\n\n        let checker = TestFrequencyChecker()\n        await self.frequencyLimits.setCheckerBlock { _ in\n            return checker\n        }\n\n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertEqual(.inAppMessage(message), automationSchedule.data)\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(automationSchedule.campaigns, info.campaigns)\n            XCTAssertEqual(\"contact ID\", info.contactID)\n\n            return preparedData\n        }\n\n        let triggerSessionID = UUID().uuidString\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: triggerSessionID\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertEqual(automationSchedule.identifier, prepared.info.scheduleID)\n        XCTAssertEqual(automationSchedule.campaigns, prepared.info.campaigns)\n        XCTAssertEqual(prepared.data, .inAppMessage(preparedData))\n        XCTAssertEqual(triggerSessionID, prepared.info.triggerSessionID)\n\n        XCTAssertNotNil(prepared.frequencyChecker)\n    }\n    \n    func testPrepareMessageCheckerError() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.frequencyLimits.setCheckerBlock { _ in\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        let triggerSessionID = UUID().uuidString\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: triggerSessionID\n        )\n\n        XCTAssertTrue(result.isSkipped)\n    }\n\n    func testAdditionalAudienceMiss() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            triggers: [],\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .skip\n            )\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return nil\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.audienceAdditionalResolver.setResult(false)\n\n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertFalse(info.additionalAudienceCheckResult)\n            return preparedData\n        }\n\n        let prepareResult = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = prepareResult else {\n            XCTFail()\n            return\n        }\n        XCTAssertFalse(prepared.info.additionalAudienceCheckResult)\n    }\n\n    func testPrepareInvalidMessage() async throws {\n        let invalidBanner = InAppMessageDisplayContent.Banner(\n            heading: nil,\n            body: nil,\n            media: nil,\n            buttons: nil,\n            buttonLayoutType: .stacked,\n            template: .mediaLeft,\n            backgroundColor: InAppMessageColor(hexColorString: \"\"),\n            dismissButtonColor:  InAppMessageColor(hexColorString: \"\"),\n            borderRadius: 5,\n            duration: 100.0,\n            placement: .top\n        )\n\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .banner(invalidBanner))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let checker = TestFrequencyChecker()\n        await self.frequencyLimits.setCheckerBlock { _ in\n            return checker\n        }\n\n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertEqual(.inAppMessage(message), automationSchedule.data)\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(automationSchedule.campaigns, info.campaigns)\n            XCTAssertEqual(\"contact ID\", info.contactID)\n\n            return preparedData\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(result.isSkipped)\n    }\n\n    func testPrepareActions() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .actions(\n                AirshipJSON.string(\"actions payload\")\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let checker = TestFrequencyChecker()\n        await self.frequencyLimits.setCheckerBlock { _ in\n            return checker\n        }\n\n\n        self.actionPreparer.prepareBlock = { actions, info in\n            XCTAssertEqual(.actions(actions), automationSchedule.data)\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(automationSchedule.campaigns, info.campaigns)\n            XCTAssertEqual(\"contact ID\", info.contactID)\n            return actions\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertEqual(automationSchedule.identifier, prepared.info.scheduleID)\n        XCTAssertEqual(automationSchedule.campaigns, prepared.info.campaigns)\n        XCTAssertEqual(prepared.data, .actions(AirshipJSON.string(\"actions payload\")))\n        XCTAssertNotNil(prepared.frequencyChecker)\n    }\n\n    func testPrepareDeferredActions() async throws {\n        let actions = try! AirshipJSON.wrap([\"some\": \"action\"])\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .deferred(\n                DeferredAutomationData(\n                    url: URL(string: \"example://\")!,\n                    retryOnTimeOut: false,\n                    type: .actions\n                )\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let checker = TestFrequencyChecker()\n        await self.frequencyLimits.setCheckerBlock { _ in\n            return checker\n        }\n\n        await self.deferredResolver.onData { request in\n            let data = try! AirshipJSON.wrap([\n                \"audience_match\": true,\n                \"actions\": actions\n            ]).toData()\n            return .success(data)\n        }\n        \n        self.actionPreparer.prepareBlock = { actionsPayload, info in\n            XCTAssertEqual(actionsPayload, actions)\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(automationSchedule.campaigns, info.campaigns)\n            XCTAssertEqual(\"contact ID\", info.contactID)\n            return actions\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertEqual(automationSchedule.identifier, prepared.info.scheduleID)\n        XCTAssertEqual(automationSchedule.campaigns, prepared.info.campaigns)\n        XCTAssertEqual(prepared.data, .actions(actions))\n        XCTAssertNotNil(prepared.frequencyChecker)\n    }\n\n    func testPrepareDeferredMessage() async throws {\n\n        let message = InAppMessage(\n            name: \"some name\",\n            displayContent: .custom(.string(\"custom\")),\n            source: .remoteData\n        )\n\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .deferred(\n                DeferredAutomationData(\n                    url: URL(string: \"example://\")!,\n                    retryOnTimeOut: false,\n                    type: .inAppMessage\n                )\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return self.deviceInfoProvider.stableContactInfo.contactID\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let checker = TestFrequencyChecker()\n        await self.frequencyLimits.setCheckerBlock { _ in\n            return checker\n        }\n\n        let expectedRequest = DeferredRequest(\n            url: URL(string: \"example://\")!,\n            channelID: self.deviceInfoProvider.channelID,\n            contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n            triggerContext: triggerContext,\n            locale: deviceInfoProvider.locale,\n            notificationOptIn: deviceInfoProvider.isUserOptedInPushNotifications\n        )\n\n        await self.deferredResolver.onData { request in\n            XCTAssertEqual(request, expectedRequest)\n            let data = try! AirshipJSON.wrap([\n                \"audience_match\": true,\n                \"message\": message\n            ]).toData()\n            return .success(data)\n        }\n\n        let preparedData = self.preparedMessageData!\n        self.messagePreparer.prepareBlock = { inAppMessage, info in\n            XCTAssertEqual(inAppMessage, message)\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(automationSchedule.campaigns, info.campaigns)\n            XCTAssertEqual(self.deviceInfoProvider.stableContactInfo.contactID, info.contactID)\n            return preparedData\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertEqual(automationSchedule.identifier, prepared.info.scheduleID)\n        XCTAssertEqual(automationSchedule.campaigns, prepared.info.campaigns)\n        XCTAssertNotNil(prepared.frequencyChecker)\n    }\n\n    func testPrepareDeferredAudienceMismatchResult() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .deferred(\n                DeferredAutomationData(\n                    url: URL(string: \"example://\")!,\n                    retryOnTimeOut: false,\n                    type: .inAppMessage\n                )\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .skip\n            )\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.deferredResolver.onData { request in\n            let data = try! AirshipJSON.wrap([\n                \"audience_match\": false\n            ]).toData()\n            return .success(data)\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        XCTAssertTrue(result.isSkipped)\n    }\n\n    func testExperiements() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            frequencyConstraintIDs: [\"constraint\"]\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let checker = TestFrequencyChecker()\n        await self.frequencyLimits.setCheckerBlock { _ in\n            return checker\n        }\n\n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertEqual(.inAppMessage(message), automationSchedule.data)\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(automationSchedule.campaigns, info.campaigns)\n            XCTAssertEqual(\"contact ID\", info.contactID)\n\n            return preparedData\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertEqual(automationSchedule.identifier, prepared.info.scheduleID)\n        XCTAssertEqual(automationSchedule.campaigns, prepared.info.campaigns)\n        XCTAssertEqual(prepared.data, .inAppMessage(preparedData))\n        XCTAssertNotNil(prepared.frequencyChecker)\n    }\n\n    func testExperiments() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\",\n            messageType: \"some message type\"\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let experimentResult = ExperimentResult(\n            channelID: \"some channel\",\n            contactID: \"contact ID\",\n            isMatch: true,\n            reportingMetadata: [AirshipJSON.string(\"reporting\")]\n        )\n\n        self.experiments.onEvaluate = { info, provider in\n            let contactID = await provider.stableContactInfo.contactID\n            XCTAssertEqual(\n                info,\n                MessageInfo(\n                    messageType: automationSchedule.messageType!,\n                    campaigns: automationSchedule.campaigns\n                )\n            )\n            XCTAssertEqual(contactID, \"contact ID\")\n            return experimentResult\n        }\n        \n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(experimentResult, info.experimentResult)\n            return preparedData\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n        XCTAssertEqual(experimentResult, prepared.info.experimentResult)\n    }\n\n    func testExperimentsDefaultMessageType() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            campaigns: \"campaigns\"\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let experimentResult = ExperimentResult(\n            channelID: \"some channel\",\n            contactID: \"contact ID\",\n            isMatch: true,\n            reportingMetadata: [AirshipJSON.string(\"reporting\")]\n        )\n\n        self.experiments.onEvaluate = { info, provider in\n            let contactID = await provider.stableContactInfo.contactID\n            XCTAssertEqual(\n                info,\n                MessageInfo(\n                    messageType: \"transactional\",\n                    campaigns: automationSchedule.campaigns\n                )\n            )\n            XCTAssertEqual(contactID, \"contact ID\")\n            return experimentResult\n        }\n\n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertEqual(automationSchedule.identifier, info.scheduleID)\n            XCTAssertEqual(experimentResult, info.experimentResult)\n            return preparedData\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n        XCTAssertEqual(experimentResult, prepared.info.experimentResult)\n    }\n\n    func testByPassExperiments() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .inAppMessage(\n                InAppMessage(name: \"name\", displayContent: .custom(.null))\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            bypassHoldoutGroups: true,\n            campaigns: \"campaigns\",\n            messageType: \"some message type\"\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        self.experiments.onEvaluate = { info, provider in\n            XCTFail()\n            return nil\n        }\n\n        let preparedData = self.preparedMessageData!\n\n        self.messagePreparer.prepareBlock = { message, info in\n            XCTAssertNil(info.experimentResult)\n            return preparedData\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertNil(prepared.info.experimentResult)\n    }\n\n    func testByPassExperimentsActions() async throws {\n        let automationSchedule = AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .actions(\n                AirshipJSON.string(\"payload\")\n            ),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            audience: AutomationAudience(\n                audienceSelector: DeviceAudienceSelector(),\n                missBehavior: .penalize\n            ),\n            bypassHoldoutGroups: false, // even if false\n            campaigns: \"campaigns\",\n            messageType: \"some message type\"\n        )\n\n        self.remoteDataAccess.contactIDBlock = { _ in\n            return \"contact ID\"\n        }\n\n        self.remoteDataAccess.requiresUpdateBlock = { _ in\n            return false\n        }\n\n        self.remoteDataAccess.bestEffortRefreshBlock = { _ in\n            return true\n        }\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        self.experiments.onEvaluate = { info, provider in\n            XCTFail()\n            return nil\n        }\n\n        self.actionPreparer.prepareBlock = { actions, info in\n            XCTAssertNil(info.experimentResult)\n            return actions\n        }\n\n        let result = await self.preparer.prepare(\n            schedule: automationSchedule,\n            triggerContext: triggerContext,\n            triggerSessionID: UUID().uuidString\n        )\n\n        guard case .prepared(let prepared) = result else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertNil(prepared.info.experimentResult)\n    }\n}\n\n\nfinal class TestPreparerDelegate<In: Sendable, Out: Sendable>: AutomationPreparerDelegate, @unchecked Sendable {\n    typealias PrepareDataIn = In\n    typealias PrepareDataOut = Out\n\n    var cancelledCalled: Bool = false\n    var cancelledBlock: (@Sendable (String) async -> Void)?\n\n    func cancelled(scheduleID: String) async {\n        cancelledCalled = true\n        await cancelledBlock!(scheduleID)\n    }\n\n    var prepareCalled: Bool = false\n    var prepareBlock: (@Sendable (In, PreparedScheduleInfo) async -> Out)?\n\n    func prepare(data: In, preparedScheduleInfo: PreparedScheduleInfo) async throws -> Out {\n        prepareCalled = true\n        return await prepareBlock!(data, preparedScheduleInfo)\n    }\n}\n\n\n\n\nextension SchedulePrepareResult {\n    var isInvalidate: Bool {\n        switch (self) {\n        case .invalidate: return true\n        default: return false\n        }\n    }\n\n    var isPrepared: Bool {\n        switch (self) {\n        case .prepared(_): return true\n        default: return false\n        }\n    }\n\n    var isCancelled: Bool {\n        switch (self) {\n        case .cancel: return true\n        default: return false\n        }\n    }\n\n    var isSkipped: Bool {\n        switch (self) {\n        case .skip: return true\n        default: return false\n        }\n    }\n\n    var isPenalize: Bool {\n        switch (self) {\n        case .penalize: return true\n        default: return false\n        }\n    }\n}\n\n\nfinal class TestDeviceInfoProvider: AudienceDeviceInfoProvider, @unchecked Sendable {\n    var sdkVersion: String = \"1.0.0\"\n\n\n    var isAirshipReady: Bool = false\n\n    var tags: Set<String> = Set()\n\n    var isChannelCreated: Bool = true\n\n    var channelID: String = UUID().uuidString\n\n    var locale: Locale = Locale.current\n\n    var appVersion: String?\n\n    var permissions: [AirshipCore.AirshipPermission : AirshipCore.AirshipPermissionStatus] = [:]\n\n    var isUserOptedInPushNotifications: Bool = false\n\n    var analyticsEnabled: Bool = false\n\n    var installDate: Date = Date()\n\n    var stableContactInfo: StableContactInfo\n\n    init(contactID: String = UUID().uuidString) {\n        self.stableContactInfo = StableContactInfo(contactID: contactID)\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationStoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class AutomationStoreTest: XCTestCase {\n\n    private let store: AutomationStore = AutomationStore(\n        appKey: UUID().uuidString,\n        inMemory: true\n    )\n\n    func testUpsertNewSchedules() async throws {\n        let data = [\"foo\": makeSchedule(identifer: \"foo\"), \"bar\": makeSchedule(identifer: \"bar\")]\n\n        let result = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\"]) { identifier, existing in\n            XCTAssertNil(existing)\n            return data[identifier]!\n        }\n\n        XCTAssertEqual(result, [data[\"foo\"], data[\"bar\"]])\n    }\n\n    func testUpsertMixedSchedules() async throws {\n        let original = [\"foo\": makeSchedule(identifer: \"foo\"), \"bar\": makeSchedule(identifer: \"bar\")]\n\n        var result = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\"]) { identifier, existing in\n            XCTAssertNil(existing)\n            return original[identifier]!\n        }\n\n        XCTAssertEqual(result, [original[\"foo\"], original[\"bar\"]])\n\n        var updated = original\n        updated[\"baz\"] = makeSchedule(identifer: \"baz\")\n        updated[\"foo\"]?.scheduleState = .finished\n\n        result = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\", \"baz\"]) { [updated] identifier, existing in\n            if let existing = existing {\n                XCTAssertTrue(existing.equalsIgnoringLastModified(original[identifier]!))\n            }\n            return updated[identifier]!\n        }\n\n        XCTAssertEqual(result, [updated[\"foo\"], updated[\"bar\"], updated[\"baz\"]])\n    }\n\n    func testUpdate() async throws {\n        let originalFoo = makeSchedule(identifer: \"foo\")\n\n        _ = try await self.store.upsertSchedules(scheduleIDs: [\"foo\"]) { identifier, existing in\n            return originalFoo\n        }\n\n        let triggerInfo = TriggeringInfo(\n            context: AirshipTriggerContext(type: \"foo\", goal: 10.0, event: \"event\"),\n            date: Date.distantPast\n        )\n\n        let preparedInfo = PreparedScheduleInfo(\n            scheduleID: \"full\",\n            productID: \"some product\",\n            campaigns: \"campaigns\",\n            contactID: \"some contact\",\n            experimentResult: ExperimentResult(\n                channelID: \"some channel\",\n                contactID: \"some contact\",\n                isMatch: true,\n                reportingMetadata: [AirshipJSON.string(\"reporing\")]\n            ),\n            triggerSessionID: \"some trigger session id\",\n            priority: 0\n        )\n\n        let date = Date()\n        let result = try await self.store.updateSchedule(scheduleID: \"foo\") { data in\n            data.executionCount = 100\n            data.triggerInfo = triggerInfo\n            data.schedule.group = \"bar\"\n            data.preparedScheduleInfo = preparedInfo\n            data.scheduleState = .paused\n            data.scheduleStateChangeDate = date\n        }\n\n        var expected = originalFoo\n        expected.schedule.group = \"bar\"\n        expected.executionCount = 100\n        expected.triggerInfo = triggerInfo\n        expected.preparedScheduleInfo = preparedInfo\n        expected.scheduleStateChangeDate = date\n        expected.scheduleState = .paused\n        XCTAssert(result!.equalsIgnoringLastModified(expected))\n    }\n\n    func testUpsertFullData() async throws {\n        var schedule = self.makeSchedule(identifer: \"full\")\n        schedule.triggerInfo = TriggeringInfo(\n            context: AirshipTriggerContext(type: \"foo\", goal: 10.0, event: \"event\"),\n            date: Date.distantPast\n        )\n\n        schedule.preparedScheduleInfo = PreparedScheduleInfo(\n            scheduleID: \"full\",\n            productID: \"some product\",\n            campaigns: \"campaigns\",\n            contactID: \"some contact\",\n            experimentResult: ExperimentResult(\n                channelID: \"some channel\",\n                contactID: \"some contact\",\n                isMatch: true,\n                reportingMetadata: [AirshipJSON.string(\"reporing\")]\n            ),\n            triggerSessionID: \"some trigger session id\",\n            priority: 0\n        )\n\n\n        let batchUpsertResult = try await self.store.upsertSchedules(scheduleIDs: [\"full\"]) { [schedule] identifier, existing in\n            return schedule\n        }\n\n        XCTAssertEqual(batchUpsertResult.count, 1)\n\n        let fetchResult = try await self.store.getSchedule(scheduleID: \"full\")\n        XCTAssertNotNil(fetchResult)\n        XCTAssertGreaterThanOrEqual(fetchResult!.lastScheduleModifiedDate, batchUpsertResult[0].lastScheduleModifiedDate)\n    }\n\n    func testUpdateDoesNotExist() async throws {\n        let result = try await self.store.updateSchedule(scheduleID: \"baz\") { data in\n            XCTFail()\n        }\n\n        XCTAssertNil(result)\n    }\n\n    func testGetSchedules() async throws {\n        let original = [\"foo\": makeSchedule(identifer: \"foo\"), \"bar\": makeSchedule(identifer: \"bar\")]\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\"]) { identifier, existing in\n            return original[identifier]!\n        }\n\n        let foo = try await self.store.getSchedule(scheduleID: \"foo\")\n        XCTAssertTrue(foo!.equalsIgnoringLastModified(original[\"foo\"]!))\n\n        let bar = try await self.store.getSchedule(scheduleID: \"bar\")\n        XCTAssertTrue(bar!.equalsIgnoringLastModified(original[\"bar\"]!))\n\n        let doesNotExist = try await self.store.getSchedule(scheduleID: \"doesNotExist\")\n        XCTAssertNil(doesNotExist)\n    }\n\n    func testGetSchedulesByGroup() async throws {\n        let original = [\n            \"foo\": makeSchedule(identifer: \"foo\", group: \"groupA\"),\n            \"bar\": makeSchedule(identifer: \"bar\"),\n            \"baz\": makeSchedule(identifer: \"baz\", group: \"groupA\")\n        ]\n\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\", \"baz\"]) { identifier, existing in\n            return original[identifier]!\n        }\n\n        let groupA = try await self.store.getSchedules(group: \"groupA\").sorted { l, r in\n            return l.schedule.identifier > r.schedule.identifier\n        }\n\n        XCTAssertTrue([original[\"foo\"]!, original[\"baz\"]!].equalsIgnoringLastModified(groupA))\n    }\n\n    func testDeleteIdentifiers() async throws {\n        let original = [\n            \"foo\": makeSchedule(identifer: \"foo\", group: \"groupA\"),\n            \"bar\": makeSchedule(identifer: \"bar\"),\n            \"baz\": makeSchedule(identifer: \"baz\", group: \"groupA\")\n        ]\n\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\", \"baz\"]) { identifier, existing in\n            return original[identifier]!\n        }\n\n        try await self.store.deleteSchedules(scheduleIDs: [\"foo\", \"doesNotExist\"])\n\n        let remaining = try await self.store.getSchedules().sorted { l, r in\n            return l.schedule.identifier > r.schedule.identifier\n        }\n\n        XCTAssertTrue([original[\"baz\"]!, original[\"bar\"]!].equalsIgnoringLastModified(remaining))\n    }\n\n    func testDeleteGroup() async throws {\n        let original = [\n            \"foo\": makeSchedule(identifer: \"foo\", group: \"groupA\"),\n            \"bar\": makeSchedule(identifer: \"bar\", group: \"groupB\"),\n            \"baz\": makeSchedule(identifer: \"baz\", group: \"groupA\")\n        ]\n\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"foo\", \"bar\", \"baz\"]) { identifier, existing in\n            return original[identifier]!\n        }\n\n        try await self.store.deleteSchedules(group: \"groupA\")\n\n        let remaining = try await self.store.getSchedules().sorted { l, r in\n            return l.schedule.identifier > r.schedule.identifier\n        }\n\n        XCTAssertTrue([original[\"bar\"]!].equalsIgnoringLastModified(remaining))\n    }\n\n    func testAssociatedData() async throws {\n        let associatedData = try AirshipJSON.string(\"some data\").toData()\n        var schedule = self.makeSchedule(identifer: \"bar\")\n        schedule.associatedData = associatedData\n\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"bar\"]) { [schedule] identifier, existing in\n            return schedule\n        }\n\n        let fromStore = try await self.store.getAssociatedData(scheduleID: \"bar\")\n\n        XCTAssertEqual(fromStore, associatedData)\n    }\n\n    func testAssociatedDataNull() async throws {\n        let schedule = self.makeSchedule(identifer: \"bar\")\n\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"bar\"]) { [schedule] identifier, existing in\n            return schedule\n        }\n\n        let fromStore = try await self.store.getAssociatedData(scheduleID: \"bar\")\n\n        XCTAssertNil(fromStore)\n    }\n\n    func testAssociatedNoSchedule() async throws {\n        let fromStore = try await self.store.getAssociatedData(scheduleID: \"bar\")\n        XCTAssertNil(fromStore)\n    }\n\n    func testIsCurrent() async throws {\n        let schedule = makeSchedule(identifer: \"test\")\n        let _ = try await self.store.upsertSchedules(scheduleIDs: [\"test\"]) { identifier, existing in\n            return schedule\n        }\n\n        let fullSchedule = try await self.store.getSchedule(scheduleID: \"test\")!\n\n        var isCurrent = try await self.store.isCurrent(\n            scheduleID: \"test\",\n            lastScheduleModifiedDate: fullSchedule.lastScheduleModifiedDate,\n            scheduleState: .idle\n        )\n        XCTAssertTrue(isCurrent)\n\n        isCurrent = try await self.store.isCurrent(\n            scheduleID: \"test\",\n            lastScheduleModifiedDate: fullSchedule.lastScheduleModifiedDate,\n            scheduleState: .paused\n        )\n        XCTAssertFalse(isCurrent)\n\n        isCurrent = try await self.store.isCurrent(\n            scheduleID: \"test\",\n            lastScheduleModifiedDate: fullSchedule.lastScheduleModifiedDate.addingTimeInterval(1),\n            scheduleState: .idle\n        )\n        XCTAssertFalse(isCurrent)\n    }\n\n    func testIsCurrentNoSchedule() async throws {\n        let isCurrent = try await self.store.isCurrent(\n            scheduleID: \"fake identifier\",\n            lastScheduleModifiedDate: Date(),\n            scheduleState: .paused\n        )\n        XCTAssertFalse(isCurrent)\n    }\n\n    private func makeSchedule(identifer: String, group: String? = nil) -> AutomationScheduleData {\n        let schedule = AutomationSchedule(\n            identifier: identifer,\n            data: .inAppMessage(\n                InAppMessage(\n                    name: \"some name\",\n                    displayContent: .custom(.string(\"Custom\"))\n                )\n            ),\n            triggers: [],\n            created: Date.distantPast,\n            group: group\n        )\n\n        return AutomationScheduleData(\n            schedule: schedule,\n            scheduleState: .idle,\n            lastScheduleModifiedDate: .distantPast,\n            scheduleStateChangeDate: Date.distantPast,\n            executionCount: 0,\n            triggerSessionID: UUID().uuidString\n        )\n    }\n}\n\nextension [AutomationScheduleData] {\n    func equalsIgnoringLastModified(_ other: [AutomationScheduleData]) -> Bool {\n        guard count == other.count else { return false }\n        return zip(self, other).allSatisfy { $0.equalsIgnoringLastModified($1) }\n    }\n}\n\nextension AutomationScheduleData {\n    func equalsIgnoringLastModified(_ other: AutomationScheduleData) -> Bool {\n        schedule == other.schedule &&\n        scheduleState == other.scheduleState &&\n        scheduleStateChangeDate == other.scheduleStateChangeDate &&\n        executionCount == other.executionCount &&\n        triggerInfo == other.triggerInfo &&\n        preparedScheduleInfo == other.preparedScheduleInfo &&\n        associatedData == other.associatedData &&\n        triggerSessionID == other.triggerSessionID\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/AutomationTriggerProcessorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class AutomationTriggerProcessorTest: XCTestCase, @unchecked Sendable {\n    private let date: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n    private let store: TestTriggerStore = TestTriggerStore()\n    private var processor: AutomationTriggerProcessor!\n    private var history: (any AutomationEventsHistory)!\n\n    override func setUp() async throws {\n        self.history = DefaultAutomationEventsHistory(clock: date)\n        self.processor = AutomationTriggerProcessor(store: store, history: self.history, date: date)\n    }\n    \n    func testRestoreSchedule() async throws {\n        self.store.stored = [\n            TriggerData(\n                scheduleID: \"unused-schedule-id\",\n                triggerID: \"unused-trigger-id\",\n                count: 0,\n                children: [:]\n            )\n        ]\n\n        let trigger = AutomationTrigger.event(.init(id: \"trigger-id\", type: .activeSession, goal: 1))\n\n        XCTAssertEqual(1, self.store.stored.count)\n        try await restoreSchedules(trigger: trigger)\n        XCTAssertEqual(0, self.store.stored.count)\n        \n        await self.processor.processEvent(.stateChanged(state: TriggerableState(appSessionID: \"foreground\")))\n\n        let result = await takeNext().first\n        XCTAssertEqual(\"schedule-id\", result?.scheduleID)\n        XCTAssertEqual(TriggerExecutionType.execution, result?.triggerExecutionType)\n        XCTAssertEqual(TriggeringInfo(\n            context: AirshipTriggerContext(\n                type: \"active_session\",\n                goal: 1.0,\n                event: .null),\n            date: self.date.now), result?.triggerInfo)\n    }\n    \n    func testUpdateTriggersResendsStatus() async throws {\n        let trigger = AutomationTrigger.event(.init(id: \"trigger-id\", type: .activeSession, goal: 1))\n\n        await self.processor.processEvent(.stateChanged(state: TriggerableState()))\n        \n        try await restoreSchedules(trigger: trigger)\n        \n        await self.processor.processEvent(.stateChanged(state: TriggerableState(appSessionID: \"foreground\")))\n        \n\n        await self.processor.updateScheduleState(scheduleID: \"schedule-id\", state: .idle)\n\n        let result = await takeNext(count: 2).last\n        XCTAssertEqual(\"schedule-id\", result?.scheduleID)\n        XCTAssertEqual(TriggerExecutionType.execution, result?.triggerExecutionType)\n        XCTAssertEqual(TriggeringInfo(\n            context: AirshipTriggerContext(\n                type: \"active_session\",\n                goal: 1.0,\n                event: .null),\n            date: self.date.now), result?.triggerInfo)\n    }\n    \n    func testCancelSchedule() async throws {\n        \n        try await restoreSchedules()\n        \n        await self.processor.processEvent(.event(type: .appInit))\n        \n        XCTAssertEqual(\n            TriggerData(\n                scheduleID: \"schedule-id\",\n                triggerID: \"default-trigger\",\n                count: 1,\n                children: [:]\n            ),\n            self.store.stored.last\n        )\n        \n        await self.processor.cancel(scheduleIDs: [\"schedule-id\"])\n        XCTAssert(self.store.stored.isEmpty)\n\n        await self.processor.processEvent(.event(type: .appInit))\n        \n        let result = await takeNext()\n        XCTAssert(result.isEmpty)\n    }\n    \n    func testCancelWithGroup() async throws {\n        let trigger = AutomationTrigger.event(.init(id: \"trigger-id-2\", type: .appInit, goal: 2))\n        let schedule = defaultSchedule(trigger: trigger, group: \"test-group\")\n        \n        try await self.processor.restoreSchedules([schedule])\n        await self.processor.processEvent(.event(type: .appInit))\n        \n        XCTAssertEqual(\n            TriggerData(\n                scheduleID: \"schedule-id\",\n                triggerID: \"trigger-id-2\",\n                count: 1,\n                children: [:]\n            ),\n            self.store.stored.last\n        )\n\n        await self.processor.cancel(group: \"test-group\")\n        XCTAssert(self.store.stored.isEmpty)\n\n        await self.processor.processEvent(.event(type: .appInit))\n        \n        let result = await takeNext()\n        XCTAssert(result.isEmpty)\n    }\n    \n    func testProcessEventEmitsResults() async throws {\n        let trigger = AutomationTrigger.event(.init(id: \"trigger-id\", type: .appInit, goal: 1))\n\n        try await restoreSchedules(trigger: trigger)\n        \n        await self.processor.processEvent(.event(type: .appInit))\n        \n        XCTAssertEqual(\n            TriggerData(\n                scheduleID: \"schedule-id\",\n                triggerID: \"trigger-id\",\n                count: 0,\n                children: [:]\n            ),\n            self.store.stored.last\n        )\n\n        let result = await takeNext()\n        XCTAssertNotNil(result)\n    }\n    \n    @MainActor\n    func testProcessEventEmitsNothingOnPause() async throws {\n        let trigger = AutomationTrigger.event(.init(id: \"trigger-id\", type: .appInit, goal: 1))\n\n        try await restoreSchedules(trigger: trigger)\n        \n        await self.processor.processEvent(.event(type: .appInit))\n        \n        var result = await takeNext()\n        XCTAssertNotNil(result)\n        \n        await self.processor.processEvent(.event(type: .appInit))\n        \n        result = await takeNext()\n        XCTAssertNotNil(result)\n        \n        self.processor.setPaused(true)\n        \n        await self.processor.processEvent(.event(type: .appInit))\n        \n        result = await takeNext()\n        XCTAssert(result.isEmpty)\n    }\n    \n    func testReplayEvents() async {\n        let triggerOld = AutomationTrigger.event(.init(id: \"trigger-id\", type: .appInit, goal: 2))\n        let oldSchedule = defaultSchedule(trigger: triggerOld)\n        \n        let event = AutomationEvent.event(type: .appInit)\n        \n        await self.processor.updateSchedules([oldSchedule])\n        await self.processor.processEvent(event)\n        await self.history.add(event)\n        \n        let triggerNew = AutomationTrigger.event(.init(id: \"new-trigger-id\", type: .appInit, goal: 1))\n        let scheduleNew = AutomationScheduleData(\n            schedule: AutomationSchedule(\n                identifier: \"new-schedule-id\",\n                data: .actions(.null),\n                triggers: [triggerNew],\n                group: nil\n            ),\n            scheduleState: .idle,\n            lastScheduleModifiedDate: self.date.now,\n            scheduleStateChangeDate: self.date.now,\n            executionCount: 0,\n            triggerSessionID: UUID().uuidString\n        )\n        \n        await self.processor.updateSchedules([oldSchedule, scheduleNew])\n        let results = await takeNext()\n        XCTAssertEqual(results.count, 1)\n        XCTAssertEqual(\"new-schedule-id\", results.first?.scheduleID)\n        XCTAssertEqual(TriggerExecutionType.execution, results.first?.triggerExecutionType)\n    }\n    \n    private func restoreSchedules(trigger: AutomationTrigger? = nil) async throws {\n        let trigger = trigger ?? AutomationTrigger.event(.init(id: \"default-trigger\", type: .appInit, goal: 2))\n\n        let schedule = defaultSchedule(trigger: trigger)\n        \n        try await self.processor.restoreSchedules([schedule])\n    }\n    \n    private func defaultSchedule(trigger: AutomationTrigger, group: String? = nil) -> AutomationScheduleData {\n        return AutomationScheduleData(\n            schedule: AutomationSchedule(\n                identifier: \"schedule-id\",\n                data: .actions(.null),\n                triggers: [trigger],\n                group: group\n            ),\n            scheduleState: .idle,\n            lastScheduleModifiedDate: self.date.now,\n            scheduleStateChangeDate: self.date.now,\n            executionCount: 0,\n            triggerSessionID: UUID().uuidString\n        )\n    }\n    \n    \n    \n    @discardableResult\n    private func takeNext(count: UInt = 1, timeout: Int = 1) async -> [TriggerResult] {\n        let collectTask = Task {\n            var result: [TriggerResult] = []\n            var iterator = await self.processor.triggerResults.makeAsyncIterator()\n            while result.count < count, !Task.isCancelled {\n                if let next = await iterator.next() {\n                    result.append(next)\n                }\n            }\n            \n            return result\n        }\n        \n        let cancel = DispatchWorkItem {\n            collectTask.cancel()\n        }\n        \n        DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(timeout), execute: cancel)\n        \n        let result = await collectTask.result.get()\n        cancel.cancel()\n        return result\n    }\n}\n\nfinal class TestTriggerStore: TriggerStoreProtocol, @unchecked Sendable {\n    \n    var stored: [TriggerData] = []\n\n    func getTrigger(scheduleID: String, triggerID: String) async throws -> AirshipAutomation.TriggerData? {\n        return stored.first(where: { $0.triggerID == triggerID && $0.scheduleID == scheduleID })\n    }\n    \n    func upsertTriggers(_ triggers: [TriggerData]) async throws {\n        let incomingIDs = triggers.map { $0.triggerID }\n        stored.removeAll { incomingIDs.contains($0.triggerID) }\n        stored.append(contentsOf: triggers)\n    }\n    \n    func deleteTriggers(excludingScheduleIDs: Set<String>) async throws {\n        stored.removeAll(where: { !excludingScheduleIDs.contains($0.scheduleID) })\n    }\n    \n    func deleteTriggers(scheduleIDs: [String]) async throws {\n        stored.removeAll(where: { scheduleIDs.contains($0.scheduleID) })\n    }\n    \n    func deleteTriggers(scheduleID: String, triggerIDs: Set<String>) async throws {\n        stored.removeAll { $0.scheduleID == scheduleID && triggerIDs.contains($0.triggerID) }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/ExecutionWindowProcessorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class ExecutionWindowProcessorTest: XCTestCase {\n\n    fileprivate struct Evaluated : Equatable, Sendable{\n        let window: ExecutionWindow\n        let date: Date\n    }\n\n    private let date: UATestDate = UATestDate(dateOverride: Date())\n    private let taskSleeper: TestTaskSleeper = TestTaskSleeper()\n    private let notificationCenter: NotificationCenter = NotificationCenter()\n    private var processor: ExecutionWindowProcessor!\n\n    private let window: ExecutionWindow = try! ExecutionWindow(include: [.weekly(daysOfWeek: [1])])\n\n    private var evaluatedWindows: AirshipAtomicValue<[Evaluated]> = .init([])\n    private var onResult: AirshipAtomicValue<(() throws -> ExecutionWindowResult)?> = .init(nil)\n\n    override func setUpWithError() throws {\n        processor = ExecutionWindowProcessor(\n            taskSleeper: taskSleeper,\n            date: date,\n            notificationCenter: notificationCenter,\n            onEvaluate: { window, date in\n                self.evaluatedWindows.update { t in\n                    var mutated = t\n                    mutated.append(Evaluated(window: window, date: date))\n                    return mutated\n                }\n                return try self.onResult.value!()\n            }\n        )\n    }\n\n    @MainActor\n    func testIsAvailable() throws {\n        onResult.value = { throw AirshipErrors.error(\"Error!\") }\n        XCTAssertFalse(processor.isActive(window: window))\n\n        onResult.value = { return .retry(100) }\n        XCTAssertFalse(processor.isActive(window: window))\n\n        onResult.value = { return .now }\n        XCTAssertTrue(processor.isActive(window: window))\n\n        let evaluated = Evaluated(window: window, date: date.now)\n        XCTAssertEqual(evaluatedWindows.value, [evaluated, evaluated, evaluated])\n    }\n\n    func testProcessError() async throws {\n        let setup = expectation(description: \"setup\")\n\n        let task = Task {\n            await self.fulfillment(of: [setup])\n            await processor.process(window: window)\n        }\n\n        taskSleeper.onSleep = { _ in\n            task.cancel()\n        }\n\n        onResult.value = {\n            throw AirshipErrors.error(\"Error!\")\n        }\n        setup.fulfill()\n\n        await task.value\n\n        XCTAssertEqual(taskSleeper.sleeps, [24.0 * 60 * 60])\n        let evaluated = Evaluated(window: window, date: date.now)\n        XCTAssertEqual(evaluatedWindows.value, [evaluated])\n    }\n\n    func testProcessRetry() async throws {\n        let setup = expectation(description: \"setup\")\n\n        let task = Task {\n            await self.fulfillment(of: [setup])\n            await processor.process(window: window)\n        }\n\n        taskSleeper.onSleep = { _ in\n            task.cancel()\n        }\n\n        onResult.value = {\n            .retry(100.0)\n        }\n        setup.fulfill()\n\n        await task.value\n\n        XCTAssertEqual(taskSleeper.sleeps, [100.0])\n        let evaluated = Evaluated(window: window, date: date.now)\n        XCTAssertEqual(evaluatedWindows.value, [evaluated])\n    }\n\n    func testLocaleChangeRechecks() async throws {\n        let setup = expectation(description: \"setup\")\n\n        let task = Task {\n            await self.fulfillment(of: [setup])\n            await processor.process(window: window)\n        }\n\n        taskSleeper.onSleep = { sleeps in\n            if sleeps.count == 1 {\n                // Actually sleep on the first one to avoid a busy loop\n                try await Task.sleep(nanoseconds: 1000000)\n            } else {\n                task.cancel()\n            }\n        }\n\n        onResult.value = {\n            .retry(1000.0)\n        }\n\n        setup.fulfill()\n\n        notificationCenter.post(name: .NSSystemTimeZoneDidChange, object: nil)\n\n        await task.value\n\n        XCTAssertEqual(taskSleeper.sleeps, [1000.0, 1000.0])\n        let evaluated = Evaluated(window: window, date: date.now)\n        XCTAssertEqual(evaluatedWindows.value, [evaluated, evaluated])\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/PreparedScheduleInfoTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\n\nfinal class PreparedScheduleInfoTest: XCTestCase {\n    func testMissingTriggerSessionID() throws {\n        let json = \"\"\"\n        {\n            \"scheduleID\": \"some schedule\"\n        }\n        \"\"\"\n\n        let info = try JSONDecoder().decode(\n            PreparedScheduleInfo.self,\n            from: json.data(using: .utf8)!\n        )\n\n        XCTAssertEqual(\"some schedule\", info.scheduleID)\n        XCTAssertFalse(info.triggerSessionID.isEmpty)\n\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Automation/Engine/PreparedTriggerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class PreparedTriggerTest: XCTestCase {\n    let date = UATestDate(offset: 0, dateOverride: Date())\n    \n    func testScheduleDatesUpdate() {\n        var trigger = EventAutomationTrigger(type: .appInit, goal: 1)\n\n        let instance = makeTrigger(trigger: .event(trigger))\n        XCTAssertNil(instance.startDate)\n        XCTAssertNil(instance.endDate)\n        XCTAssertEqual(0, instance.priority)\n\n        trigger.goal = 3\n\n        instance.update(trigger: .event(trigger), startDate: date.now, endDate: date.now, priority: 3)\n        XCTAssertEqual(date.now, instance.startDate)\n        XCTAssertEqual(date.now, instance.endDate)\n        XCTAssertEqual(3, instance.priority)\n        XCTAssertEqual(.event(trigger), instance.trigger)\n\n    }\n    \n    func testActivateTrigger() {\n        let initialState = TriggerData(\n            scheduleID: \"test\",\n            triggerID: \"trigger-id\",\n            count: 1,\n            children: [:]\n        )\n\n        let execution = makeTrigger(type: .execution, state: initialState)\n        XCTAssertFalse(execution.isActive)\n        execution.activate()\n        XCTAssert(execution.isActive)\n        XCTAssertEqual(initialState, execution.triggerData)\n\n        let cancellation = makeTrigger(type: .delayCancellation, state: initialState)\n        XCTAssertFalse(cancellation.isActive)\n        cancellation.activate()\n        XCTAssert(cancellation.isActive)\n        XCTAssertEqual(0, cancellation.triggerData.count)\n    }\n    \n    func testDiable() {\n        let instance = makeTrigger()\n        XCTAssertFalse(instance.isActive)\n        instance.activate()\n        XCTAssert(instance.isActive)\n        instance.disable()\n        XCTAssertFalse(instance.isActive)\n    }\n    \n    func testProcessEventHappyPath() throws {\n        let trigger = EventAutomationTrigger(type: .appInit, goal: 2)\n        let instance = makeTrigger(trigger: .event(trigger), type: .execution)\n        instance.activate()\n        \n        XCTAssertEqual(0, instance.triggerData.count)\n\n        var result = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(1, result?.triggerData.count)\n        XCTAssertNil(result?.triggerResult)\n\n        result = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(0, result?.triggerData.count)\n\n        let report = try XCTUnwrap(result?.triggerResult)\n        XCTAssertEqual(\"test-schedule\", report.scheduleID)\n        XCTAssertEqual(TriggerExecutionType.execution, report.triggerExecutionType)\n        XCTAssertEqual(AirshipTriggerContext(type: \"app_init\", goal: 2, event: .null), report.triggerInfo.context)\n        XCTAssertEqual(date.now, report.triggerInfo.date)\n    }\n    \n    func testProcessEventDoesNothing() {\n        let trigger = EventAutomationTrigger(type: .appInit, goal: 1)\n\n        let instance = makeTrigger(trigger: .event(trigger))\n\n        XCTAssertNil(instance.process(event: .event(type: .appInit)))\n        \n        instance.activate()\n        instance.update(\n            trigger: .event(trigger),\n            startDate: self.date.now.advanced(by: 1),\n            endDate: nil,\n            priority: 0\n        )\n\n        XCTAssertNil(instance.process(event: .event(type: .appInit)))\n\n        instance.update(\n            trigger: .event(trigger),\n            startDate: nil,\n            endDate: nil,\n            priority: 0\n        )\n        \n        XCTAssertNotNil(instance.process(event: .event(type: .appInit)))\n    }\n    \n    func testProcessEventDoesNothingForInvalidEventType() {\n        let trigger = EventAutomationTrigger(type: .background, goal: 1)\n        let instance = makeTrigger(trigger: .event(trigger))\n        instance.activate()\n        \n        XCTAssertNil(instance.process(event: .event(type: .foreground)))\n        XCTAssertNotNil(instance.process(event: .event(type: .background)))\n    }\n    \n    func testEventProcessingTypes() {\n        let check: (EventAutomationTriggerType, AutomationEvent) -> TriggerData? = { type, event in\n            let trigger = EventAutomationTrigger(type: type, goal: 3)\n            let instance = self.makeTrigger(trigger: .event(trigger))\n            instance.activate()\n            let result = instance.process(event: event)\n            return result?.triggerData\n        }\n        \n        for eventType in EventAutomationTriggerType.allCases {\n            let event = AutomationEvent.event(type: eventType, data: .null)\n            XCTAssertEqual(1, check(eventType, event)?.count)\n        }\n        \n        XCTAssertEqual(2, check(.customEventValue, .event(type: .customEventValue, data: .null, value: 2))?.count)\n        XCTAssertEqual(2, check(.customEventCount, .event(type: .customEventCount, data: .null, value: 2))?.count)\n        \n        let instance = makeTrigger()\n        instance.activate()\n\n        let state = TriggerableState(appSessionID: \"session-id\", versionUpdated: \"123\")\n        let _ = instance.process(event: .stateChanged(state: state))\n    }\n    \n    func testCompoundAndTrigger() throws {\n        let trigger = AutomationTrigger.compound(\n            .init(\n                id: \"compound\",\n                type: .and,\n                goal: 2,\n                children: [\n                    .init(trigger: .event(.init(id: \"foreground\", type: .foreground, goal: 1))),\n                    .init(trigger: .event(.init(id: \"init\", type: .appInit, goal: 1)))\n                ]\n            )\n        )\n        \n        let instance = makeTrigger(trigger: trigger)\n        \n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .background))\n        XCTAssertNil(state?.triggerResult)\n\n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        \n        var foreground = try XCTUnwrap(state?.triggerData.children[\"foreground\"])\n        XCTAssertEqual(1, foreground.count)\n\n        var appinit = try XCTUnwrap(state?.triggerData.children[\"init\"])\n        XCTAssertEqual(0, appinit.count)\n\n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        \n        /// Children reset once they are all triggered\n        foreground = try XCTUnwrap(state?.triggerData.children[\"foreground\"])\n        XCTAssertEqual(0, foreground.count)\n        appinit = try XCTUnwrap(state?.triggerData.children[\"init\"])\n        XCTAssertEqual(0, appinit.count)\n\n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n\n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNotNil(state?.triggerResult)\n    }\n    \n    func testCompoundAndComplexTrigger() throws {\n        let trigger = AutomationTrigger.compound(\n            .init(\n                id: \"compound\",\n                type: .and,\n                goal: 2,\n                children: [\n                    .init(trigger: .event(.init(id: \"foreground\", type: .foreground, goal: 1)), resetOnIncrement: true),\n                    .init(trigger: .event(.init(id: \"init\", type: .appInit, goal: 1)), resetOnIncrement: true)\n                ]\n            )\n        )\n        \n        let instance = makeTrigger(trigger: trigger)\n        \n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .background))\n        XCTAssertNil(state?.triggerResult)\n\n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        \n        var foreground = try XCTUnwrap(state?.triggerData.children[\"foreground\"])\n        XCTAssertEqual(1, foreground.count)\n\n        var appinit = try XCTUnwrap(state?.triggerData.children[\"init\"])\n        XCTAssertEqual(0, appinit.count)\n\n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        \n        foreground = try XCTUnwrap(state?.triggerData.children[\"foreground\"])\n        XCTAssertEqual(0, foreground.count) //1 because reset on increment is false\n\n        appinit = try XCTUnwrap(state?.triggerData.children[\"init\"])\n        XCTAssertEqual(0, appinit.count)\n\n        _ = instance.process(event: .event(type: .appInit))\n        state = instance.process(event: .event(type: .foreground))\n        \n        XCTAssertNotNil(state?.triggerResult)\n    }\n    \n    func testCompoundOrTrigger() throws {\n        let trigger = AutomationTrigger.compound(\n            CompoundAutomationTrigger(\n                id: \"simple-or\",\n                type: .or,\n                goal: 2,\n                children: [\n                    .init(trigger: .event(EventAutomationTrigger(id: \"foreground\", type: .foreground, goal: 2)), resetOnIncrement: true),\n                    .init(trigger: .event(EventAutomationTrigger(id: \"init\", type: .appInit, goal: 2)), resetOnIncrement: true),\n                ]))\n        \n        let instance = makeTrigger(trigger: trigger)\n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        \n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(0, state?.triggerData.count)\n        XCTAssertNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(1, state?.triggerData.count)\n        XCTAssertNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertEqual(1, state?.triggerData.count)\n        XCTAssertNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertEqual(0, state?.triggerData.count)\n        XCTAssertNotNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n    }\n    \n    \n    \n    func testCompoundComplexOrTrigger() throws {\n        let trigger = AutomationTrigger.compound(\n            CompoundAutomationTrigger(\n                id: \"complex-or\",\n                type: .or,\n                goal: 2,\n                children: [\n                    .init(trigger: .event(EventAutomationTrigger(id: \"foreground\", type: .foreground, goal: 2)), resetOnIncrement: true),\n                    .init(trigger: .event(EventAutomationTrigger(id: \"init\", type: .appInit, goal: 2))),\n                ]))\n        \n        let instance = makeTrigger(trigger: trigger)\n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(0, state?.triggerData.count)\n        XCTAssertNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(1, state?.triggerData.count)\n        XCTAssertNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n\n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertEqual(1, state?.triggerData.count)\n        XCTAssertNil(state?.triggerResult)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n\n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n\n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNotNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n    }\n    \n    func testCompoundChainTrigger() {\n        let trigger = AutomationTrigger.compound(CompoundAutomationTrigger(\n            id: \"simple-chain\",\n            type: .chain,\n            goal: 2,\n            children: [\n                .init(trigger: .event(EventAutomationTrigger(id: \"foreground\", type: .foreground, goal: 2)), isSticky: true),\n                .init(trigger: .event(EventAutomationTrigger(id: \"init\", type: .appInit, goal: 2))),\n            ]))\n        \n        let instance = makeTrigger(trigger: trigger)\n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertNil(state?.triggerData)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertNil(state?.triggerData.count)\n        \n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n\n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n\n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNotNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n    }\n    \n    func testCompoundChainTriggerWithChildState() throws {\n        let trigger = AutomationTrigger.compound(CompoundAutomationTrigger(\n            id: \"state-child-chain\",\n            type: .chain,\n            goal: 1,\n            children: [\n                .init(trigger: .event(EventAutomationTrigger(id: \"custom-event\", type: .customEventValue, goal: 1)), isSticky: true),\n                .init(trigger: .activeSession(count: 1)),\n            ]))\n        \n        let instance = makeTrigger(trigger: trigger)\n        instance.activate()\n        \n        var state = instance.process(event: .stateChanged(state: TriggerableState(appSessionID: \"test\")))\n        XCTAssertNil(state?.triggerResult)\n        \n        state = instance.process(event: .event(type: .customEventValue, data: .null, value: 1))\n        XCTAssertNotNil(state?.triggerResult)\n    }\n    \n    func testCompoundComplexChainTrigger() {\n        let trigger = AutomationTrigger.compound(CompoundAutomationTrigger(\n            id: \"complex-chain\",\n            type: .chain,\n            goal: 2,\n            children: [\n                .init(trigger: .event(EventAutomationTrigger(id: \"foreground\", type: .foreground, goal: 2))),\n                .init(trigger: .event(EventAutomationTrigger(id: \"init\", type: .appInit, goal: 2))),\n            ]))\n        \n        let instance = makeTrigger(trigger: trigger)\n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertNil(state?.triggerData)\n        \n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertNil(state?.triggerData)\n        \n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 1)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(1, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 2)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 1)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNotNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"foreground\", count: 0)\n        assertChildDataCount(parent: state?.triggerData, triggerID: \"init\", count: 0)\n    }\n    \n    func testComplexTrigger() {\n        let trigger = AutomationTrigger.compound(\n            CompoundAutomationTrigger(\n                id: \"complex-trigger\",\n                type: .and,\n                goal: 1,\n                children: [\n                    .init(trigger: AutomationTrigger.compound(\n                        CompoundAutomationTrigger(\n                            id: \"foreground-or-init\",\n                            type: .or,\n                            goal: 1,\n                            children: [\n                                .init(trigger: .event(EventAutomationTrigger(id: \"foreground\", type: .foreground, goal: 1))),\n                                .init(trigger: .event(EventAutomationTrigger(id: \"init\", type: .appInit, goal: 1)))\n                            ])\n                    )),\n                    .init(trigger: AutomationTrigger.compound(\n                        CompoundAutomationTrigger(\n                            id: \"chain-screen-background\",\n                            type: .chain,\n                            goal: 1,\n                            children: [\n                                .init(trigger: .event(EventAutomationTrigger(id: \"screen\", type: .screen, goal: 1))),\n                                .init(trigger: .event(EventAutomationTrigger(id: \"background\", type: .background, goal: 1)))\n                            ])\n                    ))\n                ]))\n        \n        let instance = makeTrigger(trigger: trigger)\n        instance.activate()\n        \n        var state = instance.process(event: .event(type: .foreground))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        \n        state = instance.process(event: .event(type: .screen))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        \n        state = instance.process(event: .event(type: .appInit))\n        XCTAssertNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n        \n        state = instance.process(event: .event(type: .background))\n        XCTAssertNotNil(state?.triggerResult)\n        XCTAssertEqual(0, state?.triggerData.count)\n    }\n    \n    private func assertChildDataCount(parent: TriggerData?, triggerID: String, count: Double, line: UInt = #line) {\n        XCTAssertEqual(count, parent?.children[triggerID]?.count, line: line)\n    }\n    \n    private func makeTrigger(trigger: AutomationTrigger? = nil, type: TriggerExecutionType = .execution, startDate: Date? = nil, endDate: Date? = nil, state: TriggerData? = nil) -> PreparedTrigger {\n        let trigger = trigger ?? AutomationTrigger.event(.init(type: .appInit, goal: 1))\n\n        return PreparedTrigger(\n            scheduleID: \"test-schedule\",\n            trigger: trigger,\n            type: type, \n            startDate: startDate,\n            endDate: endDate,\n            triggerData: state,\n            priority: 0,\n            date: date\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/ExecutionWindowTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class ExectutionWindowTest: XCTestCase {\n    private var defaultTimeZone: TimeZone = TimeZone(secondsFromGMT: 0)!\n\n    private var calendar: Calendar  {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = defaultTimeZone\n        return calendar\n    }\n\n    // Jan 1, 2024 leap year!\n    private var referenceDate: Date {\n        calendar.date(\n            from: DateComponents(\n                year: 2024,\n                month: 1,\n                day: 1,\n                hour: 0,\n                minute: 0,\n                second: 0\n            )\n        )!\n    }\n\n    func testCodable() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"weekly\",\n              \"days_of_week\": [1,2,3,4,5],\n              \"time_range\": {\n                \"start_hour\": 8,\n                \"start_minute\": 30,\n                \"end_hour\": 5,\n                \"end_minute\": 59\n              }\n            },\n          ],\n          \"exclude\": [\n            {\n              \"type\": \"daily\",\n              \"time_range\": {\n                \"start_hour\": 12,\n                \"start_minute\": 0,\n                \"end_hour\": 13,\n                \"end_minute\": 0\n              },\n              \"time_zone\": {\n                \"type\": \"local\"\n              }\n            },\n            {\n              \"type\": \"monthly\",\n              \"months\": [12],\n              \"days_of_month\": [24, 31]\n            },\n            {\n              \"type\": \"monthly\",\n              \"months\": [1],\n              \"days_of_month\": [1]\n            }\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .weekly(daysOfWeek: [1,2,3,4,5], timeRange: .init(startHour: 8, startMinute: 30, endHour: 5, endMinute: 59))\n            ],\n            exclude: [\n                .daily(timeRange: .init(startHour: 12, startMinute: 0, endHour: 13, endMinute: 0), timeZone: .local),\n                .monthly(months: [12], daysOfMonth: [24, 31]),\n                .monthly(months: [1], daysOfMonth: [1]),\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testDaily() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"daily\",\n              \"time_range\": {\n                \"start_hour\": 12,\n                \"start_minute\": 1,\n                \"end_hour\": 13,\n                \"end_minute\": 2\n              },\n              \"time_zone\": {\n                \"type\": \"utc\"\n              }\n            },\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .daily(timeRange: .init(startHour: 12, startMinute: 1, endHour: 13, endMinute: 2), timeZone: .utc)\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testInvalidDaily() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"daily\"\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidTimeRange() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"daily\",\n              \"time_range\": {\n                startMinute: -1,\n                startHour: 12,\n                endMinute: 0,\n                endHour: 1\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidTimeZoneType() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"daily\",\n              \"time_range\": {\n                \"start_hour\": 12,\n                \"start_minute\": 1,\n                \"end_hour\": 13,\n                \"end_minute\": 2\n              },\n              \"time_zone\": {\n                \"type\": \"something\"\n              }\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testTimeZoneIdentifiers() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"daily\",\n              \"time_range\": {\n                \"start_hour\": 12,\n                \"start_minute\": 1,\n                \"end_hour\": 13,\n                \"end_minute\": 2\n              },\n              \"time_zone\": {\n                \"type\": \"identifiers\",\n                \"identifiers\": [\"America/Los_Angeles\", \"Africa/Abidjan\"],\n                \"on_failure\": \"skip\",\n              }\n            },\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(startHour: 12, startMinute: 1, endHour: 13, endMinute: 2),\n                    timeZone: .identifiers([\"America/Los_Angeles\", \"Africa/Abidjan\"], onFailure: .skip)\n                )\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n\n    }\n\n    func testMonthly() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"months\": [1, 12],\n              \"days_of_month\": [1, 31]\n            },\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .monthly(months: [1, 12], daysOfMonth: [1, 31])\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testMonthlyOnlyMonths() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"months\": [1, 12]\n            },\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .monthly(months: [1, 12])\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testMonthlyOnlyDays() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"days_of_month\": [1, 31]\n            },\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .monthly(daysOfMonth: [1, 31])\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testInvalidMonthly() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\"\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidMonthlyEmpty() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"days_of_month\": [],\n              \"months\": []\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidMonthlyMonthsBelow1() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"months\": [0]\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidMonthlyMonthAbove12() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"months\": [13]\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidMonthlyDaysAbove31() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"days_of_month\": [32]\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testInvalidMonthlyDaysBelow1() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"monthly\",\n              \"days_of_month\": [0]\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testWeekly() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"weekly\",\n              \"days_of_week\": [1, 7]\n            },\n          ]\n        }\n        \"\"\"\n\n        let expected = try ExecutionWindow(\n            include: [\n                .weekly(daysOfWeek: [1, 7])\n            ]\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n\n    func testWeeklyInvalidEmptyDaysOfWeek() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"weekly\",\n              \"days_of_week\": []\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testWeeklyInvalidEmptyDaysOfWeekBelow1() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"weekly\",\n              \"days_of_week\": [0]\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testWeeklyInvalidEmptyDaysOfAbove7() throws {\n        let json = \"\"\"\n        {\n          \"include\": [\n            {\n              \"type\": \"weekly\",\n              \"days_of_week\": [8]\n            },\n          ]\n        }\n        \"\"\"\n\n        do {\n            _ = try JSONDecoder().decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testReturnNowOnMatch() throws {\n        let window = try ExecutionWindow(\n            include: [\n                .daily(timeRange: .init(startHour: 0, endHour: 23))\n            ],\n            exclude: []\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: referenceDate), .now)\n    }\n\n    func testEmptyWindow() throws {\n        let window = try ExecutionWindow()\n        XCTAssertEqual(windowAvailibility(window, date: Date()), .now)\n    }\n\n    func testIncludeTimeRangeSameStartAndEnd() throws {\n        var date = referenceDate\n        let window = try ExecutionWindow(\n            include: [\n                .daily(timeRange: .init(startHour: 0, endHour: 0))\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: referenceDate), .now)\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days - 1.seconds))\n\n        date -= 2.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.seconds))\n    }\n\n    func testIncludeTimeRange() throws {\n        var date = referenceDate\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 4\n                    )\n                )\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(3.hours))\n\n        date += 3.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(23.hours))\n    }\n\n    func testExcludeTimeRangeSameStartAndEnd() throws {\n        var date = referenceDate\n        let window = try ExecutionWindow(\n            exclude: [\n                .daily(timeRange: .init(startHour: 0, endHour: 0))\n            ]\n        )\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.seconds))\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date -= 2.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n    }\n\n    func testExcludeEndOfTimeRange() throws {\n        var date = referenceDate + 3.hours\n        let window = try ExecutionWindow(\n            exclude: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    )\n                )\n            ]\n        )\n        \n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(21.hours))\n\n        date += 21.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n    }\n\n    func testExcludeTimeRangeWrap() throws {\n        var date = calendar.date(\n            from: DateComponents(\n                year: 2024,\n                month: 1,\n                day: 1,\n                hour: 23,\n                minute: 0,\n                second: 0\n            )\n        )!\n\n        let window = try ExecutionWindow(\n            exclude: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 23,\n                        endHour: 1\n                    )\n                )\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(2.hours))\n\n        date += 1.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.hours))\n\n        date += 1.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n    }\n\n    func testIncludeAndExcludeSameRule() throws {\n        let date = referenceDate + 3.hours\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    )\n                )\n            ],\n            exclude: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    )\n                )\n            ]\n        )\n\n        let startNextDay = calendar.startOfDay(for: date + 1.days)\n        let delay = startNextDay.timeIntervalSince(date)\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(delay))\n    }\n\n    func testIncludeWeekly() throws {\n        var date = calendar.date(bySetting: .weekday, value: 4, of: referenceDate)!\n        let window = try ExecutionWindow(\n            include: [\n                .weekly(daysOfWeek: [3, 5])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days))\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days))\n\n        date += 3.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days))\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n    }\n\n    func testIncludeWeeklyTimeRange() throws {\n        var date = calendar.date(bySetting: .weekday, value: 4, of: referenceDate)!\n        let window = try ExecutionWindow(\n            include: [\n                .weekly(\n                    daysOfWeek: [3, 5],\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    )\n                )\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days + 3.hours))\n\n        date += 1.days + 3.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.seconds))\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 21.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days + 3.hours))\n    }\n\n    func testIncludeWeeklyTimeRangeWithTimeZone() throws {\n        var date = calendar.date(bySetting: .weekday, value: 4, of: referenceDate)!\n        let window = try ExecutionWindow(\n            include: [\n                .weekly(\n                    daysOfWeek: [3, 5],\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    ),\n                    timeZone: .secondsFromGMT(Int(1.hours))\n                )\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days + 2.hours))\n\n        date += 1.days + 2.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.seconds))\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 21.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days + 3.hours))\n    }\n\n    func testExcludeWeeklyTimeRange() throws {\n        var date = calendar.date(bySetting: .weekday, value: 4, of: referenceDate)!\n        let window = try ExecutionWindow(\n            exclude: [\n                .weekly(\n                    daysOfWeek: [3, 5],\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    )\n                )\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.days + 3.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(21.hours))\n\n        date += 21.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.seconds))\n\n        date += 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n    }\n\n    func testIncludeMonthly() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(months: [2, 4], daysOfMonth: [15, 10])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(40.days))\n\n        date += 40.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days))\n\n        date += 4.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(54.days))\n\n        date += 55.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days))\n\n        date += 5.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(300.days))\n    }\n\n    func testMonthlyNoMonthsAfterDay() throws {\n        let date = calendar.date(bySetting: .day, value: 16, of: referenceDate)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(daysOfMonth: [15])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(30.days))\n    }\n\n    func testMonthlyNextMonth() throws {\n        // Feb 16\n        var date = calendar.date(bySetting: .month, value: 2, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 16, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(months: [1], daysOfMonth: [15]),\n                .monthly(months: [3], daysOfMonth: [2, 3])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(15.days))\n    }\n\n    func testMonthlyNextMonthNoDays() throws {\n        // Feb 16\n        var date = calendar.date(bySetting: .month, value: 2, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 16, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(months: [1, 3])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(14.days))\n    }\n\n    func testMonthlyNextYear() throws {\n        // Feb 15\n        var date = calendar.date(bySetting: .month, value: 2, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 15, of: date)!\n\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(months: [1], daysOfMonth: [14])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(334.days))\n    }\n\n    func testIncludeMonthlyWithTimeZone() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(\n                    months: [2, 4],\n                    daysOfMonth: [15, 10],\n                    timeZone: .secondsFromGMT(Int(7.hours))\n                )\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(40.days - 7.hours))\n\n        date += 40.days - 7.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days))\n\n        date += 4.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(54.days))\n\n        date += 55.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(4.days))\n\n        date += 5.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(300.days))\n    }\n\n\n    func testImpossibleMonthlyInclude() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                // can't happen\n                .monthly(months: [2], daysOfMonth: [31], timeRange: .init(startHour: 5, endHour: 23))\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(Date.distantFuture.timeIntervalSince(referenceDate) + 5.hours))\n    }\n\n    func testMonthlySkipsInvalidMonths() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                // can't happen\n                .monthly(months: [2, 10], daysOfMonth: [31])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(304.days))\n    }\n\n    func testImpossibleMonthlyExclude() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            exclude: [\n                // can't happen\n                .monthly(months: [2], daysOfMonth: [31])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n    }\n\n    func testMonthlyWithoutMonths() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(daysOfMonth: [31])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(30.days))\n\n        date += 31.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(30.days))\n    }\n\n    func testMonthlyWithOnlyMonths() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .monthly(months: [10, 12])\n            ]\n        )\n\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(274.days))\n\n        date += 274.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        for _ in 0..<30 {\n            date += 1.days\n            XCTAssertEqual(windowAvailibility(window, date: date), .now)\n        }\n\n        date += 1.days\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(30.days))\n    }\n\n    func testEmptyMonthlyIncludeThrows() throws {\n        do {\n            _ = try ExecutionWindow(\n                include: [\n                    .monthly()\n                ]\n            )\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testEmptyMonthlyExcludeThrows() throws {\n        do {\n            _ = try ExecutionWindow(\n                exclude: [\n                    .monthly()\n                ]\n            )\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testComplexRule() throws {\n        var date = calendar.date(bySetting: .month, value: 1, of: referenceDate)!\n        date = calendar.date(bySetting: .day, value: 1, of: date)!\n\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(startHour: 1, endHour: 2),\n                    timeZone: .secondsFromGMT(Int(1.hours))\n                ),\n                .weekly(\n                    daysOfWeek: [5],\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 5\n                    ),\n                    timeZone: .utc\n                ),\n                .monthly(months: [2, 4], daysOfMonth: [2], timeRange: .init(startHour: 10, endHour: 22))\n            ],\n            exclude: [\n                .monthly(months: [1, 3, 5, 7, 9, 11])\n            ]\n        )\n\n        // Exclude monthly without days is only 1 day at a time\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days))\n\n        for _ in 0..<30 {\n            date += 1.days\n            XCTAssertEqual(windowAvailibility(window, date: date), .retry(1.days))\n        }\n\n        // Feb 1\n        date += 1.days\n        // Timezone offset for the daily rule is 1, so its makes it [0-1]\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.hours\n        // 2 hour until weekly rule for DOW 5\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(2.hours))\n\n        date += 2.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 2.hours\n        // 19 hours until the daily rule again\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(19.hours))\n\n        date += 19.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 1.hours\n        // 9 hours until the monthly rule\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(9.hours))\n\n        date += 9.hours\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date += 12.hours - 1.seconds\n        XCTAssertEqual(windowAvailibility(window, date: date), .now)\n\n        date +=  1.seconds\n        // 2 hour until the daily rule again\n        XCTAssertEqual(windowAvailibility(window, date: date), .retry(2.hours))\n    }\n\n\n    func testTransitionOutOfDST() throws {\n        // Sun March 10 2024 we transition from PDT to PST\n        self.defaultTimeZone = TimeZone(identifier: \"America/Los_Angeles\")!\n\n        let midnightOf = calendar.date(\n            from: DateComponents(\n                timeZone: self.defaultTimeZone,\n                year: 2024,\n                month: 3,\n                day: 10,\n                hour: 0,\n                minute: 0,\n                second: 0\n            )\n        )!\n\n\n        // Sun March 10 2024\n        let transition = calendar.date(\n            from: DateComponents(\n                timeZone: self.defaultTimeZone,\n                year: 2024,\n                month: 3,\n                day: 10,\n                hour: 3,\n                minute: 0,\n                second: 0\n            )\n        )!\n\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 2,\n                        endHour: 4\n                    )\n                )\n            ]\n        )\n\n        // 12:00 PST\n        XCTAssertEqual(windowAvailibility(window, date: midnightOf), .retry(2.hours))\n\n        // 3:00 PDT\n        XCTAssertEqual(windowAvailibility(window, date: transition), .now)\n\n        // 4:00 PDT\n        XCTAssertEqual(windowAvailibility(window, date: transition + 1.hours), .retry(22.hours))\n    }\n\n    func testTransitionToDST() throws {\n        // Sun Nov 3 2024 we transition from PST to PDT\n        self.defaultTimeZone = TimeZone(identifier: \"America/Los_Angeles\")!\n\n        let midnightOf = calendar.date(\n            from: DateComponents(\n                timeZone: self.defaultTimeZone,\n                year: 2024,\n                month: 11,\n                day: 3,\n                hour: 0,\n                minute: 0,\n                second: 0\n            )\n        )!\n\n\n        // Sun March 10 2024\n        let transition = calendar.date(\n            from: DateComponents(\n                timeZone: self.defaultTimeZone,\n                year: 2024,\n                month: 3,\n                day: 10,\n                hour: 1,\n                minute: 0,\n                second: 0\n            )\n        )!\n\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 2,\n                        endHour: 4\n                    )\n                )\n            ]\n        )\n\n        // 12:00 PDT\n        XCTAssertEqual(windowAvailibility(window, date: midnightOf), .retry(3.hours))\n\n        // 1:00 PST\n        XCTAssertEqual(windowAvailibility(window, date: transition), .retry(1.hours))\n\n        // 2:00 PDT\n        XCTAssertEqual(windowAvailibility(window, date: transition + 1.hours), .now)\n    }\n\n    func testErrorTimeZoneIdentifiersFailed() throws {\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 3,\n                        endHour: 0\n                    ),\n                    timeZone: .identifiers([\"Does not exist\"], onFailure: .error)\n                )\n            ]\n        )\n\n        do {\n            _ = try window.nextAvailability(date: referenceDate)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testSkipTimeZoneIdentifiersFailed() throws {\n        let window = try ExecutionWindow(\n            include: [\n                .daily(\n                    timeRange: .init(\n                        startHour: 0,\n                        endHour: 10\n                    ),\n                    timeZone: .identifiers([\"Does not exist\"], onFailure: .skip)\n                )\n            ]\n        )\n\n        let result = try window.nextAvailability(date: referenceDate)\n        XCTAssertEqual(result, .now)\n    }\n\n    private func windowAvailibility(_ window: ExecutionWindow, date: Date) -> ExecutionWindowResult {\n        try! window.nextAvailability(date: date, currentTimeZone: defaultTimeZone)\n    }\n\n    func verify(json: String, expected: ExecutionWindow, line: UInt = #line) throws {\n        let decoder = JSONDecoder()\n        let encoder = JSONEncoder()\n\n        let fromJSON = try decoder.decode(ExecutionWindow.self, from: json.data(using: .utf8)!)\n        XCTAssertEqual(fromJSON, expected, line: line)\n\n        let roundTrip = try decoder.decode(ExecutionWindow.self, from: try encoder.encode(fromJSON))\n        XCTAssertEqual(roundTrip, fromJSON, line: line)\n    }\n}\n\nextension Date {\n    func local(calendar: Calendar) -> Date {\n        return self.advanced(by: Double(calendar.timeZone.secondsFromGMT()))\n    }\n    \n    static func fromMidnight(seconds: TimeInterval, calendar: Calendar) -> Date {\n        return Date(timeIntervalSince1970: 1704060000 + seconds - 3600)\n    }\n}\n\nfileprivate extension ExecutionWindow.TimeZone {\n    static func secondsFromGMT(_ seconds: Int) -> ExecutionWindow.TimeZone {\n        let timeZoneID = TimeZone(secondsFromGMT: seconds)!.identifier\n        return .identifiers([timeZoneID], onFailure: .error)\n    }\n}\n\n\nfileprivate extension Int {\n    var days: TimeInterval {\n        return TimeInterval(self) * 60 * 60 * 24\n    }\n\n    var hours: TimeInterval {\n        TimeInterval(self) * 60 * 60\n    }\n\n    var minutes: TimeInterval {\n        TimeInterval(self) * 60\n    }\n\n    var seconds: TimeInterval {\n        TimeInterval(self)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Analytics/DefaultInAppDisplayImpressionRuleProviderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class DefaultInAppDisplayImpressionRuleProviderTest: XCTestCase {\n\n    let provider = DefaultInAppDisplayImpressionRuleProvider()\n\n    func testCustomMessage() throws {\n        let rule = provider.impressionRules(\n            for: InAppMessage(name: \"woot\", displayContent: .custom(.string(\"neat\")))\n        )\n        XCTAssertEqual(rule, .once)\n    }\n\n    func testFullscreenMessage() throws {\n        let rule = provider.impressionRules(\n            for: InAppMessage(\n                name: \"woot\",\n                displayContent: .fullscreen(.init(buttons: [], template: .headerBodyMedia))\n            )\n        )\n        XCTAssertEqual(rule, .once)\n    }\n\n    func testModalMessage() throws {\n        let rule = provider.impressionRules(\n            for: InAppMessage(\n                name: \"woot\",\n                displayContent: .modal(.init(buttons: [], template: .headerBodyMedia))\n            )\n        )\n        XCTAssertEqual(rule, .once)\n    }\n\n    func testBannerMessage() throws {\n        let rule = provider.impressionRules(\n            for: InAppMessage(\n                name: \"woot\",\n                displayContent: .banner(.init(buttons: [], template: .mediaLeft))\n            )\n        )\n        XCTAssertEqual(rule, .once)\n    }\n\n    func testModalThomas() throws {\n        let airshipLayout = \"\"\"\n        {\n          \"version\":1,\n          \"presentation\":{\n             \"type\":\"modal\",\n             \"default_placement\":{\n                \"size\":{\n                   \"width\":\"50%\",\n                   \"height\":\"50%\"\n                }\n             }\n          },\n          \"view\":{\n             \"type\":\"container\",\n             \"items\":[]\n          }\n        }\n        \"\"\"\n\n\n        let rule = provider.impressionRules(\n            for: InAppMessage(\n                name: \"woot\",\n                displayContent: .airshipLayout(\n                    try! JSONDecoder().decode(AirshipLayout.self, from: airshipLayout.data(using: .utf8)!)\n                )\n            )\n        )\n        XCTAssertEqual(rule, .once)\n    }\n\n    func testBannerThomas() throws {\n        let airshipLayout = \"\"\"\n        {\n          \"version\":1,\n          \"presentation\":{\n             \"type\":\"banner\",\n             \"default_placement\":{\n                \"position\": \"top\",\n                \"size\":{\n                   \"width\":\"50%\",\n                   \"height\":\"50%\"\n                }\n             }\n          },\n          \"view\":{\n             \"type\":\"container\",\n             \"items\":[]\n          }\n        }\n        \"\"\"\n\n        let rule = provider.impressionRules(\n            for: InAppMessage(\n                name: \"woot\",\n                displayContent: .airshipLayout(\n                    try! JSONDecoder().decode(AirshipLayout.self, from: airshipLayout.data(using: .utf8)!)\n                )\n            )\n        )\n        XCTAssertEqual(rule, .once)\n    }\n\n    func testEmbeddedThomas() throws {\n        let airshipLayout = \"\"\"\n        {\n          \"version\":1,\n          \"presentation\":{\n             \"type\":\"embedded\",\n             \"embedded_id\":\"home_banner\",\n             \"default_placement\":{\n                \"size\":{\n                   \"width\":\"50%\",\n                   \"height\":\"50%\"\n                }\n             }\n          },\n          \"view\":{\n             \"type\":\"container\",\n             \"items\":[]\n          }\n        }\n        \"\"\"\n\n        let rule = provider.impressionRules(\n            for: InAppMessage(\n                name: \"woot\",\n                displayContent: .airshipLayout(\n                    try! JSONDecoder().decode(AirshipLayout.self, from: airshipLayout.data(using: .utf8)!)\n                )\n            )\n        )\n        XCTAssertEqual(rule, .interval(1800.0))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Analytics/InAppMessageAnalyticsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipAutomation\n@testable import AirshipCore\nimport Foundation\n\n@MainActor\nstruct InAppMessageAnalyticsTest {\n\n    private let eventRecorder = EventRecorder()\n    private let historyStore = TestDisplayHistoryStore()\n    private let preparedInfo = PreparedScheduleInfo(\n        scheduleID: UUID().uuidString,\n        productID: UUID().uuidString,\n        campaigns: AirshipJSON.string(UUID().uuidString),\n        contactID: UUID().uuidString,\n        experimentResult: ExperimentResult(\n            channelID: UUID().uuidString,\n            contactID: UUID().uuidString,\n            isMatch: true,\n            reportingMetadata: [AirshipJSON.string(UUID().uuidString)]\n        ),\n        reportingContext: AirshipJSON.string(UUID().uuidString),\n        triggerSessionID: UUID().uuidString,\n        priority: 0\n    )\n\n    @Test\n    func testSource() async throws {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .remoteData\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore, \n            displayHistory: MessageDisplayHistory()\n        )\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n\n        let data = eventRecorder.eventData.first!\n        let expectedID = ThomasLayoutEventMessageID.airship(\n            identifier: self.preparedInfo.scheduleID,\n            campaigns: self.preparedInfo.campaigns\n        )\n        #expect(data.messageID == expectedID)\n        #expect(data.source == .airship)\n    }\n\n    @Test\n    func testAppDefined() async throws {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .appDefined\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory()\n        )\n\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n\n        let data = eventRecorder.eventData.first!\n\n        let expectedID = ThomasLayoutEventMessageID.appDefined(\n            identifier: self.preparedInfo.scheduleID\n        )\n        #expect(data.messageID == expectedID)\n        #expect(data.source == .appDefined)\n    }\n\n    @Test\n    func testLegacyMessageID() async throws {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory()\n        )\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n\n        let data = eventRecorder.eventData.first!\n        let expectedID = ThomasLayoutEventMessageID.legacy(\n            identifier: self.preparedInfo.scheduleID\n        )\n        #expect(data.messageID == expectedID)\n        #expect(data.source == .airship)\n    }\n\n    @Test\n    func testData() async throws {\n        let thomasLayoutContext  = ThomasLayoutContext(\n            pager: ThomasLayoutContext.Pager(\n                identifier: UUID().uuidString,\n                pageIdentifier: UUID().uuidString,\n                pageIndex: 1,\n                completed: false,\n                count: 2\n            ),\n            button: ThomasLayoutContext.Button(identifier: UUID().uuidString),\n            form: ThomasLayoutContext.Form(\n            identifier: UUID().uuidString,\n            submitted: true,\n            type: UUID().uuidString,\n            responseType: UUID().uuidString\n            )\n        )\n\n        let expectedContext = ThomasLayoutEventContext.makeContext(\n            reportingContext: preparedInfo.reportingContext,\n            experimentsResult: preparedInfo.experimentResult,\n            layoutContext: thomasLayoutContext,\n            displayContext: .init(\n                triggerSessionID: preparedInfo.triggerSessionID,\n                isFirstDisplay: true,\n                isFirstDisplayTriggerSessionID: true\n            )\n        )\n\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory()\n        )\n\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: thomasLayoutContext)\n\n        let data = self.eventRecorder.eventData.first!\n        #expect(data.context == expectedContext)\n        #expect(data.renderedLocale == AirshipJSON.string(\"rendered locale\"))\n        #expect(data.event.name == EventType.customEvent)\n    }\n    \n    @Test\n    func testSingleImpression() async throws {\n        let date = UATestDate(offset: 0, dateOverride: Date())\n        \n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory(),\n            date: date\n        )\n\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n\n        \n        let impression = eventRecorder.lastRecordedImpression!\n        #expect(preparedInfo.scheduleID == impression.entityID)\n        #expect(AirshipMeteredUsageType.inAppExperienceImpression == impression.usageType)\n        #expect(preparedInfo.productID == impression.product)\n        #expect(preparedInfo.reportingContext == impression.reportingContext)\n        #expect(date.now == impression.timestamp)\n        #expect(preparedInfo.contactID == impression.contactID)\n\n        eventRecorder.lastRecordedImpression = nil\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        #expect(eventRecorder.lastRecordedImpression == nil)\n\n        let displayHistory = await self.historyStore.get(\n            scheduleID: preparedInfo.scheduleID\n        )\n        #expect(displayHistory.lastImpression?.date == date.now)\n        #expect(displayHistory.lastImpression?.triggerSessionID == preparedInfo.triggerSessionID)\n    }\n\n    @Test\n    func testImpressionInterval() async throws {\n        let date = UATestDate(offset: 0, dateOverride: Date())\n\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .interval(10.0),\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory(),\n            date: date\n        )\n\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n\n        let impression = eventRecorder.lastRecordedImpression!\n        #expect(preparedInfo.scheduleID == impression.entityID)\n        #expect(AirshipMeteredUsageType.inAppExperienceImpression == impression.usageType)\n        #expect(preparedInfo.productID == impression.product)\n        #expect(preparedInfo.reportingContext == impression.reportingContext)\n        #expect(date.now == impression.timestamp)\n        #expect(preparedInfo.contactID == impression.contactID)\n\n        eventRecorder.lastRecordedImpression = nil\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        #expect(eventRecorder.lastRecordedImpression == nil)\n\n        var displayHistory = await self.historyStore.get(\n            scheduleID: preparedInfo.scheduleID\n        )\n        #expect(displayHistory.lastImpression?.date == date.now)\n        #expect(displayHistory.lastImpression?.triggerSessionID == preparedInfo.triggerSessionID)\n\n\n        date.offset += 9.9\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        #expect(eventRecorder.lastRecordedImpression == nil)\n\n        date.offset += 0.1\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        assert(eventRecorder.lastRecordedImpression != nil)\n\n        displayHistory = await self.historyStore.get(\n            scheduleID: preparedInfo.scheduleID\n        )\n        #expect(displayHistory.lastImpression?.date == date.now)\n        #expect(displayHistory.lastImpression?.triggerSessionID == preparedInfo.triggerSessionID)\n    }\n\n    @Test\n    func testReportingDisabled() async throws {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                isReportingEnabled: false,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory()\n        )\n\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        assert(self.eventRecorder.eventData.isEmpty)\n\n        // impressions are still recorded\n        assert(eventRecorder.lastRecordedImpression != nil)\n    }\n\n\n    @Test\n    func testDisplayUpdatesHistory() async {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                isReportingEnabled: true,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory()\n        )\n\n        var displayHistory = await self.historyStore.get(\n            scheduleID: preparedInfo.scheduleID\n        )\n        #expect(displayHistory.lastDisplay?.triggerSessionID == nil)\n\n\n        displayHistory = await self.historyStore.get(\n            scheduleID: preparedInfo.scheduleID\n        )\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n    }\n\n    @Test\n    func testDisplayContextNewIAA() async {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                isReportingEnabled: true,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory()\n        )\n\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n\n        let firstDisplayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: preparedInfo.triggerSessionID,\n            isFirstDisplay: true,\n            isFirstDisplayTriggerSessionID: true\n        )\n\n        let secondDisplayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: preparedInfo.triggerSessionID,\n            isFirstDisplay: false,\n            isFirstDisplayTriggerSessionID: false\n        )\n\n        let expected = [\n            // event before a display\n            firstDisplayContext,\n            // first display\n            firstDisplayContext,\n            // event after display\n            firstDisplayContext,\n            // second display\n            secondDisplayContext,\n            // event after display\n            secondDisplayContext\n        ]\n\n        let displayContexts = self.eventRecorder.eventData.map { $0.context!.display }\n        #expect(displayContexts == expected)\n    }\n\n    @Test\n    func testDisplayContextPreviouslyDisplayIAX() async {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                isReportingEnabled: true,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory(\n                lastDisplay: .init(triggerSessionID: UUID().uuidString)\n            )\n        )\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n\n        let firstDisplayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: preparedInfo.triggerSessionID,\n            isFirstDisplay: false,\n            isFirstDisplayTriggerSessionID: true\n        )\n\n        let secondDisplayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: preparedInfo.triggerSessionID,\n            isFirstDisplay: false,\n            isFirstDisplayTriggerSessionID: false\n        )\n\n        let expected = [\n            // event before a display\n            firstDisplayContext,\n            // first display\n            firstDisplayContext,\n            // event after display\n            firstDisplayContext,\n            // second display\n            secondDisplayContext,\n            // event after display\n            secondDisplayContext\n        ]\n\n        let displayContexts = self.eventRecorder.eventData.map { $0.context!.display }\n        #expect(displayContexts == expected)\n    }\n\n    @Test\n    func testDisplayContextSameTriggerSessionID() async {\n        let analytics = InAppMessageAnalytics(\n            preparedScheduleInfo: preparedInfo,\n            message: InAppMessage(\n                name: \"name\",\n                displayContent: .custom(.string(\"custom\")),\n                source: .legacyPush,\n                isReportingEnabled: true,\n                renderedLocale: AirshipJSON.string(\"rendered locale\")\n            ),\n            displayImpressionRule: .once,\n            eventRecorder: eventRecorder,\n            historyStore: historyStore,\n            displayHistory: MessageDisplayHistory(\n                lastDisplay: .init(triggerSessionID: preparedInfo.triggerSessionID)\n            )\n        )\n\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        analytics.recordEvent(TestThomasLayoutEvent(), layoutContext: nil)\n\n        let firstDisplayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: preparedInfo.triggerSessionID,\n            isFirstDisplay: false,\n            isFirstDisplayTriggerSessionID: false\n        )\n\n        let secondDisplayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: preparedInfo.triggerSessionID,\n            isFirstDisplay: false,\n            isFirstDisplayTriggerSessionID: false\n        )\n\n        let expected = [\n            // event before a display\n            firstDisplayContext,\n            // first display\n            firstDisplayContext,\n            // event after display\n            firstDisplayContext,\n            // second display\n            secondDisplayContext,\n            // event after display\n            secondDisplayContext\n        ]\n\n        let displayContexts = self.eventRecorder.eventData.map { $0.context!.display }\n        #expect(displayContexts == expected)\n    }\n}\n\nfinal class EventRecorder: ThomasLayoutEventRecorderProtocol, @unchecked Sendable {\n\n    var lastRecordedImpression: AirshipMeteredUsageEvent?\n\n    var eventData: [ThomasLayoutEventData] = []\n    func recordEvent(inAppEventData: ThomasLayoutEventData) {\n        eventData.append(inAppEventData)\n    }\n\n    func recordImpressionEvent(_ event: AirshipMeteredUsageEvent) {\n        lastRecordedImpression = event\n    }\n}\n\n\nfinal class TestDisplayHistoryStore: MessageDisplayHistoryStoreProtocol, @unchecked Sendable {\n    var stored: [String: MessageDisplayHistory] = [:]\n\n    func set(_ history: MessageDisplayHistory, scheduleID: String) {\n        stored[scheduleID] = history\n    }\n    \n    func get(scheduleID: String) async -> MessageDisplayHistory {\n        return stored[scheduleID] ?? MessageDisplayHistory()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Assets/AssetCacheManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\nimport AirshipCore\n\n@testable\nimport AirshipAutomation\n\nfinal class AssetCacheManagerTest: XCTestCase {\n    class TestAssetDownloader: AssetDownloader, @unchecked Sendable {\n        var downloadResult: Result<URL, Error>?\n        var downloadDelaySeconds: TimeInterval = 0\n        var customDownloadHandler: ((URL) async throws -> URL)?\n\n        func downloadAsset(remoteURL: URL) async throws -> URL {\n            // Simulate a network delay\n            if downloadDelaySeconds > 0 {\n                let delayNanoseconds = UInt64(downloadDelaySeconds * 1_000_000_000)  // Convert seconds to nanoseconds\n                try await Task.sleep(nanoseconds: delayNanoseconds)\n            }\n\n            if let customHandler = customDownloadHandler {\n                return try await customHandler(remoteURL)\n            }\n\n            switch downloadResult {\n            case .success(let url):\n                return url\n            case .failure(let error):\n                throw error\n            case .none:\n                fatalError(\"Download result wasn't set.\")\n            }\n        }\n    }\n\n    class TestAssetFileManager: AssetFileManager, @unchecked Sendable {\n        var onEnsureCacheRootDirectory: ((_ rootPathComponent: String) -> URL)?\n        var onEnsureDirectory: ((_ identifier: String) -> URL)?\n        var onMoveAsset: ((_ tempURL: URL, _ cacheURL: URL) throws -> ())?\n        var onAssetItemExists: ((_ cacheURL: URL) -> Bool )?\n        var onClearAssets: ((_ cacheURL: URL) -> ())?\n\n        var rootDirectory: URL?\n\n        func assetItemExists(at cacheURL: URL) -> Bool {\n            return self.onAssetItemExists?(cacheURL) ?? false\n        }\n\n        func ensureCacheDirectory(identifier: String) throws -> URL {\n            if onEnsureDirectory == nil {\n                XCTFail(\"Testing block onEnsureDirectory testing block must be implemented and return a URL\")\n            }\n\n            return self.onEnsureDirectory!(identifier)\n        }\n\n        func ensureCacheRootDirectory(rootPathComponent: String) throws -> URL {\n            if onEnsureCacheRootDirectory == nil {\n                XCTFail(\"Testing block onEnsureCacheRootDirectory must be implemented and return a URL\")\n            }\n\n            return self.onEnsureCacheRootDirectory!(rootPathComponent)\n        }\n\n        func moveAsset(from tempURL: URL, to cacheURL: URL) throws {\n            try self.onMoveAsset?(tempURL, cacheURL)\n        }\n\n        func clearAssets(cacheURL: URL) throws {\n            self.onClearAssets?(cacheURL)\n        }\n    }\n\n    /// Tests that calling cache assets on two remote URLs will result in a file move to the correct directory with those two assets\n    func testCacheTwoAssets() async throws {\n        let downloader = TestAssetDownloader()\n        downloader.downloadResult = .success(URL(fileURLWithPath: \"/temp/asset\"))\n\n        let assetRemoteURL1 = URL(string:\"http://airship.com/asset1\")!\n        let assetRemoteURL2 = URL(string:\"http://airship.com/asset2\")!\n\n        let testScheduleIdentifier = \"test-schedule-id\"\n\n        let expectedRootPathComponent = \"com.urbanairship.iamassetcache\"\n\n        let expectedAsset1Filename = assetRemoteURL1.assetFilename\n        let expectedAsset2Filename = assetRemoteURL2.assetFilename\n\n        let expectedRootCacheDirectory = URL(fileURLWithPath:\"test-user-cache/\\(expectedRootPathComponent)/\")\n        let expectedCacheDirectory = expectedRootCacheDirectory.appendingPathComponent(testScheduleIdentifier, isDirectory: true)\n\n        let expectedFile1URL = expectedCacheDirectory.appendingPathComponent(assetRemoteURL1.assetFilename, isDirectory:false)\n        let expectedFile2URL = expectedCacheDirectory.appendingPathComponent(assetRemoteURL2.assetFilename, isDirectory:false)\n\n        let fileManager = TestAssetFileManager()\n        \n        var shouldExist = false\n\n        fileManager.onEnsureCacheRootDirectory = { rootPathComponent in\n            /// Check root path component is used for the root directory\n            XCTAssertEqual(rootPathComponent, expectedRootPathComponent)\n            return expectedRootCacheDirectory\n        }\n\n        fileManager.onEnsureDirectory = { identifier in\n            /// Check cache directory is the root path + expected schedule identifier\n            XCTAssertEqual(identifier, expectedCacheDirectory.lastPathComponent)\n            return expectedCacheDirectory\n        }\n\n        fileManager.onAssetItemExists = { url in\n            /// If we're checking the status of the cache directory\n            if expectedCacheDirectory == url {\n                return true\n            }\n\n            /// If we're checking the status of file 1\n            if expectedFile1URL == url, shouldExist {\n                return true\n            }\n\n            /// If we're checking the status of file 2\n            if expectedFile2URL == url, shouldExist {\n                return true\n            }\n            \n            if shouldExist {\n                XCTFail()\n            }\n            \n            return false\n        }\n\n        let asset1MovedToCache = expectation(description: \"Test asset 1 moved to cache\")\n        let asset2MovedToCache = expectation(description: \"Test asset 2 moved to cache\")\n\n        fileManager.onMoveAsset = { tempURL, cachedURL in\n            if expectedAsset1Filename == cachedURL.lastPathComponent {\n                asset1MovedToCache.fulfill()\n            }\n\n            if expectedAsset2Filename == cachedURL.lastPathComponent {\n                asset2MovedToCache.fulfill()\n            }\n        }\n\n        let manager = AssetCacheManager(assetDownloader: downloader, assetFileManager: fileManager)\n        await manager.clearCache(identifier: testScheduleIdentifier)\n\n        do {\n            let cachedAssets = try await manager.cacheAssets(identifier: testScheduleIdentifier, assets: [assetRemoteURL1.path, assetRemoteURL2.path])\n            \n            shouldExist = true\n\n            XCTAssertTrue(cachedAssets.isCached(remoteURL: assetRemoteURL1))\n            XCTAssertTrue(cachedAssets.isCached(remoteURL: assetRemoteURL2))\n\n            XCTAssertEqual(cachedAssets.cachedURL(remoteURL: assetRemoteURL1), expectedFile1URL)\n            XCTAssertEqual(cachedAssets.cachedURL(remoteURL: assetRemoteURL2), expectedFile2URL)\n\n            await fulfillment(of: [asset1MovedToCache, asset2MovedToCache], timeout: 1)\n\n        } catch {\n            XCTFail(\"Caching assets should succeed: \\(error)\")\n        }\n    }\n\n    func testClearCacheDuringActiveDownload() async throws {\n        let downloader = TestAssetDownloader()\n        downloader.downloadResult = .success(URL(fileURLWithPath: \"/temp/asset\"))\n        downloader.downloadDelaySeconds = 0.5  // Set a delay to ensure clearCache is called while mock downloading is in progress\n\n        let fileManager = TestAssetFileManager()\n\n        fileManager.onEnsureCacheRootDirectory = { rootPathComponent in\n            return URL(fileURLWithPath: \"/path/to/cache\")\n        }\n\n        fileManager.onEnsureDirectory = { url in\n            return URL(fileURLWithPath: \"/path/to/cache/identifier\")\n        }\n\n        let manager = AssetCacheManager(assetDownloader: downloader, assetFileManager: fileManager)\n        let identifier = \"testIdentifier\"\n\n        // Start caching assets in a separate task to allow it to run in parallel\n        let cacheTask = Task {\n            try await manager.cacheAssets(identifier: identifier, assets: [\"http://airship.com/asset\"])\n        }\n\n        // Give the cacheTask a moment to start be assigned to the task map\n        try await Task.sleep(nanoseconds: 100_000_000) // 0.1 seconds\n\n        // Clear the cache while the caching task is still in progress\n        await manager.clearCache(identifier: identifier)\n\n        // Verify that the caching task was canceled\n        var isCancelled = false\n        do {\n            _ = try await cacheTask.result.get()\n        } catch {\n            if (error as? CancellationError) != nil {\n                isCancelled = true\n            } else {\n                XCTFail(\"Expected a CancellationError, but received: \\(error)\")\n            }\n        }\n        XCTAssertTrue(isCancelled, \"The caching task should be canceled after clearing the cache.\")\n    }\n\n    /// Tests that duplicate URLs in the assets array are deduplicated before processing\n    func testCacheDuplicateAssets() async throws {\n        let downloader = TestAssetDownloader()\n        downloader.downloadResult = .success(URL(fileURLWithPath: \"/temp/asset\"))\n\n        let assetRemoteURL = URL(string:\"http://airship.com/duplicate-asset\")!\n        let testScheduleIdentifier = \"test-duplicate-schedule-id\"\n        let expectedRootPathComponent = \"com.urbanairship.iamassetcache\"\n\n        let expectedRootCacheDirectory = URL(fileURLWithPath:\"test-user-cache/\\(expectedRootPathComponent)/\")\n        let expectedCacheDirectory = expectedRootCacheDirectory.appendingPathComponent(testScheduleIdentifier, isDirectory: true)\n        let expectedFileURL = expectedCacheDirectory.appendingPathComponent(assetRemoteURL.assetFilename, isDirectory:false)\n\n        let fileManager = TestAssetFileManager()\n        var downloadCount = 0\n        var moveCount = 0\n\n        // Track how many times download is called\n        downloader.customDownloadHandler = { remoteURL in\n            downloadCount += 1\n            return URL(fileURLWithPath: \"/temp/asset-\\(downloadCount)\")\n        }\n\n        fileManager.onEnsureCacheRootDirectory = { _ in\n            return expectedRootCacheDirectory\n        }\n\n        fileManager.onEnsureDirectory = { _ in\n            return expectedCacheDirectory\n        }\n\n        fileManager.onAssetItemExists = { url in\n            if expectedCacheDirectory == url {\n                return true\n            }\n            // First check returns false, subsequent checks return true\n            return moveCount > 0 && url == expectedFileURL\n        }\n\n        fileManager.onMoveAsset = { tempURL, cachedURL in\n            moveCount += 1\n            XCTAssertEqual(cachedURL, expectedFileURL)\n        }\n\n        let manager = AssetCacheManager(assetDownloader: downloader, assetFileManager: fileManager)\n\n        // Pass the same URL three times (simulating the bug scenario)\n        let duplicateAssets = [assetRemoteURL.absoluteString, assetRemoteURL.absoluteString, assetRemoteURL.absoluteString]\n\n        do {\n            let cachedAssets = try await manager.cacheAssets(identifier: testScheduleIdentifier, assets: duplicateAssets)\n\n            // Should only download and move once despite duplicate URLs\n            XCTAssertEqual(downloadCount, 1, \"Should only download once for duplicate URLs\")\n            XCTAssertEqual(moveCount, 1, \"Should only move once for duplicate URLs\")\n\n            XCTAssertTrue(cachedAssets.isCached(remoteURL: assetRemoteURL))\n            XCTAssertEqual(cachedAssets.cachedURL(remoteURL: assetRemoteURL), expectedFileURL)\n        } catch {\n            XCTFail(\"Caching duplicate assets should succeed: \\(error)\")\n        }\n    }\n\n    /// Tests that concurrent caching of the same asset handles race conditions gracefully\n    // TODO: This test needs to be redesigned to properly test the race condition handling\n    func disabled_testConcurrentCachingSameAsset() async throws {\n        let downloader = TestAssetDownloader()\n        downloader.downloadDelaySeconds = 0.1 // Add small delay to increase chance of race condition\n\n        let assetRemoteURL = URL(string:\"http://airship.com/concurrent-asset\")!\n        let testScheduleIdentifier1 = \"test-concurrent-schedule-1\"\n        let testScheduleIdentifier2 = \"test-concurrent-schedule-2\"\n\n        let fileManager = TestAssetFileManager()\n        var moveAttempts = 0\n        let moveAttemptsSemaphore = AirshipLock()\n\n        fileManager.rootDirectory = URL(fileURLWithPath: \"/test-cache\")\n\n        fileManager.onEnsureDirectory = { identifier in\n            return URL(fileURLWithPath: \"/test-cache/\\(identifier)\")\n        }\n\n        var cachedFiles = Set<String>()\n\n        fileManager.onAssetItemExists = { url in\n            // Return true for directories\n            if url.path.contains(\"/test-cache/test-concurrent-schedule\") && !url.path.contains(\".\") {\n                return true\n            }\n            // Check if file has been cached\n            return cachedFiles.contains(url.path)\n        }\n\n        var firstMoveCompleted = false\n        fileManager.onMoveAsset = { tempURL, cachedURL in\n            moveAttemptsSemaphore.lock()\n            defer { moveAttemptsSemaphore.unlock() }\n\n            moveAttempts += 1\n\n            // Simulate the race condition - second attempt fails with \"file exists\"\n            if moveAttempts == 2 && firstMoveCompleted {\n                throw NSError(domain: NSCocoaErrorDomain, code: NSFileWriteFileExistsError, userInfo: nil)\n            }\n\n            if moveAttempts == 1 {\n                firstMoveCompleted = true\n                cachedFiles.insert(cachedURL.path)\n            }\n        }\n\n        downloader.customDownloadHandler = { _ in\n            return URL(fileURLWithPath: \"/temp/asset-\\(UUID().uuidString)\")\n        }\n\n        let manager = AssetCacheManager(assetDownloader: downloader, assetFileManager: fileManager)\n\n        // Start two concurrent caching operations for the same asset\n        async let cache1 = manager.cacheAssets(identifier: testScheduleIdentifier1, assets: [assetRemoteURL.absoluteString])\n        async let cache2 = manager.cacheAssets(identifier: testScheduleIdentifier2, assets: [assetRemoteURL.absoluteString])\n\n        do {\n            // Both should succeed despite potential race condition\n            let (result1, result2) = try await (cache1, cache2)\n\n            XCTAssertNotNil(result1)\n            XCTAssertNotNil(result2)\n\n            // At least 2 move attempts should have been made\n            moveAttemptsSemaphore.lock()\n            let finalMoveAttempts = moveAttempts\n            moveAttemptsSemaphore.unlock()\n\n            XCTAssertGreaterThanOrEqual(finalMoveAttempts, 1, \"Should have attempted to move at least once\")\n        } catch {\n            XCTFail(\"Concurrent caching should handle race conditions gracefully: \\(error)\")\n        }\n    }\n}\n\nfileprivate extension URL {\n    var assetFilename: String {\n        return AirshipUtils.sha256Hash(input: self.path)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Assets/DefaultAssetDownloaderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\nfinal class TestAssetDownloaderSession: AssetDownloaderSession, @unchecked Sendable {\n    var nextData: Data?\n    var nextError: Error?\n    var nextResponse: URLResponse?\n\n    func autoResumingDataTask(with url: URL, completion: @escaping (Data?, URLResponse?, Error?) -> Void) -> AirshipCancellable {\n        completion(nextData, nextResponse, nextError)\n\n        return CancellableValueHolder<String>() { _ in }\n    }\n}\n\n@testable import AirshipAutomation\nfinal class DefaultAssetDownloaderTest: XCTestCase {\n    var downloader: DefaultAssetDownloader!\n    var mockSession: TestAssetDownloaderSession!\n    let testURL = URL(string: \"https://airship.com/whatever\")!\n\n    override func setUpWithError() throws {\n        try super.setUpWithError()\n        mockSession = TestAssetDownloaderSession()\n        downloader = DefaultAssetDownloader(session: mockSession)\n    }\n\n    func testDownloadAssetDataMatches() async throws {\n        let expectedData = Data(\"Cool story\".utf8)\n        mockSession.nextData = expectedData\n\n        let tempURL = try await downloader.downloadAsset(remoteURL: testURL)\n\n        let downloadedData = try Data(contentsOf: tempURL)\n\n        XCTAssertTrue(FileManager.default.fileExists(atPath: tempURL.path), \"Downloaded file should exist at the temp URL\")\n        XCTAssertEqual(downloadedData, expectedData, \"Downloaded data at the temp URL should match the expected data.\")\n    }\n\n    override func tearDownWithError() throws {\n        let fileManager = FileManager.default\n        let tempFileURL = fileManager.temporaryDirectory.appendingPathComponent(testURL.lastPathComponent)\n        if fileManager.fileExists(atPath: tempFileURL.path) {\n            try fileManager.removeItem(at: tempFileURL)\n        }\n\n        downloader = nil\n        mockSession = nil\n        try super.tearDownWithError()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Assets/DefaultAssetFileManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipAutomation\nfinal class DefaultAssetFileManagerTest: XCTestCase {\n    func testEnsureCacheRootDirectory() {\n        let rootPathComponent = \"testCacheRoot\"\n        let assetManager = DefaultAssetFileManager(rootPathComponent: rootPathComponent)\n        let fileManager = FileManager.default\n        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!\n        let expectedCacheRootDirectory = cacheDirectory.appendingPathComponent(rootPathComponent, isDirectory: true)\n\n        /// Ensure the initial state is clean\n        try? fileManager.removeItem(at: expectedCacheRootDirectory)\n\n        /// Test when nothing is there\n        XCTAssertEqual(assetManager.rootDirectory, expectedCacheRootDirectory, \"The method did not return the expected URL when the directory was not present initially.\")\n\n        /// Remove root and create a file in its place\n        try? fileManager.removeItem(at: expectedCacheRootDirectory)\n        fileManager.createFile(atPath: expectedCacheRootDirectory.path, contents: Data(\"TestData\".utf8), attributes: nil)\n\n        /// Test when a file is in the directory\n        XCTAssertEqual(assetManager.rootDirectory, expectedCacheRootDirectory, \"The method did not return the expected URL when a file was present at the directory location.\")\n        var isDir: ObjCBool = false\n        XCTAssertTrue(fileManager.fileExists(atPath: expectedCacheRootDirectory.path, isDirectory: &isDir) && isDir.boolValue, \"A directory was not created in place of the file.\")\n    }\n\n    func testEnsureCacheDirectory() {\n        let rootPathComponent = \"testCacheRoot\"\n        let testIdentifier = \"testIdentifier\"\n        let assetManager = DefaultAssetFileManager(rootPathComponent: rootPathComponent)\n\n        let fileManager = FileManager.default\n        let cacheDirectory = fileManager.urls(for: .cachesDirectory, in: .userDomainMask).first!\n        let expectedCacheRootDirectory = cacheDirectory.appendingPathComponent(rootPathComponent, isDirectory: true)\n        let expectedCacheDirectory = expectedCacheRootDirectory.appendingPathComponent(testIdentifier, isDirectory: true)\n\n        XCTAssertEqual(try? assetManager.ensureCacheDirectory(identifier: testIdentifier), expectedCacheDirectory)\n    }\n\n    func testClearAssetsSuccess() throws {\n        let rootPathComponent = \"testCacheRoot\"\n        let assetManager = DefaultAssetFileManager(rootPathComponent: rootPathComponent)\n        let cacheURL = FileManager.default.temporaryDirectory.appendingPathComponent(\"testAssets\")\n        let identifier = \"testIdentifier\"\n\n        let assetsPath = cacheURL.appendingPathComponent(identifier)\n        try? FileManager.default.createDirectory(at: assetsPath, withIntermediateDirectories: true)\n        FileManager.default.createFile(atPath: assetsPath.appendingPathComponent(\"file1\").path, contents: Data(), attributes: nil)\n\n        try assetManager.clearAssets(cacheURL: cacheURL)\n\n        let directoryExists: Bool = FileManager.default.fileExists(atPath: assetsPath.path)\n        XCTAssertFalse(directoryExists, \"Not all assets were cleared for the identifier.\")\n\n        /// Cleanup\n        try? FileManager.default.removeItem(at: cacheURL)\n    }\n\n    func testMoveAssetSuccess() {\n        let rootPathComponent = \"testCacheRoot\"\n        let assetManager = DefaultAssetFileManager(rootPathComponent: rootPathComponent)\n        let tempURL = FileManager.default.temporaryDirectory.appendingPathComponent(\"tempFile\")\n        let cacheURL = FileManager.default.temporaryDirectory.appendingPathComponent(\"cacheFile\")\n\n        FileManager.default.createFile(atPath: tempURL.path, contents: Data(\"TestData\".utf8), attributes: nil)\n\n        do {\n            try assetManager.moveAsset(from: tempURL, to: cacheURL)\n            XCTAssertTrue(FileManager.default.fileExists(atPath: cacheURL.path), \"The file was not successfully moved to the cache URL.\")\n            XCTAssertFalse(FileManager.default.fileExists(atPath: tempURL.path), \"The temp was not successfully cleaned up after being moved to the cache URL.\")\n        } catch {\n            XCTFail(\"Failed to move asset: \\(error)\")\n        }\n\n        /// Cleanup\n        try? FileManager.default.removeItem(at: tempURL)\n        try? FileManager.default.removeItem(at: cacheURL)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/DefaultInAppActionRunnerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n@testable import AirshipAutomation\n\n@MainActor\nstruct DefaultInAppActionRunnerTest {\n\n    private let analytics: TestInAppMessageAnalytics = TestInAppMessageAnalytics()\n\n    @Test\n    func testCustomEventContext() {\n        let layoutContext = ThomasLayoutContext(\n            button: ThomasLayoutContext.Button(identifier: \"bar\")\n        )\n\n        let customEventContext = InAppCustomEventContext(\n            id: ThomasLayoutEventMessageID.appDefined(identifier: \"foo\"),\n            context: ThomasLayoutEventContext()\n        )\n\n        analytics.onMakeCustomEventContext = { lc in\n            #expect(layoutContext == lc)\n            return customEventContext\n        }\n\n        let runner = DefaultInAppActionRunner(analytics: analytics, trackPermissionResults: true)\n        var metadata: [String: Sendable] = [:]\n        runner.extendMetadata(&metadata, layoutContext: layoutContext)\n\n        #expect(customEventContext == metadata[AddCustomEventAction._inAppMetadata] as? InAppCustomEventContext)\n    }\n\n    @Test\n    func testCustomEventContextNilLayoutContext() {\n        let customEventContext = InAppCustomEventContext(\n            id: ThomasLayoutEventMessageID.appDefined(identifier: \"foo\"),\n            context: ThomasLayoutEventContext()\n        )\n\n        analytics.onMakeCustomEventContext = { lc in\n            #expect(lc == nil)\n            return customEventContext\n        }\n\n        let runner = DefaultInAppActionRunner(analytics: analytics, trackPermissionResults: true)\n        var metadata: [String: Sendable] = [:]\n        runner.extendMetadata(&metadata, layoutContext: nil)\n\n        #expect(customEventContext == metadata[AddCustomEventAction._inAppMetadata] as? InAppCustomEventContext)\n    }\n\n    @Test\n    func testTrackPermissionResults() async throws {\n        let layoutContext = ThomasLayoutContext(\n            button: ThomasLayoutContext.Button(identifier: \"bar\")\n        )\n\n        analytics.onMakeCustomEventContext = { _ in return nil }\n\n        let runner = DefaultInAppActionRunner(analytics: analytics, trackPermissionResults: true)\n        var metadata: [String: Sendable] = [:]\n        runner.extendMetadata(&metadata, layoutContext: layoutContext)\n\n        let resultReceiver = metadata[PromptPermissionAction.resultReceiverMetadataKey] as! PermissionResultReceiver\n        await resultReceiver(.displayNotifications, .granted, .granted)\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPermissionResultEvent(\n                        permission: .displayNotifications,\n                        startingStatus: .granted,\n                        endingStatus: .granted\n                    ),\n                    layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testTrackPermissionResultsNoContext() async throws {\n        analytics.onMakeCustomEventContext = { _ in return nil }\n\n        let runner = DefaultInAppActionRunner(analytics: analytics, trackPermissionResults: true)\n        var metadata: [String: Sendable] = [:]\n        runner.extendMetadata(&metadata, layoutContext: nil)\n\n        let resultReceiver = metadata[PromptPermissionAction.resultReceiverMetadataKey] as! PermissionResultReceiver\n        await resultReceiver(.displayNotifications, .granted, .granted)\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPermissionResultEvent(\n                        permission: .displayNotifications,\n                        startingStatus: .granted,\n                        endingStatus: .granted\n                    ),\n                    nil\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testTrackPermissionRusultsDisabled() async {\n        analytics.onMakeCustomEventContext = { _ in return nil }\n\n        let runner = DefaultInAppActionRunner(analytics: analytics, trackPermissionResults: false)\n        var metadata: [String: Sendable] = [:]\n        runner.extendMetadata(&metadata, layoutContext: nil)\n\n        let resultReceiver = metadata[PromptPermissionAction.resultReceiverMetadataKey] as? PermissionResultReceiver\n        #expect(resultReceiver == nil)\n    }\n\n    private func verifyEvents(\n        _ expected: [(ThomasLayoutEvent, ThomasLayoutContext?)],\n        sourceLocation: SourceLocation = #_sourceLocation\n    ) throws {\n        #expect(expected.count == self.analytics.events.count, sourceLocation: sourceLocation)\n\n        try expected.indices.forEach { index in\n            let expectedEvent = expected[index]\n            let actual = analytics.events[index]\n            #expect(actual.0.name == expectedEvent.0.name, sourceLocation: sourceLocation)\n            let actualData = try AirshipJSON.wrap(actual.0.data)\n            let expectedData = try AirshipJSON.wrap(expectedEvent.0.data)\n            #expect(actualData == expectedData, sourceLocation: sourceLocation)\n            #expect(actual.1 == expectedEvent.1, sourceLocation: sourceLocation)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Adapter/AirshipLayoutDisplayAdapterTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class AirshipLayoutDisplayAdapterTest: XCTestCase {\n\n    private let networkChecker: TestNetworkChecker = TestNetworkChecker()\n    private let assets: TestCachedAssets = TestCachedAssets()\n\n    func testIsReadyNoAssets() async throws {\n        let message = InAppMessage(\n            name: \"no assets\", \n            displayContent: .banner(.init())\n        )\n        XCTAssertTrue(message.urlInfos.isEmpty)\n\n        let adapter = try makeAdapter(message)\n\n        await networkChecker.setConnected(false)\n        let isReady = await adapter.isReady\n        XCTAssertTrue(isReady)\n    }\n\n    func testIsReadyImageAsset() async throws {\n        let message = InAppMessage(\n            name: \"image assets\",\n            displayContent: .banner(\n                .init(media: .init(url: \"some-url\", type: .image))\n            )\n        )\n\n        let adapter = try makeAdapter(message)\n\n        await networkChecker.setConnected(false)\n        var isReady = await adapter.isReady\n        XCTAssertFalse(isReady)\n\n        self.assets.cached.append(URL(string: \"some-url\")!)\n        isReady = await adapter.isReady\n        XCTAssertTrue(isReady)\n\n        self.assets.cached.removeAll()\n        isReady = await adapter.isReady\n        XCTAssertFalse(isReady)\n\n        await networkChecker.setConnected(true)\n        isReady = await adapter.isReady\n        XCTAssertTrue(isReady)\n    }\n\n    func testIsReadyVideoAsset() async throws {\n        let message = InAppMessage(\n            name: \"video assets\",\n            displayContent: .banner(\n                .init(media: .init(url: \"some-url\", type: .video))\n            )\n        )\n\n        let adapter = try makeAdapter(message)\n\n        // Caching is not checked for videos\n        self.assets.cached.append(URL(string: \"some-url\")!)\n\n        await networkChecker.setConnected(false)\n        var isReady = await adapter.isReady\n        XCTAssertFalse(isReady)\n\n        await networkChecker.setConnected(true)\n        isReady = await adapter.isReady\n        XCTAssertTrue(isReady)\n    }\n\n    func testIsReadyHTMLAsset() async throws {\n        let message = InAppMessage(\n            name: \"video assets\",\n            displayContent: .html(\n                .init(url: \"some-url\")\n            )\n        )\n\n        let adapter = try makeAdapter(message)\n\n        // Caching is not checked for html\n        self.assets.cached.append(URL(string: \"some-url\")!)\n\n        await networkChecker.setConnected(false)\n        var isReady = await adapter.isReady\n        XCTAssertFalse(isReady)\n\n        await networkChecker.setConnected(true)\n        isReady = await adapter.isReady\n        XCTAssertTrue(isReady)\n    }\n\n    func testWaitForReadyNetwork() async throws {\n        let message = InAppMessage(\n            name: \"video assets\",\n            displayContent: .html(\n                .init(url: \"some-url\")\n            )\n        )\n        let adapter = try makeAdapter(message)\n\n        let waitingReady = expectation(description: \"waiting is ready\")\n        let isReady = expectation(description: \"is ready\")\n\n        Task {\n            waitingReady.fulfill()\n            await adapter.waitForReady()\n            isReady.fulfill()\n        }\n\n        await self.fulfillment(of: [waitingReady])\n        Task { [networkChecker] in\n            await networkChecker.setConnected(true)\n        }\n\n        await self.fulfillment(of: [isReady])\n    }\n\n    private func makeAdapter(\n        _ message: InAppMessage\n    ) throws -> AirshipLayoutDisplayAdapter  {\n        return try AirshipLayoutDisplayAdapter(\n            message: message,\n            priority: 0,\n            assets: self.assets,\n            networkChecker: self.networkChecker\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Adapter/CustomDisplayAdapterWrapperTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\n\nfinal class CustomDisplayAdapterWrapperTest: XCTestCase {\n    private let testAdapter: TestCustomDisplayAdapter = TestCustomDisplayAdapter()\n    private var wrapper: CustomDisplayAdapterWrapper!\n\n    override func setUp() async throws {\n        self.wrapper = CustomDisplayAdapterWrapper(adapter: testAdapter)\n    }\n\n    func testIsReady() async {\n        await self.testAdapter.setReady(true)\n        var isReady = await self.wrapper.isReady\n        XCTAssertTrue(isReady)\n\n        await self.testAdapter.setReady(false)\n        isReady = await self.wrapper.isReady\n        XCTAssertFalse(isReady)\n    }\n\n    func testWaitForReady() async {\n        await self.testAdapter.setReady(false)\n\n        let waitingReady = expectation(description: \"waiting is ready\")\n        let isReady = expectation(description: \"is ready\")\n        Task { [wrapper] in\n            waitingReady.fulfill()\n            await wrapper!.waitForReady()\n            isReady.fulfill()\n        }\n\n        await self.fulfillment(of: [waitingReady])\n        Task { [testAdapter] in\n            await testAdapter.setReady(true)\n        }\n\n        await self.fulfillment(of: [isReady])\n    }\n}\n\n\nfileprivate final class TestCustomDisplayAdapter: CustomDisplayAdapter {\n\n    private let _isReady: AirshipMainActorValue<Bool> = AirshipMainActorValue(false)\n\n    @MainActor\n    func setReady(_ ready: Bool) {\n        _isReady.set(ready)\n    }\n\n    @MainActor\n    var isReady: Bool { return _isReady.value }\n\n    @MainActor\n    func waitForReady() async {\n        for await isReady  in _isReady.updates {\n            if (isReady) {\n                return\n            }\n        }\n    }\n\n    func display(scene: UIWindowScene) async -> CustomDisplayResolution {\n        // Cant test this due ot the scene\n        return .userDismissed\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Adapter/DisplayAdapterFactoryTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class DisplayAdapterFactoryTest: XCTestCase {\n\n    private let factory: DisplayAdapterFactory = DisplayAdapterFactory()\n    private let assets: TestCachedAssets = TestCachedAssets()\n\n    func testAirshipAdapter() async throws {\n        try await verifyAirshipAdapter(\n            displayContent: .modal(.init(buttons: [], template: .headerBodyMedia))\n        )\n\n        try await verifyAirshipAdapter(\n            displayContent: .banner(.init(buttons: [], template: .mediaLeft))\n        )\n\n        try await verifyAirshipAdapter(\n            displayContent: .fullscreen(.init(buttons: [], template: .headerBodyMedia))\n        )\n\n        try await verifyAirshipAdapter(\n            displayContent: .html(.init(url: \"some url\"))\n        )\n\n        let airshipLayout = \"\"\"\n        {\n          \"version\":1,\n          \"presentation\":{\n             \"type\":\"embedded\",\n             \"embedded_id\":\"home_banner\",\n             \"default_placement\":{\n                \"size\":{\n                   \"width\":\"50%\",\n                   \"height\":\"50%\"\n                }\n             }\n          },\n          \"view\":{\n             \"type\":\"container\",\n             \"items\":[]\n          }\n        }\n        \"\"\"\n\n        try await verifyAirshipAdapter(\n            displayContent: .airshipLayout(\n                try! JSONDecoder().decode(AirshipLayout.self, from: airshipLayout.data(using: .utf8)!)\n            )\n        )\n    }\n\n    func testCustomAdapters() async throws {\n        try await verifyCustomAdapter(\n            forType: .modal,\n            displayContent: .modal(.init(buttons: [], template: .headerBodyMedia))\n        )\n\n        try await verifyCustomAdapter(\n            forType: .banner,\n            displayContent: .banner(.init(buttons: [], template: .mediaLeft))\n        )\n\n        try await verifyCustomAdapter(\n            forType: .fullscreen,\n            displayContent: .fullscreen(.init(buttons: [], template: .headerBodyMedia))\n        )\n\n        try await verifyCustomAdapter(\n            forType: .html,\n            displayContent: .html(.init(url: \"some url\"))\n        )\n\n        try await verifyCustomAdapter(\n            forType: .custom,\n            displayContent: .custom(.string(\"custom\"))\n        )\n    }\n\n    func testCustomThrowsNoAdapter() async throws {\n        let message = InAppMessage(\n            name: \"Airship layout\",\n            displayContent: .custom(.string(\"custom\"))\n        )\n\n        do {\n            let _ = try await factory.makeAdapter(\n                args: DisplayAdapterArgs(\n                    message: message, assets: assets, priority: 0, _actionRunner: TestInAppActionRunner()\n                )\n            )\n            XCTFail(\"Wrong adapter\")\n        } catch {}\n    }\n\n\n    private func verifyAirshipAdapter(\n        displayContent: InAppMessageDisplayContent,\n        line: UInt = #line\n    ) async throws {\n        let message = InAppMessage(\n            name: \"\",\n            displayContent: displayContent\n        )\n\n        let adapter = try await factory.makeAdapter(\n            args: DisplayAdapterArgs(\n                message: message, assets: assets, priority: 0, _actionRunner: TestInAppActionRunner()\n            )\n        )\n\n        guard adapter as? AirshipLayoutDisplayAdapter != nil else {\n            XCTFail(\"Wrong adapter\", line: line)\n            return\n        }\n    }\n\n    private func verifyCustomAdapter(\n        forType type: CustomDisplayAdapterType,\n        displayContent: InAppMessageDisplayContent,\n        line: UInt = #line\n    ) async throws {\n        let message = InAppMessage(\n            name: \"\",\n            displayContent: displayContent\n        )\n\n        let assets = self.assets\n        let adapter = TestCustomDisplayAdapter()\n        await factory.setAdapterFactoryBlock(forType: type) { args in\n            guard\n                let incomingAssets = args.assets as? TestCachedAssets,\n                incomingAssets === assets,\n                message == args.message\n            else {\n                XCTFail(\"Invalid args\", line: line)\n                return nil\n            }\n\n            return adapter\n        }\n\n        let result = try await factory.makeAdapter(\n            args: DisplayAdapterArgs(\n                message: message, assets: assets, priority: 0, _actionRunner: TestInAppActionRunner()\n            )\n        )\n\n        guard\n            let wrappedAdapter = result as? CustomDisplayAdapterWrapper,\n            let unwrapped = wrappedAdapter.adapter as? TestCustomDisplayAdapter,\n            unwrapped === adapter\n        else {\n            XCTFail(\"Wrong adapter\", line: line)\n            return\n        }\n    }\n}\n\nfileprivate final class TestCustomDisplayAdapter: CustomDisplayAdapter, Sendable {\n    @MainActor\n    var isReady: Bool { return true }\n\n    func waitForReady() async {\n\n    }\n    \n    @MainActor\n    func display(scene: UIWindowScene) async -> CustomDisplayResolution {\n        return .timedOut\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Adapter/InAppMessageDisplayListenerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\n\nclass InAppMessageDisplayListenerTests: XCTestCase {\n\n    private let analytics: TestInAppMessageAnalytics = TestInAppMessageAnalytics()\n    private var listener: InAppMessageDisplayListener!\n    private let result: AirshipMainActorValue<DisplayResult?> = AirshipMainActorValue(nil)\n    private var timer: TestActiveTimer!\n\n    @MainActor\n    override func setUp() {\n        self.timer = TestActiveTimer()\n        listener = InAppMessageDisplayListener(analytics: analytics, timer: timer) { [result] displayResult in\n            result.set(displayResult)\n        }\n    }\n\n    @MainActor\n    func testOnAppear() async {\n        XCTAssertFalse(timer.isStarted)\n\n        listener.onAppear()\n\n        verifyEvents([ThomasLayoutDisplayEvent()])\n        XCTAssertTrue(timer.isStarted)\n\n        listener.onAppear()\n\n        verifyEvents([ThomasLayoutDisplayEvent(), ThomasLayoutDisplayEvent()])\n        XCTAssertNil(self.result.value)\n    }\n\n    @MainActor\n    func testOnButtonDismissed() {\n        self.timer.start()\n        self.timer.time = 10\n\n        let buttonInfo = InAppMessageButtonInfo(\n            identifier: \"button id\",\n            label: .init(text: \"button label\"),\n            behavior: .dismiss\n        )\n\n        listener.onButtonDismissed(buttonInfo: buttonInfo)\n\n        verifyEvents(\n            [\n                ThomasLayoutResolutionEvent.buttonTap(\n                    identifier: \"button id\",\n                    description: \"button label\",\n                    displayTime: 10\n                )\n            ]\n        )\n\n        XCTAssertFalse(timer.isStarted)\n        XCTAssertEqual(self.result.value, .finished)\n    }\n\n    @MainActor\n    func testOnButtonCancel() {\n        self.timer.start()\n        self.timer.time = 10\n\n        let buttonInfo = InAppMessageButtonInfo(\n            identifier: \"button id\",\n            label: .init(text: \"button label\"),\n            behavior: .cancel\n        )\n\n        listener.onButtonDismissed(buttonInfo: buttonInfo)\n\n        verifyEvents(\n            [\n                ThomasLayoutResolutionEvent.buttonTap(\n                    identifier: \"button id\",\n                    description: \"button label\",\n                    displayTime: 10\n                )\n            ]\n        )\n\n        XCTAssertFalse(timer.isStarted)\n        XCTAssertEqual(self.result.value, .cancel)\n    }\n\n    @MainActor\n    func testOnTimedOut() {\n        self.timer.start()\n        self.timer.time = 3\n\n        listener.onTimedOut()\n\n        verifyEvents([ThomasLayoutResolutionEvent.timedOut(displayTime: 3)])\n        XCTAssertFalse(timer.isStarted)\n        XCTAssertEqual(self.result.value, .finished)\n    }\n\n    @MainActor\n    func testOnUserDismissed() {\n        self.timer.start()\n        self.timer.time = 3\n\n        listener.onUserDismissed()\n\n        verifyEvents([ThomasLayoutResolutionEvent.userDismissed(displayTime: 3)])\n        XCTAssertFalse(timer.isStarted)\n        XCTAssertEqual(self.result.value, .finished)\n    }\n\n    @MainActor\n    func testOnMessageTapDismissed() {\n        self.timer.start()\n        self.timer.time = 2\n\n        listener.onMessageTapDismissed()\n\n        verifyEvents([ThomasLayoutResolutionEvent.messageTap(displayTime: 2)])\n        XCTAssertEqual(self.result.value, .finished)\n    }\n\n    private func verifyEvents(_ expected: [ThomasLayoutEvent], line: UInt = #line) {\n        XCTAssertEqual(expected.count, self.analytics.events.count, line: line)\n\n        expected.indices.forEach { index in\n            let expectedEvent = expected[index]\n            let event = analytics.events[index].0\n            XCTAssertEqual(event.name, expectedEvent.name, line: line)\n            XCTAssertEqual(try! AirshipJSON.wrap(event.data), try! AirshipJSON.wrap(expectedEvent.data), line: line)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Coordinators/DefaultDisplayCoordinatorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class DefaultDisplayCoordinatorTest: XCTestCase {\n\n    private let stateTracker: TestAppStateTracker = TestAppStateTracker()\n    private var displayCoordinator: DefaultDisplayCoordinator!\n    private let taskSleeper: TestTaskSleeper = TestTaskSleeper()\n\n    let fooSchedule = InAppMessage(name: \"foo\", displayContent: .custom(.string(\"foo\")))\n\n    @MainActor\n    override func setUp() async throws {\n        displayCoordinator = DefaultDisplayCoordinator(\n            displayInterval: 10.0,\n            appStateTracker: self.stateTracker,\n            taskSleeper: self.taskSleeper\n        )\n    }\n\n    @MainActor\n    func testIsReady() throws {\n        self.stateTracker.currentState = .active\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n        self.stateTracker.currentState = .background\n        XCTAssertFalse(self.displayCoordinator.isReady)\n\n        self.stateTracker.currentState = .inactive\n        XCTAssertFalse(self.displayCoordinator.isReady)\n    }\n\n    @MainActor\n    func testIsReadyLocking() async throws {\n        self.stateTracker.currentState = .active\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n        self.displayCoordinator.messageWillDisplay(fooSchedule)\n        XCTAssertFalse(self.displayCoordinator.isReady)\n\n        self.displayCoordinator.messageFinishedDisplaying(fooSchedule)\n        await self.displayCoordinator.waitForReady()\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n        XCTAssertEqual([10], self.taskSleeper.sleeps)\n    }\n\n    @MainActor\n    func testWaitForReady() async throws {\n        self.stateTracker.currentState = .background\n\n        let ready = Task { [displayCoordinator] in\n            await displayCoordinator!.waitForReady()\n        }\n\n        self.stateTracker.currentState = .active\n        await ready.value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Coordinators/DisplayCoordinatorManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class DisplayCoordinatorManagerTest: XCTestCase {\n\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var manager: DisplayCoordinatorManager!\n    \n    @MainActor\n    override func setUp() async throws {\n        manager = DisplayCoordinatorManager(dataStore: dataStore)\n    }\n\n    func testDefaultAdapter() throws {\n        let message = InAppMessage(name: \"\", displayContent: .custom(.string(\"\")))\n        let adapter = manager.displayCoordinator(message: message)\n        XCTAssertNotNil(adapter as? DefaultDisplayCoordinator)\n    }\n\n    func testDefaultAdapterEmbedded() throws {\n        let airshipLayout = \"\"\"\n        {\n          \"version\":1,\n          \"presentation\":{\n             \"type\":\"embedded\",\n             \"embedded_id\":\"home_banner\",\n             \"default_placement\":{\n                \"size\":{\n                   \"width\":\"50%\",\n                   \"height\":\"50%\"\n                }\n             }\n          },\n          \"view\":{\n             \"type\":\"container\",\n             \"items\":[]\n          }\n        }\n        \"\"\"\n\n        let message = InAppMessage(\n            name: \"\",\n            displayContent: .airshipLayout(\n                try! JSONDecoder().decode(AirshipLayout.self, from: airshipLayout.data(using: .utf8)!)\n            )\n        )\n        let adapter = manager.displayCoordinator(message: message)\n        XCTAssertNotNil(adapter as? ImmediateDisplayCoordinator)\n    }\n\n    func testStandardBehavior() throws {\n        let message = InAppMessage(\n            name: \"\",\n            displayContent: .custom(.string(\"\")),\n            displayBehavior: .standard\n        )\n\n        let adapter = manager.displayCoordinator(message: message)\n        XCTAssertNotNil(adapter as? DefaultDisplayCoordinator)\n    }\n\n    func testImmediateBehavior() throws {\n        let message = InAppMessage(\n            name: \"\",\n            displayContent: .custom(.string(\"\")),\n            displayBehavior: .immediate\n        )\n\n        let adapter = manager.displayCoordinator(message: message)\n        XCTAssertNotNil(adapter as? ImmediateDisplayCoordinator)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/Display Coordinators/ImmediateDisplayCoordinatorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class ImmediateDisplayCoordinatorTest: XCTestCase {\n\n    private let stateTracker: TestAppStateTracker = TestAppStateTracker()\n    private var displayCoordinator: ImmediateDisplayCoordinator!\n\n    @MainActor\n    override func setUp() async throws {\n        displayCoordinator = ImmediateDisplayCoordinator(\n            appStateTracker: self.stateTracker\n        )\n    }\n\n    @MainActor\n    func testIsReady() throws {\n        self.stateTracker.currentState = .active\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n        self.stateTracker.currentState = .background\n        XCTAssertFalse(self.displayCoordinator.isReady)\n\n        self.stateTracker.currentState = .inactive\n        XCTAssertFalse(self.displayCoordinator.isReady)\n    }\n\n    @MainActor\n    func testWaitForReady() async throws {\n        self.stateTracker.currentState = .background\n\n        let ready = Task { [displayCoordinator] in\n            await displayCoordinator!.waitForReady()\n        }\n\n        self.stateTracker.currentState = .active\n        await ready.value\n    }\n\n    @MainActor\n    func testDisplayMultiple() throws {\n        self.stateTracker.currentState = .active\n\n        let foo = InAppMessage(name: \"foo\", displayContent: .custom(.string(\"foo\")))\n        let bar = InAppMessage(name: \"bar\", displayContent: .custom(.string(\"bar\")))\n\n\n        self.displayCoordinator.messageWillDisplay(foo)\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n\n        self.displayCoordinator.messageWillDisplay(bar)\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n        self.displayCoordinator.messageFinishedDisplaying(foo)\n        XCTAssertTrue(self.displayCoordinator.isReady)\n\n        self.displayCoordinator.messageFinishedDisplaying(bar)\n        XCTAssertTrue(self.displayCoordinator.isReady)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageAutomationExecutorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class InAppMessageAutomationExecutorTest: XCTestCase {\n\n    private let sceneManager: TestSceneManager = TestSceneManager()\n    private let assetManager: TestAssetManager = TestAssetManager()\n    private let analyticsFactory: TestAnalyticsFactory = TestAnalyticsFactory()\n    private var conditionsChangedNotifier: ScheduleConditionsChangedNotifier!\n    private let analytics: TestInAppMessageAnalytics = TestInAppMessageAnalytics()\n    private let actionRunner: TestInAppActionRunner = TestInAppActionRunner()\n    private var displayAdapter: TestDisplayAdapter!\n\n\n    private let preparedInfo: PreparedScheduleInfo = PreparedScheduleInfo(\n        scheduleID: UUID().uuidString,\n        productID: UUID().uuidString,\n        campaigns: .string(UUID().uuidString),\n        contactID: UUID().uuidString,\n        reportingContext: .string(UUID().uuidString),\n        triggerSessionID: UUID().uuidString,\n        priority: 0\n    )\n\n    private var displayCoordinator: TestDisplayCoordinator!\n    private var preparedData: PreparedInAppMessageData!\n    private var executor: InAppMessageAutomationExecutor!\n\n    @MainActor\n    override func setUp() async throws {\n        self.displayAdapter = TestDisplayAdapter()\n        self.conditionsChangedNotifier = ScheduleConditionsChangedNotifier()\n        self.displayCoordinator = TestDisplayCoordinator()\n        self.preparedData = PreparedInAppMessageData(\n            message: InAppMessage(\n                name: \"\",\n                displayContent: .custom(.string(\"\")),\n                actions: \"actions payload\"\n            ),\n            displayAdapter: self.displayAdapter,\n            displayCoordinator: self.displayCoordinator,\n            analytics: analytics,\n            actionRunner: actionRunner\n        )\n\n        self.executor = InAppMessageAutomationExecutor(\n            sceneManager: sceneManager,\n            assetManager: assetManager,\n            analyticsFactory: analyticsFactory,\n            scheduleConditionsChangedNotifier: conditionsChangedNotifier\n        )\n\n        self.analyticsFactory.setOnMake { _, _ in\n            return self.analytics\n        }\n    }\n\n    @MainActor\n    func testIsReady() {\n        self.displayAdapter.isReady = true\n        self.displayCoordinator.isReady = true\n        XCTAssertEqual(\n            self.executor.isReady(data: preparedData, preparedScheduleInfo: preparedInfo),\n            .ready\n        )\n    }\n\n    @MainActor\n    func testNotReadyAdapter() {\n        self.displayAdapter.isReady = false\n        self.displayCoordinator.isReady = true\n        XCTAssertEqual(\n            self.executor.isReady(data: preparedData, preparedScheduleInfo: preparedInfo),\n            .notReady\n        )\n    }\n\n    @MainActor\n    func testNotReadyCoordinator() {\n        self.displayAdapter.isReady = true\n        self.displayCoordinator.isReady = false\n        XCTAssertEqual(\n            self.executor.isReady(data: preparedData, preparedScheduleInfo: preparedInfo),\n            .notReady\n        )\n    }\n\n    @MainActor\n    func testIsReadyDelegate() {\n        self.displayAdapter.isReady = true\n        self.displayCoordinator.isReady = true\n\n        let delegate = TestDisplayDelegate()\n        delegate.onIsReady = { [preparedData, preparedInfo] message, scheduleID in\n            XCTAssertEqual(message, preparedData!.message)\n            XCTAssertEqual(scheduleID, preparedInfo.scheduleID)\n            return true\n        }\n        self.executor.displayDelegate = delegate\n\n        XCTAssertEqual(\n            self.executor.isReady(data: preparedData, preparedScheduleInfo: preparedInfo),\n            .ready\n        )\n\n        delegate.onIsReady = { [preparedData, preparedInfo] message, scheduleID in\n            XCTAssertEqual(message, preparedData!.message)\n            XCTAssertEqual(scheduleID, preparedInfo.scheduleID)\n            return false\n        }\n\n        XCTAssertEqual(\n            self.executor.isReady(data: preparedData, preparedScheduleInfo: preparedInfo),\n            .notReady\n        )\n    }\n\n    func testInterrupted() async throws {\n        let schedule = AutomationSchedule(\n            identifier: preparedInfo.scheduleID,\n            triggers: [],\n            data: .inAppMessage(preparedData.message)\n        )\n\n        _ = await self.executor.interrupted(schedule: schedule, preparedScheduleInfo: preparedInfo)\n        let cleared = await self.assetManager.cleared\n        XCTAssertEqual([self.preparedInfo.scheduleID], cleared)\n        XCTAssertEqual(analytics.events.first!.0.name, ThomasLayoutResolutionEvent.interrupted().name)\n    }\n\n    @MainActor\n    func testExecute() async throws  {\n        self.displayAdapter.onDisplay = { [preparedData] displayTarget, incomingAnalytics in\n            XCTAssertTrue(preparedData!.analytics === incomingAnalytics)\n            return .finished\n        }\n\n        let result =  try await self.executor.execute(data: preparedData, preparedScheduleInfo: preparedInfo)\n        XCTAssertTrue(self.displayAdapter.displayed)\n        XCTAssertEqual(result, .finished)\n    }\n\n    @MainActor\n    func testExecuteInControlGroup() async throws  {\n        let scene = TestScene()\n        self.sceneManager.onScene = { [preparedData] message in\n            XCTAssertEqual(message, preparedData!.message)\n            return scene\n        }\n\n        let experimentResult = ExperimentResult(\n            channelID: \"some channel\",\n            contactID: \"some contact\",\n            isMatch: true,\n            reportingMetadata: []\n        )\n        var preparedInfo = preparedInfo\n        preparedInfo.experimentResult = experimentResult\n\n        let result = try await self.executor.execute(data: preparedData, preparedScheduleInfo: preparedInfo)\n\n        XCTAssertEqual(analytics.events.first!.0.name, ThomasLayoutResolutionEvent.control(experimentResult: experimentResult).name)\n        XCTAssertFalse(self.displayAdapter.displayed)\n        XCTAssertEqual(result, .finished)\n        XCTAssertTrue(self.actionRunner.actionPayloads.isEmpty)\n    }\n\n    @MainActor\n    func testExecuteDisplayAdapter() async throws  {\n        let delegate = TestDisplayDelegate()\n        self.executor.displayDelegate = delegate\n\n        delegate.onWillDisplay = { [preparedData, preparedInfo] message, scheduleID in\n            XCTAssertEqual(message, preparedData!.message)\n            XCTAssertEqual(scheduleID, preparedInfo.scheduleID)\n        }\n\n        delegate.onFinishedDisplaying = { [preparedData, preparedInfo] message, scheduleID in\n            XCTAssertEqual(message, preparedData!.message)\n            XCTAssertEqual(scheduleID, preparedInfo.scheduleID)\n        }\n\n        self.sceneManager.onScene = { _ in\n            return TestScene()\n        }\n\n        self.displayAdapter.onDisplay = { _, _ in\n            XCTAssertTrue(delegate.onWillDisplayCalled)\n            XCTAssertFalse(delegate.onFinishedDisplayingCalled)\n            return .finished\n        }\n\n        let result = try await self.executor.execute(data: preparedData, preparedScheduleInfo: preparedInfo)\n        XCTAssertTrue(delegate.onWillDisplayCalled)\n        XCTAssertTrue(delegate.onWillDisplayCalled)\n        XCTAssertTrue(self.displayAdapter.displayed)\n        XCTAssertEqual(result, .finished)\n    }\n\n    @MainActor\n    func testExecuteDisplayException() async throws  {\n        let scene = TestScene()\n        self.sceneManager.onScene = { [preparedData] message in\n            XCTAssertEqual(message, preparedData!.message)\n            return scene\n        }\n\n        let analytics = TestInAppMessageAnalytics()\n        self.analyticsFactory.onMake = { [preparedData, preparedInfo] incomingInfo, incomingMessage in\n            XCTAssertEqual(incomingInfo, preparedInfo)\n            XCTAssertEqual(incomingMessage, preparedData!.message)\n            return analytics\n        }\n\n\n        self.displayAdapter.onDisplay = { incomingScene, incomingAnalytics in\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        let result =  try await self.executor.execute(data: preparedData, preparedScheduleInfo: preparedInfo)\n\n        XCTAssertTrue(self.displayAdapter.displayed)\n        XCTAssertEqual(result, .retry)\n        XCTAssertTrue(self.actionRunner.actionPayloads.isEmpty)\n    }\n\n    @MainActor\n    func testAdditionalAudienceCheckMiss() async throws  {\n        self.displayAdapter.onDisplay = { incomingScene, incomingAnalytics in\n            throw AirshipErrors.error(\"Failed\")\n        }\n        var preparedInfo = preparedInfo\n        preparedInfo.additionalAudienceCheckResult = false\n\n        let result =  try await self.executor.execute(\n            data: preparedData,\n            preparedScheduleInfo: preparedInfo\n        )\n\n        XCTAssertEqual(analytics.events.first!.0.name, ThomasLayoutResolutionEvent.audienceExcluded().name)\n        XCTAssertFalse(self.displayAdapter.displayed)\n        XCTAssertEqual(result, .finished)\n        XCTAssertTrue(self.actionRunner.actionPayloads.isEmpty)\n    }\n\n    @MainActor\n    func testDisplayTargetNoScene() async throws  {\n        self.sceneManager.onScene = { _ in\n            throw AirshipErrors.error(\"Fail\")\n        }\n\n        self.displayAdapter.onDisplay = { displayTarget, _ in\n            _ = try displayTarget.sceneProvider()\n            return .cancel\n        }\n\n        let result = try await self.executor.execute(data: preparedData, preparedScheduleInfo: preparedInfo)\n        XCTAssertEqual(result, .retry)\n    }\n\n    @MainActor\n    func testExecuteCancel() async throws  {\n\n        self.displayAdapter.onDisplay = { [preparedData] displayTarget, incomingAnalytics in\n            XCTAssertTrue(preparedData!.analytics === incomingAnalytics)\n            return .cancel\n        }\n\n        let result =  try await self.executor.execute(data: preparedData, preparedScheduleInfo: preparedInfo)\n\n        XCTAssertTrue(self.displayAdapter.displayed)\n        XCTAssertEqual(result, .cancel)\n        XCTAssertEqual(self.actionRunner.actionPayloads.first!.0, self.preparedData.message.actions)\n    }\n}\n\n\nfileprivate final class TestDisplayDelegate: InAppMessageDisplayDelegate, @unchecked Sendable {\n    @MainActor\n    var onIsReady: ((InAppMessage, String) -> Bool)?\n\n    @MainActor\n    var onWillDisplay: ((InAppMessage, String) -> Void)?\n\n    @MainActor\n    var onWillDisplayCalled: Bool = false\n\n    @MainActor\n    var onFinishedDisplaying: ((InAppMessage, String) -> Void)?\n\n    @MainActor\n    var onFinishedDisplayingCalled: Bool = false\n\n    @MainActor\n    func isMessageReadyToDisplay(_ message: InAppMessage, scheduleID: String) -> Bool {\n        return self.onIsReady!(message, scheduleID)\n    }\n    \n    @MainActor\n    func messageWillDisplay(_ message: InAppMessage, scheduleID: String) {\n        self.onWillDisplay!(message, scheduleID)\n        self.onWillDisplayCalled = true\n    }\n    \n    @MainActor\n    func messageFinishedDisplaying(_ message: InAppMessage, scheduleID: String) {\n        self.onFinishedDisplaying!(message, scheduleID)\n        self.onFinishedDisplayingCalled = true\n    }\n}\n\nfileprivate final class TestScene: WindowSceneHolder {\n    var scene: UIWindowScene {\n        fatalError(\"not able to create a window scene\")\n    }\n}\n\nfileprivate final class TestSceneManager: InAppMessageSceneManagerProtocol, @unchecked Sendable {\n    var delegate: InAppMessageSceneDelegate?\n    \n    @MainActor\n    var onScene: ((InAppMessage) throws -> TestScene)?\n\n    func scene(forMessage: InAppMessage) throws -> WindowSceneHolder {\n        return try self.onScene!(forMessage)\n    }\n}\n\n\nfinal class TestAnalyticsFactory: InAppMessageAnalyticsFactoryProtocol, @unchecked Sendable {\n    func makeAnalytics(preparedScheduleInfo: PreparedScheduleInfo, message: InAppMessage) async -> any InAppMessageAnalyticsProtocol {\n        return await self.onMake!(preparedScheduleInfo, message)\n    }\n\n    @MainActor\n    var onMake: ((PreparedScheduleInfo, InAppMessage) async -> InAppMessageAnalyticsProtocol)?\n\n\n    @MainActor\n    func setOnMake(onMake: @escaping @Sendable (PreparedScheduleInfo, InAppMessage) -> InAppMessageAnalyticsProtocol) {\n        self.onMake = onMake\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageAutomationPreparerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class InAppMessageAutomationPreparerTest: XCTestCase {\n\n    private let displayCoordinatorManager: TestDisplayCoordinatorManager = TestDisplayCoordinatorManager()\n    private let displayAdapterFactory: TestDisplayAdapterFactory = TestDisplayAdapterFactory()\n    private let assetManager: TestAssetManager = TestAssetManager()\n    private let analyticsFactory: TestAnalyticsFactory = TestAnalyticsFactory()\n    private let analytics: TestInAppMessageAnalytics = TestInAppMessageAnalytics()\n    private let actionRunnerFactory: TestInAppActionRunnerFactory = TestInAppActionRunnerFactory()\n\n    private var preparer: InAppMessageAutomationPreparer!\n    private let message: InAppMessage = InAppMessage(\n        name: \"\",\n        displayContent: .banner(.init(media: .init(url: \"some-url\", type: .image)))\n    )\n\n    private let preparedScheduleInfo: PreparedScheduleInfo = PreparedScheduleInfo(\n        scheduleID: UUID().uuidString,\n        campaigns: \"campigns\",\n        contactID: UUID().uuidString,\n        experimentResult: nil,\n        triggerSessionID: UUID().uuidString,\n        priority: 0\n    )\n\n    override func setUp() async throws {\n        await analyticsFactory.setOnMake { [analytics] _, _ in\n            return analytics\n        }\n        self.preparer = InAppMessageAutomationPreparer(\n            assetManager: assetManager,\n            displayCoordinatorManager: displayCoordinatorManager,\n            displayAdapterFactory: displayAdapterFactory,\n            analyticsFactory: analyticsFactory,\n            actionRunnerFactory: actionRunnerFactory\n        )\n\n        actionRunnerFactory.onMake = { _, _ in return TestInAppActionRunner() }\n    }\n\n    func testPrepare() async throws {\n        let runner = TestInAppActionRunner()\n        actionRunnerFactory.onMake = { _, _ in return runner }\n\n        let cachedAssets = TestCachedAssets()\n        await self.assetManager.setOnCache { [preparedScheduleInfo] identifier, assets in\n            XCTAssertEqual(identifier, preparedScheduleInfo.scheduleID)\n            XCTAssertEqual([\"some-url\"], assets)\n            return cachedAssets\n        }\n\n        let displayCoordinator = await TestDisplayCoordinator()\n        self.displayCoordinatorManager.onCoordinator = { [message] incoming in\n            XCTAssertEqual(message, incoming)\n            return displayCoordinator\n        }\n\n        let displayAdapter = await TestDisplayAdapter()\n        self.displayAdapterFactory.onMake = { [message] args in\n            XCTAssertEqual(message, args.message)\n            let incomingAssets = args.assets as? TestCachedAssets\n            XCTAssertTrue(incomingAssets === cachedAssets)\n            return displayAdapter\n        }\n\n        let results = try await self.preparer.prepare(data: message, preparedScheduleInfo: preparedScheduleInfo)\n\n        XCTAssertEqual(self.message, results.message)\n        XCTAssertTrue(displayCoordinator === results.displayCoordinator)\n        XCTAssertTrue(displayAdapter === (results.displayAdapter as? TestDisplayAdapter))\n        XCTAssertTrue(runner === (results.actionRunner as? TestInAppActionRunner))\n    }\n\n    func testPrepareFailedAssets() async throws {\n        let displayCoordinator = await TestDisplayCoordinator()\n        let adapter = await TestDisplayAdapter()\n        \n        self.displayCoordinatorManager.onCoordinator = { _ in\n            return displayCoordinator\n        }\n\n        self.displayAdapterFactory.onMake = { _ in\n            return adapter\n        }\n\n        await self.assetManager.setOnCache { identifier, assets in\n            throw AirshipErrors.error(\"failed\")\n        }\n\n        do {\n            _ = try await self.preparer.prepare(data: message, preparedScheduleInfo: preparedScheduleInfo)\n            XCTFail(\"should throw\")\n        } catch {}\n    }\n\n    func testPrepareFailedAdapter() async throws {\n        let displayCoordinator = await TestDisplayCoordinator()\n        self.displayCoordinatorManager.onCoordinator = { _ in\n            return displayCoordinator\n        }\n\n        self.displayAdapterFactory.onMake = { _ in\n            throw AirshipErrors.error(\"failed\")\n        }\n\n        await self.assetManager.setOnCache { _, _ in\n            return TestCachedAssets()\n        }\n\n        do {\n            _ = try await self.preparer.prepare(data: message, preparedScheduleInfo: preparedScheduleInfo)\n            XCTFail(\"should throw\")\n        } catch {}\n    }\n\n    func testCancelled() async throws {\n        let scheduleID = UUID().uuidString\n        await self.preparer.cancelled(scheduleID: scheduleID)\n\n        let cleared = await self.assetManager.cleared\n        XCTAssertEqual(cleared, [scheduleID])\n    }\n\n}\n\nfileprivate final class TestDisplayCoordinatorManager: DisplayCoordinatorManagerProtocol, @unchecked Sendable {\n    var displayInterval: TimeInterval = 0.0\n    var onCoordinator: ((InAppMessage) -> DisplayCoordinator)?\n    func displayCoordinator(message: InAppMessage) -> DisplayCoordinator {\n        self.onCoordinator!(message)\n    }\n}\n\nfileprivate final class TestDisplayAdapterFactory: DisplayAdapterFactoryProtocol, @unchecked Sendable {\n    var onMake: ((DisplayAdapterArgs) throws -> DisplayAdapter)?\n\n    func setAdapterFactoryBlock(forType: CustomDisplayAdapterType, factoryBlock: @escaping @Sendable (DisplayAdapterArgs) -> (any CustomDisplayAdapter)?) {\n\n    }\n    \n    func makeAdapter(args: DisplayAdapterArgs) throws -> any DisplayAdapter {\n        return try self.onMake!(args)\n    }\n}\n\n\n\nfinal class TestInAppActionRunnerFactory: InAppActionRunnerFactoryProtocol, @unchecked Sendable {\n    var onMake: ((InAppMessage, InAppMessageAnalyticsProtocol) -> InternalInAppActionRunner)?\n\n\n    func makeRunner(message: InAppMessage, analytics: any InAppMessageAnalyticsProtocol) -> any InternalInAppActionRunner {\n        return self.onMake!(message, analytics)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageContentValidationTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\nimport AirshipCore\n\nfinal class InAppMessageContentValidationTest: XCTestCase {\n\n    private var validHeading: InAppMessageTextInfo!\n    private var validBody: InAppMessageTextInfo!\n    private var validMedia: InAppMessageMediaInfo!\n    // Assuming invalid media would have an invalid URL or type, but keeping it simple here\n    private var validButtonLabel: InAppMessageTextInfo!\n    private var validButton: InAppMessageButtonInfo!\n\n    private let validText = \"Valid Text\"\n    private let validIdentifier = \"d17a055c-ed67-4101-b65f-cd28b5904c84\"\n\n    private let validURL = \"some://image.png\"\n\n    private let validColor = InAppMessageColor(hexColorString: \"#ffffff\")\n    private let validFontFam = [\"sans-serif\"]\n\n\n    private var emptyHeading: InAppMessageTextInfo!\n    private var emptyBody: InAppMessageTextInfo!\n    private var emptyMedia: InAppMessageMediaInfo!\n    private var emptyButtonLabel: InAppMessageTextInfo!\n    private var emptyButton: InAppMessageButtonInfo!\n\n    private var validVideoMedia: InAppMessageMediaInfo!\n    private var validYoutubeMedia: InAppMessageMediaInfo!\n\n    override func setUp() {\n        super.setUp()\n        // Valid components\n        validHeading = InAppMessageTextInfo(text: validText, color: validColor, size: 22.0, fontFamilies: validFontFam, alignment: .center)\n        validBody = InAppMessageTextInfo(text: validText, color: validColor, size: 16.0, fontFamilies: validFontFam, alignment: .center)\n        validMedia = InAppMessageMediaInfo(url: validURL, type: .image, description: validText)\n        validButtonLabel = InAppMessageTextInfo(text: validText, color: validColor, size: 10, fontFamilies: validFontFam, style: [.bold])\n        validButton = InAppMessageButtonInfo(identifier: validIdentifier, label: validButtonLabel, actions: [:], backgroundColor: validColor, borderColor: validColor, borderRadius: 2)\n\n        // Empty components\n        emptyHeading = InAppMessageTextInfo(text: \"\", color: validColor, size: 22.0, fontFamilies: validFontFam, alignment: .center)\n        emptyBody = InAppMessageTextInfo(text: \"\", color: validColor, size: 16.0, fontFamilies: validFontFam, alignment: .center)\n        emptyMedia = InAppMessageMediaInfo(url: \"\", type: .image, description: \"\")\n        emptyButtonLabel = InAppMessageTextInfo(text: \"\", color: validColor, size: 10, fontFamilies: validFontFam, style: [.bold])\n        emptyButton = InAppMessageButtonInfo(identifier: \"\", label: validButtonLabel, actions: [:], backgroundColor: validColor, borderColor: validColor, borderRadius: 2)\n\n        validVideoMedia = InAppMessageMediaInfo(url: validURL, type: .video, description: validText)\n        validYoutubeMedia = InAppMessageMediaInfo(url: validURL, type: .video, description: validText)\n    }\n\n    func testBanner() {\n        let valid = InAppMessageDisplayContent.Banner(\n            heading: validHeading,\n            body: validBody,\n            media: validMedia,\n            buttons: [validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaLeft,\n            backgroundColor: validColor,\n            dismissButtonColor: validColor,\n            borderRadius: 5,\n            duration: 100.0,\n            placement: .top\n        )\n\n        XCTAssertTrue(valid.validate())\n    }\n\n    func testInvalidBanner() {\n        /// No heading or body\n        let noHeaderOrBodyContent = InAppMessageDisplayContent.Banner(\n            heading: nil,\n            body: nil,\n            media: validMedia,\n            buttons: [validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaLeft,\n            backgroundColor: validColor,\n            dismissButtonColor: validColor,\n            borderRadius: 5,\n            duration: 100.0,\n            placement: .top\n        )\n\n        let tooManyButtons = InAppMessageDisplayContent.Banner(\n            heading: validHeading,\n            body: validBody,\n            media: validYoutubeMedia,\n            buttons: [validButton, validButton, validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaLeft,\n            backgroundColor: validColor,\n            dismissButtonColor: validColor,\n            borderRadius: 5,\n            duration: 100.0,\n            placement: .top\n        )\n\n        XCTAssertFalse(noHeaderOrBodyContent.validate())\n        XCTAssertFalse(tooManyButtons.validate())\n    }\n\n    func testModal() {\n        let valid = InAppMessageDisplayContent.Modal(\n            heading: validHeading,\n            body: validBody,\n            media: validMedia,\n            footer: validButton,\n            buttons: [validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaHeaderBody,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor,\n            borderRadius: 5,\n            allowFullscreenDisplay: true\n        )\n\n        XCTAssertTrue(valid.validate())\n    }\n\n    func testInvalidModal() {\n        let emptyHeadingAndBody = InAppMessageDisplayContent.Modal(\n            heading: emptyHeading,\n            body: emptyBody,\n            media: validMedia,\n            footer: validButton,\n            buttons: [validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaHeaderBody,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor,\n            borderRadius: 5,\n            allowFullscreenDisplay: true\n        )\n\n        let tooManyButtons = InAppMessageDisplayContent.Modal(\n            heading: emptyHeading,\n            body: emptyBody,\n            media: validMedia,\n            footer: validButton,\n            buttons: [validButton, validButton, validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaHeaderBody,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor,\n            borderRadius: 5,\n            allowFullscreenDisplay: true\n        )\n\n        XCTAssertFalse(tooManyButtons.validate())\n        XCTAssertFalse(emptyHeadingAndBody.validate())\n    }\n\n    func testFullscreen() {\n        let valid = InAppMessageDisplayContent.Fullscreen(\n            heading: validHeading,\n            body: validBody,\n            media: validMedia,\n            footer: validButton,\n            buttons: [validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaHeaderBody,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor\n        )\n\n        XCTAssertTrue(valid.validate())\n    }\n\n    func testInvalidFullscreen() {\n        let emptyHeadingAndBody = InAppMessageDisplayContent.Fullscreen(\n            heading: emptyHeading,\n            body: emptyBody,\n            media: validMedia,\n            footer: validButton,\n            buttons: [validButton, validButton, validButton, validButton, validButton, validButton],\n            buttonLayoutType: .stacked,\n            template: .mediaHeaderBody,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor)\n\n        XCTAssertFalse(emptyHeadingAndBody.validate())\n    }\n\n    func testHTML() {\n        let valid = InAppMessageDisplayContent.HTML(\n            url: validURL,\n            height: 100,\n            width: 100,\n            aspectLock: true,\n            requiresConnectivity: true,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor,\n            borderRadius: 5,\n            allowFullscreen: true\n        )\n\n        XCTAssertTrue(valid.validate())\n    }\n\n    func testInvalidHTML() {\n        let emptyURL = InAppMessageDisplayContent.HTML(\n            url: \"\",\n            height: 100,\n            width: 100,\n            aspectLock: true,\n            requiresConnectivity: true,\n            dismissButtonColor: validColor,\n            backgroundColor: validColor,\n            borderRadius: 5,\n            allowFullscreen: true\n        )\n\n        XCTAssertFalse(emptyURL.validate())\n    }\n\n    func testTextInfo() {\n        XCTAssertTrue(validHeading.validate())\n        XCTAssertTrue(validBody.validate())\n        XCTAssertFalse(emptyHeading.validate())\n        XCTAssertFalse(emptyBody.validate())\n    }\n\n    func testButtonInfo() {\n        XCTAssertTrue(validButton.validate())\n        XCTAssertFalse(emptyButton.validate())\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class InAppMessageTest: XCTestCase {\n    \n    func testBanner() throws {\n        let json = \"\"\"\n          {\n             \"source\": \"remote-data\",\n             \"display\" : {\n                \"allow_fullscreen_display\" : true,\n                \"background_color\" : \"#ffffff\",\n                \"body\" : {\n                   \"alignment\" : \"center\",\n                   \"color\" : \"#000000\",\n                   \"font_family\" : [\n                      \"sans-serif\"\n                   ],\n                   \"size\" : 16,\n                   \"text\" : \"Big body\"\n                },\n                \"border_radius\" : 5,\n                \"button_layout\" : \"stacked\",\n                \"buttons\" : [\n                   {\n                      \"actions\" : {},\n                      \"background_color\" : \"#63aff2\",\n                      \"border_color\" : \"#63aff2\",\n                      \"border_radius\" : 2,\n                      \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                      \"label\" : {\n                         \"color\" : \"#ffffff\",\n                         \"font_family\" : [\n                            \"sans-serif\"\n                         ],\n                         \"size\" : 10,\n                         \"style\" : [\n                            \"bold\"\n                         ],\n                         \"text\" : \"Touch it\"\n                      }\n                   }\n                ],\n                \"dismiss_button_color\" : \"#000000\",\n                \"heading\" : {\n                   \"alignment\" : \"center\",\n                   \"color\" : \"#63aff2\",\n                   \"font_family\" : [\n                      \"sans-serif\"\n                   ],\n                   \"size\" : 22,\n                   \"text\" : \"Boom\"\n                },\n                \"media\" : {\n                   \"description\" : \"Image\",\n                   \"type\" : \"image\",\n                   \"url\" : \"some://image\"\n                },\n                \"template\" : \"media_left\",\n                \"placement\" : \"top\",\n                \"duration\" : 100.0\n             },\n             \"display_type\" : \"banner\",\n             \"name\" : \"woot\"\n          }\n        \"\"\"\n\n        let expected = InAppMessage(\n            name: \"woot\",\n            displayContent: .banner(\n                .init(\n                    heading: .init(\n                        text: \"Boom\",\n                        color: .init(hexColorString: \"#63aff2\"),\n                        size: 22.0,\n                        fontFamilies: [\"sans-serif\"],\n                        alignment: .center\n                    ),\n                    body: .init(\n                        text: \"Big body\",\n                        color: .init(hexColorString: \"#000000\"),\n                        size: 16.0,\n                        fontFamilies: [\"sans-serif\"],\n                        alignment: .center\n                    ),\n                    media: .init(\n                        url: \"some://image\",\n                        type: .image,\n                        description: \"Image\"\n                    ),\n                    buttons: [\n                        .init(\n                            identifier: \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                            label: .init(\n                                text: \"Touch it\",\n                                color: .init(hexColorString: \"#ffffff\"),\n                                size: 10,\n                                fontFamilies: [\"sans-serif\"],\n                                style: [.bold]\n                            ),\n                            actions: [:],\n                            backgroundColor: .init(hexColorString: \"#63aff2\"),\n                            borderColor: .init(hexColorString: \"#63aff2\"),\n                            borderRadius: 2\n                        )\n                    ],\n                    buttonLayoutType: .stacked,\n                    template: .mediaLeft,\n                    backgroundColor: .init(hexColorString: \"#ffffff\"),\n                    dismissButtonColor: .init(hexColorString: \"#000000\"),\n                    borderRadius: 5,\n                    duration: 100.0,\n                    placement: .top\n                )\n            ),\n            source: .remoteData\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testModal() throws {\n        let json = \"\"\"\n          {\n             \"source\": \"app-defined\",\n             \"display\" : {\n                \"allow_fullscreen_display\" : true,\n                \"background_color\" : \"#ffffff\",\n                \"body\" : {\n                   \"alignment\" : \"center\",\n                   \"color\" : \"#000000\",\n                   \"font_family\" : [\n                      \"sans-serif\"\n                   ],\n                   \"size\" : 16,\n                   \"text\" : \"Big body\"\n                },\n                \"border_radius\" : 5,\n                \"button_layout\" : \"stacked\",\n                \"buttons\" : [\n                   {\n                      \"actions\" : {},\n                      \"background_color\" : \"#63aff2\",\n                      \"border_color\" : \"#63aff2\",\n                      \"border_radius\" : 2,\n                      \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                      \"label\" : {\n                         \"color\" : \"#ffffff\",\n                         \"font_family\" : [\n                            \"sans-serif\"\n                         ],\n                         \"size\" : 10,\n                         \"style\" : [\n                            \"bold\"\n                         ],\n                         \"text\" : \"Touch it\"\n                      }\n                   }\n                ],\n                \"dismiss_button_color\" : \"#000000\",\n                \"heading\" : {\n                   \"alignment\" : \"center\",\n                   \"color\" : \"#63aff2\",\n                   \"font_family\" : [\n                      \"sans-serif\"\n                   ],\n                   \"size\" : 22,\n                   \"text\" : \"Boom\"\n                },\n                \"media\" : {\n                   \"description\" : \"Image\",\n                   \"type\" : \"image\",\n                   \"url\" : \"some://image\"\n                },\n                \"template\" : \"media_header_body\",\n             },\n             \"display_type\" : \"modal\",\n             \"name\" : \"woot\"\n          }\n        \"\"\"\n\n        let expected = InAppMessage(\n            name: \"woot\",\n            displayContent: .modal(\n                .init(\n                    heading: .init(\n                        text: \"Boom\",\n                        color: .init(hexColorString: \"#63aff2\"),\n                        size: 22.0,\n                        fontFamilies: [\"sans-serif\"],\n                        alignment: .center\n                    ),\n                    body: .init(\n                        text: \"Big body\",\n                        color: .init(hexColorString: \"#000000\"),\n                        size: 16.0,\n                        fontFamilies: [\"sans-serif\"],\n                        alignment: .center\n                    ),\n                    media: .init(\n                        url: \"some://image\",\n                        type: .image,\n                        description: \"Image\"\n                    ),\n                    buttons: [\n                        .init(\n                            identifier: \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                            label: .init(\n                                text: \"Touch it\",\n                                color: .init(hexColorString: \"#ffffff\"),\n                                size: 10,\n                                fontFamilies: [\"sans-serif\"],\n                                style: [.bold]\n                            ),\n                            actions: [:],\n                            backgroundColor: .init(hexColorString: \"#63aff2\"),\n                            borderColor: .init(hexColorString: \"#63aff2\"),\n                            borderRadius: 2\n                        )\n                    ],\n                    buttonLayoutType: .stacked,\n                    template: .mediaHeaderBody,\n                    dismissButtonColor: .init(hexColorString: \"#000000\"),\n                    backgroundColor: .init(hexColorString: \"#ffffff\"),\n                    borderRadius: 5,\n                    allowFullscreenDisplay: true\n                )\n            ),\n            source: .appDefined\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testFullscreen() throws {\n        let json = \"\"\"\n          {\n             \"source\": \"app-defined\",\n             \"display\" : {\n                \"background_color\" : \"#ffffff\",\n                \"body\" : {\n                   \"alignment\" : \"center\",\n                   \"color\" : \"#000000\",\n                   \"font_family\" : [\n                      \"sans-serif\"\n                   ],\n                   \"size\" : 16,\n                   \"text\" : \"Big body\"\n                },\n                \"button_layout\" : \"stacked\",\n                \"buttons\" : [\n                   {\n                      \"actions\" : {},\n                      \"background_color\" : \"#63aff2\",\n                      \"border_color\" : \"#63aff2\",\n                      \"border_radius\" : 2,\n                      \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                      \"label\" : {\n                         \"color\" : \"#ffffff\",\n                         \"font_family\" : [\n                            \"sans-serif\"\n                         ],\n                         \"size\" : 10,\n                         \"style\" : [\n                            \"bold\"\n                         ],\n                         \"text\" : \"Touch it\"\n                      }\n                   }\n                ],\n                \"dismiss_button_color\" : \"#000000\",\n                \"heading\" : {\n                   \"alignment\" : \"center\",\n                   \"color\" : \"#63aff2\",\n                   \"font_family\" : [\n                      \"sans-serif\"\n                   ],\n                   \"size\" : 22,\n                   \"text\" : \"Boom\"\n                },\n                \"media\" : {\n                   \"description\" : \"Image\",\n                   \"type\" : \"image\",\n                   \"url\" : \"some://image\"\n                },\n                \"template\" : \"media_header_body\",\n             },\n             \"display_type\" : \"fullscreen\",\n             \"name\" : \"woot\"\n          }\n        \"\"\"\n\n        let expected = InAppMessage(\n            name: \"woot\",\n            displayContent: .fullscreen(\n                .init(\n                    heading: .init(\n                        text: \"Boom\",\n                        color: .init(hexColorString: \"#63aff2\"),\n                        size: 22.0,\n                        fontFamilies: [\"sans-serif\"],\n                        alignment: .center\n                    ),\n                    body: .init(\n                        text: \"Big body\",\n                        color: .init(hexColorString: \"#000000\"),\n                        size: 16.0,\n                        fontFamilies: [\"sans-serif\"],\n                        alignment: .center\n                    ),\n                    media: .init(\n                        url: \"some://image\",\n                        type: .image,\n                        description: \"Image\"\n                    ),\n                    buttons: [\n                        .init(\n                            identifier: \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                            label: .init(\n                                text: \"Touch it\",\n                                color: .init(hexColorString: \"#ffffff\"),\n                                size: 10,\n                                fontFamilies: [\"sans-serif\"],\n                                style: [.bold]\n                            ),\n                            actions: [:],\n                            backgroundColor: .init(hexColorString: \"#63aff2\"),\n                            borderColor: .init(hexColorString: \"#63aff2\"),\n                            borderRadius: 2\n                        )\n                    ],\n                    buttonLayoutType: .stacked,\n                    template: .mediaHeaderBody,\n                    dismissButtonColor: .init(hexColorString: \"#000000\"),\n                    backgroundColor: .init(hexColorString: \"#ffffff\")\n                )\n            ),\n            source: .appDefined\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testHTML() throws {\n        let json = \"\"\"\n         {\n             \"display\" : {\n                \"allow_fullscreen_display\" : false,\n                \"background_color\" : \"#00000000\",\n                \"border_radius\" : 5,\n                \"dismiss_button_color\" : \"#000000\",\n                \"url\" : \"some://url\"\n             },\n             \"display_type\" : \"html\",\n             \"name\" : \"Thanks page\"\n         }\n        \"\"\"\n\n        let expected = InAppMessage(\n            name: \"Thanks page\",\n            displayContent: .html(\n                .init(\n                    url: \"some://url\",\n                    dismissButtonColor: .init(hexColorString: \"#000000\"),\n                    backgroundColor: .init(hexColorString: \"#00000000\"),\n                    borderRadius: 5.0,\n                    allowFullscreen: false\n                )\n            ),\n            source: nil\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testCustom() throws {\n        let json = \"\"\"\n          {\n             \"source\": \"app-defined\",\n             \"display\" : {\n                \"cool\": \"story\"\n             },\n             \"display_type\" : \"custom\",\n             \"name\" : \"woot\"\n          }\n        \"\"\"\n\n        let expected = InAppMessage(\n            name: \"woot\",\n            displayContent: .custom(\n                [\"cool\": \"story\"]\n            ),\n            source: .appDefined\n        )\n\n        try verify(json: json, expected: expected)\n    }\n\n    func testAirshipLayout() throws {\n        let airshipLayout = \"\"\"\n        {\n          \"version\":1,\n          \"presentation\":{\n             \"type\":\"embedded\",\n             \"embedded_id\":\"home_banner\",\n             \"default_placement\":{\n                \"size\":{\n                   \"width\":\"50%\",\n                   \"height\":\"50%\"\n                }\n             }\n          },\n          \"view\":{\n             \"type\":\"container\",\n             \"items\":[]\n          }\n        }\n        \"\"\"\n\n        let json = \"\"\"\n          {\n             \"source\": \"remote-data\",\n             \"display\" : { \"layout\": \\(airshipLayout) },\n             \"display_type\" : \"layout\",\n             \"name\" : \"Airship layout\"\n          }\n        \"\"\"\n\n        let expectedLayout = try! JSONDecoder().decode(AirshipLayout.self, from: airshipLayout.data(using: .utf8)!)\n        let expected = InAppMessage(\n            name: \"Airship layout\",\n            displayContent: .airshipLayout(\n                expectedLayout\n            ),\n            source: .remoteData\n        )\n                \n        try verify(json: json, expected: expected)\n    }\n    \n    func testNamePropertyDefaultsToEmptyString() throws {\n        let json = \"\"\"\n           {\n              \"source\": \"app-defined\",\n              \"display\" : {\n                 \"cool\": \"story\"\n              },\n              \"display_type\" : \"custom\",\n           }\n         \"\"\"\n        \n        let expected = InAppMessage(\n            name: \"\",\n            displayContent: .custom(\n                [\"cool\": \"story\"]\n            ),\n            source: .appDefined\n        )\n        \n        try verify(json: json, expected: expected)\n    }\n\n    func verify(json: String, expected: InAppMessage) throws {\n        let decoder = JSONDecoder()\n        let encoder = JSONEncoder()\n\n        let fromJSON = try decoder.decode(InAppMessage.self, from: json.data(using: .utf8)!)\n        XCTAssertEqual(fromJSON, expected)\n\n        let roundTrip = try decoder.decode(InAppMessage.self, from: try encoder.encode(fromJSON))\n        XCTAssertEqual(roundTrip, fromJSON)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/InAppMessageThemeTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\n\nfinal class InAppMessageThemeTest: XCTestCase {\n\n    private var testBundle: Bundle!\n\n    override func setUpWithError() throws {\n        testBundle = Bundle(for: type(of: self))\n    }\n\n    func testBannerParsing() throws {\n        var bannerTheme = InAppMessageTheme.Banner.defaultTheme\n        try bannerTheme.applyPlist(plistName: \"Valid-UAInAppMessageBannerStyle\", bundle: testBundle)\n\n        // default is 24 horizontal padding\n        XCTAssertEqual(1, bannerTheme.padding.top)\n        XCTAssertEqual(2, bannerTheme.padding.bottom)\n        XCTAssertEqual(27, bannerTheme.padding.leading)\n        XCTAssertEqual(28, bannerTheme.padding.trailing)\n\n\n        XCTAssertEqual(5, bannerTheme.header.letterSpacing)\n        XCTAssertEqual(6, bannerTheme.header.lineSpacing)\n        XCTAssertEqual(7, bannerTheme.header.padding.top)\n        XCTAssertEqual(8, bannerTheme.header.padding.bottom)\n        XCTAssertEqual(9, bannerTheme.header.padding.leading)\n        XCTAssertEqual(10, bannerTheme.header.padding.trailing)\n        XCTAssertEqual(11, bannerTheme.body.letterSpacing)\n        XCTAssertEqual(12, bannerTheme.body.lineSpacing)\n        XCTAssertEqual(13, bannerTheme.body.padding.top)\n        XCTAssertEqual(14, bannerTheme.body.padding.bottom)\n        XCTAssertEqual(15, bannerTheme.body.padding.leading)\n        XCTAssertEqual(16, bannerTheme.body.padding.trailing)\n        XCTAssertEqual(17, bannerTheme.media.padding.top)\n        XCTAssertEqual(18, bannerTheme.media.padding.bottom)\n        XCTAssertEqual(19, bannerTheme.media.padding.leading)\n        XCTAssertEqual(20, bannerTheme.media.padding.trailing)\n        XCTAssertEqual(21, bannerTheme.buttons.height)\n        XCTAssertEqual(22, bannerTheme.buttons.padding.top)\n        XCTAssertEqual(23, bannerTheme.buttons.padding.bottom)\n        XCTAssertEqual(24, bannerTheme.buttons.padding.leading)\n        XCTAssertEqual(25, bannerTheme.buttons.padding.trailing)\n        XCTAssertEqual(26, bannerTheme.maxWidth)\n        XCTAssertEqual(27, bannerTheme.tapOpacity)\n        XCTAssertEqual(28, bannerTheme.shadow.radius)\n        XCTAssertEqual(29, bannerTheme.shadow.xOffset)\n        XCTAssertEqual(30, bannerTheme.shadow.yOffset)\n        XCTAssertEqual(\"003100\".airshipToColor() , bannerTheme.shadow.color)\n    }\n\n    func testModalParsing() throws {\n        var modalTheme = InAppMessageTheme.Modal.defaultTheme\n        try modalTheme.applyPlist(plistName: \"Valid-UAInAppMessageModalStyle\", bundle: testBundle)\n\n\n        // default is 24 horizontal, 48 vertical\n        XCTAssertEqual(49, modalTheme.padding.top)\n        XCTAssertEqual(50, modalTheme.padding.bottom)\n        XCTAssertEqual(27, modalTheme.padding.leading)\n        XCTAssertEqual(28, modalTheme.padding.trailing)\n\n        XCTAssertEqual(5, modalTheme.header.letterSpacing)\n        XCTAssertEqual(6, modalTheme.header.lineSpacing)\n        XCTAssertEqual(7, modalTheme.header.padding.top)\n        XCTAssertEqual(8, modalTheme.header.padding.bottom)\n        XCTAssertEqual(9, modalTheme.header.padding.leading)\n        XCTAssertEqual(10, modalTheme.header.padding.trailing)\n        XCTAssertEqual(11, modalTheme.body.letterSpacing)\n        XCTAssertEqual(12, modalTheme.body.lineSpacing)\n        XCTAssertEqual(13, modalTheme.body.padding.top)\n        XCTAssertEqual(14, modalTheme.body.padding.bottom)\n        XCTAssertEqual(15, modalTheme.body.padding.leading)\n        XCTAssertEqual(16, modalTheme.body.padding.trailing)\n\n        /// Default is -24 horizontal padding\n        XCTAssertEqual(17, modalTheme.media.padding.top)\n        XCTAssertEqual(18, modalTheme.media.padding.bottom)\n        XCTAssertEqual(-5, modalTheme.media.padding.leading)\n        XCTAssertEqual(-4, modalTheme.media.padding.trailing)\n\n        XCTAssertEqual(21, modalTheme.buttons.height)\n        XCTAssertEqual(22, modalTheme.buttons.stackedSpacing)\n        XCTAssertEqual(23, modalTheme.buttons.separatedSpacing)\n        XCTAssertEqual(24, modalTheme.buttons.padding.top)\n        XCTAssertEqual(25, modalTheme.buttons.padding.bottom)\n        XCTAssertEqual(26, modalTheme.buttons.padding.leading)\n        XCTAssertEqual(27, modalTheme.buttons.padding.trailing)\n        XCTAssertEqual(28, modalTheme.maxWidth)\n        XCTAssertEqual(29, modalTheme.maxHeight)\n        XCTAssertEqual(\"testDismissIconResourceName\", modalTheme.dismissIconResource)\n    }\n\n    func testFullScreenParsing() throws {\n        var fullscreenTheme = InAppMessageTheme.Fullscreen.defaultTheme\n        try fullscreenTheme.applyPlist(plistName: \"Valid-UAInAppMessageFullScreenStyle\", bundle: testBundle)\n\n        // default is 24 on all sides\n        XCTAssertEqual(25, fullscreenTheme.padding.top)\n        XCTAssertEqual(26, fullscreenTheme.padding.bottom)\n        XCTAssertEqual(27, fullscreenTheme.padding.leading)\n        XCTAssertEqual(28, fullscreenTheme.padding.trailing)\n\n        XCTAssertEqual(5, fullscreenTheme.header.letterSpacing)\n        XCTAssertEqual(6, fullscreenTheme.header.lineSpacing)\n        XCTAssertEqual(7, fullscreenTheme.header.padding.top)\n        XCTAssertEqual(8, fullscreenTheme.header.padding.bottom)\n        XCTAssertEqual(9, fullscreenTheme.header.padding.leading)\n        XCTAssertEqual(10, fullscreenTheme.header.padding.trailing)\n        XCTAssertEqual(11, fullscreenTheme.body.letterSpacing)\n        XCTAssertEqual(12, fullscreenTheme.body.lineSpacing)\n        XCTAssertEqual(13, fullscreenTheme.body.padding.top)\n        XCTAssertEqual(14, fullscreenTheme.body.padding.bottom)\n        XCTAssertEqual(15, fullscreenTheme.body.padding.leading)\n        XCTAssertEqual(16, fullscreenTheme.body.padding.trailing)\n\n        /// Default is -24 horizontal padding\n        XCTAssertEqual(17, fullscreenTheme.media.padding.top)\n        XCTAssertEqual(18, fullscreenTheme.media.padding.bottom)\n        XCTAssertEqual(-5, fullscreenTheme.media.padding.leading)\n        XCTAssertEqual(-4, fullscreenTheme.media.padding.trailing)\n\n        XCTAssertEqual(21, fullscreenTheme.buttons.height)\n        XCTAssertEqual(22, fullscreenTheme.buttons.stackedSpacing)\n        XCTAssertEqual(23, fullscreenTheme.buttons.separatedSpacing)\n        XCTAssertEqual(24, fullscreenTheme.buttons.padding.top)\n        XCTAssertEqual(25, fullscreenTheme.buttons.padding.bottom)\n        XCTAssertEqual(26, fullscreenTheme.buttons.padding.leading)\n        XCTAssertEqual(27, fullscreenTheme.buttons.padding.trailing)\n        XCTAssertEqual(\"testDismissIconResourceName\", fullscreenTheme.dismissIconResource)\n    }\n\n    func testHTMLParsing() throws {\n        var htmlTheme = InAppMessageTheme.HTML.defaultTheme\n        try htmlTheme.applyPlist(plistName: \"Valid-UAInAppMessageHTMLStyle\", bundle: testBundle)\n\n        XCTAssertTrue(htmlTheme.hideDismissIcon == true)\n\n        // default is 24 horizontal, 48 vertical\n        XCTAssertEqual(49, htmlTheme.padding.top)\n        XCTAssertEqual(50, htmlTheme.padding.bottom)\n        XCTAssertEqual(27, htmlTheme.padding.leading)\n        XCTAssertEqual(28, htmlTheme.padding.trailing)\n\n        XCTAssertEqual(\"testDismissIconResourceName\", htmlTheme.dismissIconResource)\n        XCTAssertEqual(28, htmlTheme.maxWidth)\n        XCTAssertEqual(29, htmlTheme.maxHeight)\n    }\n\n\n    /// Test when plist parsing fails the theme is equivalent to its default values\n    func testBannerDefaults() {\n        var theme = InAppMessageTheme.Banner.defaultTheme\n        try? theme.applyPlist(plistName: \"Non-existent plist name\", bundle: testBundle)\n\n        XCTAssertEqual(theme, InAppMessageTheme.Banner.defaultTheme)\n    }\n\n    /// Test when plist parsing fails the theme is equivalent to its default values\n    func testModalDefaults() {\n        var theme = InAppMessageTheme.Modal.defaultTheme\n        try? theme.applyPlist(plistName: \"Non-existent plist name\", bundle: testBundle)\n\n        XCTAssertEqual(theme, InAppMessageTheme.Modal.defaultTheme)\n    }\n\n    /// Test when plist parsing fails the theme is equivalent to its default values\n    func testFullscreenDefaults() {\n        var theme = InAppMessageTheme.Fullscreen.defaultTheme\n        try? theme.applyPlist(plistName: \"Non-existent plist name\", bundle: testBundle)\n\n        XCTAssertEqual(theme, InAppMessageTheme.Fullscreen.defaultTheme)\n    }\n    \n    /// Test when plist parsing fails the theme is equivalent to its default values\n    func testHTMLDefaults() {\n        var theme = InAppMessageTheme.HTML.defaultTheme\n        try? theme.applyPlist(plistName: \"Non-existent plist name\", bundle: testBundle)\n\n        XCTAssertEqual(theme, InAppMessageTheme.HTML.defaultTheme)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessage/View/InAppMessageNativeBridgeExtensionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipAutomation\nimport AirshipCore\nimport WebKit\n\nfinal class InAppMessageNativeBridgeExtensionTest: XCTestCase {\n\n    func testExtras() async throws {\n        let message = InAppMessage(\n            name: \"some name\",\n            displayContent: .custom(\"custom\"),\n            extras: [\"cool\": \"value\"]\n        )\n\n        let jsProtocol = TestJSProtocol()\n        let bridgeExtension = InAppMessageNativeBridgeExtension(message: message)\n        await bridgeExtension.extendJavaScriptEnvironment(jsProtocol, webView: WKWebView())\n\n\n        XCTAssertEqual(jsProtocol.getters, [\"getMessageExtras\": message.extras])\n    }\n\n    func testExtrasWrongType() async throws {\n        let message = InAppMessage(\n            name: \"some name\",\n            displayContent: .custom(\"custom\"),\n            extras: \"value\"\n        )\n\n        let jsProtocol = TestJSProtocol()\n        let bridgeExtension = InAppMessageNativeBridgeExtension(message: message)\n        await bridgeExtension.extendJavaScriptEnvironment(jsProtocol, webView: WKWebView())\n\n\n        XCTAssertEqual(jsProtocol.getters, [\"getMessageExtras\": .object([:])])\n    }\n\n\n    func testExtrasMissing() async throws {\n        let message = InAppMessage(\n            name: \"some name\",\n            displayContent: .custom(.string(\"custom\")),\n            extras: nil\n        )\n\n        let jsProtocol = TestJSProtocol()\n        let bridgeExtension = InAppMessageNativeBridgeExtension(message: message)\n        await bridgeExtension.extendJavaScriptEnvironment(jsProtocol, webView: WKWebView())\n\n\n        XCTAssertEqual(jsProtocol.getters, [\"getMessageExtras\": .object([:])])\n    }\n}\n\n\nfileprivate final class TestJSProtocol: JavaScriptEnvironmentProtocol, @unchecked Sendable {\n    var getters: [String: AirshipJSON] = [:]\n\n    func add(_ getter: String, string: String?) {\n        getters[getter] = try! AirshipJSON.wrap(string)\n    }\n    \n    func add(_ getter: String, number: Double?) {\n        getters[getter] = try! AirshipJSON.wrap(number)\n    }\n    \n    func add(_ getter: String, dictionary: [AnyHashable : Any]?) {\n        getters[getter] = try! AirshipJSON.wrap(dictionary)\n    }\n    \n    func build() async -> String {\n        return \"\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Invalid-UAInAppMessageBannerStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>headerStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<false/>\n\t\t<key>lineHeight</key>\n\t\t<date>2018-04-18T18:56:01Z</date>\n\t</dict>\n\t<key>bodyStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<date>2018-04-18T18:55:52Z</date>\n\t\t<key>lineHeight</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>buttonStyle</key>\n\t<dict>\n\t\t<key>buttonHeight</key>\n\t\t<integer>0</integer>\n\t\t<key>stackedButtonSpacing</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<false/>\n\t\t<key>bottom</key>\n\t\t<dict/>\n\t\t<key>leading</key>\n\t\t<string>0</string>\n\t\t<key>trailing</key>\n\t\t<integer>0</integer>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Invalid-UAInAppMessageFullScreenStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>headerStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<false/>\n\t\t<key>lineHeight</key>\n\t\t<date>2018-04-18T18:56:01Z</date>\n\t</dict>\n\t<key>bodyStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<date>2018-04-18T18:55:52Z</date>\n\t\t<key>lineHeight</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>buttonStyle</key>\n\t<dict>\n\t\t<key>buttonHeight</key>\n\t\t<integer>0</integer>\n\t\t<key>stackedButtonSpacing</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<false/>\n\t\t<key>bottom</key>\n\t\t<dict/>\n\t\t<key>leading</key>\n\t\t<string>0</string>\n\t\t<key>trailing</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>dismissIconResource</key>\n\t<date>2018-04-24T22:29:07Z</date>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Invalid-UAInAppMessageModalStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>headerStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<false/>\n\t\t<key>lineHeight</key>\n\t\t<date>2018-04-18T18:56:01Z</date>\n\t</dict>\n\t<key>bodyStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<date>2018-04-18T18:55:52Z</date>\n\t\t<key>lineHeight</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>buttonStyle</key>\n\t<dict>\n\t\t<key>buttonHeight</key>\n\t\t<integer>0</integer>\n\t\t<key>stackedButtonSpacing</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<false/>\n\t\t<key>bottom</key>\n\t\t<dict/>\n\t\t<key>leading</key>\n\t\t<string>0</string>\n\t\t<key>trailing</key>\n\t\t<integer>0</integer>\n\t</dict>\n\t<key>dismissIconResource</key>\n\t<date>2018-04-24T22:29:07Z</date>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Valid-UAInAppMessageBannerStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<integer>1</integer>\n\t\t<key>bottom</key>\n\t\t<integer>2</integer>\n\t\t<key>leading</key>\n\t\t<integer>3</integer>\n\t\t<key>trailing</key>\n\t\t<integer>4</integer>\n\t</dict>\n\t<key>maxWidth</key>\n\t<integer>26</integer>\n\t<key>tapOpacity</key>\n\t<integer>27</integer>\n\t<key>headerStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<integer>5</integer>\n\t\t<key>lineSpacing</key>\n\t\t<integer>6</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>7</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>8</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>9</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>10</integer>\n\t\t</dict>\n\t</dict>\n\t<key>bodyStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<integer>11</integer>\n\t\t<key>lineSpacing</key>\n\t\t<integer>12</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>13</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>14</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>15</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>16</integer>\n\t\t</dict>\n\t</dict>\n\t<key>mediaStyle</key>\n\t<dict>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>17</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>18</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>19</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>20</integer>\n\t\t</dict>\n\t</dict>\n\t<key>buttonStyle</key>\n\t<dict>\n\t\t<key>buttonHeight</key>\n\t\t<integer>21</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>22</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>23</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>24</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>25</integer>\n\t\t</dict>\n\t</dict>\n\t<key>shadowStyle</key>\n\t<dict>\n\t\t<key>colorHex</key>\n\t\t<string>003100</string>\n\t\t<key>radius</key>\n\t\t<integer>28</integer>\n\t\t<key>xOffset</key>\n\t\t<integer>29</integer>\n\t\t<key>yOffset</key>\n\t\t<integer>30</integer>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Valid-UAInAppMessageFullScreenStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<integer>1</integer>\n\t\t<key>bottom</key>\n\t\t<integer>2</integer>\n\t\t<key>leading</key>\n\t\t<integer>3</integer>\n\t\t<key>trailing</key>\n\t\t<integer>4</integer>\n\t</dict>\n\t<key>headerStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<integer>5</integer>\n\t\t<key>lineSpacing</key>\n\t\t<integer>6</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>7</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>8</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>9</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>10</integer>\n\t\t</dict>\n\t</dict>\n\t<key>bodyStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<integer>11</integer>\n\t\t<key>lineSpacing</key>\n\t\t<integer>12</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>13</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>14</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>15</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>16</integer>\n\t\t</dict>\n\t</dict>\n\t<key>mediaStyle</key>\n\t<dict>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>17</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>18</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>19</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>20</integer>\n\t\t</dict>\n\t</dict>\n\t<key>buttonStyle</key>\n\t<dict>\n\t\t<key>buttonHeight</key>\n\t\t<integer>21</integer>\n\t\t<key>stackedButtonSpacing</key>\n\t\t<integer>22</integer>\n\t\t<key>separatedButtonSpacing</key>\n\t\t<integer>23</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>24</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>25</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>26</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>27</integer>\n\t\t</dict>\n\t</dict>\n\t<key>dismissIconResource</key>\n\t<string>testDismissIconResourceName</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Valid-UAInAppMessageHTMLStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>hideDismissIcon</key>\n\t<true/>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<integer>1</integer>\n\t\t<key>bottom</key>\n\t\t<integer>2</integer>\n\t\t<key>leading</key>\n\t\t<integer>3</integer>\n\t\t<key>trailing</key>\n\t\t<integer>4</integer>\n\t</dict>\n\t<key>dismissIconResource</key>\n\t<string>testDismissIconResourceName</string>\n\t<key>maxWidth</key>\n\t<integer>28</integer>\n\t<key>maxHeight</key>\n\t<integer>29</integer>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/InAppMessaging/Valid-UAInAppMessageModalStyle.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>additionalPadding</key>\n\t<dict>\n\t\t<key>top</key>\n\t\t<integer>1</integer>\n\t\t<key>bottom</key>\n\t\t<integer>2</integer>\n\t\t<key>leading</key>\n\t\t<integer>3</integer>\n\t\t<key>trailing</key>\n\t\t<integer>4</integer>\n\t</dict>\n\t<key>headerStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<integer>5</integer>\n\t\t<key>lineSpacing</key>\n\t\t<integer>6</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>7</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>8</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>9</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>10</integer>\n\t\t</dict>\n\t</dict>\n\t<key>bodyStyle</key>\n\t<dict>\n\t\t<key>letterSpacing</key>\n\t\t<integer>11</integer>\n\t\t<key>lineSpacing</key>\n\t\t<integer>12</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>13</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>14</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>15</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>16</integer>\n\t\t</dict>\n\t</dict>\n\t<key>mediaStyle</key>\n\t<dict>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>17</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>18</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>19</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>20</integer>\n\t\t</dict>\n\t</dict>\n\t<key>buttonStyle</key>\n\t<dict>\n\t\t<key>buttonHeight</key>\n\t\t<integer>21</integer>\n\t\t<key>stackedButtonSpacing</key>\n\t\t<integer>22</integer>\n\t\t<key>separatedButtonSpacing</key>\n\t\t<integer>23</integer>\n\t\t<key>additionalPadding</key>\n\t\t<dict>\n\t\t\t<key>top</key>\n\t\t\t<integer>24</integer>\n\t\t\t<key>bottom</key>\n\t\t\t<integer>25</integer>\n\t\t\t<key>leading</key>\n\t\t\t<integer>26</integer>\n\t\t\t<key>trailing</key>\n\t\t\t<integer>27</integer>\n\t\t</dict>\n\t</dict>\n\t<key>dismissIconResource</key>\n\t<string>testDismissIconResourceName</string>\n\t<key>maxWidth</key>\n\t<integer>28</integer>\n\t<key>maxHeight</key>\n\t<integer>29</integer>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Legacy/LegacyInAppAnalyticsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipAutomation\n@testable import AirshipCore\n\n@MainActor\nstruct LegacyInAppAnalyticsTest {\n\n    private let recoder: EventRecorder = EventRecorder()\n    private let analytics: LegacyInAppAnalytics!\n\n    init() {\n        self.analytics = LegacyInAppAnalytics(recorder: recoder)\n    }\n\n    @Test\n    func testDirectOpen() throws {\n        self.analytics.recordDirectOpenEvent(scheduleID: \"some schedule\")\n        let eventData = try #require(recoder.eventData.first)\n        #expect(eventData.context == nil)\n        #expect(eventData.renderedLocale == nil)\n        #expect(eventData.messageID == .legacy(identifier: \"some schedule\"))\n        #expect(eventData.source == .airship)\n\n        let expectedJSON = \"\"\"\n        {\n           \"type\":\"direct_open\"\n        }\n        \"\"\"\n\n        #expect(eventData.event.name.reportingName == \"in_app_resolution\")\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try eventData.event.bodyJSON\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testReplaced() throws {\n        self.analytics.recordReplacedEvent(scheduleID: \"some schedule\", replacementID: \"replacement id\")\n        let eventData = try #require(recoder.eventData.first)\n        #expect(eventData.context == nil)\n        #expect(eventData.renderedLocale == nil)\n        #expect(eventData.messageID == .legacy(identifier: \"some schedule\"))\n        #expect(eventData.source == .airship)\n\n        let expectedJSON = \"\"\"\n        {\n           \"type\":\"replaced\",\n           \"replacement_id\": \"replacement id\"\n        }\n        \"\"\"\n\n        #expect(eventData.event.name.reportingName == \"in_app_resolution\")\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try eventData.event.bodyJSON\n        #expect(actual == expected)    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Legacy/LegacyInAppMessageTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class LegacyInAppMessageTest: XCTestCase {\n    let date = UATestDate(offset: 0, dateOverride: Date())\n    \n    func testParseMinPayload() {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        let message = LegacyInAppMessage(payload: payload, date: date)!\n        \n        XCTAssertNil(message.campaigns)\n        XCTAssertNil(message.messageType)\n        XCTAssertEqual(60 * 60 * 24 * 30, message.expiry.timeIntervalSince(date.now))\n        XCTAssertEqual(15, message.duration)\n        XCTAssertNil(message.extra)\n        XCTAssertEqual(LegacyInAppMessage.DisplayType.banner, message.displayType)\n        XCTAssertEqual(LegacyInAppMessage.Position.bottom, message.position)\n        XCTAssertNil(message.primaryColor)\n        XCTAssertNil(message.secondaryColor)\n        XCTAssertNil(message.buttonGroup)\n        XCTAssertNil(message.buttonActions)\n        XCTAssertNil(message.onClick)\n    }\n    \n    func testParseMaxPayload() {\n        date.offset = 1\n        \n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\",\n                \"position\": \"top\",\n                \"primary_color\": \"#ABCDEF\",\n                \"secondary_color\": \"#FEDCBA\",\n                \"duration\": 100.0,\n            ],\n            \"extra\": [\"extra_value\": \"some text\"],\n            \"expiry\": AirshipDateFormatter.string(fromDate: date.now, format: .isoDelimitter),\n            \"actions\": [\n                \"on_click\": [\"onclick\": \"action\"],\n                \"button_group\": \"button group\",\n                \"button_actions\": [\"name\": [\"test\": \"json\"]],\n            ],\n            \"campaigns\": [\"test-campaing\": \"json\"],\n            \"message_type\": \"test-message\"\n        ]\n        \n        let message = LegacyInAppMessage(payload: payload, date: date)!\n        \n        XCTAssertEqual(try! AirshipJSON.wrap([\"test-campaing\": \"json\"]), message.campaigns)\n        XCTAssertEqual(\"test-message\", message.messageType)\n        XCTAssertEqual(\n            AirshipDateFormatter.string(fromDate: date.now, format: .isoDelimitter),\n            AirshipDateFormatter.string(fromDate: message.expiry, format: .isoDelimitter)\n        )\n        XCTAssertEqual(100, message.duration)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"extra_value\": \"some text\"]), message.extra)\n        XCTAssertEqual(LegacyInAppMessage.DisplayType.banner, message.displayType)\n        XCTAssertEqual(LegacyInAppMessage.Position.top, message.position)\n        XCTAssertEqual(\"#ABCDEF\", message.primaryColor)\n        XCTAssertEqual(\"#FEDCBA\", message.secondaryColor)\n        XCTAssertEqual(\"button group\", message.buttonGroup)\n        XCTAssertEqual([\"name\": try! AirshipJSON.wrap([\"test\": \"json\"])], message.buttonActions)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"onclick\": \"action\"]), message.onClick)\n    }\n    \n    func testOverrideId() {\n        let payload: [String : Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        let overridId = \"override\"\n        \n        let message = LegacyInAppMessage(payload: payload, overrideId: overridId)!\n        XCTAssertEqual(overridId, message.identifier)\n    }\n    \n    func testOverrideOnClick() {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        let overridJson = try! AirshipJSON.wrap([\"test\": \"json\"])\n        \n        let message = LegacyInAppMessage(payload: payload, overrideOnClick: overridJson)!\n        XCTAssertEqual(overridJson, message.onClick)\n    }\n    \n    func testMissingRequiredFields() {\n        var payload: [String: Any] = [\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        XCTAssertNil(LegacyInAppMessage(payload: payload))\n        \n        payload = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"alert\": \"test alert\"\n            ]\n        ]\n        XCTAssertNil(LegacyInAppMessage(payload: payload))\n        \n        payload = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n            ]\n        ]\n        XCTAssertNil(LegacyInAppMessage(payload: payload))\n        \n        payload = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"invalid\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        XCTAssertNil(LegacyInAppMessage(payload: payload))\n    }\n}\n\nextension Dictionary {\n    func toNsDictionary() -> NSDictionary {\n        return NSDictionary(dictionary: self)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Legacy/LegacyInAppMessagingTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class LegacyInAppMessagingTest: XCTestCase {\n    \n    private let analytics = TestLegacyAnalytics()\n    private let engine = TestAutomationEngine()\n    private let datastore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let date = UATestDate(offset: 0, dateOverride: Date())\n    private var airshipTestInstance: TestAirshipInstance!\n\n    private var subject: DefaultLegacyInAppMessaging!\n\n    @MainActor\n    override func setUp() async throws {\n        airshipTestInstance = TestAirshipInstance()\n        let push = TestPush()\n        push.combinedCategories = NotificationCategories.defaultCategories()\n\n        airshipTestInstance.components = [push]\n        airshipTestInstance.makeShared()\n\n        createSubject()\n    }\n\n    override func tearDown() {\n        TestAirshipInstance.clearShared()\n    }\n\n    private func createSubject() {\n        subject = DefaultLegacyInAppMessaging(\n            analytics: analytics,\n            dataStore: datastore,\n            automationEngine: engine,\n            date: date\n        )\n    }\n    \n    func testOldDataCleanedUpOnInit() {\n        \n        let keys = [\"UAPendingInAppMessage\", \"UAAutoDisplayInAppMessageDataStoreKey\", \"UALastDisplayedInAppMessageID\"]\n        \n        keys.forEach { key in\n            datastore.setObject(\"\\(key)-test-value\", forKey: key)\n        }\n        \n        keys.forEach { key in\n            XCTAssert(datastore.keyExists(key))\n        }\n        \n        createSubject()\n        \n        keys.forEach { key in\n            XCTAssertFalse(datastore.keyExists(key))\n        }\n    }\n\n    func testPendingMessageStorage() {\n        XCTAssertNil(subject.pendingMessageID)\n\n        subject.pendingMessageID = \"message-id\"\n        XCTAssertEqual(\"message-id\", subject.pendingMessageID)\n    }\n    \n    func testAsapFlagStorage() async {\n        var value = await subject.displayASAPEnabled\n        XCTAssertTrue(value)\n        \n        await MainActor.run { [messaging = self.subject!] in\n            messaging.displayASAPEnabled = false\n        }\n        \n        value = await subject.displayASAPEnabled\n        XCTAssertFalse(value)\n    }\n    \n    func testNotificationResponseCancelsPendingMessage() async throws {\n        let pendingMessageID = \"pending\"\n        \n        XCTAssertNil(subject.pendingMessageID)\n        await assertLastCancalledScheduleIDEquals(nil)\n        \n        subject.pendingMessageID = pendingMessageID\n        \n        let response = try UNNotificationResponse.with(userInfo: [\n            \"com.urbanairship.in_app\": [],\n            \"_\": pendingMessageID\n        ])\n        \n        await subject.receivedNotificationResponse(response)\n        \n        await assertLastCancalledScheduleIDEquals(pendingMessageID)\n        XCTAssertNil(subject.pendingMessageID)\n    }\n\n    func testNotificationResponseRecordsDirectOpen() async throws {\n        let pendingMessageID = \"pending\"\n        subject.pendingMessageID = pendingMessageID\n\n        let response = try UNNotificationResponse.with(userInfo: [\n            \"com.urbanairship.in_app\": [],\n            \"_\": pendingMessageID\n        ])\n\n        await subject.receivedNotificationResponse(response)\n\n        XCTAssertEqual([pendingMessageID], self.analytics.directOpen)\n    }\n\n    func testNotificationResponseDoesNothingOnIdMismatch() async throws {\n        \n        XCTAssertNil(subject.pendingMessageID)\n        await assertLastCancalledScheduleIDEquals(nil)\n        \n        subject.pendingMessageID = \"mismatched\"\n        \n        let response = try UNNotificationResponse.with(userInfo: [\n            \"com.urbanairship.in_app\": [],\n            \"_\": \"pendingMessageID\"\n        ])\n        \n        await subject.receivedNotificationResponse(response)\n        \n        XCTAssertEqual(\"mismatched\", subject.pendingMessageID)\n        await assertLastCancalledScheduleIDEquals(nil)\n    }\n    \n    func testNotificationResponseDoesNothingIfNoPending() async throws {\n        \n        XCTAssertNil(subject.pendingMessageID)\n        await assertLastCancalledScheduleIDEquals(nil)\n        \n        let response = try UNNotificationResponse.with(userInfo: [\n            \"com.urbanairship.in_app\": [],\n            \"_\": \"pendingMessageID\"\n        ])\n        \n        await subject.receivedNotificationResponse(response)\n        \n        XCTAssertNil(subject.pendingMessageID)\n        await assertLastCancalledScheduleIDEquals(nil)\n    }\n    \n    func testReceiveRemoteNotificationSchedulesMessageWithDefaults() async throws {\n        let messageId = \"test-id\"\n        let payload: [String: Any] = [\n            \"identifier\": messageId,\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        await assertLastCancalledScheduleIDEquals(nil)\n        await assertEmptySchedules()\n        \n        subject.pendingMessageID = \"some-pending\"\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        let schedule = try await requireFirstSchedule()\n        \n        await assertLastCancalledScheduleIDEquals(\"some-pending\")\n        XCTAssertEqual(messageId, subject.pendingMessageID)\n        \n        XCTAssertEqual(messageId, schedule.identifier)\n        XCTAssertEqual(1, schedule.triggers.count)\n        \n        guard case .event(let trigger) = schedule.triggers.first else {\n            XCTFail()\n            return\n        }\n        XCTAssertEqual(1.0, trigger.goal)\n        XCTAssertNil(trigger.predicate)\n        XCTAssertEqual(EventAutomationTriggerType.activeSession, trigger.type)\n\n        XCTAssertEqual(date.now, schedule.created)\n        let month: TimeInterval = 60 * 60 * 24 * 30.0\n        XCTAssertEqual(schedule.end, date.now + month)\n        XCTAssertNil(schedule.campaigns)\n        XCTAssertNil(schedule.messageType)\n        \n        let inAppMessage: InAppMessage\n        switch schedule.data {\n        case .inAppMessage(let message):\n            inAppMessage = message\n        default:\n            fatalError(\"unsupported schedule data\")\n        }\n        \n        XCTAssertEqual(\"test alert\", inAppMessage.name)\n        XCTAssertEqual(InAppMessageSource.legacyPush, inAppMessage.source)\n        XCTAssertNil(inAppMessage.extras)\n        \n        let banner: InAppMessageDisplayContent.Banner\n        switch inAppMessage.displayContent {\n        case .banner(let model):\n            banner = model\n        default:\n            fatalError(\"unsupported display content\")\n        }\n        \n        XCTAssertEqual(\"test alert\", banner.body?.text)\n        XCTAssertEqual(\"#1C1C1C\", banner.body?.color?.hexColorString)\n        XCTAssertEqual(InAppMessageButtonLayoutType.separate, banner.buttonLayoutType)\n        XCTAssertEqual(\"#FFFFFF\", banner.backgroundColor?.hexColorString)\n        XCTAssertEqual(\"#1C1C1C\", banner.dismissButtonColor?.hexColorString)\n        XCTAssertEqual(2, banner.borderRadius)\n        XCTAssertEqual(15, banner.duration)\n        XCTAssertEqual(InAppMessageDisplayContent.Banner.Placement.bottom, banner.placement)\n        XCTAssertEqual(nil, banner.actions)\n        XCTAssertNil(banner.buttons)\n    }\n\n    func testReceiveNotificationRecordsReplacement() async throws {\n        subject.pendingMessageID = \"some-pending\"\n\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n\n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n        \n        XCTAssertEqual(\"some-pending\", self.analytics.replaced.first!.0)\n        XCTAssertEqual(\"test-id\", self.analytics.replaced.first!.1)\n    }\n\n    func testReceiveRemoteNotificationSchedulesMessage() async throws {\n        let messageId = \"test-id\"\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\",\n                \"position\": \"top\",\n                \"duration\": 100.0,\n                \"primary_color\": \"#ABCDEF\",\n                \"secondary_color\": \"#FEDCBA\",\n            ],\n            \"extra\": [\"extra_value\": \"some text\"],\n            \"expiry\": AirshipDateFormatter.string(fromDate: date.now, format: .isoDelimitter),\n            \"actions\": [\n                \"on_click\": [\"onclick\": \"action\"],\n                \"button_group\": \"ua_shop_now_share\",\n                \"button_actions\": [\"shop_now\": [\"test\": \"json\"], \"share\": [\"test-2\": \"json-2\"]],\n            ],\n            \"campaigns\": [\"test-campaing\": \"json\"],\n            \"message_type\": \"test-message\"\n        ]\n        \n        await assertLastCancalledScheduleIDEquals(nil)\n        await assertEmptySchedules()\n        \n        subject.pendingMessageID = \"some-pending\"\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        let schedule = try await requireFirstSchedule()\n        \n        await assertLastCancalledScheduleIDEquals(\"some-pending\")\n        XCTAssertEqual(messageId, subject.pendingMessageID)\n        \n        XCTAssertEqual(messageId, schedule.identifier)\n        XCTAssertEqual(1, schedule.triggers.count)\n        \n        guard case .event(let trigger) = schedule.triggers.first else {\n            XCTFail()\n            return\n        }\n        XCTAssertEqual(1.0, trigger.goal)\n        XCTAssertNil(trigger.predicate)\n        XCTAssertEqual(EventAutomationTriggerType.activeSession, trigger.type)\n\n        XCTAssertEqual(date.now, schedule.created)\n        let timeDiff = schedule.end?.timeIntervalSince(date.now) ?? 0\n        XCTAssert(fabs(timeDiff) < 1)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"test-campaing\": \"json\"]), schedule.campaigns)\n        XCTAssertEqual(\"test-message\", schedule.messageType)\n        \n        let inAppMessage: InAppMessage\n        switch schedule.data {\n        case .inAppMessage(let message):\n            inAppMessage = message\n        default:\n            fatalError(\"unsupported schedule data\")\n        }\n        \n        XCTAssertEqual(\"test alert\", inAppMessage.name)\n        XCTAssertEqual(InAppMessageSource.legacyPush, inAppMessage.source)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"extra_value\": \"some text\"]), inAppMessage.extras)\n        \n        let banner: InAppMessageDisplayContent.Banner\n        switch inAppMessage.displayContent {\n        case .banner(let model):\n            banner = model\n        default:\n            fatalError(\"unsupported display content\")\n        }\n        \n        XCTAssertEqual(\"test alert\", banner.body?.text)\n        XCTAssertEqual(\"#FEDCBA\", banner.body?.color?.hexColorString)\n        XCTAssertEqual(InAppMessageButtonLayoutType.separate, banner.buttonLayoutType)\n        XCTAssertEqual(\"#ABCDEF\", banner.backgroundColor?.hexColorString)\n        XCTAssertEqual(\"#FEDCBA\", banner.dismissButtonColor?.hexColorString)\n        XCTAssertEqual(2, banner.borderRadius)\n        XCTAssertEqual(100, banner.duration)\n        XCTAssertEqual(InAppMessageDisplayContent.Banner.Placement.top, banner.placement)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"onclick\": \"action\"]), banner.actions)\n        \n        let buttons = try! XCTUnwrap(banner.buttons)\n        XCTAssertEqual(2, buttons.count)\n        \n        let shopNowButton = buttons[0]\n        XCTAssertEqual(\"shop_now\", shopNowButton.identifier)\n        XCTAssertEqual(\"Shop Now\", shopNowButton.label.text)\n        XCTAssertEqual(\"#ABCDEF\", shopNowButton.label.color?.hexColorString)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"test\": \"json\"]), shopNowButton.actions)\n        XCTAssertEqual(\"#FEDCBA\", shopNowButton.backgroundColor?.hexColorString)\n        XCTAssertEqual(2, shopNowButton.borderRadius)\n        \n        let share = buttons[1]\n        XCTAssertEqual(\"share\", share.identifier)\n        XCTAssertEqual(\"Share\", share.label.text)\n        XCTAssertEqual(\"#ABCDEF\", share.label.color?.hexColorString)\n        XCTAssertEqual(try! AirshipJSON.wrap([\"test-2\": \"json-2\"]), share.actions)\n        XCTAssertEqual(\"#FEDCBA\", share.backgroundColor?.hexColorString)\n        XCTAssertEqual(2, share.borderRadius)\n    }\n    \n    func testTriggertIsLessAgressiveIfNotDisplayAsap() async throws {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        await MainActor.run { [messaging = self.subject!] in\n            messaging.displayASAPEnabled = false\n        }\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        let schedule = try await requireFirstSchedule()\n        \n        guard case .event(let trigger) = schedule.triggers.first else {\n            XCTFail()\n            return\n        }\n\n        XCTAssertEqual(1.0, trigger.goal)\n        XCTAssertNil(trigger.predicate)\n        XCTAssertEqual(EventAutomationTriggerType.foreground, trigger.type)\n    }\n    \n    func testCustomMessageConverter() async throws {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        let overridenId = \"converter override id\"\n        \n        await MainActor.run { [messaging = self.subject!] in\n            messaging.customMessageConverter = { input in\n                return AutomationSchedule(\n                    identifier: overridenId,\n                    triggers: [],\n                    data: .inAppMessage(InAppMessage(name: \"overriden\", displayContent: .banner(InAppMessageDisplayContent.Banner()))))\n            }\n        }\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n        \n        let schedule = try await requireFirstSchedule()\n        XCTAssertEqual(overridenId, schedule.identifier)\n    }\n    \n    func testMessageExtenderFunction() async throws {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        let extendedMessageName = \"extended message name\"\n        \n        await MainActor.run { [messaging = self.subject!] in\n            messaging.messageExtender = { input in\n                input.name = extendedMessageName\n            }\n        }\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        let schedule = try await requireFirstSchedule()\n        let inAppMessage: InAppMessage\n        switch schedule.data {\n        case .inAppMessage(let message):\n            inAppMessage = message\n        default:\n            fatalError(\"unsupported schedule data\")\n        }\n        \n        XCTAssertEqual(extendedMessageName, inAppMessage.name)\n    }\n    \n    func testScheduleExtendFunction() async throws {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n\n        \n        await MainActor.run { [messaging = self.subject!] in\n            messaging.scheduleExtender = { input in\n                input.limit = 10\n            }\n        }\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\"com.urbanairship.in_app\": payload])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        let schedule = try await requireFirstSchedule()\n        XCTAssertEqual(10, schedule.limit)\n    }\n    \n    func testReceiveRemoteIgnoresNonlegacyMessages() async throws {\n        \n        await assertLastCancalledScheduleIDEquals(nil)\n        await assertEmptySchedules()\n        \n        subject.pendingMessageID = \"some-pending\"\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([:])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        await assertLastCancalledScheduleIDEquals(nil)\n        await assertEmptySchedules()\n        XCTAssertEqual(\"some-pending\", subject.pendingMessageID)\n    }\n    \n    func testReceiveRemoteNotificationHandlesMessageIdOverride() async throws {\n        let messageId = \"overriden\"\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        await assertEmptySchedules()\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\n                \"com.urbanairship.in_app\": payload,\n                \"_\": messageId\n            ])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n\n        let schedules = await engine.schedules\n        XCTAssertTrue(schedules.contains(where: { $0.identifier == messageId }))\n        XCTAssertEqual(messageId, subject.pendingMessageID)\n    }\n    \n    func testReceiveRemoteNotificationOverridesOnClick() async throws {\n        let payload: [String: Any] = [\n            \"identifier\": \"test-id\",\n            \"display\": [\n                \"type\": \"banner\",\n                \"alert\": \"test alert\"\n            ]\n        ]\n        \n        await assertEmptySchedules()\n        \n        let onClickJson = try AirshipJSON.wrap([\"onclick\": \"overriden\"])\n        \n        let result = await subject.receivedRemoteNotification(\n            try! AirshipJSON.wrap([\n                \"com.urbanairship.in_app\": payload,\n                \"_uamid\": onClickJson.unWrap()!\n            ])\n        )\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n        \n        let schedule = try await requireFirstSchedule()\n        \n        switch schedule.data {\n        case .inAppMessage(let message):\n            switch message.displayContent {\n            case .banner(let banner):\n                XCTAssertEqual(onClickJson, banner.actions)\n            default:\n                fatalError(\"unsupported display content\")\n            }\n        default:\n            fatalError(\"unsupported schedule data type\")\n        }\n    }\n\n    private func requireFirstSchedule(line: UInt = #line) async throws -> AutomationSchedule {\n        let schedule = await engine.schedules.first\n        return try XCTUnwrap(schedule)\n    }\n\n    private func assertEmptySchedules(line: UInt = #line) async {\n        let schedules = await engine.schedules\n        XCTAssert(schedules.isEmpty)\n    }\n\n    private func assertLastCancalledScheduleIDEquals(_ value: String?) async {\n        let lastCancelledScheduleId = await engine.cancelledSchedules.last\n        XCTAssertEqual(lastCancelledScheduleId, value)\n    }\n\n}\n\nprivate final class KeyedArchiver: NSKeyedArchiver {\n//    override func decodeObject(of classes: [AnyClass]?, forKey key: String) -> Any? {\n//        return \"\"\n//    }\n    override func decodeObject(forKey _: String) -> Any { \"\" }\n    override func decodeInt64(forKey key: String) -> Int64 { 0 }\n}\n\nprivate extension UNNotificationResponse {\n    static func with(\n        userInfo: [AnyHashable: Any],\n        actionIdentifier: String = UNNotificationDefaultActionIdentifier\n    ) throws -> UNNotificationResponse {\n        let content = UNMutableNotificationContent()\n        content.userInfo = userInfo\n        let request = UNNotificationRequest(\n            identifier: \"\",\n            content: content,\n            trigger: nil\n        )\n        \n        let coder = KeyedArchiver(requiringSecureCoding: false)\n\n        let notification = try XCTUnwrap(UNNotification(coder: coder))\n        notification.setValue(request, forKey: \"request\")\n\n        let response = try XCTUnwrap(UNNotificationResponse(coder: coder))\n        response.setValue(notification, forKey: \"notification\")\n        response.setValue(actionIdentifier, forKey: \"actionIdentifier\")\n        \n        coder.finishEncoding()\n        return response\n    }\n}\n\nfileprivate final class TestLegacyAnalytics: LegacyInAppAnalyticsProtocol, @unchecked Sendable {\n    var replaced: [(String, String)] = []\n    var directOpen: [String] = []\n    func recordReplacedEvent(scheduleID: String, replacementID: String) {\n        replaced.append((scheduleID, replacementID))\n    }\n\n    func recordDirectOpenEvent(scheduleID: String) {\n        directOpen.append(scheduleID)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Limits/FrequencyLimitManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class FrequencyLimitManagerTest: AirshipBaseTest {\n\n    private var manager: FrequencyLimitManager!\n    private let date: UATestDate = UATestDate(offset: 0, dateOverride: Date(timeIntervalSince1970: 0))\n    private let store: FrequencyLimitStore = FrequencyLimitStore(\n        appKey: UUID().uuidString,\n        inMemory: true\n    )\n\n    override func setUpWithError() throws {\n        self.manager = FrequencyLimitManager(\n            dataStore: self.store,\n            date: self.date\n        )\n    }\n    \n    @MainActor\n    func testGetCheckerNoLimits() async throws {\n        let frequencyChecker = try await self.manager.getFrequencyChecker(constraintIDs: [])\n        XCTAssertNotNil(frequencyChecker)\n        XCTAssertTrue(frequencyChecker.checkAndIncrement())\n        XCTAssertFalse(frequencyChecker.isOverLimit)\n    }\n    \n    @MainActor\n    func testSingleChecker() async throws {\n        let constraint = FrequencyConstraint(\n            identifier: \"foo\",\n            range: 10,\n            count: 2\n        )\n\n        try await self.manager.setConstraints([constraint])\n\n        var constraints = try await self.store.fetchConstraints()\n        XCTAssertEqual(constraints.count, 1);\n        \n        let startDate = Date(timeIntervalSince1970: 0)\n        self.date.dateOverride = startDate\n            \n        let frequencyChecker = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\"])\n\n        constraints = try await self.store.fetchConstraints()\n        XCTAssertEqual(constraints.count, 1)\n        XCTAssertFalse(frequencyChecker.isOverLimit)\n        XCTAssertTrue(frequencyChecker.checkAndIncrement())\n\n        self.date.offset = 1\n        XCTAssertFalse(frequencyChecker.isOverLimit)\n        XCTAssertTrue(frequencyChecker.checkAndIncrement())\n\n        // We should now be over the limit\n        XCTAssertTrue(frequencyChecker.isOverLimit)\n        XCTAssertFalse(frequencyChecker.checkAndIncrement())\n\n        // After the range has passed we should no longer be over the limit\n        self.date.offset = 11\n        XCTAssertFalse(frequencyChecker.isOverLimit)\n\n        // One more increment should push us back over the limit\n        XCTAssertTrue(frequencyChecker.checkAndIncrement())\n        XCTAssertTrue(frequencyChecker.isOverLimit)\n\n        await self.manager.writePending()\n\n        let occurrences = try await self.store.fetchConstraints([\"foo\"])\n            .first!\n            .occurrences\n            .map { occurence in\n                occurence.timestamp.timeIntervalSince1970\n            }\n\n        // We should only have three occurrences, since the last check and increment should be a no-op\n        XCTAssertEqual(Set<Double>([0, 1, 11]), Set(occurrences))\n    }\n\n    @MainActor\n    func testMultipleCheckers() async throws {\n        let constraint = FrequencyConstraint(\n            identifier: \"foo\",\n            range: 10,\n            count: 2\n        )\n\n        try await self.manager.setConstraints([constraint])\n\n        let checker1 = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\"])\n        let checker2 = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\"])\n\n        let constraints = try await self.store.fetchConstraints()\n        XCTAssertEqual(constraints.count, 1)\n\n        XCTAssertFalse(checker1.isOverLimit)\n        XCTAssertFalse(checker2.isOverLimit)\n\n        XCTAssertTrue(checker1.checkAndIncrement())\n\n        self.date.offset = 1\n        XCTAssertTrue(checker2.checkAndIncrement())\n\n        // We should now be over the limit\n        XCTAssertTrue(checker1.isOverLimit)\n        XCTAssertTrue(checker2.isOverLimit)\n\n        // After the range has passed we should no longer be over the limit\n        self.date.offset = 11\n        XCTAssertFalse(checker1.isOverLimit)\n        XCTAssertFalse(checker2.isOverLimit)\n\n        // The first check and increment should succeed, and the next should put us back over the limit again\n        XCTAssertTrue(checker1.checkAndIncrement())\n\n        self.date.offset = 1\n        XCTAssertFalse(checker2.checkAndIncrement())\n\n        await self.manager.writePending()\n\n        let occurrences = try await self.store.fetchConstraints([\"foo\"])\n            .first!\n            .occurrences\n            .map { occurence in\n                occurence.timestamp.timeIntervalSince1970\n            }\n\n        // We should only have three occurrences, since the last check and increment should be a no-op\n        XCTAssertEqual(Set<Double>([0, 1, 11]), Set(occurrences))\n    }\n\n    @MainActor\n    func testMultipleConstraints() async throws {\n        let constraint1 = FrequencyConstraint(\n            identifier: \"foo\",\n            range: 10,\n            count: 2\n        )\n\n        let constraint2 = FrequencyConstraint(\n            identifier: \"bar\",\n            range: 2, count: 1\n        )\n\n        try await self.manager.setConstraints([constraint1, constraint2])\n\n        let checker = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\", \"bar\"])\n\n        XCTAssertFalse(checker.isOverLimit)\n        var result = checker.checkAndIncrement()\n        XCTAssertTrue(result)\n\n        self.date.offset = 1\n        // We should now be violating constraint 2\n        XCTAssertTrue(checker.isOverLimit)\n        result = checker.checkAndIncrement()\n        XCTAssertFalse(result)\n\n        self.date.offset = 3\n        // We should no longer be violating constraint 2\n        XCTAssertFalse(checker.isOverLimit)\n        result = checker.checkAndIncrement()\n        XCTAssertTrue(result)\n\n        // We should now be violating constraint 1\n        self.date.offset = 9\n        XCTAssertTrue(checker.isOverLimit)\n        result = checker.checkAndIncrement()\n        XCTAssertFalse(result)\n\n        // We should now be violating neither constraint\n        self.date.offset = 11\n        XCTAssertFalse(checker.isOverLimit)\n\n        // One more increment should hit the limit\n        result = checker.checkAndIncrement()\n        XCTAssertTrue(result)\n        XCTAssertTrue(checker.isOverLimit)\n    }\n\n    @MainActor\n    func testConstraintRemovedMidCheck() async throws {\n        let constraint1 = FrequencyConstraint(\n            identifier: \"foo\",\n            range: 10,\n            count: 2\n        )\n\n        let constraint2 = FrequencyConstraint(\n            identifier: \"bar\",\n            range: 20,\n            count: 2\n        )\n\n        try await self.manager.setConstraints([constraint1, constraint2])\n\n        let checker = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\", \"bar\"])\n\n        try await self.manager.setConstraints(\n            [\n                FrequencyConstraint(\n                    identifier: \"bar\",\n                    range: 10,\n                    count: 10\n                )\n            ]\n        )\n\n        XCTAssertTrue(checker.checkAndIncrement())\n        self.date.offset = 1\n        XCTAssertTrue(checker.checkAndIncrement())\n        self.date.offset = 1\n        XCTAssertTrue(checker.checkAndIncrement())\n\n        await self.manager.writePending()\n\n        // Foo should not exist\n        let fooInfo = try await self.store.fetchConstraints([\"foo\"])\n        XCTAssertEqual(fooInfo.count, 0)\n\n        // Bar should have the two occurences\n        let barInfo = try await self.store.fetchConstraints([\"bar\"])\n        XCTAssertEqual(barInfo.first?.occurrences.count, 3);\n    }\n\n    @MainActor\n    func testUpdateConstraintRangeClearsOccurrences() async throws {\n        try await self.manager.setConstraints(\n            [\n                FrequencyConstraint(\n                    identifier: \"foo\",\n                    range: 10,\n                    count: 2\n                )\n            ]\n        )\n\n        let checker = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\"])\n        _ = checker.checkAndIncrement()\n        await self.manager.writePending()\n\n        try await self.manager.setConstraints(\n            [\n                FrequencyConstraint(\n                    identifier: \"foo\",\n                    range: 20,\n                    count: 2\n                )\n            ]\n        )\n\n        await self.manager.writePending()\n\n        let fooInfo = try await self.store.fetchConstraints([\"foo\"])\n        XCTAssertEqual(fooInfo.first?.occurrences.count, 0);\n    }\n\n    func testUpdateConstraintCountDoesNotClearCount() async throws {\n        try await self.manager.setConstraints(\n            [\n                FrequencyConstraint(\n                    identifier: \"foo\",\n                    range: 10,\n                    count: 2\n                )\n            ]\n        )\n\n        let checker = try await self.manager.getFrequencyChecker(constraintIDs: [\"foo\"])\n        let result = await checker.checkAndIncrement()\n        XCTAssertTrue(result)\n\n        try await self.manager.setConstraints(\n            [\n                FrequencyConstraint(\n                    identifier: \"foo\",\n                    range: 10,\n                    count: 3\n                )\n            ]\n        )\n\n        await self.manager.writePending()\n\n        let fooInfo = try await self.store.fetchConstraints([\"foo\"])\n        XCTAssertEqual(fooInfo.first?.occurrences.count, 1);\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/RemoteData/AutomationRemoteDataAccessTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\nimport AirshipCore\n@testable\nimport AirshipAutomation\n\nfinal class AutomationRemoteDataAccessTest: XCTestCase {\n    private let remoteData: TestRemoteData = TestRemoteData()\n    private let networkChecker: TestNetworkChecker = TestNetworkChecker()\n    private var subject: AutomationRemoteDataAccess!\n\n    override func setUpWithError() throws {\n        subject = AutomationRemoteDataAccess(\n            remoteData: remoteData,\n            network: networkChecker\n        )\n    }\n\n    func testIsCurrentTrue() async {\n        let info = makeRemoteDataInfo()\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        remoteData.isCurrent = true\n        let isCurrent = await subject.isCurrent(schedule: schedule)\n        XCTAssertTrue(isCurrent)\n    }\n\n    func testIsCurrentFalse() async {\n        let info = makeRemoteDataInfo()\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        remoteData.isCurrent = false\n        let isCurrent = await subject.isCurrent(schedule: schedule)\n        XCTAssertFalse(isCurrent)\n    }\n\n    func testIsCurrentNilRemoteDataInfo() async {\n        let schedule = makeSchedule(remoteDataInfo: nil)\n\n        remoteData.isCurrent = true\n        let isCurrent = await subject.isCurrent(schedule: schedule)\n        XCTAssertFalse(isCurrent)\n    }\n\n    func testRequiresUpdateUpToDate() async {\n        let info = makeRemoteDataInfo(.app)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        remoteData.isCurrent = true\n        remoteData.status[.app] = .upToDate\n\n        let requiresUpdate = await subject.requiresUpdate(schedule: schedule)\n        XCTAssertFalse(requiresUpdate)\n    }\n\n    func testRequiresUpdateStale() async {\n        let info = makeRemoteDataInfo(.app)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        remoteData.isCurrent = true\n        remoteData.status[.app] = .stale\n\n        let requiresUpdate = await subject.requiresUpdate(schedule: schedule)\n        XCTAssertFalse(requiresUpdate)\n    }\n\n    func testRequiresUpdateOutOfDate() async {\n        let info = makeRemoteDataInfo(.app)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        remoteData.isCurrent = true\n        remoteData.status[.app] = .outOfDate\n\n        let requiresUpdate = await subject.requiresUpdate(schedule: schedule)\n        XCTAssertTrue(requiresUpdate)\n    }\n\n    func testRequiresUpdateNotCurrent() async {\n        let info = makeRemoteDataInfo(.app)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        remoteData.isCurrent = false\n        remoteData.status[.app] = .upToDate\n\n        let requiresUpdate = await subject.requiresUpdate(schedule: schedule)\n        XCTAssertTrue(requiresUpdate)\n    }\n\n    func testRequiresUpdateNilRemoteDataInfo() async {\n        remoteData.isCurrent = false\n        remoteData.status[.app] = .upToDate\n\n        let schedule = makeSchedule(remoteDataInfo: nil)\n\n        let requiresUpdate = await subject.requiresUpdate(schedule: schedule)\n        XCTAssertTrue(requiresUpdate)\n    }\n\n    func testRequiresUpdateRightSource() async {\n        remoteData.isCurrent = true\n        remoteData.status[.app] = .outOfDate\n        remoteData.status[.contact] = .upToDate\n\n        let requiresUpdateContact = await subject.requiresUpdate(\n            schedule: makeSchedule(remoteDataInfo: makeRemoteDataInfo(.contact))\n        )\n        XCTAssertFalse(requiresUpdateContact)\n\n        let requiresUpdateApp = await subject.requiresUpdate(\n            schedule: makeSchedule(remoteDataInfo: makeRemoteDataInfo(.app))\n        )\n        XCTAssertTrue(requiresUpdateApp)\n    }\n\n    func testWaitForFullRefresh() async {\n        let info = makeRemoteDataInfo(.contact)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        let expectation = XCTestExpectation()\n        self.remoteData.waitForRefreshBlock = { source, maxTime in\n            XCTAssertEqual(source, .contact)\n            XCTAssertNil(maxTime)\n            expectation.fulfill()\n        }\n        \n\n        await subject.waitFullRefresh(schedule: schedule)\n        await self.fulfillment(of: [expectation])\n    }\n\n    func testWaitForFullRefreshNilInfo() async {\n        let expectation = XCTestExpectation()\n        self.remoteData.waitForRefreshBlock = { source, maxTime in\n            XCTAssertEqual(source, .app)\n            XCTAssertNil(maxTime)\n            expectation.fulfill()\n        }\n\n        let schedule = makeSchedule(remoteDataInfo: nil)\n        await subject.waitFullRefresh(schedule: schedule)\n        await self.fulfillment(of: [expectation])\n    }\n\n    func testBestEffortRefresh() async {\n        await self.networkChecker.setConnected(true)\n        remoteData.isCurrent = true\n        let info = makeRemoteDataInfo(.contact)\n        self.remoteData.status[.contact] = .stale\n\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        let expectation = XCTestExpectation()\n        self.remoteData.waitForRefreshAttemptBlock = { source, maxTime in\n            XCTAssertEqual(source, .contact)\n            XCTAssertNil(maxTime)\n            expectation.fulfill()\n        }\n\n        let result = await subject.bestEffortRefresh(schedule: schedule)\n        await self.fulfillment(of: [expectation])\n        XCTAssertTrue(result)\n    }\n\n    func testBestEffortRefreshNotCurrentAfterAttempt() async {\n        await self.networkChecker.setConnected(true)\n        remoteData.isCurrent = true\n        let info = makeRemoteDataInfo(.contact)\n        self.remoteData.status[.contact] = .stale\n\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        let expectation = XCTestExpectation()\n        self.remoteData.waitForRefreshAttemptBlock = { source, maxTime in\n            self.remoteData.isCurrent = false\n            expectation.fulfill()\n        }\n\n        let result = await subject.bestEffortRefresh(schedule: schedule)\n        await self.fulfillment(of: [expectation])\n        XCTAssertFalse(result)\n    }\n\n    func testBestEffortRefreshNotCurrentReturnsNil() async {\n        await self.networkChecker.setConnected(true)\n        remoteData.isCurrent = false\n        let info = makeRemoteDataInfo(.contact)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        self.remoteData.status[.contact] = .stale\n\n        self.remoteData.waitForRefreshAttemptBlock = { _, _ in\n            XCTFail()\n        }\n\n        let result = await subject.bestEffortRefresh(schedule: schedule)\n        XCTAssertFalse(result)\n    }\n\n    func testBestEffortRefreshNotConnected() async {\n        await self.networkChecker.setConnected(false)\n        remoteData.isCurrent = true\n        let info = makeRemoteDataInfo(.contact)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        self.remoteData.status[.contact] = .stale\n\n        self.remoteData.waitForRefreshAttemptBlock = { _, _ in\n            XCTFail()\n        }\n\n        let result = await subject.bestEffortRefresh(schedule: schedule)\n        XCTAssertTrue(result)\n    }\n\n    func testNotifyOutdated() async {\n        let info = makeRemoteDataInfo(.contact)\n        let schedule = makeSchedule(remoteDataInfo: info)\n\n        await self.subject.notifyOutdated(schedule: schedule)\n        XCTAssertEqual(self.remoteData.notifiedOutdatedInfos, [info])\n    }\n    \n    func testRemoteDataInfoIgnoresInvalidSchedules() throws {\n             let validSchedule = \"\"\"\n                {\n                    \"id\": \"test_schedule\",\n                    \"triggers\": [\n                        {\n                            \"type\": \"custom_event_count\",\n                            \"goal\": 1,\n                            \"id\": \"json-id\"\n                        }\n                    ],\n                    \"group\": \"test_group\",\n                    \"priority\": 2,\n                    \"limit\": 5,\n                    \"start\": \"2023-12-20T00:00:00Z\",\n                    \"end\": \"2023-12-21T00:00:00Z\",\n                    \"audience\": {},\n                    \"delay\": {},\n                    \"interval\": 3600,\n                    \"type\": \"actions\",\n                    \"actions\": {\n                        \"foo\": \"bar\",\n                    },\n                    \"bypass_holdout_groups\": true,\n                    \"edit_grace_period\": 7,\n                    \"metadata\": {},\n                    \"frequency_constraint_ids\": [\"constraint1\", \"constraint2\"],\n                    \"message_type\": \"test_type\",\n                    \"last_updated\": \"2023-12-20T12:30:00Z\",\n                    \"created\": \"2023-12-20T12:00:00Z\"\n                }\n                \"\"\"\n             let invalidSchedule = \"\"\"\n                {\n                    \"priority\": 2,\n                    \"limit\": 5,\n                    \"start\": \"2023-12-20T00:00:00Z\",\n                    \"end\": \"2023-12-21T00:00:00Z\",\n                    \"audience\": {},\n                    \"delay\": {},\n                    \"interval\": 3600,\n                    \"type\": \"actions\",\n                    \"actions\": {\n                        \"foo\": \"bar\",\n                    },\n                    \"bypass_holdout_groups\": true,\n                    \"edit_grace_period\": 7,\n                    \"metadata\": {},\n                    \"frequency_constraint_ids\": [\"constraint1\", \"constraint2\"],\n                    \"message_type\": \"test_type\",\n                    \"last_updated\": \"2023-12-20T12:30:00Z\",\n                    \"created\": \"2023-12-20T12:00:00Z\"\n                }\n                \"\"\"\n\n             let dataJson = try AirshipJSON.from(json: \"{\\\"in_app_messages\\\": [\\(validSchedule), \\(invalidSchedule)]}\")\n             let payload = RemoteDataPayload(\n                 type: \"schedule_test\",\n                 timestamp: Date(),\n                 data: dataJson,\n                 remoteDataInfo: nil)\n\n             let decoded: InAppRemoteData.Data = try payload.data.decode()\n             XCTAssertEqual(1, decoded.schedules.count)\n             XCTAssertEqual(\"test_schedule\", decoded.schedules.first?.identifier)\n             // Invalid schedule without ID can't be tracked\n             XCTAssertTrue(decoded.failedSchedules.isEmpty)\n         }\n    \n    func testRemoteDataInfoTracksFailedSchedules() throws {\n        let validSchedule = \"\"\"\n           {\n               \"id\": \"valid_schedule\",\n               \"triggers\": [\n                   {\n                       \"type\": \"custom_event_count\",\n                       \"goal\": 1,\n                       \"id\": \"json-id\"\n                   }\n               ],\n               \"type\": \"actions\",\n               \"actions\": {\n                   \"foo\": \"bar\"\n               }\n           }\n           \"\"\"\n        // Invalid schedule WITH an ID and created date (missing required triggers)\n        let invalidScheduleWithID = \"\"\"\n           {\n               \"id\": \"failed_schedule_id\",\n               \"created\": \"2023-12-20T12:00:00Z\",\n               \"type\": \"actions\",\n               \"actions\": {\n                   \"foo\": \"bar\"\n               }\n           }\n           \"\"\"\n\n        let dataJson = try AirshipJSON.from(json: \"{\\\"in_app_messages\\\": [\\(validSchedule), \\(invalidScheduleWithID)]}\")\n        let payload = RemoteDataPayload(\n            type: \"schedule_test\",\n            timestamp: Date(),\n            data: dataJson,\n            remoteDataInfo: nil)\n\n        let decoded: InAppRemoteData.Data = try payload.data.decode()\n        XCTAssertEqual(1, decoded.schedules.count)\n        XCTAssertEqual(\"valid_schedule\", decoded.schedules.first?.identifier)\n        // Failed schedule with ID should be tracked\n        XCTAssertEqual(decoded.failedSchedules.map { $0.identifier}, [\"failed_schedule_id\"])\n        // Verify created date is captured as createdDate\n        let expectedCreatedDate = AirshipDateFormatter.date(fromISOString: \"2023-12-20T12:00:00Z\")\n        XCTAssertEqual(decoded.failedSchedules.first?.createdDate, expectedCreatedDate)\n    }\n    \n    func testFromPayloadsAggregatesFailedSchedules() throws {\n        let validSchedule = \"\"\"\n           {\n               \"id\": \"valid_schedule\",\n               \"triggers\": [\n                   {\n                       \"type\": \"custom_event_count\",\n                       \"goal\": 1,\n                       \"id\": \"json-id\"\n                   }\n               ],\n               \"type\": \"actions\",\n               \"actions\": {\n                   \"foo\": \"bar\"\n               }\n           }\n           \"\"\"\n        // Invalid schedule WITH an ID and created date (missing required triggers)\n        let invalidScheduleWithID = \"\"\"\n           {\n               \"id\": \"failed_schedule_id\",\n               \"created\": \"2023-12-20T12:00:00Z\",\n               \"type\": \"actions\",\n               \"actions\": {\n                   \"foo\": \"bar\"\n               }\n           }\n           \"\"\"\n\n        let dataJson = try AirshipJSON.from(json: \"{\\\"in_app_messages\\\": [\\(validSchedule), \\(invalidScheduleWithID)]}\")\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"https://airship.test\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n        let payload = RemoteDataPayload(\n            type: \"in_app_messages\",\n            timestamp: Date(),\n            data: dataJson,\n            remoteDataInfo: remoteDataInfo)\n        \n        let inAppRemoteData = InAppRemoteData.fromPayloads([payload])\n        \n        // Verify aggregate failedSchedules on InAppRemoteData\n        XCTAssertEqual(inAppRemoteData.failedSchedules.map { $0.identifier}, [\"failed_schedule_id\"])\n        // Verify created date is captured as createdDate\n        let expectedCreatedDate = AirshipDateFormatter.date(fromISOString: \"2023-12-20T12:00:00Z\")\n        XCTAssertEqual(inAppRemoteData.failedSchedules.first?.createdDate, expectedCreatedDate)\n    }\n    \n    private func makeSchedule(remoteDataInfo: RemoteDataInfo?) -> AutomationSchedule {\n        return AutomationSchedule(\n            identifier: UUID().uuidString,\n            data: .actions(AirshipJSON.null),\n            triggers: [],\n            created: Date(),\n            lastUpdated: Date(),\n            metadata: try! AirshipJSON.wrap([\n                \"com.urbanairship.iaa.REMOTE_DATA_INFO\": remoteDataInfo\n            ])\n        )\n    }\n    private func makeRemoteDataInfo(_ source: RemoteDataSource = .app) -> RemoteDataInfo {\n        return RemoteDataInfo(\n            url: URL(string: \"https://airship.test\")!,\n            lastModifiedTime: nil,\n            source: source\n        )\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/RemoteData/AutomationRemoteDataSubscriberTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\nimport AirshipCore\n@testable\nimport AirshipAutomation\n\nfinal class AutomationRemoteDataSubscriberTest: XCTestCase {\n    private let remoteDataAccess: TestRemoteDataAccess = TestRemoteDataAccess()\n    private let engine: TestAutomationEngine = TestAutomationEngine()\n    private let frequencyLimits: TestFrequencyLimitManager = TestFrequencyLimitManager()\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n\n    private var subscriber: AutomationRemoteDataSubscriber!\n\n    override func setUp() async throws {\n        self.subscriber = AutomationRemoteDataSubscriber(\n            dataStore: dataStore,\n            remoteDataAccess: remoteDataAccess,\n            engine: engine,\n            frequencyLimitManager: frequencyLimits\n        )\n    }\n\n    func testSchedulingAutomations() async throws {\n        let appSchedules = makeSchedules(source: .app)\n        let contactSchedules = makeSchedules(source: .contact)\n\n        let data = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: appSchedules,\n                        constraints: []\n                    ),\n                    timestamp: Date()\n                ),\n                .contact: .init(\n                    data: .init(\n                        schedules: contactSchedules,\n                        constraints: []\n                    ),\n                    timestamp: Date()\n                )\n            ]\n        )\n\n        await self.subscriber.subscribe()\n\n        let appExpectation = expectation(description: \"schedules saved\")\n        let contactExpectation = expectation(description: \"schedules saved\")\n\n        await self.engine.setOnUpsert { schedules in\n            if (schedules == appSchedules) {\n                appExpectation.fulfill()\n            } else if (schedules == contactSchedules) {\n                contactExpectation.fulfill()\n            } else {\n                XCTFail()\n            }\n        }\n\n        self.remoteDataAccess.updatesSubject.send(data)\n        await self.fulfillment(of: [appExpectation, contactExpectation])\n    }\n\n    func testEmptyPayloadStopsSchedules() async throws {\n        let appSchedules = makeSchedules(source: .app)\n\n        await self.engine.setSchedules(appSchedules)\n\n        let emptyData = InAppRemoteData(\n            payloads: [:]\n        )\n\n        await self.subscriber.subscribe()\n\n        let stopExpectation = expectation(description: \"schedules stopped\")\n        await self.engine.setOnStop { schedules in\n            XCTAssertEqual(schedules, appSchedules)\n            stopExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(emptyData)\n        await self.fulfillment(of: [stopExpectation])\n    }\n\n    func testIgnoreSchedulesNoLongerScheduled() async throws {\n        await self.subscriber.subscribe()\n\n        let date = Date()\n\n        let firstUpdateSchedules = makeSchedules(source: .app, count: 4)\n        let firstUpdate = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: firstUpdateSchedules,\n                        constraints: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: RemoteDataInfo(\n                        url: URL(string: \"some-url\")!,\n                        lastModifiedTime: nil,\n                        source: .app\n                    )\n                )\n            ]\n        )\n\n        let firstUpdateExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { schedules in\n            XCTAssertEqual(schedules, firstUpdateSchedules)\n            firstUpdateExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(firstUpdate)\n        await self.fulfillment(of: [firstUpdateExpectation])\n\n        await self.engine.setSchedules(firstUpdateSchedules)\n\n        let secondUpdateSchedules = firstUpdateSchedules + makeSchedules(source: .app, count: 4, created: date)\n        let secondUpdate = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: secondUpdateSchedules,\n                        constraints: []\n                    ),\n                    timestamp: date + 100.0,\n                    remoteDataInfo: RemoteDataInfo(\n                        url: URL(string: \"some-url\")!,\n                        lastModifiedTime: nil,\n                        source: .app\n                    )\n                )\n            ]\n        )\n\n        let secondUpdateExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { schedules in\n            // Should still be the first update schedules since the second updates are older\n            XCTAssertEqual(schedules, firstUpdateSchedules)\n            secondUpdateExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(secondUpdate)\n        await self.fulfillment(of: [secondUpdateExpectation])\n    }\n\n    func testOlderSchedulesMinSDKVersion() async throws {\n\n        self.subscriber = AutomationRemoteDataSubscriber(\n            dataStore: dataStore,\n            remoteDataAccess: remoteDataAccess,\n            engine: engine,\n            frequencyLimitManager: frequencyLimits,\n            airshipSDKVersion: \"1.0.0\"\n        )\n        await self.subscriber.subscribe()\n\n\n        let date = Date()\n        let firstUpdateSchedules = makeSchedules(source: .app, count: 4)\n        let firstUpdate = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: firstUpdateSchedules,\n                        constraints: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: RemoteDataInfo(\n                        url: URL(string: \"some-url\")!,\n                        lastModifiedTime: nil,\n                        source: .app\n                    )\n                )\n            ]\n        )\n\n\n\n        let firstUpdateExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { schedules in\n            XCTAssertEqual(schedules, firstUpdateSchedules)\n            firstUpdateExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(firstUpdate)\n        await self.fulfillment(of: [firstUpdateExpectation])\n\n        await self.subscriber.unsubscribe()\n        // Update sdk version\n        self.subscriber = AutomationRemoteDataSubscriber(\n            dataStore: dataStore,\n            remoteDataAccess: remoteDataAccess,\n            engine: engine,\n            frequencyLimitManager: frequencyLimits,\n            airshipSDKVersion: \"2.0.0\"\n        )\n        await self.subscriber.subscribe()\n\n        await self.engine.setSchedules(firstUpdateSchedules)\n\n        let secondUpdateSchedules = firstUpdateSchedules + makeSchedules(\n            source: .app, \n            count: 4,\n            minSDKVersion: \"2.0.0\",\n            created: date\n        )\n\n        let secondUpdate = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: secondUpdateSchedules,\n                        constraints: []\n                    ),\n                    timestamp: date + 100.0\n                )\n            ]\n        )\n\n        let secondUpdateExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { schedules in\n            XCTAssertEqual(schedules, secondUpdateSchedules)\n            secondUpdateExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(secondUpdate)\n        await self.fulfillment(of: [secondUpdateExpectation])\n    }\n\n    func testSamePayloadSkipsAutomations() async throws {\n        await self.subscriber.subscribe()\n\n        let date = Date()\n        let schedules = makeSchedules(source: .app, count: 4)\n        let update = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: schedules,\n                        constraints: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: RemoteDataInfo(\n                        url: URL(string: \"some-url\")!,\n                        lastModifiedTime: nil,\n                        source: .app\n                    )\n                )\n            ]\n        )\n\n        let expecation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { scheduled in\n            XCTAssertEqual(scheduled, schedules)\n            expecation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(update)\n        self.remoteDataAccess.updatesSubject.send(update)\n        await self.fulfillment(of: [expecation])\n    }\n\n    func testRemoteDataInfoChangeUpdatesSchedules() async throws {\n        await self.subscriber.subscribe()\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        let date = Date()\n        let schedules = try makeSchedules(source: .app, count: 4).map { schedule in\n            var mutable = schedule\n            mutable.metadata = try AirshipJSON.wrap(remoteDataInfo)\n            return mutable\n        }\n\n        let firstExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { scheduled in\n            XCTAssertEqual(scheduled, schedules)\n            firstExpectation.fulfill()\n        }\n\n\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: schedules,\n                        constraints: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n\n        await self.fulfillment(of: [firstExpectation])\n\n        await self.engine.setSchedules(schedules)\n\n        let updatedRemoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-other-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        let updatedSchedules = try schedules.map { schedule in\n            var mutable = schedule\n            mutable.metadata = try AirshipJSON.wrap(updatedRemoteDataInfo)\n            return mutable\n        }\n\n        let secondExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { scheduled in\n            XCTAssertEqual(scheduled, updatedSchedules)\n            secondExpectation.fulfill()\n        }\n\n        // udpate again with different remote-data info\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: updatedSchedules,\n                        constraints: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: updatedRemoteDataInfo\n                )\n            ]\n        ))\n\n        await self.fulfillment(of: [secondExpectation])\n    }\n    \n\n    func testPayloadDateChangeAutomations() async throws {\n        await self.subscriber.subscribe()\n\n        let date = Date()\n        let schedules = makeSchedules(source: .app, count: 4)\n\n        let firstExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { scheduled in\n            XCTAssertEqual(scheduled, schedules)\n            firstExpectation.fulfill()\n        }\n\n        let remoteDateInfo = RemoteDataInfo(\n            url: URL(string: \"some-other-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: schedules,\n                        constraints: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDateInfo\n                )\n            ]\n        ))\n\n        await self.fulfillment(of: [firstExpectation])\n\n        await self.engine.setSchedules(schedules)\n\n        let secondExpectation = expectation(description: \"schedules saved\")\n        await self.engine.setOnUpsert { scheduled in\n            XCTAssertEqual(scheduled, schedules)\n            secondExpectation.fulfill()\n        }\n\n\n        // update again with different date\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: schedules,\n                        constraints: []\n                    ),\n                    timestamp: date + 1,\n                    remoteDataInfo: remoteDateInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [secondExpectation])\n    }\n\n    func testConstraints() async throws {\n        let appConstraints = [\n            FrequencyConstraint(identifier: \"foo\", range: 100, count: 10),\n            FrequencyConstraint(identifier: \"bar\", range: 100, count: 10)\n        ]\n        let contactConstraints = [\n            FrequencyConstraint(identifier: \"foo\", range: 1, count: 1),\n            FrequencyConstraint(identifier: \"baz\", range: 1, count: 1)\n        ]\n\n        let data = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [],\n                        constraints: appConstraints\n                    ),\n                    timestamp: Date()\n                ),\n                .contact: .init(\n                    data: .init(\n                        schedules: [],\n                        constraints: contactConstraints\n                    ),\n                    timestamp: Date()\n                ),\n            ]\n        )\n\n        await self.subscriber.subscribe()\n\n        let expectation = expectation(description: \"constraints saved\")\n        await self.frequencyLimits.setOnConstraints { constraints in\n            XCTAssertEqual(constraints, appConstraints + contactConstraints)\n            expectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(data)\n        await self.fulfillment(of: [expectation])\n    }\n\n    // MARK: - Failed schedule tracking tests\n\n    func testFailedScheduleCarriedForwardAndRetriedOnSDKUpdate() async throws {\n        let date = Date()\n        let scheduleA = makeSchedule(source: .app, created: date)\n        let failedB = FailedScheduleRecord(\n            identifier: \"failed_schedule_B\",\n            createdDate: date,\n            minSDKVersion: nil\n        )\n\n        // First sync (SDK 1.0.0): A succeeds, B fails\n        self.subscriber = AutomationRemoteDataSubscriber(\n            dataStore: dataStore,\n            remoteDataAccess: remoteDataAccess,\n            engine: engine,\n            frequencyLimitManager: frequencyLimits,\n            airshipSDKVersion: \"1.0.0\"\n        )\n        await self.subscriber.subscribe()\n\n        let firstExpectation = expectation(description: \"first sync upsert\")\n        await self.engine.setOnUpsert { schedules in\n            XCTAssertEqual(schedules, [scheduleA])\n            firstExpectation.fulfill()\n        }\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA],\n                        constraints: [],\n                        failedSchedules: [failedB]\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [firstExpectation])\n\n        // Now simulate SDK update: recreate subscriber with new version\n        await self.subscriber.unsubscribe()\n        await self.engine.setSchedules([scheduleA])\n\n        self.subscriber = AutomationRemoteDataSubscriber(\n            dataStore: dataStore,\n            remoteDataAccess: remoteDataAccess,\n            engine: engine,\n            frequencyLimitManager: frequencyLimits,\n            airshipSDKVersion: \"2.0.0\"\n        )\n        await self.subscriber.subscribe()\n\n        // Second sync: B now parses successfully\n        let scheduleB = makeSchedule(\n            source: .app,\n            identifier: \"failed_schedule_B\",\n            created: date\n        )\n\n        let secondExpectation = expectation(description: \"retry sync upsert\")\n        await self.engine.setOnUpsert { schedules in\n            let ids = Set(schedules.map { $0.identifier })\n            XCTAssertTrue(ids.contains(\"failed_schedule_B\"))\n            secondExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA, scheduleB],\n                        constraints: [],\n                        failedSchedules: []\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [secondExpectation])\n    }\n\n    func testFailedScheduleNowParsesOnServerFix() async throws {\n        let date = Date()\n        let scheduleA = makeSchedule(source: .app, created: date)\n        let failedB = FailedScheduleRecord(\n            identifier: \"failed_schedule_B\",\n            createdDate: date,\n            minSDKVersion: nil\n        )\n\n        await self.subscriber.subscribe()\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        // First sync: A succeeds, B fails\n        let firstExpectation = expectation(description: \"first sync\")\n        await self.engine.setOnUpsert { schedules in\n            XCTAssertEqual(schedules.map { $0.identifier }, [scheduleA.identifier])\n            firstExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA],\n                        constraints: [],\n                        failedSchedules: [failedB]\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [firstExpectation])\n\n        await self.engine.setSchedules([scheduleA])\n\n        // Second sync: server fixed B, new timestamp\n        let scheduleB = makeSchedule(\n            source: .app,\n            identifier: \"failed_schedule_B\",\n            created: date\n        )\n\n        let secondExpectation = expectation(description: \"server fix sync\")\n        await self.engine.setOnUpsert { schedules in\n            let ids = Set(schedules.map { $0.identifier })\n            XCTAssertTrue(ids.contains(\"failed_schedule_B\"))\n            secondExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA, scheduleB],\n                        constraints: [],\n                        failedSchedules: []\n                    ),\n                    timestamp: date + 100,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [secondExpectation])\n    }\n\n    func testFailedScheduleRemovedFromRemoteData() async throws {\n        let date = Date()\n        let scheduleA = makeSchedule(source: .app, created: date)\n        let failedB = FailedScheduleRecord(\n            identifier: \"failed_schedule_B\",\n            createdDate: date,\n            minSDKVersion: nil\n        )\n\n        await self.subscriber.subscribe()\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        // First sync: A succeeds, B fails\n        let firstExpectation = expectation(description: \"first sync\")\n        await self.engine.setOnUpsert { _ in\n            firstExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA],\n                        constraints: [],\n                        failedSchedules: [failedB]\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [firstExpectation])\n\n        await self.engine.setSchedules([scheduleA])\n\n        // Second sync: B removed entirely from remote data, new timestamp\n        let secondExpectation = expectation(description: \"second sync\")\n        await self.engine.setOnUpsert { schedules in\n            let ids = schedules.map { $0.identifier }\n            XCTAssertFalse(ids.contains(\"failed_schedule_B\"))\n            secondExpectation.fulfill()\n        }\n\n        self.remoteDataAccess.updatesSubject.send(InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA],\n                        constraints: [],\n                        failedSchedules: []\n                    ),\n                    timestamp: date + 100,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        ))\n        await self.fulfillment(of: [secondExpectation])\n    }\n\n    func testSamePayloadWithFailuresSkipsProcessing() async throws {\n        let date = Date()\n        let scheduleA = makeSchedule(source: .app, created: date)\n        let failedB = FailedScheduleRecord(\n            identifier: \"failed_schedule_B\",\n            createdDate: date,\n            minSDKVersion: nil\n        )\n\n        await self.subscriber.subscribe()\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        let payload = InAppRemoteData(\n            payloads: [\n                .app: .init(\n                    data: .init(\n                        schedules: [scheduleA],\n                        constraints: [],\n                        failedSchedules: [failedB]\n                    ),\n                    timestamp: date,\n                    remoteDataInfo: remoteDataInfo\n                )\n            ]\n        )\n\n        let upsertExpectation = expectation(description: \"upsert called once\")\n        await self.engine.setOnUpsert { _ in\n            upsertExpectation.fulfill()\n        }\n\n        // Send the same payload twice — upsert should only fire once\n        self.remoteDataAccess.updatesSubject.send(payload)\n        self.remoteDataAccess.updatesSubject.send(payload)\n        await self.fulfillment(of: [upsertExpectation])\n    }\n\n    // MARK: - Helpers\n\n    private func makeSchedules(\n        source: RemoteDataSource,\n        count: UInt = UInt.random(in: 1..<10),\n        minSDKVersion: String? = nil,\n        created: Date = Date()\n    ) -> [AutomationSchedule] {\n        return (1...count).map { _ in\n            makeSchedule(source: source, minSDKVersion: minSDKVersion, created: created)\n        }\n    }\n\n    private func makeSchedule(\n        source: RemoteDataSource,\n        identifier: String = UUID().uuidString,\n        minSDKVersion: String? = nil,\n        created: Date = Date()\n    ) -> AutomationSchedule {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-test-url/\")!,\n            lastModifiedTime: nil,\n            source: source\n        )\n        return AutomationSchedule(\n            identifier: identifier,\n            data: .actions(.string(\"actions\")),\n            triggers: [AutomationTrigger.activeSession(count: 1)],\n            created: created,\n            metadata: try! AirshipJSON.wrap([\n                InAppRemoteData.remoteInfoMetadataKey: remoteDataInfo\n            ]),\n            minSDKVersion: minSDKVersion\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/RemoteData/AutomationSourceInfoStoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\nimport AirshipCore\n@testable\nimport AirshipAutomation\n\nfinal class AutomationSourceInfoStoreTest: XCTestCase {\n\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var infoStore: AutomationSourceInfoStore!\n\n    override func setUp() async throws {\n        self.infoStore = AutomationSourceInfoStore(dataStore: dataStore)\n    }\n\n    func testMigrateChannel() throws {\n        let lastPayloadTimestamp = Date() - 1000.0\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url://\")!,\n            lastModifiedTime: UUID().uuidString,\n            source: .app\n        )\n\n        dataStore.setObject(lastPayloadTimestamp, forKey: \"UAInAppRemoteDataClient.LastPayloadTimeStamp\")\n        dataStore.setObject(\"17.9.9\", forKey: \"UAInAppRemoteDataClient.LastSDKVersion\")\n        dataStore.setSafeCodable(remoteDataInfo, forKey: \"UAInAppRemoteDataClient.LastRemoteDataInfo\")\n        dataStore.setSafeCodable(remoteDataInfo, forKey: \"UAInAppRemoteDataClient.LastPayloadMetadata\")\n\n        let expected = AutomationSourceInfo(\n            remoteDataInfo: nil,\n            payloadTimestamp: lastPayloadTimestamp,\n            airshipSDKVersion: \"17.9.9\"\n        )\n\n        let actual = self.infoStore.getSourceInfo(source: .app, contactID: nil)\n\n        XCTAssertEqual(expected, actual)\n    }\n\n    func testMigrateContact() throws {\n        let lastPayloadTimestamp = Date() - 1000.0\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some-url://\")!,\n            lastModifiedTime: UUID().uuidString,\n            source: .contact\n        )\n\n        dataStore.setObject(lastPayloadTimestamp, forKey: \"UAInAppRemoteDataClient.LastPayloadTimeStamp.Contactfoo\")\n        dataStore.setObject(\"17.9.9\", forKey: \"UAInAppRemoteDataClient.LastSDKVersion.Contactfoo\")\n        dataStore.setSafeCodable(remoteDataInfo, forKey: \"UAInAppRemoteDataClient.LastRemoteDataInfo.Contactfoo\")\n        dataStore.setSafeCodable(remoteDataInfo, forKey: \"UAInAppRemoteDataClient.LastPayloadMetadata.Contactfoo\")\n\n        let expected = AutomationSourceInfo(\n            remoteDataInfo: nil,\n            payloadTimestamp: lastPayloadTimestamp,\n            airshipSDKVersion: \"17.9.9\"\n        )\n\n        let actual = self.infoStore.getSourceInfo(source: .contact, contactID: \"foo\")\n\n        XCTAssertEqual(expected, actual)\n    }\n\n    func testAppStoreIgnoreContactID() throws {\n        let sourceInfo = AutomationSourceInfo(\n            remoteDataInfo: nil,\n            payloadTimestamp: Date(),\n            airshipSDKVersion: \"17.9.9\",\n            failedSchedules: []\n        )\n        self.infoStore.setSourceInfo(sourceInfo, source: .app, contactID: \"foo\")\n\n        XCTAssertEqual(\n            sourceInfo,\n            self.infoStore.getSourceInfo(source: .app, contactID: nil)\n        )\n\n        XCTAssertEqual(\n            sourceInfo,\n            self.infoStore.getSourceInfo(source: .app, contactID: \"foo\")\n        )\n\n        XCTAssertEqual(\n            sourceInfo,\n            self.infoStore.getSourceInfo(source: .app, contactID: UUID().uuidString)\n        )\n    }\n\n    func testContactStoreRespectsContactID() throws {\n        let sourceInfo = AutomationSourceInfo(\n            remoteDataInfo: nil,\n            payloadTimestamp: Date(),\n            airshipSDKVersion: \"17.9.9\",\n            failedSchedules: []\n        )\n        self.infoStore.setSourceInfo(sourceInfo, source: .contact, contactID: \"foo\")\n\n        XCTAssertNil(\n            self.infoStore.getSourceInfo(source: .contact, contactID: nil)\n        )\n\n        XCTAssertNil(\n            self.infoStore.getSourceInfo(source: .contact, contactID: UUID().uuidString)\n        )\n\n        XCTAssertEqual(\n            sourceInfo,\n            self.infoStore.getSourceInfo(source: .contact, contactID: \"foo\")\n        )\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestActionRunner.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class TestActionRunner: AutomationActionRunnerProtocol, @unchecked Sendable {\n\n    var actions: AirshipJSON?\n    var situation: ActionSituation?\n    var metadata: [String: Sendable]?\n\n    func runActions(_ actions: AirshipCore.AirshipJSON, situation: ActionSituation, metadata: [String : Sendable]) async {\n        self.actions = actions\n        self.situation = situation\n        self.metadata = metadata\n    }\n\n    func runActionsAsync(_ actions: AirshipCore.AirshipJSON, situation: ActionSituation, metadata: [String : Sendable]) {\n        self.actions = actions\n        self.situation = situation\n        self.metadata = metadata\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestActiveTimer.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n@testable import AirshipAutomation\n\nimport AirshipCore\n\n@MainActor\nfinal class TestActiveTimer: AirshipTimerProtocol {\n    var time: TimeInterval = 0\n    var isStarted: Bool = false\n\n    func start() {\n        isStarted = true\n    }\n    \n    func stop() {\n        isStarted = false\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestAutomationEngine.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nactor TestAutomationEngine: AutomationEngineProtocol {\n    \n    @MainActor\n    var isPaused: Bool = false\n    @MainActor\n    var isExecutionPaused: Bool = false\n    var isStarted: Bool = false\n\n    private var onUpsert: (@Sendable ([AutomationSchedule]) async throws -> Void)?\n    private var onStop: (@Sendable ([AutomationSchedule]) async throws -> Void)?\n    private var onCancel: (@Sendable ([AutomationSchedule]) async throws -> Void)?\n    \n    private(set) var cancelledSchedules: [String] = []\n\n    @MainActor\n    func setEnginePaused(_ paused: Bool) {\n        self.isPaused = true\n    }\n\n    @MainActor\n    func setExecutionPaused(_ paused: Bool) {\n        self.isExecutionPaused = true\n    }\n\n\n    func start() {\n        isStarted = true\n    }\n\n\n    func setOnStop(_ onStop: @escaping @Sendable ([AutomationSchedule]) async throws -> Void) {\n        self.onStop = onStop\n    }\n\n    func stopSchedules(identifiers: [String]) async throws {\n        try await self.onStop!(schedules)\n    }\n\n    func setOnUpsert(_ onUpsert: @escaping @Sendable ([AutomationSchedule]) async throws -> Void) {\n        self.onUpsert = onUpsert\n    }\n\n    func upsertSchedules(_ schedules: [AutomationSchedule]) async throws {\n        self.schedules.removeAll { schedule in\n            schedules.contains { incoming in\n                incoming.identifier == schedule.identifier\n            }\n        }\n\n        self.schedules.append(contentsOf: schedules)\n        try await self.onUpsert?(schedules)\n    }\n    \n    func cancelSchedule(identifier: String) async throws {\n\n    }\n    \n    func cancelSchedules(identifiers: [String]) async throws {\n        self.cancelledSchedules.append(contentsOf: identifiers)\n\n        let set = Set(identifiers)\n        self.schedules.removeAll(where: { set.contains($0.identifier) })\n    }\n\n    func cancelSchedules(group: String) async throws {\n        self.schedules.removeAll(where: { $0.group == group })\n    }\n    \n    func cancelSchedulesWith(type: AutomationSchedule.ScheduleType) async throws {\n        self.schedules.removeAll { schedule in\n            switch schedule.data {\n            case .actions: return true\n            default: return false\n            }\n        }\n    }\n\n\n    private(set) var schedules: [AutomationSchedule] = []\n\n    func setSchedules(_ schedules: [AutomationSchedule]) {\n        self.schedules = schedules\n    }\n\n    func getSchedule(identifier: String) async throws -> AutomationSchedule? {\n        throw AirshipErrors.error(\"Not implemented\")\n\n    }\n\n    func getSchedule(identifier: String) async throws -> AutomationSchedule {\n        throw AirshipErrors.error(\"Not implemented\")\n    }\n    \n    func getSchedules(group: String) async throws -> [AutomationSchedule] {\n        throw AirshipErrors.error(\"Not implemented\")\n    }\n    \n    func scheduleConditionsChanged() {\n\n    }\n    \n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestCachedAssets.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class TestCachedAssets: AirshipCachedAssetsProtocol, @unchecked Sendable {\n\n    var cached: [URL] = []\n\n    func cachedURL(remoteURL: URL) -> URL? {\n        return isCached(remoteURL: remoteURL) ? remoteURL : nil\n    }\n    \n    func isCached(remoteURL: URL) -> Bool {\n        return cached.contains(remoteURL)\n    }\n}\n\nfinal actor TestAssetManager: AssetCacheManagerProtocol {\n    var cleared: [String] = []\n    var onCache: (@Sendable (String, [String]) async throws -> any AirshipCachedAssetsProtocol)?\n\n    func setOnCache(_ onCache: @escaping @Sendable (String, [String]) async throws -> any AirshipCachedAssetsProtocol) {\n        self.onCache = onCache\n    }\n\n    func cacheAssets(identifier: String, assets: [String]) async throws -> any AirshipCachedAssetsProtocol {\n        try await self.onCache!(identifier, assets)\n    }\n    \n    func clearCache(identifier: String) async {\n        cleared.append(identifier)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestDisplayAdapter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\n@MainActor\nfinal class TestDisplayAdapter: DisplayAdapter, @unchecked Sendable {\n    var isReady: Bool = true\n\n    func waitForReady() async {\n        \n    }\n\n    var onDisplay: ((AirshipDisplayTarget, any InAppMessageAnalyticsProtocol) async throws -> DisplayResult)?\n\n    var displayed: Bool = false\n\n    func display(displayTarget: AirshipDisplayTarget, analytics: any InAppMessageAnalyticsProtocol) async throws -> DisplayResult {\n        self.displayed = true\n        return try await self.onDisplay!(displayTarget, analytics)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestDisplayCoordinator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nclass TestDisplayCoordinator: DisplayCoordinator {\n    var isReady: Bool = true\n\n    func messageWillDisplay(_ message: InAppMessage) {\n\n    }\n\n    func messageFinishedDisplaying(_ message: InAppMessage) {\n\n    }\n\n    func waitForReady() async {\n        \n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestFrequencyLimitsManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\n\nfinal actor TestFrequencyLimitManager: FrequencyLimitManagerProtocol {\n    private var constraints: [FrequencyConstraint] = []\n\n\n    private var checkerBlock: (@Sendable ([String]) async throws -> FrequencyCheckerProtocol)?\n\n\n    func setCheckerBlock(_ checkerBlock: @Sendable @escaping ([String]) throws -> FrequencyCheckerProtocol) {\n        self.checkerBlock = checkerBlock\n    }\n\n    private var onConstraints: (@Sendable ([FrequencyConstraint]) async throws -> Void)?\n    func setOnConstraints(_ onConstraints: @escaping @Sendable ([FrequencyConstraint]) async throws -> Void) {\n        self.onConstraints = onConstraints\n    }\n    func setConstraints(_ constraints: [FrequencyConstraint]) async throws {\n        self.constraints = constraints\n        try await onConstraints?(constraints)\n    }\n\n    func getFrequencyChecker(constraintIDs: [String]?) async throws -> FrequencyCheckerProtocol {\n        guard let constraintIDs = constraintIDs, !constraintIDs.isEmpty else {\n            return FrequencyChecker(isOverLimitBlock: { false }, checkAndIncrementBlock: { true })\n        }\n\n        return try await self.checkerBlock!(constraintIDs)\n    }\n\n}\n\nfinal class TestFrequencyChecker: FrequencyCheckerProtocol, @unchecked Sendable {\n    var isOverLimit: Bool = false\n    var checkAndIncrementBlock: (() -> Bool)?\n    var checkAndIncrementCalled: Bool = false\n\n    func checkAndIncrement() -> Bool {\n        checkAndIncrementCalled = true\n        return checkAndIncrementBlock!()\n    }\n\n    @MainActor\n    func setIsOverLimit(_ isOverLimit: Bool) {\n        self.isOverLimit = isOverLimit\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestInAppMessageAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class TestInAppMessageAnalytics: InAppMessageAnalyticsProtocol, @unchecked Sendable {\n\n    var onMakeCustomEventContext: ((ThomasLayoutContext?) -> InAppCustomEventContext?)?\n    func makeCustomEventContext(layoutContext: ThomasLayoutContext?) -> InAppCustomEventContext? {\n        return onMakeCustomEventContext!(layoutContext)\n    }\n    \n    var events: [(ThomasLayoutEvent, ThomasLayoutContext?)] = []\n    var impressionsRecored: UInt = 0\n    func recordEvent(_ event: ThomasLayoutEvent, layoutContext: ThomasLayoutContext?) {\n        events.append((event, layoutContext))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestInAppMessageAutomationExecutor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class TestInAppMessageAutomationExecutor: AutomationExecutorDelegate, @unchecked Sendable {  typealias ExecutionData = PreparedInAppMessageData\n    func isReady(data: AirshipAutomation.PreparedInAppMessageData, preparedScheduleInfo: AirshipAutomation.PreparedScheduleInfo) -> AirshipAutomation.ScheduleReadyResult {\n        return .ready\n    }\n    \n    func execute(data: AirshipAutomation.PreparedInAppMessageData, preparedScheduleInfo: AirshipAutomation.PreparedScheduleInfo) async throws -> AirshipAutomation.ScheduleExecuteResult {\n        return .finished\n    }\n    \n    func interrupted(schedule: AirshipAutomation.AutomationSchedule, preparedScheduleInfo: AirshipAutomation.PreparedScheduleInfo) async -> AirshipAutomation.InterruptedBehavior {\n        return .finish\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Test Utils/TestRemoteDataAccess.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class TestRemoteDataAccess: AutomationRemoteDataAccessProtocol, @unchecked Sendable {\n    func source(forSchedule schedule: AutomationSchedule) -> AirshipCore.RemoteDataSource? {\n        return .app\n    }\n\n    \n    var isCurrentBlock: ((AutomationSchedule) async -> Bool)?\n    var bestEffortRefreshBlock: ((AutomationSchedule) async -> Bool)?\n    var requiresUpdateBlock: ((AutomationSchedule) async -> Bool)?\n    var waitFullRefreshBlock: ((AutomationSchedule) async -> Void)?\n\n    var contactIDBlock: ((AutomationSchedule) -> String?)?\n\n    var notifiedOutdatedSchedules: [AutomationSchedule] = []\n\n    let updatesSubject = PassthroughSubject<InAppRemoteData, Never>()\n\n    var publisher: AnyPublisher<InAppRemoteData, Never> {\n        return updatesSubject.eraseToAnyPublisher()\n    }\n\n    func isCurrent(schedule: AutomationSchedule) async -> Bool {\n        return await isCurrentBlock!(schedule)\n    }\n\n    func requiresUpdate(schedule: AutomationSchedule) async -> Bool {\n        return await requiresUpdateBlock!(schedule)\n    }\n\n\n    func waitFullRefresh(schedule: AutomationSchedule) async {\n        await waitFullRefreshBlock!(schedule)\n    }\n\n    func bestEffortRefresh(schedule: AutomationSchedule) async -> Bool {\n        await bestEffortRefreshBlock!(schedule)\n    }\n\n    func notifyOutdated(schedule: AutomationSchedule) async {\n        notifiedOutdatedSchedules.append(schedule)\n    }\n\n    func contactID(forSchedule schedule: AutomationSchedule) -> String? {\n        return contactIDBlock!(schedule)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Utils/ActiveTimerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nfinal class ActiveTimerTest: XCTestCase {\n    var subject: ActiveTimer!\n    \n    private let date = UATestDate(offset: 0, dateOverride: Date())\n    private let notificationCenter = NotificationCenter()\n    private let stateTracker = TestAppStateTracker()\n    \n    @MainActor\n    private func createSubject(state: AirshipCore.ApplicationState = .active) {\n        stateTracker.currentState = state\n        \n        subject = ActiveTimer(\n            appStateTracker: stateTracker,\n            notificationCenter: AirshipNotificationCenter(notificationCenter: notificationCenter),\n            date: date\n        )\n    }\n\n    @MainActor\n    func testManualStartStopWorks() {\n        createSubject()\n        \n        subject.start()\n        date.offset = 2\n        \n        XCTAssertEqual(2, subject.time)\n        \n        date.offset = 3\n        subject.stop()\n        XCTAssertEqual(3, subject.time)\n    }\n    \n    @MainActor\n    func testMultipleSessions() {\n        createSubject()\n        subject.start()\n        date.offset = 1\n        XCTAssertEqual(1, subject.time)\n        subject.stop()\n        \n        date.offset += 1\n        XCTAssertEqual(1, subject.time)\n        subject.start()\n        date.offset += 2\n        subject.stop()\n        XCTAssertEqual(3, subject.time)\n        \n        date.offset += 1\n        XCTAssertEqual(3, subject.time)\n    }\n    \n    @MainActor\n    func testStartDoesntWorkIfAppInBackground() {\n        createSubject(state: .background)\n        subject.start()\n        date.offset = 2\n        \n        XCTAssertEqual(0, subject.time)\n    }\n    \n    @MainActor\n    func testDoubleStartDoesntRestCounter() {\n        createSubject()\n        \n        subject.start()\n        date.offset = 2\n        XCTAssertEqual(2, subject.time)\n        date.offset = 3\n        subject.start()\n        date.offset = 2\n        subject.stop()\n        XCTAssertEqual(2, subject.time)\n    }\n    \n    @MainActor\n    func testDoubleStopDoesntDoubleCounter() {\n        createSubject()\n        subject.start()\n        date.offset = 3\n        subject.stop()\n        \n        XCTAssertEqual(3, subject.time)\n        \n        date.offset = 5\n        subject.stop()\n        \n        XCTAssertEqual(3, subject.time)\n    }\n    \n    @MainActor\n    func testHandlingAppState() {\n        createSubject(state: .background)\n        \n        subject.start()\n        date.offset = 3\n        XCTAssertEqual(0, subject.time)\n        notificationCenter.post(name: AppStateTracker.didBecomeActiveNotification, object: nil)\n        date.offset += 3\n        XCTAssertEqual(3, subject.time)\n        \n        notificationCenter.post(name: AppStateTracker.willResignActiveNotification, object: nil)\n        date.offset = 5\n        XCTAssertEqual(3, subject.time)\n    }\n    \n    @MainActor\n    func testActiveNotificationDoesNothingOnDisabledTimer() {\n        createSubject(state: .background)\n        XCTAssertEqual(0, subject.time)\n        \n        notificationCenter.post(name: AppStateTracker.didBecomeActiveNotification, object: nil)\n        date.offset += 3\n        XCTAssertEqual(0, subject.time)\n        \n    }\n    \n    @MainActor\n    func testTimerStopsOnEnteringBackground() {\n        createSubject()\n        subject.start()\n        date.offset = 2\n        XCTAssertEqual(2, subject.time)\n        \n        notificationCenter.post(name: AppStateTracker.willResignActiveNotification, object: nil)\n        date.offset = 5\n        XCTAssertEqual(2, subject.time)\n        \n        subject.stop()\n        XCTAssertEqual(2, subject.time)\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Utils/AirshipAsyncSemaphoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipAutomation\n@testable import AirshipCore\n\nstruct AirshipAsyncSemaphoreTest {\n\n    @Test\n    func testPermitAllowsExecution() async throws {\n        let semaphore = AirshipAsyncSemaphore(value: 1)\n\n        let result = try await semaphore.withPermit {\n            return \"success\"\n        }\n\n        #expect(result == \"success\")\n    }\n\n    @Test\n    func testMutualExclusionWithOnePermit() async throws {\n        let semaphore = AirshipAsyncSemaphore(value: 1)\n        let concurrent: AirshipActorValue<Int> = AirshipActorValue(0)\n        let maxConcurrent: AirshipActorValue<Int> = AirshipActorValue(0)\n        let completedCount: AirshipActorValue<Int> = AirshipActorValue(0)\n\n        async let first: () = semaphore.withPermit {\n            let current = await concurrent.getAndUpdate { $0 += 1 }\n            await maxConcurrent.update { $0 = max($0, current) }\n            try await Task.sleep(nanoseconds: 50_000_000)\n            await concurrent.update { $0 -= 1 }\n            await completedCount.update { $0 += 1 }\n        }\n\n        async let second: () = semaphore.withPermit {\n            let current = await concurrent.getAndUpdate { $0 += 1 }\n            await maxConcurrent.update { $0 = max($0, current) }\n            try await Task.sleep(nanoseconds: 50_000_000)\n            await concurrent.update { $0 -= 1 }\n            await completedCount.update { $0 += 1 }\n        }\n\n        async let third: () = semaphore.withPermit {\n            let current = await concurrent.getAndUpdate { $0 += 1 }\n            await maxConcurrent.update { $0 = max($0, current) }\n            try await Task.sleep(nanoseconds: 50_000_000)\n            await concurrent.update { $0 -= 1 }\n            await completedCount.update { $0 += 1 }\n        }\n\n        _ = try await (first, second, third)\n\n        await #expect(maxConcurrent.value <= 1)\n        await #expect(completedCount.value == 3)\n    }\n\n    @Test\n    func testConcurrencyLimitWithTwoPermits() async throws {\n        let semaphore = AirshipAsyncSemaphore(value: 2)\n        let concurrent: AirshipActorValue<Int> = AirshipActorValue(0)\n        let maxConcurrent: AirshipActorValue<Int> = AirshipActorValue(0)\n        let completedCount: AirshipActorValue<Int> = AirshipActorValue(0)\n\n        async let first: () = semaphore.withPermit {\n            let current = await concurrent.getAndUpdate { $0 += 1 }\n            await maxConcurrent.update { $0 = max($0, current) }\n            try await Task.sleep(nanoseconds: 50_000_000)\n            await concurrent.update { $0 -= 1 }\n            await completedCount.update { $0 += 1 }\n        }\n\n        async let second: () = semaphore.withPermit {\n            let current = await concurrent.getAndUpdate { $0 += 1 }\n            await maxConcurrent.update { $0 = max($0, current) }\n            try await Task.sleep(nanoseconds: 50_000_000)\n            await concurrent.update { $0 -= 1 }\n            await completedCount.update { $0 += 1 }\n        }\n\n        async let third: () = semaphore.withPermit {\n            let current = await concurrent.getAndUpdate { $0 += 1 }\n            await maxConcurrent.update { $0 = max($0, current) }\n            try await Task.sleep(nanoseconds: 50_000_000)\n            await concurrent.update { $0 -= 1 }\n            await completedCount.update { $0 += 1 }\n        }\n\n        _ = try await (first, second, third)\n\n        await #expect(maxConcurrent.value <= 2)\n        await #expect(completedCount.value == 3)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipAutomation/Tests/Utils/RetryingQueueTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\nimport AirshipCore\n@testable\nimport AirshipAutomation\n\nfinal class RetryingQueueTests: XCTestCase {\n\n    private let taskSleeper: TestTaskSleeper = TestTaskSleeper()\n\n    func testState() async throws {\n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            taskSleeper: taskSleeper\n        )\n\n        let result = await queue.run(name: \"testState\") { state in\n            let runCount: Int = await state.value(key: \"runCount\") ?? 1\n            await state.setValue(runCount + 1, key: \"runCount\")\n\n            if (runCount == 6) {\n                return .success(result: runCount, ignoreReturnOrder: true)\n            }\n\n            return .retry\n        }\n\n        XCTAssertEqual(6, result)\n    }\n    \n    func testExecutionOrderPriorities() async throws {\n        \n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            maxConcurrentOperations: 1\n        )\n        \n        let numbers = await withTaskGroup(of: Int.self) { group in\n            group.addTask {\n                return await queue.run(name: \"3\", priority: 3) { _ in\n                    try await Task.sleep(nanoseconds: 1_000_000)\n                    return .success(result: 3, ignoreReturnOrder: false)\n                }\n            }\n            group.addTask {\n                return await queue.run(name: \"2\", priority: 2) { _ in\n                    try await Task.sleep(nanoseconds: 1_000_000)\n                    return .success(result: 2, ignoreReturnOrder: false)\n                }\n            }\n            group.addTask {\n                return await queue.run(name: \"1\", priority: 1) { _ in\n                    try await Task.sleep(nanoseconds: 1_000_000)\n                    return .success(result: 1, ignoreReturnOrder: false)\n                }\n            }\n\n            var result = [Int]()\n            \n            for await item in group {\n                result.append(item)\n            }\n            \n            return result\n        }\n        \n        XCTAssertEqual([1, 2, 3], numbers)\n    }\n\n    func testRetryAfter0() async throws {\n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            initialBackOff: 10,\n            maxBackOff: 60,\n            taskSleeper: taskSleeper\n        )\n\n        let result = await queue.run(name: \"testRetryAfter0\") { state in\n            let runCount: Int = await state.value(key: \"runCount\") ?? 1\n            await state.setValue(runCount + 1, key: \"runCount\")\n\n            if (runCount == 1) {\n                return .retryAfter(0)\n            }\n\n            if (runCount == 3) {\n                return .success(result: 0, ignoreReturnOrder: true)\n            }\n\n            return .retry\n\n        }\n\n        XCTAssertEqual(0, result)\n        XCTAssertEqual([0, 10], self.taskSleeper.sleeps)\n    }\n\n    func testBackOff() async throws {\n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            initialBackOff: 10,\n            maxBackOff: 60,\n            taskSleeper: taskSleeper\n        )\n\n        let result = await queue.run(name: \"testBackOff\") { state in\n            let runCount: Int = await state.value(key: \"runCount\") ?? 1\n            await state.setValue(runCount + 1, key: \"runCount\")\n\n            if (runCount == 6) {\n                return .success(result: 0, ignoreReturnOrder: true)\n            }\n\n            return .retry\n\n        }\n\n        XCTAssertEqual(0, result)\n        XCTAssertEqual([10, 20, 40, 60, 60], self.taskSleeper.sleeps)\n    }\n\n    func testRetryAfterCanExceedMaxBackOff() async throws {\n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            initialBackOff: 10,\n            maxBackOff: 60,\n            taskSleeper: taskSleeper\n        )\n\n        let result = await queue.run(name: \"testRetryAfterCanExceedMaxBackOff\") { state in\n            let runCount: Int = await state.value(key: \"runCount\") ?? 1\n            await state.setValue(runCount + 1, key: \"runCount\")\n\n            if (runCount == 2) {\n                return .retryAfter(10000)\n            }\n\n            if (runCount == 4) {\n                return .success(result: 0, ignoreReturnOrder: true)\n            }\n\n            return .retry\n        }\n\n        XCTAssertEqual(0, result)\n        XCTAssertEqual([10, 10000, 60], self.taskSleeper.sleeps)\n    }\n\n    func testThrowsRetries() async throws {\n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            initialBackOff: 10,\n            maxBackOff: 60,\n            taskSleeper: taskSleeper\n        )\n\n        let result = await queue.run(name: \"testRetryAfterCanExceedMaxBackOff\") { state in\n            let isFirstRun: Bool = await state.value(key: \"isFirstRun\") ?? true\n            await state.setValue(false, key: \"isFirstRun\")\n\n            if (isFirstRun) {\n                throw AirshipErrors.error(\"failed\")\n            }\n\n            return .success(result: 0)\n        }\n\n        XCTAssertEqual(0, result)\n        XCTAssertEqual([10], self.taskSleeper.sleeps)\n    }\n\n    func testDeadLock() async throws {\n           let queue = RetryingQueue<String>(\n            id: \"test\",\n               maxConcurrentOperations: 1,\n               maxPendingResults: 1\n           )\n\n           let coordinator = DeadlockTestCoordinator()\n\n           let expectationA = XCTestExpectation(description: \"Task A completed\")\n           let expectationB = XCTestExpectation(description: \"Task B completed\")\n\n           Task {\n               let result = await queue.run(name: \"Task A\", priority: 10) { _ in\n                   print(\"\\(Date()): Task A: Started work.\")\n                   await coordinator.signalTaskBShouldBeAdded()\n                   await coordinator.waitForTaskAFinishWork()\n                   return .success(result: \"A\")\n               }\n               XCTAssertEqual(result, \"A\")\n               expectationA.fulfill()\n           }\n\n           await coordinator.waitForTaskBToBeAdded()\n\n           Task {\n               let result = await queue.run(name: \"Task B\", priority: 0) { _ in\n                   return .success(result: \"B\")\n               }\n               XCTAssertEqual(result, \"B\")\n               expectationB.fulfill()\n           }\n\n           await coordinator.signalTaskAFinishWork()\n\n           await fulfillment(of: [expectationA, expectationB], timeout: 2.0)\n       }\n\n    func testRetryDoesNotBlock() async throws {\n\n        let queue = RetryingQueue<Int>(\n            id: \"test\",\n            maxConcurrentOperations: 3,\n            initialBackOff: 10\n        )\n\n        let taskNumber = ActorValue<Int>(1)\n        let startedTasks = ActorValue<Int>(0)\n        let results = ActorValue<[Int]>([])\n\n        let completed = expectation(description: \"Completed\")\n\n        for _ in 1...2 {\n            Task { @MainActor in\n                let myTaskNumber = await taskNumber.getAndUpdate { task in\n                    task + 1\n                }\n\n                let result = await queue.run(name: \"Task \\(myTaskNumber)\") { state in\n                    let isFirstRun = await state.value(key: \"isFirstRun\") ?? true\n                    await state.setValue(false, key: \"isFirstRun\")\n\n                    if (isFirstRun) {\n                        await startedTasks.update { task in\n                           task + 1\n                       }\n                    }\n\n                    while (await startedTasks.get() != 2) {\n                        await Task.yield()\n                    }\n\n                    if (myTaskNumber == 1 && isFirstRun) {\n                        return .retryAfter(0.2)\n                    }\n\n                    return .success(result: myTaskNumber)\n                }\n\n                await results.update { current in\n                    var current = current\n                    current.append(result)\n\n                    defer {\n                        if (current.count == 2) {\n                            completed.fulfill()\n                        }\n                    }\n\n                    return current\n                }\n            }\n        }\n\n        await fulfillment(of: [completed])\n        let resultsValue = await results.get()\n        XCTAssertEqual(resultsValue, [2,1])\n    }\n}\n\nactor ActorValue<T: Sendable> {\n    private var value: T\n    \n    init(_ value: T) {\n        self.value = value\n    }\n    \n    func set(_ value: T) {\n        self.value = value\n    }\n    \n    func get() -> T {\n        return value\n    }\n    \n    func getAndUpdate(block: @Sendable (T) -> T) -> T {\n        let value = value\n        self.value = block(self.value)\n        return value\n    }\n    \n    \n    func update(block: @Sendable (T) -> T) {\n        self.value = block(self.value)\n    }\n}\n\nfinal class TestTaskSleeper : AirshipTaskSleeper, @unchecked Sendable {\n    var sleeps : [TimeInterval] = []\n\n    func sleep(timeInterval: TimeInterval) async throws {\n        sleeps.append(timeInterval)\n        try await self.onSleep?(sleeps)\n        await Task.yield()\n    }\n\n    var onSleep: (([TimeInterval]) async throws -> Void)?\n}\n\nprivate actor DeadlockTestCoordinator {\n    private var taskBShouldBeAddedContinuation: CheckedContinuation<Void, Never>?\n    private var taskAFinishWorkContinuation: CheckedContinuation<Void, Never>?\n\n    func waitForTaskBToBeAdded() async {\n        await withCheckedContinuation { continuation in\n            self.taskBShouldBeAddedContinuation = continuation\n        }\n    }\n\n    func signalTaskBShouldBeAdded() {\n        taskBShouldBeAddedContinuation?.resume()\n    }\n\n    func waitForTaskAFinishWork() async {\n        await withCheckedContinuation { continuation in\n            self.taskAFinishWorkContinuation = continuation\n        }\n    }\n\n    func signalTaskAFinishWork() {\n        taskAFinishWorkContinuation?.resume()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipBasement/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipBasement/Source/AirshipLogHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Protocol used by Airship to log all log messages within the SDK.\n/// A custom log handlers should be set on `AirshipConfig.logHandler`.\npublic protocol AirshipLogHandler: Sendable {\n\n    /// Called to log a message.\n    /// - Parameters:\n    ///     - logLevel: The Airship log level.\n    ///     - message: The log message.\n    ///     - fileID: The file ID.\n    ///     - line: The line number.\n    ///     - function: The function.\n    func log(\n        logLevel: AirshipLogLevel,\n        message: String,\n        fileID: String,\n        line: UInt,\n        function: String\n    )\n}\n"
  },
  {
    "path": "Airship/AirshipBasement/Source/AirshipLogPrivacyLevel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport os\n\n/// Represents the possible log privacy level.\npublic enum AirshipLogPrivacyLevel: String, Sendable, Decodable {\n    /**\n     * Private log privacy level. Set by default.\n     */\n    case `private` = \"private\"\n\n    /**\n     * Public log privacy level. Logs publicly when set via the AirshipConfig.\n     */\n    case `public` = \"public\"\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        do {\n            let stringValue = try container.decode(String.self)\n            switch(stringValue) {\n            case \"private\":\n                self = .private\n            case \"public\":\n                self = .public\n            default:\n                self = .private\n            }\n        } catch {\n            guard let intValue = try? container.decode(Int.self) else {\n                throw error\n            }\n            \n            switch(intValue) {\n                \n            case 0:\n                self = .private\n            case 1:\n                self = .public\n            default:\n                throw error\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipBasement/Source/AirshipLogger.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport os\n\n///\n/// Airship logger.\n///\n/// - Note: For internal use only. :nodoc:\npublic final class AirshipLogger: Sendable {\n    // Configuration for the logger\n    private static let configuration: Configuration = Configuration()\n\n    static var logLevel: AirshipLogLevel {\n        return configuration.storage.logLevel\n    }\n\n    static var logHandler: any AirshipLogHandler {\n        return configuration.storage.handler\n    }\n\n    /// Configures the logger. Called once during takeOff before we use the logger, so it should be\n    /// thread safe by convention. If we run into issues with this, we may need to introduce locking or\n    /// create a single instance that we inject everywhere.\n    /// - Parameters:\n    ///     - logLevel: The log level\n    ///     - handler: The log handler\n    @MainActor\n    @_spi(AirshipInternal)\n    public static func configure(\n        logLevel: AirshipLogLevel,\n        handler: (any AirshipLogHandler)\n    ) {\n        configuration.configure(logLevel: logLevel, handler: handler)\n    }\n\n    fileprivate final class Configuration: @unchecked Sendable {\n        struct Storage: Sendable {\n            var logLevel: AirshipLogLevel\n            var handler: any AirshipLogHandler\n        }\n\n        var storage: Storage = .init(\n            logLevel: .error,\n            handler: DefaultLogHandler(privacyLevel: .private)\n        )\n\n        @MainActor\n        func configure(\n            logLevel: AirshipLogLevel,\n            handler: (any AirshipLogHandler)\n        ) {\n            let storage = Storage(logLevel: logLevel, handler: handler)\n            // Replace both logLevel and handler at the same time\n            self.storage = storage\n        }\n    }\n\n    public static func trace(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n\n        log(\n            logLevel: AirshipLogLevel.verbose,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    public static func debug(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n\n        log(\n            logLevel: AirshipLogLevel.debug,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    public static func info(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: AirshipLogLevel.info,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    public static func importantInfo(\n        _ message: String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: AirshipLogLevel.info,\n            message: message,\n            fileID: fileID,\n            line: line,\n            function: function,\n            skipLogLevelCheck: true\n        )\n    }\n\n    public static func warn(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: AirshipLogLevel.warn,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    public static func error(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: AirshipLogLevel.error,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    public static func impError(\n        _ message: @autoclosure () -> String,\n        skipLogLevelCheck: Bool = true,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: AirshipLogLevel.error,\n            message: \"🚨Airship Implementation Error🚨: \\(message())\",\n            fileID: fileID,\n            line: line,\n            function: function,\n            skipLogLevelCheck: skipLogLevelCheck\n        )\n    }\n\n    static func log(\n        logLevel: AirshipLogLevel,\n        message: @autoclosure () -> String,\n        fileID: String,\n        line: UInt,\n        function: String,\n        skipLogLevelCheck: Bool = false\n    ) {\n\n        guard self.logLevel != .none, self.logLevel != .undefined else {\n            return\n        }\n\n        if skipLogLevelCheck || self.logLevel.intValue >= logLevel.intValue {\n            logHandler.log(\n                logLevel: logLevel,\n                message: message(),\n                fileID: fileID,\n                line: line,\n                function: function\n            )\n        }\n    }\n}\n\nfileprivate extension AirshipLogLevel {\n    var intValue: Int {\n        switch(self) {\n        case .undefined:\n            -1\n        case .none:\n            0\n        case .error:\n            1\n        case .warn:\n            2\n        case .info:\n            3\n        case .debug:\n            4\n        case .verbose:\n            5\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipBasement/Source/DefaultLogHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport os\n\n/// Default log handler. Logs to either os.Logger or just prints depending on OS version.\n@_spi(AirshipInternal)\npublic final class DefaultLogHandler: AirshipLogHandler {\n    private let privacyLevel: AirshipLogPrivacyLevel\n\n    public init(privacyLevel: AirshipLogPrivacyLevel) {\n        self.privacyLevel = privacyLevel\n    }\n\n    private static let logger: Logger = Logger(\n        subsystem: Bundle.main.bundleIdentifier ?? \"\",\n        category: \"Airship\"\n    )\n\n    public func log(\n        logLevel: AirshipLogLevel,\n        message: String,\n        fileID: String,\n        line: UInt,\n        function: String\n    ) {\n        let logMessage = \"[\\(logLevel.icon)] [\\(logLevel.initial)] \\(fileID) \\(function) [Line \\(line)] \\(message)\"\n        switch self.privacyLevel {\n        case .private:\n            DefaultLogHandler.logger.log(\n                level: logLevel.logType,\n                \"\\(logMessage, privacy: .private)\"\n            )\n        case .public:\n            DefaultLogHandler.logger.notice(\n                \"\\(logMessage, privacy: .public)\"\n            )\n        }\n    }\n}\n\nextension AirshipLogLevel {\n    fileprivate var initial: String {\n        switch self {\n        case .verbose: return \"V\"\n        case .debug: return \"D\"\n        case .info: return \"I\"\n        case .warn: return \"W\"\n        case .error: return \"E\"\n        default: return \"U\"\n        }\n    }\n    \n    var icon: String {\n        switch(self) {\n        case .error: return \"❌\"\n        case .warn: return \"⚠️\"\n        case .info: return \"🔹\"\n        case .debug: return \"🛠️\"\n        case .verbose: return \"📖\"\n        default: return \"\"\n        }\n    }\n\n    fileprivate var logType: OSLogType {\n        switch self {\n        case .verbose: return OSLogType.debug\n        case .debug: return OSLogType.debug\n        case .info: return OSLogType.info\n        case .warn: return OSLogType.default\n        case .error: return OSLogType.error\n        default: return OSLogType.default\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipBasement/Source/LogLevel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents the possible log levels.\npublic enum AirshipLogLevel: String, Sendable, Decodable {\n    /**\n     * Undefined log level.\n     */\n    case undefined\n\n    /**\n     * No log messages.\n     */\n    case none\n\n    /**\n     * Log error messages.\n     *\n     * Used for critical errors, parse exceptions and other situations that cannot be gracefully handled.\n     */\n    case error\n\n    /**\n     * Log warning messages.\n     *\n     * Used for API deprecations, invalid setup and other potentially problematic situations.\n     */\n    case warn\n\n    /**\n     * Log informative messages.\n     *\n     * Used for reporting general SDK status.\n     */\n    case info\n\n    /**\n     * Log debugging messages.\n     *\n     * Used for reporting general SDK status with more detailed information.\n     */\n    case debug\n\n    /**\n     * Log detailed verbose messages.\n     *\n     * Used for reporting highly detailed SDK status that can be useful when debugging and troubleshooting.\n     */\n    case verbose\n    \n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        do {\n            let stringValue = try container.decode(String.self)\n            switch(stringValue.lowercased()) {\n            case \"undefined\":\n                self = .undefined\n            case \"none\":\n                self = .none\n            case \"error\":\n                self = .error\n            case \"warn\":\n                self = .warn\n            case \"info\":\n                self = .info\n            case \"debug\":\n                self = .debug\n            case \"verbose\":\n                self = .verbose\n            default:\n                self = .undefined\n            }\n        } catch {\n            guard let intValue = try? container.decode(Int.self) else {\n                throw error\n            }\n            \n            switch(intValue) {\n            case -1:\n                self = .undefined\n            case 0:\n                self = .none\n            case 1:\n                self = .error\n            case 2:\n                self = .warn\n            case 3:\n                self = .info\n            case 4:\n                self = .debug\n            case 5:\n                self = .verbose\n            default:\n                throw error\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipConfig.xcconfig",
    "content": "//* Copyright Airship and Contributors */\n\nCURRENT_PROJECT_VERSION = 20.6.2\n"
  },
  {
    "path": "Airship/AirshipCore/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/PrivacyInfo.xcprivacy",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSPrivacyCollectedDataTypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSPrivacyCollectedDataType</key>\n\t\t\t<string>NSPrivacyCollectedDataTypeProductInteraction</string>\n\t\t\t<key>NSPrivacyCollectedDataTypeLinked</key>\n\t\t\t<false/>\n\t\t\t<key>NSPrivacyCollectedDataTypeTracking</key>\n\t\t\t<false/>\n\t\t\t<key>NSPrivacyCollectedDataTypePurposes</key>\n\t\t\t<array>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeDeveloperAdvertising</string>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyCollectedDataType</key>\n\t\t\t<string>NSPrivacyCollectedDataTypeOtherDataTypes</string>\n\t\t\t<key>NSPrivacyCollectedDataTypeLinked</key>\n\t\t\t<false/>\n\t\t\t<key>NSPrivacyCollectedDataTypeTracking</key>\n\t\t\t<false/>\n\t\t\t<key>NSPrivacyCollectedDataTypePurposes</key>\n\t\t\t<array>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeAnalytics</string>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeProductPersonalization</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyCollectedDataType</key>\n\t\t\t<string>NSPrivacyCollectedDataTypeUserID</string>\n\t\t\t<key>NSPrivacyCollectedDataTypeLinked</key>\n\t\t\t<false/>\n\t\t\t<key>NSPrivacyCollectedDataTypeTracking</key>\n\t\t\t<false/>\n\t\t\t<key>NSPrivacyCollectedDataTypePurposes</key>\n\t\t\t<array>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeDeveloperAdvertising</string>\n\t\t\t\t<string>NSPrivacyCollectedDataTypePurposeAppFunctionality</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSPrivacyTracking</key>\n\t<false/>\n\t<key>NSPrivacyAccessedAPITypes</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryUserDefaults</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>CA92.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>NSPrivacyAccessedAPIType</key>\n\t\t\t<string>NSPrivacyAccessedAPICategoryFileTimestamp</string>\n\t\t\t<key>NSPrivacyAccessedAPITypeReasons</key>\n\t\t\t<array>\n\t\t\t\t<string>C617.1</string>\n\t\t\t</array>\n\t\t</dict>\n\t</array>\n\t<key>NSPrivacyTrackingDomains</key>\n\t<array/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/UAEvents.xcdatamodeld/UAEvents.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22222\" systemVersion=\"22G120\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAEventData\" representedClassName=\"UAEventData\" syncable=\"YES\">\n        <attribute name=\"bytes\" optional=\"YES\" attributeType=\"Integer 32\" defaultValueString=\"0\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"sessionID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"storeDate\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"time\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UAMeteredUsage.xcdatamodeld/UAMeteredUsage.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22222\" systemVersion=\"22G120\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAMeteredUsageEventData\" representedClassName=\"UAMeteredUsageEventData\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"identifier\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UANativeBridge",
    "content": "if (typeof UAirship === 'undefined') {\n  UAirship = (function() {\n    var urbanAirship = (typeof _UAirship === 'object') ? Object.create(_UAirship) : {}\n\n    var actionCallbacks = {}\n      , callbackID = 0\n\n    function invoke(url) {\n      var f = document.createElement('iframe')\n      f.style.display = 'none'\n      f.src = url\n\n      document.body.appendChild(f)\n      f.parentNode.removeChild(f)\n    }\n\n    urbanAirship.close = function() {\n      invoke('uairship://close')\n    }\n\n    urbanAirship.dismiss = function(resolution) {\n      var encodedResolution = encodeURIComponent(JSON.stringify(resolution))\n      invoke('uairship://dismiss/' + encodedResolution)\n    }\n    \n    urbanAirship.setNamedUser = function(namedUser) {\n      var encodedNamedUser = encodeURIComponent(namedUser)\n      invoke('uairship://named_user?id=' + encodedNamedUser)\n    }\n    \n    urbanAirship.editTags = function() {\n      return TagEditor()\n    }\n\n    function TagEditor() {\n      var actions = []\n      var editor = {}\n\n      editor.addTag = function(tag) {\n        if (tag) {\n          var encodedUrl = encodeURIComponent('uairship://run-basic-actions?add_tags_action=' + encodeURIComponent(tag))\n          actions.push(encodedUrl)\n        }\n        return editor\n      }\n\n      editor.removeTag = function(tag) {\n        if (tag) {\n          var encodedUrl = encodeURIComponent('uairship://run-basic-actions?remove_tags_action=' + encodeURIComponent(tag))\n          actions.push(encodedUrl)\n        }\n        return editor\n      }\n\n      editor.apply = function() {\n        var url = 'uairship://multi?'\n        var i = 0\n        actions.forEach(function(action) {\n          if (i != 0) {\n            url += '&' + action\n          } else {\n            url += action\n          }\n          i++\n        })\n        invoke(url)\n        actions = []\n        return editor\n      }\n\n      return editor\n    }\n\n    urbanAirship.runAction = function(actionName, argument, callback) {\n      var callbackKey = 'ua-cb-' + (++callbackID)\n\n      actionCallbacks[callbackKey] = function(err, data) {\n        if (!callback) {\n          return;\n        }\n\n        if(err) {\n          callback(err)\n        } else {\n          callback(null, data)\n        }\n      }\n\n      var encodedArgument = encodeURIComponent(JSON.stringify(argument))\n      invoke('uairship://run-action-cb/' + actionName + '/' + encodedArgument + '/' + callbackKey)\n    }\n\n    urbanAirship.finishAction = function(err, data, callbackKey) {\n      if(actionCallbacks[callbackKey]) {\n        actionCallbacks[callbackKey](err, data)\n        delete actionCallbacks[callbackKey]\n      }\n    }\n\n    return urbanAirship\n  })()\n\n  // Fire the ready event\n  var uaLibraryReadyEvent = document.createEvent('Event')\n\n  uaLibraryReadyEvent.initEvent('ualibraryready', true, true)\n  document.dispatchEvent(uaLibraryReadyEvent)\n\n  UAirship.isReady = true\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/UANotificationCategories.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>ua_accept_decline_background</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>accept</string>\n            <key>title</key>\n            <string>Accept</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_accept</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>decline</string>\n            <key>title</key>\n            <string>Decline</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_decline</string>\n        </dict>\n    </array>\n    <key>ua_accept_decline_foreground</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>accept</string>\n            <key>title</key>\n            <string>Accept</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_accept</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>decline</string>\n            <key>title</key>\n            <string>Decline</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_decline</string>\n        </dict>\n    </array>\n    <key>ua_buy_now</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>buy_now</string>\n            <key>title</key>\n            <string>Buy Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_buy_now</string>\n        </dict>\n    </array>\n    <key>ua_buy_now_share</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>buy_now</string>\n            <key>title</key>\n            <string>Buy Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_buy_now</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_download</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>download</string>\n            <key>title</key>\n            <string>Download</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_download</string>\n        </dict>\n    </array>\n    <key>ua_download_share</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>download</string>\n            <key>title</key>\n            <string>Download</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_download</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_follow</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>follow</string>\n            <key>title</key>\n            <string>Follow</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_follow</string>\n        </dict>\n    </array>\n    <key>ua_follow_share</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>follow</string>\n            <key>title</key>\n            <string>Follow</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_follow</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_icons_happy_sad</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>happy</string>\n            <key>title</key>\n            <string>😀</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>sad</string>\n            <key>title</key>\n            <string>😞</string>\n        </dict>\n    </array>\n    <key>ua_icons_up_down</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>up</string>\n            <key>title</key>\n            <string>👍</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>down</string>\n            <key>title</key>\n            <string>👎</string>\n        </dict>\n    </array>\n    <key>ua_like</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>like</string>\n            <key>title</key>\n            <string>Like</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_like</string>\n        </dict>\n    </array>\n    <key>ua_like_dislike</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>like</string>\n            <key>title</key>\n            <string>Like</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_like</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>dislike</string>\n            <key>title</key>\n            <string>Dislike</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_dislike</string>\n        </dict>\n    </array>\n    <key>ua_like_share</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>like</string>\n            <key>title</key>\n            <string>Like</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_like</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_more_like_less_like</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>more_like</string>\n            <key>title</key>\n            <string>More Like This</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_more_like</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>less_like</string>\n            <key>title</key>\n            <string>Less Like This</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_less_like</string>\n        </dict>\n    </array>\n    <key>ua_opt_in</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>opt_in</string>\n            <key>title</key>\n            <string>Opt-in</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_opt_in</string>\n        </dict>\n    </array>\n    <key>ua_opt_in_share</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>opt_in</string>\n            <key>title</key>\n            <string>Opt-in</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_opt_in</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_opt_out</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>destructive</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>opt_out</string>\n            <key>title</key>\n            <string>Opt-out</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_opt_out</string>\n        </dict>\n    </array>\n    <key>ua_opt_out_share</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>destructive</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>opt_out</string>\n            <key>title</key>\n            <string>Opt-out</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_opt_out</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_remind_me_later</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>remind</string>\n            <key>title</key>\n            <string>Remind Me Later</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_remind</string>\n        </dict>\n    </array>\n    <key>ua_remind_share</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>remind</string>\n            <key>title</key>\n            <string>Remind Me Later</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_remind</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_share</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_shop_now</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>shop_now</string>\n            <key>title</key>\n            <string>Shop Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_shop_now</string>\n        </dict>\n    </array>\n    <key>ua_shop_now_share</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>shop_now</string>\n            <key>title</key>\n            <string>Shop Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_shop_now</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_unfollow</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>destructive</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>unfollow</string>\n            <key>title</key>\n            <string>Unfollow</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_unfollow</string>\n        </dict>\n    </array>\n    <key>ua_unfollow_share</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>destructive</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>unfollow</string>\n            <key>title</key>\n            <string>Unfollow</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_unfollow</string>\n        </dict>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>share</string>\n            <key>title</key>\n            <string>Share</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_share</string>\n        </dict>\n    </array>\n    <key>ua_yes_no_background</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>yes</string>\n            <key>title</key>\n            <string>Yes</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_yes</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>no</string>\n            <key>title</key>\n            <string>No</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_no</string>\n        </dict>\n    </array>\n    <key>ua_yes_no_foreground</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>yes</string>\n            <key>title</key>\n            <string>Yes</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_yes</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>no</string>\n            <key>title</key>\n            <string>No</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_no</string>\n        </dict>\n    </array>\n    <key>ua_add_calendar_remind</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>add_calendar</string>\n            <key>title</key>\n            <string>Add to Calendar</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_add_to_calendar</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>remind</string>\n            <key>title</key>\n            <string>Remind Me Later</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_remind</string>\n        </dict>\n    </array>\n    <key>ua_add</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>add</string>\n            <key>title</key>\n            <string>Add</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_add</string>\n        </dict>\n    </array>\n    <key>ua_save</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>save</string>\n            <key>title</key>\n            <string>Save</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_save</string>\n        </dict>\n    </array>\n    <key>ua_follow_save</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>follow</string>\n            <key>title</key>\n            <string>Follow</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_follow</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>save</string>\n            <key>title</key>\n            <string>Save</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_save</string>\n        </dict>\n    </array>\n    <key>ua_opt_in_remind</key>\n    <array>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>opt_in</string>\n            <key>title</key>\n            <string>Opt-in</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_opt_in</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>remind</string>\n            <key>title</key>\n            <string>Remind Me Later</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_remind</string>\n        </dict>\n    </array>\n    <key>ua_more_info</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>more_info</string>\n            <key>title</key>\n            <string>Tell Me More</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_tell_me_more</string>\n        </dict>\n    </array>\n    <key>ua_rate</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>rate</string>\n            <key>title</key>\n            <string>Rate Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_rate_now</string>\n        </dict>\n    </array>\n    <key>ua_rate_remind</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>rate</string>\n            <key>title</key>\n            <string>Rate Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_rate_now</string>\n        </dict>\n        <dict>\n            <key>authenticationRequired</key>\n            <true/>\n            <key>foreground</key>\n            <false/>\n            <key>identifier</key>\n            <string>remind</string>\n            <key>title</key>\n            <string>Remind Me Later</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_remind</string>\n        </dict>\n    </array>\n    <key>ua_search</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>search</string>\n            <key>title</key>\n            <string>Search</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_search</string>\n        </dict>\n    </array>\n    <key>ua_book</key>\n    <array>\n        <dict>\n            <key>foreground</key>\n            <true/>\n            <key>identifier</key>\n            <string>book</string>\n            <key>title</key>\n            <string>Book Now</string>\n            <key>title_resource</key>\n            <string>ua_notification_button_book_now</string>\n        </dict>\n    </array>\n</dict>\n</plist>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteData.xcdatamodeld/.xccurrentversion",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>_XCCurrentVersionName</key>\n\t<string>UARemoteData 4.xcdatamodel</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 2.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"15508\" systemVersion=\"18G1012\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UARemoteDataStorePayload\" representedClassName=\"UARemoteDataStorePayload\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAJSONValueTransformer\"/>\n        <attribute name=\"metadata\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"timestamp\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n    <elements>\n        <element name=\"UARemoteDataStorePayload\" positionX=\"0\" positionY=\"0\" width=\"128\" height=\"103\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 3.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"21754\" systemVersion=\"22E261\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UARemoteDataStorePayload\" representedClassName=\"UARemoteDataStorePayload\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAJSONValueTransformer\"/>\n        <attribute name=\"remoteDataInfo\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"timestamp\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 4.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22522\" systemVersion=\"23D56\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UARemoteDataStorePayload\" representedClassName=\"UARemoteDataStorePayload\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"remoteDataInfo\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"timestamp\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"13240\" systemVersion=\"16G29\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UARemoteDataStorePayload\" representedClassName=\"UARemoteDataStorePayload\" syncable=\"YES\">\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAJSONValueTransformer\" syncable=\"YES\"/>\n        <attribute name=\"timestamp\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\" syncable=\"YES\"/>\n        <attribute name=\"type\" optional=\"YES\" attributeType=\"String\" syncable=\"YES\"/>\n    </entity>\n    <elements>\n        <element name=\"UARemoteDataStorePayload\" positionX=\"0\" positionY=\"0\" width=\"0\" height=\"0\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteDataMappingV1toV4.xcmappingmodel/xcmapping.xml",
    "content": "<?xml version=\"1.0\" standalone=\"yes\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n    <databaseInfo>\n        <version>134481920</version>\n        <UUID>FF576309-6C27-47EB-974A-9FA726A81DC1</UUID>\n        <nextObjectID>107</nextObjectID>\n        <metadata>\n            <plist version=\"1.0\">\n                <dict>\n                    <key>NSPersistenceFrameworkVersion</key>\n                    <integer>1344</integer>\n                    <key>NSStoreModelVersionChecksumKey</key>\n                    <string>bMpud663vz0bXQE24C6Rh4MvJ5jVnzsD2sI3njZkKbc=</string>\n                    <key>NSStoreModelVersionHashes</key>\n                    <dict>\n                        <key>XDDevAttributeMapping</key>\n                        <data>\n\t\t0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=\n\t\t</data>\n                        <key>XDDevEntityMapping</key>\n                        <data>\n\t\tqeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=\n\t\t</data>\n                        <key>XDDevMappingModel</key>\n                        <data>\n\t\tEqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=\n\t\t</data>\n                        <key>XDDevPropertyMapping</key>\n                        <data>\n\t\tXN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA=\n\t\t</data>\n                        <key>XDDevRelationshipMapping</key>\n                        <data>\n\t\takYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs=\n\t\t</data>\n                    </dict>\n                    <key>NSStoreModelVersionHashesDigest</key>\n                    <string>+Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A==</string>\n                    <key>NSStoreModelVersionHashesVersion</key>\n                    <integer>3</integer>\n                    <key>NSStoreModelVersionIdentifiers</key>\n                    <array>\n                        <string></string>\n                    </array>\n                </dict>\n            </plist>\n        </metadata>\n    </databaseInfo>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z102\">\n        <attribute name=\"name\" type=\"string\">remoteDataInfo</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z105\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z103\">\n        <attribute name=\"name\" type=\"string\">type</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z105\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z104\">\n        <attribute name=\"name\" type=\"string\">timestamp</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z105\"></relationship>\n    </object>\n    <object type=\"XDDEVENTITYMAPPING\" id=\"z105\">\n        <attribute name=\"migrationpolicyclassname\" type=\"string\">UARemoteDataMappingV1toV4</attribute>\n        <attribute name=\"sourcename\" type=\"string\">UARemoteDataStorePayload</attribute>\n        <attribute name=\"mappingtypename\" type=\"string\">Undefined</attribute>\n        <attribute name=\"mappingnumber\" type=\"int16\">1</attribute>\n        <attribute name=\"destinationname\" type=\"string\">UARemoteDataStorePayload</attribute>\n        <attribute name=\"autogenerateexpression\" type=\"bool\">1</attribute>\n        <relationship name=\"mappingmodel\" type=\"1/1\" destination=\"XDDEVMAPPINGMODEL\" idrefs=\"z107\"></relationship>\n        <relationship name=\"attributemappings\" type=\"0/0\" destination=\"XDDEVATTRIBUTEMAPPING\" idrefs=\"z102 z103 z104 z106\"></relationship>\n        <relationship name=\"relationshipmappings\" type=\"0/0\" destination=\"XDDEVRELATIONSHIPMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z106\">\n        <attribute name=\"name\" type=\"string\">data</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z105\"></relationship>\n    </object>\n    <object type=\"XDDEVMAPPINGMODEL\" id=\"z107\">\n        <attribute name=\"sourcemodelpath\" type=\"string\">AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData.xcdatamodel</attribute>\n        <attribute name=\"sourcemodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCYAAsADAAZADUANgA3AD8AQABbAFwAXQBjAGQAcACGAIcAiACJAIoAiwCMAI0AjgCPAKgAqwCyALgAxwDWANkA6AD3APoAWgEKARkBHQEhATABNgE3AT8BTgFPAVgBYgFjAWQBZQF6AXsBgwGEAYUBkQGlAaYBpwGoAakBqgGrAawBrQG8AcsB2gHeAe0B/AH9AgwCGwIqAjYCSAJJAkoCSwJMAk0CTgJPAl4CbQJ8AosCjAKbAqoCuQLBAtYC1wLfAusC/wMOAx0DLAMwAz8DTgNdA2wDewOHA5kDqAO3A8YD1QPWA+UD9AP1BAQEGQQaBCIELgRCBFEEYARvBHMEggSRBKAErwS+BMoE3ATrBPoFCQUYBRkFKAU3BUYFRwVKBVMFVwVbBV8FZwVqBW4Fb1UkbnVsbNYADQAOAA8AEAARABIAEwAUABUAFgAXABhfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXV94ZF9tb2RlbE5hbWVcX3hkX2NvbW1lbnRzXxAVX2NvbmZpZ3VyYXRpb25zQnlOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoCXgACAlICVgJbeABoAGwAcAB0AHgAfACAADgAhACIAIwAkACUAJgAnACgAKQAJACcAFQAtAC4ALwAwADEAJwAnABVfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASAkoCQgAGABIAAgJGAkxAAgAWAA4AEgASAAFBTWUVT0wA4ADkADgA6ADwAPldOUy5rZXlzWk5TLm9iamVjdHOhADuABqEAPYAHgCVfEBhVQVJlbW90ZURhdGFTdG9yZVBheWxvYWTfEBAAQQBCAEMARAAfAEUARgAhAEcASAAOACMASQBKACYASwBMAE0AJwAnABMAUQBSAC8AJwBMAFUAOwBMAFgAWQBaXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zZHVwbGljYXRlc18QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNzdG9yYWdlW19pc0Fic3RyYWN0gAmALYAEgASAAoAKgI2ABIAJgI+ABoAJgI6ACAgSPs2SdFdvcmRlcmVk0wA4ADkADgBeAGAAPqEAX4ALoQBhgAyAJV5YRF9QU3RlcmVvdHlwZdkAHwAjAGUADgAmAGYAIQBLAGcAPQBfAEwAawAVACcALwBaAG9fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAB4ALgAmALIAAgAQIgA3TADgAOQAOAHEAewA+qQByAHMAdAB1AHYAdwB4AHkAeoAOgA+AEIARgBKAE4AUgBWAFqkAfAB9AH4AfwCAAIEAggCDAISAF4AbgByAHoAfgCGAI4AmgCqAJV8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAZWERQTVVuaXF1ZW5lc3NDb25zdHJhaW50c18QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAZWERfUFNLX2ZldGNoUmVxdWVzdHNBcnJheV8QEVhEX1BTS19pc0Fic3RyYWN0XxAPWERfUFNLX3VzZXJJbmZvXxATWERfUFNLX2NsYXNzTWFwcGluZ18QFlhEX1BTS19lbnRpdHlDbGFzc05hbWXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQCbABUAYQBaAFoAWgAvAFoAogByAFoAWgAVAFpVX3R5cGVYX2RlZmF1bHRcX2Fzc29jaWF0aW9uW19pc1JlYWRPbmx5WV9pc1N0YXRpY1lfaXNVbmlxdWVaX2lzRGVyaXZlZFpfaXNPcmRlcmVkXF9pc0NvbXBvc2l0ZVdfaXNMZWFmgACAGIAAgAwICAgIgBqADggIgAAI0gA5AA4AqQCqoIAZ0gCsAK0ArgCvWiRjbGFzc25hbWVYJGNsYXNzZXNeTlNNdXRhYmxlQXJyYXmjAK4AsACxV05TQXJyYXlYTlNPYmplY3TSAKwArQCzALRfEBBYRFVNTFByb3BlcnR5SW1wpAC1ALYAtwCxXxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAGEAWgBaAFoALwBaAKIAcwBaAFoAFQBagACAAIAAgAwICAgIgBqADwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAyQAVAGEAWgBaAFoALwBaAKIAdABaAFoAFQBagACAHYAAgAwICAgIgBqAEAgIgAAI0gA5AA4A1wCqoIAZ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAGEAWgBaAFoALwBaAKIAdQBaAFoAFQBagACAAIAAgAwICAgIgBqAEQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA6gAVAGEAWgBaAFoALwBaAKIAdgBaAFoAFQBagACAIIAAgAwICAgIgBqAEggIgAAI0gA5AA4A+ACqoIAZ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAGEAWgBaAFoALwBaAKIAdwBaAFoAFQBagACAIoAAgAwICAgIgBqAEwgIgAAICN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAQwAFQBhAFoAWgBaAC8AWgCiAHgAWgBaABUAWoAAgCSAAIAMCAgICIAagBQICIAACNMAOAA5AA4BGgEbAD6goIAl0gCsAK0BHgEfXxATTlNNdXRhYmxlRGljdGlvbmFyeaMBHgEgALFcTlNEaWN0aW9uYXJ53xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBIwAVAGEAWgBaAFoALwBaAKIAeQBaAFoAFQBagACAJ4AAgAwICAgIgBqAFQgIgAAI1gAjAA4AJgBLAB8AIQExATIAFQBaABUAL4AogCmAAAiAAF8QFFhER2VuZXJpY1JlY29yZENsYXNz0gCsAK0BOAE5XVhEVU1MQ2xhc3NJbXCmAToBOwE8AT0BPgCxXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBQQAVAGEAWgBaAFoALwBaAKIAegBaAFoAFQBagACAK4AAgAwICAgIgBqAFggIgAAIXxAYVUFSZW1vdGVEYXRhU3RvcmVQYXlsb2Fk0gCsAK0BUAFRXxASWERVTUxTdGVyZW90eXBlSW1wpwFSAVMBVAFVAVYBVwCxXxASWERVTUxTdGVyZW90eXBlSW1wXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADgFZAV0APqMBWgFbAVyALoAvgDCjAV4BXwFggDGAXIB1gCVUdHlwZVRkYXRhWXRpbWVzdGFtcN8QEgCQAJEAkgFmAB8AlACVAWcAIQCTAWgAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAXAALwBaAEwAWgF0AVoAWgBaAXgAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIAzCIAJCIBbgC4ICIAyCBJkPybt0wA4ADkADgF8AX8APqIBfQF+gDSANaIBgAGBgDaASoAlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAfACMBhgAOACYBhwAhAEsBiAFeAX0ATABrABUAJwAvAFoBkF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAxgDSACYAsgACABAiAN9MAOAA5AA4BkgGbAD6oAZMBlAGVAZYBlwGYAZkBmoA4gDmAOoA7gDyAPYA+gD+oAZwBnQGeAZ8BoAGhAaIBo4BAgEGAQoBEgEWAR4BIgEmAJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFNLX2lzSW5kZXhlZF8QElhEX1BQU0tfaXNPcHRpb25hbF8QGlhEX1BQU0tfaXNTcG90bGlnaHRJbmRleGVkXxARWERfUFBTS19lbGVtZW50SURfEBNYRF9QUFNLX2lzVHJhbnNpZW503xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYAAWgBaAFoALwBaAKIBkwBaAFoAFQBagACAIoAAgDYICAgIgBqAOAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYAAWgBaAFoALwBaAKIBlABaAFoAFQBagACAAIAAgDYICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBzQAVAYAAWgBaAFoALwBaAKIBlQBaAFoAFQBagACAQ4AAgDYICAgIgBqAOggIgAAI0wA4ADkADgHbAdwAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBgABaAFoAWgAvAFoAogGWAFoAWgAVAFqAAIAigACANggICAiAGoA7CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHvABUBgABaAFoAWgAvAFoAogGXAFoAWgAVAFqAAIBGgACANggICAiAGoA8CAiAAAgJ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYAAWgBaAFoALwBaAKIBmABaAFoAFQBagACAIoAAgDYICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYAAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAAIAAgDYICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYAAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAIoAAgDYICAgIgBqAPwgIgAAI2QAfACMCKwAOACYCLAAhAEsCLQFeAX4ATABrABUAJwAvAFoCNV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAxgDWACYAsgACABAiAS9MAOAA5AA4CNwI/AD6nAjgCOQI6AjsCPAI9Aj6ATIBNgE6AT4BQgFGAUqcCQAJBAkICQwJEAkUCRoBTgFSAVYBWgFiAWYBagCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBgQBaAFoAWgAvAFoAogI4AFoAWgAVAFqAAIAAgACASggICAiAGoBMCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBgQBaAFoAWgAvAFoAogI5AFoAWgAVAFqAAIAigACASggICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBgQBaAFoAWgAvAFoAogI6AFoAWgAVAFqAAIAAgACASggICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQJ+ABUBgQBaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIBXgACASggICAiAGoBPCAiAAAgRArzfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBgQBaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAAgACASggICAiAGoBQCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBgQBaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACASggICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBgQBaAFoAWgAvAFoAogI+AFoAWgAVAFqAAIAAgACASggICAiAGoBSCAiAAAjSAKwArQK6ArtdWERQTUF0dHJpYnV0ZaYCvAK9Ar4CvwLAALFdWERQTUF0dHJpYnV0ZVxYRFBNUHJvcGVydHlfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEBIAkACRAJICwgAfAJQAlQLDACEAkwLEAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgLMAC8AWgBMAFoBdAFbAFoAWgLUAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAXgiACQiAW4AvCAiAXQgS2MBJA9MAOAA5AA4C2ALbAD6iAX0BfoA0gDWiAtwC3YBfgGqAJdkAHwAjAuAADgAmAuEAIQBLAuIBXwF9AEwAawAVACcALwBaAupfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAXIA0gAmALIAAgAQIgGDTADgAOQAOAuwC9QA+qAGTAZQBlQGWAZcBmAGZAZqAOIA5gDqAO4A8gD2APoA/qAL2AvcC+AL5AvoC+wL8Av2AYYBigGOAZYBmgGeAaIBpgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC3ABaAFoAWgAvAFoAogGTAFoAWgAVAFqAAIAigACAXwgICAiAGoA4CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC3ABaAFoAWgAvAFoAogGUAFoAWgAVAFqAAIAAgACAXwgICAiAGoA5CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQMfABUC3ABaAFoAWgAvAFoAogGVAFoAWgAVAFqAAIBkgACAXwgICAiAGoA6CAiAAAjTADgAOQAOAy0DLgA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLcAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIBfCAgICIAagDsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAe8AFQLcAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgEaAAIBfCAgICIAagDwICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLcAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgCKAAIBfCAgICIAagD0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLcAFoAWgBaAC8AWgCiAZkAWgBaABUAWoAAgACAAIBfCAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLcAFoAWgBaAC8AWgCiAZoAWgBaABUAWoAAgCKAAIBfCAgICIAagD8ICIAACNkAHwAjA3wADgAmA30AIQBLA34BXwF+AEwAawAVACcALwBaA4ZfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAXIA1gAmALIAAgAQIgGvTADgAOQAOA4gDkAA+pwI4AjkCOgI7AjwCPQI+gEyATYBOgE+AUIBRgFKnA5EDkgOTA5QDlQOWA5eAbIBtgG6Ab4BxgHKAdIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAt0AWgBaAFoALwBaAKICOABaAFoAFQBagACAAIAAgGoICAgIgBqATAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAt0AWgBaAFoALwBaAKICOQBaAFoAFQBagACAIoAAgGoICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAt0AWgBaAFoALwBaAKICOgBaAFoAFQBagACAAIAAgGoICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDyAAVAt0AWgBaAFoALwBaAKICOwBaAFoAFQBagACAcIAAgGoICAgIgBqATwgIgAAIEQcI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAt0AWgBaAFoALwBaAKICPABaAFoAFQBagACAAIAAgGoICAgIgBqAUAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUD5wAVAt0AWgBaAFoALwBaAKICPQBaAFoAFQBagACAc4AAgGoICAgIgBqAUQgIgAAIXxAWVUFKU09OVmFsdWVUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLdAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgACAAIBqCAgICIAagFIICIAACN8QEgCQAJEAkgQFAB8AlACVBAYAIQCTBAcAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBA8ALwBaAEwAWgF0AVwAWgBaBBcAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIB3CIAJCIBbgDAICIB2CBKeIf/f0wA4ADkADgQbBB4APqIBfQF+gDSANaIEHwQggHiAg4Al2QAfACMEIwAOACYEJAAhAEsEJQFgAX0ATABrABUAJwAvAFoELV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB1gDSACYAsgACABAiAedMAOAA5AA4ELwQ4AD6oAZMBlAGVAZYBlwGYAZkBmoA4gDmAOoA7gDyAPYA+gD+oBDkEOgQ7BDwEPQQ+BD8EQIB6gHuAfIB+gH+AgICBgIKAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQfAFoAWgBaAC8AWgCiAZMAWgBaABUAWoAAgCKAAIB4CAgICIAagDgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQfAFoAWgBaAC8AWgCiAZQAWgBaABUAWoAAgACAAIB4CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBGIAFQQfAFoAWgBaAC8AWgCiAZUAWgBaABUAWoAAgH2AAIB4CAgICIAagDoICIAACNMAOAA5AA4EcARxAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBB8AWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgHgICAgIgBqAOwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB7wAVBB8AWgBaAFoALwBaAKIBlwBaAFoAFQBagACARoAAgHgICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBB8AWgBaAFoALwBaAKIBmABaAFoAFQBagACAIoAAgHgICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBB8AWgBaAFoALwBaAKIBmQBaAFoAFQBagACAAIAAgHgICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBB8AWgBaAFoALwBaAKIBmgBaAFoAFQBagACAIoAAgHgICAgIgBqAPwgIgAAI2QAfACMEvwAOACYEwAAhAEsEwQFgAX4ATABrABUAJwAvAFoEyV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB1gDWACYAsgACABAiAhNMAOAA5AA4EywTTAD6nAjgCOQI6AjsCPAI9Aj6ATIBNgE6AT4BQgFGAUqcE1ATVBNYE1wTYBNkE2oCFgIaAh4CIgIqAi4CMgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIABaAFoAWgAvAFoAogI4AFoAWgAVAFqAAIAAgACAgwgICAiAGoBMCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIABaAFoAWgAvAFoAogI5AFoAWgAVAFqAAIAigACAgwgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIABaAFoAWgAvAFoAogI6AFoAWgAVAFqAAIAAgACAgwgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQULABUEIABaAFoAWgAvAFoAogI7AFoAWgAVAFqAAICJgACAgwgICAiAGoBPCAiAAAgRA4TfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIABaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAAgACAgwgICAiAGoBQCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIABaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACAgwgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIABaAFoAWgAvAFoAogI+AFoAWgAVAFqAAIAAgACAgwgICAiAGoBSCAiAAAhaZHVwbGljYXRlc9IAOQAOBUgAqqCAGdIArACtBUsFTFpYRFBNRW50aXR5pwVNBU4FTwVQBVEFUgCxWlhEUE1FbnRpdHldWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOBVQFVQA+oKCAJdMAOAA5AA4FWAVZAD6goIAl0wA4ADkADgVcBV0APqCggCXSAKwArQVgBWFeWERNb2RlbFBhY2thZ2WmBWIFYwVkBWUFZgCxXlhETW9kZWxQYWNrYWdlXxAPWERVTUxQYWNrYWdlSW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNIAOQAOBWgAqqCAGdMAOAA5AA4FawVsAD6goIAlUNIArACtBXAFcVlYRFBNTW9kZWyjBXAFcgCxV1hETW9kZWwACAAZACIALAAxADoAPwBRAFYAWwBdAZABlgGvAcEByAHWAeMB+wIVAhcCGQIbAh0CHwIhAloCeQKWArUCxwLnAu4DDAMYAzQDOgNcA30DkAOSA5QDlgOYA5oDnAOeA6ADogOkA6YDqAOqA6wDrQOxA74DxgPRA9QD1gPZA9sD3QP4BDsEXwSDBKYEzQTtBRQFOwVbBX8FowWvBbEFswW1BbcFuQW7Bb0FvwXBBcMFxQXHBckFywXMBdEF2QXmBekF6wXuBfAF8gYBBiYGSgZxBpUGlwaZBpsGnQafBqEGogakBrEGxAbGBsgGygbMBs4G0AbSBtQG1gbpBusG7QbvBvEG8wb1BvcG+Qb7Bv0HEwcmB0IHXwd7B48HoQe3B9AIDwgVCB4IKwg3CEEISwhWCGEIbgh2CHgIegh8CH4IfwiACIEIggiECIYIhwiICIoIiwiUCJUIlwigCKsItAjDCMoI0gjbCOQI9wkACRMJKgk8CXsJfQl/CYEJgwmECYUJhgmHCYkJiwmMCY0JjwmQCc8J0QnTCdUJ1wnYCdkJ2gnbCd0J3wngCeEJ4wnkCe0J7gnwCi8KMQozCjUKNwo4CjkKOgo7Cj0KPwpACkEKQwpECoMKhQqHCokKiwqMCo0KjgqPCpEKkwqUCpUKlwqYCqEKogqkCuMK5QrnCukK6wrsCu0K7grvCvEK8wr0CvUK9wr4CvkLOAs6CzwLPgtAC0ELQgtDC0QLRgtIC0kLSgtMC00LWgtbC1wLXgtnC30LhAuRC9AL0gvUC9YL2AvZC9oL2wvcC94L4AvhC+IL5AvlC/4MAAwCDAQMBQwHDB4MJww1DEIMUAxlDHkMkAyiDOEM4wzlDOcM6QzqDOsM7AztDO8M8QzyDPMM9Qz2DRENGg0vDT4NUw1hDXYNig2hDbMNwA3HDckNyw3NDdQN1g3YDdoN3A3hDeYN8A47Dl4Ofg6eDqAOog6kDqYOqA6pDqoOrA6tDq8OsA6yDrQOtQ62DrgOuQ6+DssO0A7SDtQO2Q7bDt0O3w70DwkPLg9SD3kPnQ+fD6EPow+lD6cPqQ+qD6wPuQ/KD8wPzg/QD9IP1A/WD9gP2g/rD+0P7w/xD/MP9Q/3D/kP+w/9EBsQORBMEGAQdRCSEKYQvBD7EP0Q/xEBEQMRBBEFEQYRBxEJEQsRDBENEQ8REBFPEVERUxFVEVcRWBFZEVoRWxFdEV8RYBFhEWMRZBGjEaURpxGpEasRrBGtEa4RrxGxEbMRtBG1EbcRuBHFEcYRxxHJEggSChIMEg4SEBIREhISExIUEhYSGBIZEhoSHBIdElwSXhJgEmISZBJlEmYSZxJoEmoSbBJtEm4ScBJxEnISsRKzErUStxK5EroSuxK8Er0SvxLBEsISwxLFEsYTBRMHEwkTCxMNEw4TDxMQExETExMVExYTFxMZExoTWRNbE10TXxNhE2ITYxNkE2UTZxNpE2oTaxNtE24TkxO3E94UAhQEFAYUCBQKFAwUDhQPFBEUHhQtFC8UMRQzFDUUNxQ5FDsUShRMFE4UUBRSFFQUVhRYFFoUehSlFL8U2BTyFRIVNRV0FXYVeBV6FXwVfRV+FX8VgBWCFYQVhRWGFYgViRXIFcoVzBXOFdAV0RXSFdMV1BXWFdgV2RXaFdwV3RYcFh4WIBYiFiQWJRYmFicWKBYqFiwWLRYuFjAWMRZwFnIWdBZ2FngWeRZ6FnsWfBZ+FoAWgRaCFoQWhRaIFscWyRbLFs0WzxbQFtEW0hbTFtUW1xbYFtkW2xbcFxsXHRcfFyEXIxckFyUXJhcnFykXKxcsFy0XLxcwF28XcRdzF3UXdxd4F3kXehd7F30XfxeAF4EXgxeEF40XmxeoF7YXwxfWF+0X/xhKGG0YjRitGK8YsRizGLUYtxi4GLkYuxi8GL4YvxjBGMMYxBjFGMcYyBjNGNoY3xjhGOMY6BjqGOwY7hkTGTcZXhmCGYQZhhmIGYoZjBmOGY8ZkRmeGa8ZsRmzGbUZtxm5GbsZvRm/GdAZ0hnUGdYZ2BnaGdwZ3hngGeIaIRojGiUaJxopGioaKxosGi0aLxoxGjIaMxo1GjYadRp3Gnkaexp9Gn4afxqAGoEagxqFGoYahxqJGooayRrLGs0azxrRGtIa0xrUGtUa1xrZGtoa2xrdGt4a6xrsGu0a7xsuGzAbMhs0GzYbNxs4GzkbOhs8Gz4bPxtAG0IbQxuCG4QbhhuIG4obixuMG40bjhuQG5IbkxuUG5YblxvWG9gb2hvcG94b3xvgG+Eb4hvkG+Yb5xvoG+ob6xwqHCwcLhwwHDIcMxw0HDUcNhw4HDocOxw8HD4cPxx+HIAcghyEHIYchxyIHIkcihyMHI4cjxyQHJIckxy4HNwdAx0nHSkdKx0tHS8dMR0zHTQdNh1DHVIdVB1WHVgdWh1cHV4dYB1vHXEdcx11HXcdeR17HX0dfx2+HcAdwh3EHcYdxx3IHckdyh3MHc4dzx3QHdId0x4SHhQeFh4YHhoeGx4cHh0eHh4gHiIeIx4kHiYeJx5mHmgeah5sHm4ebx5wHnEech50HnYedx54Hnoeex66Hrwevh7AHsIewx7EHsUexh7IHsoeyx7MHs4ezx7SHxEfEx8VHxcfGR8aHxsfHB8dHx8fIR8iHyMfJR8mH2UfZx9pH2sfbR9uH28fcB9xH3MfdR92H3cfeR96H5Mf0h/UH9Yf2B/aH9sf3B/dH94f4B/iH+Mf5B/mH+cgMiBVIHUglSCXIJkgmyCdIJ8goCChIKMgpCCmIKcgqSCrIKwgrSCvILAgtSDCIMcgySDLINAg0iDUINYg+yEfIUYhaiFsIW4hcCFyIXQhdiF3IXkhhiGXIZkhmyGdIZ8hoSGjIaUhpyG4IbohvCG+IcAhwiHEIcYhyCHKIgkiCyINIg8iESISIhMiFCIVIhciGSIaIhsiHSIeIl0iXyJhImMiZSJmImciaCJpImsibSJuIm8icSJyIrEisyK1IrciuSK6IrsivCK9Ir8iwSLCIsMixSLGItMi1CLVItcjFiMYIxojHCMeIx8jICMhIyIjJCMmIycjKCMqIysjaiNsI24jcCNyI3MjdCN1I3YjeCN6I3sjfCN+I38jviPAI8IjxCPGI8cjyCPJI8ojzCPOI88j0CPSI9MkEiQUJBYkGCQaJBskHCQdJB4kICQiJCMkJCQmJCckZiRoJGokbCRuJG8kcCRxJHIkdCR2JHckeCR6JHskoCTEJOslDyURJRMlFSUXJRklGyUcJR4lKyU6JTwlPiVAJUIlRCVGJUglVyVZJVslXSVfJWElYyVlJWclpiWoJaolrCWuJa8lsCWxJbIltCW2JbcluCW6Jbsl+iX8Jf4mACYCJgMmBCYFJgYmCCYKJgsmDCYOJg8mTiZQJlImVCZWJlcmWCZZJlomXCZeJl8mYCZiJmMmoiakJqYmqCaqJqsmrCatJq4msCayJrMmtCa2Jrcmuib5Jvsm/Sb/JwEnAicDJwQnBScHJwknCicLJw0nDidNJ08nUSdTJ1UnVidXJ1gnWSdbJ10nXidfJ2EnYiehJ6MnpSenJ6knqierJ6wnrSevJ7EnsiezJ7UntifBJ8onyyfNJ9Yn4SfwJ/soCSgeKDIoSShbKGgoaShqKGwoeSh6KHsofSiKKIsojCiOKJcopiizKMIo1CjoKP8pESkaKRspHSkqKSspLCkuKS8pOClCKUkAAAAAAAACAgAAAAAAAAVzAAAAAAAAAAAAAAAAAAApUQ==\n</attribute>\n        <attribute name=\"destinationmodelpath\" type=\"string\">AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 4.xcdatamodel</attribute>\n        <attribute name=\"destinationmodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCxAAsADAAZADUANgA3AD8AQABbAFwAXQBjAGQAcACGAIcAiACJAIoAiwCMAI0AjgCPAKgAqwCyALgAxwDWANkA6AD3APoAWgEKARkBHQEhATABNgE3AT8BTgFPAVgBZAFlAWYBZwFoAX0BfgGGAYcBiAGUAagBqQGqAasBrAGtAa4BrwGwAb8BzgHdAeEB8AH/AgACDwIeAi0COQJLAkwCTQJOAk8CUAJRAlICYQJwAn8CjgKPAp4CrQKuAr0CxQLaAtsC4wLvAwMDEgMhAzADNANDA1IDYQNwA38DiwOdA6wDuwPKA9kD2gPpA/gEBwQcBB0EJQQxBEUEVARjBHIEdgSFBJQEowSyBMEEzQTfBO4E/QUMBRsFHAUrBToFSQVeBV8FZwVzBYcFlgWlBbQFuAXHBdYF5QX0BgMGDwYhBjAGPwZOBl0GbAZ7BnwGiwaMBo8GmAacBqAGpAasBq8Gswa0VSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgLCAAICtgK6Ar94AGgAbABwAHQAeAB8AIAAOACEAIgAjACQAJQAmACcAKAApAAkAJwAVAC0ALgAvADAAMQAnACcAFV8QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABICrgKmAAYAEgACAqoCsEACABYADgASABIAAUFNZRVPTADgAOQAOADoAPAA+V05TLmtleXNaTlMub2JqZWN0c6EAO4AGoQA9gAeAJV8QGFVBUmVtb3RlRGF0YVN0b3JlUGF5bG9hZN8QEABBAEIAQwBEAB8ARQBGACEARwBIAA4AIwBJAEoAJgBLAEwATQAnACcAEwBRAFIALwAnAEwAVQA7AEwAWABZAFpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAtgASABIACgAqApoAEgAmAqIAGgAmAp4AICBLXSp5XV29yZGVyZWTTADgAOQAOAF4AYAA+oQBfgAuhAGGADIAlXlhEX1BTdGVyZW90eXBl2QAfACMAZQAOACYAZgAhAEsAZwA9AF8ATABrABUAJwAvAFoAb18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAsgACABAiADdMAOAA5AA4AcQB7AD6pAHIAcwB0AHUAdgB3AHgAeQB6gA6AD4AQgBGAEoATgBSAFYAWqQB8AH0AfgB/AIAAgQCCAIMAhIAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAJsAFQBhAFoAWgBaAC8AWgCiAHIAWgBaABUAWlVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADkADgCpAKqggBnSAKwArQCuAK9aJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMArgCwALFXTlNBcnJheVhOU09iamVjdNIArACtALMAtF8QEFhEVU1MUHJvcGVydHlJbXCkALUAtgC3ALFfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogBzAFoAWgAVAFqAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDJABUAYQBaAFoAWgAvAFoAogB0AFoAWgAVAFqAAIAdgACADAgICAiAGoAQCAiAAAjSADkADgDXAKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogB1AFoAWgAVAFqAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDqABUAYQBaAFoAWgAvAFoAogB2AFoAWgAVAFqAAIAggACADAgICAiAGoASCAiAAAjSADkADgD4AKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUAYQBaAFoAWgAvAFoAogB3AFoAWgAVAFqAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBDAAVAGEAWgBaAFoALwBaAKIAeABaAFoAFQBagACAJIAAgAwICAgIgBqAFAgIgAAI0wA4ADkADgEaARsAPqCggCXSAKwArQEeAR9fEBNOU011dGFibGVEaWN0aW9uYXJ5owEeASAAsVxOU0RpY3Rpb25hcnnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEjABUAYQBaAFoAWgAvAFoAogB5AFoAWgAVAFqAAIAngACADAgICAiAGoAVCAiAAAjWACMADgAmAEsAHwAhATEBMgAVAFoAFQAvgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKwArQE4ATldWERVTUxDbGFzc0ltcKYBOgE7ATwBPQE+ALFdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQFBABUAYQBaAFoAWgAvAFoAogB6AFoAWgAVAFqAAIArgACADAgICAiAGoAWCAiAAAhfEBhVQVJlbW90ZURhdGFTdG9yZVBheWxvYWTSAKwArQFQAVFfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVIBUwFUAVUBVgFXALFfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOAVkBXgA+pAFaAVsBXAFdgC6AL4AwgDGkAV8BYAFhAWKAMoBegHaAjoAlVGRhdGFZdGltZXN0YW1wVHR5cGVecmVtb3RlRGF0YUluZm/fEBIAkACRAJIBaQAfAJQAlQFqACEAkwFrAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgFzAC8AWgBMAFoBdwFaAFoAWgF7AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiANAiACQiAXYAuCAiAMwgS0KMGmNMAOAA5AA4BfwGCAD6iAYABgYA1gDaiAYMBhIA3gEuAJV8QElhEX1BQcm9wU3RlcmVvdHlwZV8QElhEX1BBdHRfU3RlcmVvdHlwZdkAHwAjAYkADgAmAYoAIQBLAYsBXwGAAEwAawAVACcALwBaAZNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA1gAmALIAAgAQIgDjTADgAOQAOAZUBngA+qAGWAZcBmAGZAZoBmwGcAZ2AOYA6gDuAPIA9gD6AP4BAqAGfAaABoQGiAaMBpAGlAaaAQYBCgEOARYBGgEiASYBKgCVfEBtYRF9QUFNLX2lzU3RvcmVkSW5UcnV0aEZpbGVfEBtYRF9QUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBBYRF9QUFNLX3VzZXJJbmZvXxARWERfUFBTS19pc0luZGV4ZWRfEBJYRF9QUFNLX2lzT3B0aW9uYWxfEBpYRF9QUFNLX2lzU3BvdGxpZ2h0SW5kZXhlZF8QEVhEX1BQU0tfZWxlbWVudElEXxATWERfUFBTS19pc1RyYW5zaWVudN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIA3CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIA3CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAdAAFQGDAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgESAAIA3CAgICIAagDsICIAACNMAOAA5AA4B3gHfAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgDcICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVAYMAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgDcICAgIgBqAPQgIgAAICd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZsAWgBaABUAWoAAgCKAAIA3CAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZwAWgBaABUAWoAAgACAAIA3CAgICIAagD8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZ0AWgBaABUAWoAAgCKAAIA3CAgICIAagEAICIAACNkAHwAjAi4ADgAmAi8AIQBLAjABXwGBAEwAawAVACcALwBaAjhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA2gAmALIAAgAQIgEzTADgAOQAOAjoCQgA+pwI7AjwCPQI+Aj8CQAJBgE2AToBPgFCAUYBSgFOnAkMCRAJFAkYCRwJIAkmAVIBVgFaAV4BZgFqAXIAlXxAdWERfUEF0dEtfZGVmYXVsdFZhbHVlQXNTdHJpbmdfEChYRF9QQXR0S19hbGxvd3NFeHRlcm5hbEJpbmFyeURhdGFTdG9yYWdlXxAXWERfUEF0dEtfbWluVmFsdWVTdHJpbmdfEBZYRF9QQXR0S19hdHRyaWJ1dGVUeXBlXxAXWERfUEF0dEtfbWF4VmFsdWVTdHJpbmdfEB1YRF9QQXR0S192YWx1ZVRyYW5zZm9ybWVyTmFtZV8QIFhEX1BBdHRLX3JlZ3VsYXJFeHByZXNzaW9uU3RyaW5n3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICOwBaAFoAFQBagACAAIAAgEsICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYQAWgBaAFoALwBaAKICPABaAFoAFQBagACAIoAAgEsICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPQBaAFoAFQBagACAAIAAgEsICAgIgBqATwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCgQAVAYQAWgBaAFoALwBaAKICPgBaAFoAFQBagACAWIAAgEsICAgIgBqAUAgIgAAIEQPo3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPwBaAFoAFQBagACAAIAAgEsICAgIgBqAUQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCoAAVAYQAWgBaAFoALwBaAKICQABaAFoAFQBagACAW4AAgEsICAgIgBqAUggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgEsICAgIgBqAUwgIgAAI0gCsAK0CvgK/XVhEUE1BdHRyaWJ1dGWmAsACwQLCAsMCxACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAsYAHwCUAJUCxwAhAJMCyACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC0AAvAFoATABaAXcBWwBaAFoC2ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGAIgAkIgF2ALwgIgF8IEuJjXEjTADgAOQAOAtwC3wA+ogGAAYGANYA2ogLgAuGAYYBsgCXZAB8AIwLkAA4AJgLlACEASwLmAWABgABMAGsAFQAnAC8AWgLuXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANYAJgCyAAIAECIBi0wA4ADkADgLwAvkAPqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgC+gL7AvwC/QL+Av8DAAMBgGOAZIBlgGeAaIBpgGqAa4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgGEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAuAAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgGEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDIwAVAuAAWgBaAFoALwBaAKIBmABaAFoAFQBagACAZoAAgGEICAgIgBqAOwgIgAAI0wA4ADkADgMxAzIAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAYQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUC4ABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAYQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAYQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4ABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAYQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAYQgICAiAGoBACAiAAAjZAB8AIwOAAA4AJgOBACEASwOCAWABgQBMAGsAFQAnAC8AWgOKXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANoAJgCyAAIAECIBt0wA4ADkADgOMA5QAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwOVA5YDlwOYA5kDmgObgG6Ab4BwgHGAc4B0gHWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAIBsCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLhAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAIBsCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAIBsCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA8wAFQLhAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgHKAAIBsCAgICIAagFAICIAACBEDhN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAIBsCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgACAAIBsCAgICIAagFIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAIBsCAgICIAagFMICIAACN8QEgCQAJEAkgQIAB8AlACVBAkAIQCTBAoAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBBIALwBaAEwAWgF3AVwAWgBaBBoAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIB4CIAJCIBdgDAICIB3CBJpFGYe0wA4ADkADgQeBCEAPqIBgAGBgDWANqIEIgQjgHmAhIAl2QAfACMEJgAOACYEJwAhAEsEKAFhAYAATABrABUAJwAvAFoEMF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB2gDWACYAsgACABAiAetMAOAA5AA4EMgQ7AD6oAZYBlwGYAZkBmgGbAZwBnYA5gDqAO4A8gD2APoA/gECoBDwEPQQ+BD8EQARBBEIEQ4B7gHyAfYB/gICAgYCCgIOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQiAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIB5CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQiAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIB5CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBGUAFQQiAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgH6AAIB5CAgICIAagDsICIAACNMAOAA5AA4EcwR0AD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgHkICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVBCIAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgHkICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBmwBaAFoAFQBagACAIoAAgHkICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCIAWgBaAFoALwBaAKIBnABaAFoAFQBagACAAIAAgHkICAgIgBqAPwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBnQBaAFoAFQBagACAIoAAgHkICAgIgBqAQAgIgAAI2QAfACMEwgAOACYEwwAhAEsExAFhAYEATABrABUAJwAvAFoEzF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB2gDaACYAsgACABAiAhdMAOAA5AA4EzgTWAD6nAjsCPAI9Aj4CPwJAAkGATYBOgE+AUIBRgFKAU6cE1wTYBNkE2gTbBNwE3YCGgIeAiICJgIuAjICNgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIAAgACAhAgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIwBaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAigACAhAgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACAhAgICAiAGoBPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQUOABUEIwBaAFoAWgAvAFoAogI+AFoAWgAVAFqAAICKgACAhAgICAiAGoBQCAiAAAgRArzfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI/AFoAWgAVAFqAAIAAgACAhAgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogJAAFoAWgAVAFqAAIAAgACAhAgICAiAGoBSCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogJBAFoAWgAVAFqAAIAAgACAhAgICAiAGoBTCAiAAAjfEBIAkACRAJIFSgAfAJQAlQVLACEAkwVMAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgVUAC8AWgBMAFoBdwFdAFoAWgVcAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAkAiACQiAXYAxCAiAjwgTAAAAARwpHGPTADgAOQAOBWAFYwA+ogGAAYGANYA2ogVkBWWAkYCcgCXZAB8AIwVoAA4AJgVpACEASwVqAWIBgABMAGsAFQAnAC8AWgVyXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgI6ANYAJgCyAAIAECICS0wA4ADkADgV0BX0APqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgFfgV/BYAFgQWCBYMFhAWFgJOAlICVgJeAmICZgJqAm4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWQAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgJEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWQAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgJEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFpwAVBWQAWgBaAFoALwBaAKIBmABaAFoAFQBagACAloAAgJEICAgIgBqAOwgIgAAI0wA4ADkADgW1BbYAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAkQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUFZABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAkQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAkQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAkQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAkQgICAiAGoBACAiAAAjZAB8AIwYEAA4AJgYFACEASwYGAWIBgQBMAGsAFQAnAC8AWgYOXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgI6ANoAJgCyAAIAECICd0wA4ADkADgYQBhgAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwYZBhoGGwYcBh0GHgYfgJ6An4CggKGAooCjgKWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAICcCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVlAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAICcCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAICcCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAoEAFQVlAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgFiAAICcCAgICIAagFAICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAICcCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBm4AFQVlAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgKSAAICcCAgICIAagFIICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAICcCAgICIAagFMICIAACFpkdXBsaWNhdGVz0gA5AA4GjQCqoIAZ0gCsAK0GkAaRWlhEUE1FbnRpdHmnBpIGkwaUBpUGlgaXALFaWERQTUVudGl0eV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4GmQaaAD6goIAl0wA4ADkADgadBp4APqCggCXTADgAOQAOBqEGogA+oKCAJdIArACtBqUGpl5YRE1vZGVsUGFja2FnZaYGpwaoBqkGqgarALFeWERNb2RlbFBhY2thZ2VfEA9YRFVNTFBhY2thZ2VJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0gA5AA4GrQCqoIAZ0wA4ADkADgawBrEAPqCggCVQ0gCsAK0GtQa2WVhEUE1Nb2RlbKMGtQa3ALFXWERNb2RlbAAIABkAIgAsADEAOgA/AFEAVgBbAF0BwgHIAeEB8wH6AggCFQItAkcCSQJLAk0CTwJRAlMCjAKrAsgC5wL5AxkDIAM+A0oDZgNsA44DrwPCA8QDxgPIA8oDzAPOA9AD0gPUA9YD2APaA9wD3gPfA+MD8AP4BAMEBgQIBAsEDQQPBCoEbQSRBLUE2AT/BR8FRgVtBY0FsQXVBeEF4wXlBecF6QXrBe0F7wXxBfMF9QX3BfkF+wX9Bf4GAwYLBhgGGwYdBiAGIgYkBjMGWAZ8BqMGxwbJBssGzQbPBtEG0wbUBtYG4wb2BvgG+gb8Bv4HAAcCBwQHBgcIBxsHHQcfByEHIwclBycHKQcrBy0HLwdFB1gHdAeRB60HwQfTB+kIAghBCEcIUAhdCGkIcwh9CIgIkwigCKgIqgisCK4IsAixCLIIswi0CLYIuAi5CLoIvAi9CMYIxwjJCNII3QjmCPUI/AkECQ0JFgkpCTIJRQlcCW4JrQmvCbEJswm1CbYJtwm4CbkJuwm9Cb4JvwnBCcIKAQoDCgUKBwoJCgoKCwoMCg0KDwoRChIKEwoVChYKHwogCiIKYQpjCmUKZwppCmoKawpsCm0KbwpxCnIKcwp1CnYKtQq3CrkKuwq9Cr4KvwrACsEKwwrFCsYKxwrJCsoK0wrUCtYLFQsXCxkLGwsdCx4LHwsgCyELIwslCyYLJwspCyoLKwtqC2wLbgtwC3ILcwt0C3ULdgt4C3oLewt8C34LfwuMC40LjguQC5kLrwu2C8MMAgwEDAYMCAwKDAsMDAwNDA4MEAwSDBMMFAwWDBcMMAwyDDQMNgw3DDkMUAxZDGcMdAyCDJcMqwzCDNQNEw0VDRcNGQ0bDRwNHQ0eDR8NIQ0jDSQNJQ0nDSgNQw1MDWENcA2FDZMNqA28DdMN5Q3yDfsN/Q3/DgEOAw4MDg4OEA4SDhQOFg4bDiUOKg45DoQOpw7HDucO6Q7rDu0O7w7xDvIO8w71DvYO+A75DvsO/Q7+Dv8PAQ8CDwcPFA8ZDxsPHQ8iDyQPJg8oDz0PUg93D5sPwg/mD+gP6g/sD+4P8A/yD/MP9RACEBMQFRAXEBkQGxAdEB8QIRAjEDQQNhA4EDoQPBA+EEAQQhBEEEYQZBCCEJUQqRC+ENsQ7xEFEUQRRhFIEUoRTBFNEU4RTxFQEVIRVBFVEVYRWBFZEZgRmhGcEZ4RoBGhEaIRoxGkEaYRqBGpEaoRrBGtEewR7hHwEfIR9BH1EfYR9xH4EfoR/BH9Ef4SABIBEg4SDxIQEhISURJTElUSVxJZEloSWxJcEl0SXxJhEmISYxJlEmYSpRKnEqkSqxKtEq4SrxKwErESsxK1ErYStxK5EroSuxL6EvwS/hMAEwITAxMEEwUTBhMIEwoTCxMMEw4TDxNOE1ATUhNUE1YTVxNYE1kTWhNcE14TXxNgE2ITYxOiE6QTphOoE6oTqxOsE60TrhOwE7ITsxO0E7YTtxPcFAAUJxRLFE0UTxRRFFMUVRRXFFgUWhRnFHYUeBR6FHwUfhSAFIIUhBSTFJUUlxSZFJsUnRSfFKEUoxTDFO4VCBUhFTsVWxV+Fb0VvxXBFcMVxRXGFccVyBXJFcsVzRXOFc8V0RXSFhEWExYVFhcWGRYaFhsWHBYdFh8WIRYiFiMWJRYmFmUWZxZpFmsWbRZuFm8WcBZxFnMWdRZ2FncWeRZ6FrkWuxa9Fr8WwRbCFsMWxBbFFscWyRbKFssWzRbOFtEXEBcSFxQXFhcYFxkXGhcbFxwXHhcgFyEXIhckFyUXZBdmF2gXahdsF20XbhdvF3AXchd0F3UXdhd4F3kXoBffF+EX4xflF+cX6BfpF+oX6xftF+8X8BfxF/MX9Bf9GAsYGBgmGDMYRhhdGG8YuhjdGP0ZHRkfGSEZIxklGScZKBkpGSsZLBkuGS8ZMRkzGTQZNRk3GTgZPRlKGU8ZURlTGVgZWhlcGV4ZgxmnGc4Z8hn0GfYZ+Bn6GfwZ/hn/GgEaDhofGiEaIxolGicaKRorGi0aLxpAGkIaRBpGGkgaShpMGk4aUBpSGpEakxqVGpcamRqaGpsanBqdGp8aoRqiGqMapRqmGuUa5xrpGusa7RruGu8a8BrxGvMa9Rr2Gvca+Rr6GzkbOxs9Gz8bQRtCG0MbRBtFG0cbSRtKG0sbTRtOG1sbXBtdG18bnhugG6IbpBumG6cbqBupG6obrBuuG68bsBuyG7Mb8hv0G/Yb+Bv6G/sb/Bv9G/4cABwCHAMcBBwGHAccRhxIHEocTBxOHE8cUBxRHFIcVBxWHFccWBxaHFscmhycHJ4coByiHKMcpBylHKYcqByqHKscrByuHK8c7hzwHPIc9Bz2HPcc+Bz5HPoc/Bz+HP8dAB0CHQMdKB1MHXMdlx2ZHZsdnR2fHaEdox2kHaYdsx3CHcQdxh3IHcodzB3OHdAd3x3hHeMd5R3nHekd6x3tHe8eLh4wHjIeNB42HjceOB45HjoePB4+Hj8eQB5CHkMegh6EHoYeiB6KHosejB6NHo4ekB6SHpMelB6WHpce1h7YHtoe3B7eHt8e4B7hHuIe5B7mHuce6B7qHusfKh8sHy4fMB8yHzMfNB81HzYfOB86HzsfPB8+Hz8fQh+BH4MfhR+HH4kfih+LH4wfjR+PH5Efkh+TH5Uflh/VH9cf2R/bH90f3h/fH+Af4R/jH+Uf5h/nH+kf6iApICsgLSAvIDEgMiAzIDQgNSA3IDkgOiA7ID0gPiCJIKwgzCDsIO4g8CDyIPQg9iD3IPgg+iD7IP0g/iEAIQIhAyEEIQYhByEMIRkhHiEgISIhJyEpISshLSFSIXYhnSHBIcMhxSHHIckhyyHNIc4h0CHdIe4h8CHyIfQh9iH4Ifoh/CH+Ig8iESITIhUiFyIZIhsiHSIfIiEiYCJiImQiZiJoImkiaiJrImwibiJwInEiciJ0InUitCK2IrgiuiK8Ir0iviK/IsAiwiLEIsUixiLIIskjCCMKIwwjDiMQIxEjEiMTIxQjFiMYIxkjGiMcIx0jKiMrIywjLiNtI28jcSNzI3UjdiN3I3gjeSN7I30jfiN/I4EjgiPBI8MjxSPHI8kjyiPLI8wjzSPPI9Ej0iPTI9Uj1iQVJBckGSQbJB0kHiQfJCAkISQjJCUkJiQnJCkkKiRpJGskbSRvJHEkciRzJHQkdSR3JHkkeiR7JH0kfiS9JL8kwSTDJMUkxiTHJMgkySTLJM0kziTPJNEk0iT3JRslQiVmJWglaiVsJW4lcCVyJXMldSWCJZElkyWVJZclmSWbJZ0lnyWuJbAlsiW0JbYluCW6JbwlviX9Jf8mASYDJgUmBiYHJggmCSYLJg0mDiYPJhEmEiZRJlMmVSZXJlkmWiZbJlwmXSZfJmEmYiZjJmUmZialJqcmqSarJq0mriavJrAmsSazJrUmtia3Jrkmuib5Jvsm/Sb/JwEnAicDJwQnBScHJwknCicLJw0nDicRJ1AnUidUJ1YnWCdZJ1onWydcJ14nYCdhJ2InZCdlJ6QnpieoJ6onrCetJ64nryewJ7IntCe1J7YnuCe5J/gn+if8J/4oACgBKAIoAygEKAYoCCgJKAooDCgNKFgoeyibKLsovSi/KMEowyjFKMYoxyjJKMoozCjNKM8o0SjSKNMo1SjWKN8o7CjxKPMo9Sj6KPwo/ikAKSUpSSlwKZQplimYKZopnCmeKaApoSmjKbApwSnDKcUpxynJKcspzSnPKdEp4inkKeYp6CnqKewp7inwKfIp9CozKjUqNyo5KjsqPCo9Kj4qPypBKkMqRCpFKkcqSCqHKokqiyqNKo8qkCqRKpIqkyqVKpcqmCqZKpsqnCrbKt0q3yrhKuMq5CrlKuYq5yrpKusq7CrtKu8q8Cr9Kv4q/ysBK0ArQitEK0YrSCtJK0orSytMK04rUCtRK1IrVCtVK5QrliuYK5ornCudK54rnyugK6IrpCulK6YrqCupK+gr6ivsK+4r8CvxK/Ir8yv0K/Yr+Cv5K/or/Cv9LDwsPixALEIsRCxFLEYsRyxILEosTCxNLE4sUCxRLJAskiyULJYsmCyZLJosmyycLJ4soCyhLKIspCylLMos7i0VLTktOy09LT8tQS1DLUUtRi1ILVUtZC1mLWgtai1sLW4tcC1yLYEtgy2FLYctiS2LLY0tjy2RLdAt0i3ULdYt2C3ZLdot2y3cLd4t4C3hLeIt5C3lLiQuJi4oLiouLC4tLi4uLy4wLjIuNC41LjYuOC45Lnguei58Ln4ugC6BLoIugy6ELoYuiC6JLooujC6NLswuzi7QLtIu1C7VLtYu1y7YLtou3C7dLt4u4C7hLyAvIi8kLyYvKC8pLyovKy8sLy4vMC8xLzIvNC81L3Qvdi94L3ovfC99L34vfy+AL4IvhC+FL4YviC+JL7Av7y/xL/Mv9S/3L/gv+S/6L/sv/S//MAAwATADMAQwDzAYMBkwGzAkMC8wPjBJMFcwbDCAMJcwqTC2MLcwuDC6MMcwyDDJMMsw2DDZMNow3DDlMPQxATEQMSIxNjFNMV8xaDFpMWsxeDF5MXoxfDF9MYYxkDGXAAAAAAAAAgIAAAAAAAAGuAAAAAAAAAAAAAAAAAAAMZ8=\n</attribute>\n        <relationship name=\"entitymappings\" type=\"0/0\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z105\"></relationship>\n    </object>\n</database>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteDataMappingV2toV4.xcmappingmodel/xcmapping.xml",
    "content": "<?xml version=\"1.0\" standalone=\"yes\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n    <databaseInfo>\n        <version>134481920</version>\n        <UUID>9D2078F8-1057-4DE0-B169-596FECABDEDC</UUID>\n        <nextObjectID>107</nextObjectID>\n        <metadata>\n            <plist version=\"1.0\">\n                <dict>\n                    <key>NSPersistenceFrameworkVersion</key>\n                    <integer>1344</integer>\n                    <key>NSStoreModelVersionChecksumKey</key>\n                    <string>bMpud663vz0bXQE24C6Rh4MvJ5jVnzsD2sI3njZkKbc=</string>\n                    <key>NSStoreModelVersionHashes</key>\n                    <dict>\n                        <key>XDDevAttributeMapping</key>\n                        <data>\n\t\t0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=\n\t\t</data>\n                        <key>XDDevEntityMapping</key>\n                        <data>\n\t\tqeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=\n\t\t</data>\n                        <key>XDDevMappingModel</key>\n                        <data>\n\t\tEqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=\n\t\t</data>\n                        <key>XDDevPropertyMapping</key>\n                        <data>\n\t\tXN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA=\n\t\t</data>\n                        <key>XDDevRelationshipMapping</key>\n                        <data>\n\t\takYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs=\n\t\t</data>\n                    </dict>\n                    <key>NSStoreModelVersionHashesDigest</key>\n                    <string>+Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A==</string>\n                    <key>NSStoreModelVersionHashesVersion</key>\n                    <integer>3</integer>\n                    <key>NSStoreModelVersionIdentifiers</key>\n                    <array>\n                        <string></string>\n                    </array>\n                </dict>\n            </plist>\n        </metadata>\n    </databaseInfo>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z102\">\n        <attribute name=\"name\" type=\"string\">remoteDataInfo</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z106\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z103\">\n        <attribute name=\"name\" type=\"string\">timestamp</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z106\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z104\">\n        <attribute name=\"name\" type=\"string\">data</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z106\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z105\">\n        <attribute name=\"name\" type=\"string\">type</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z106\"></relationship>\n    </object>\n    <object type=\"XDDEVENTITYMAPPING\" id=\"z106\">\n        <attribute name=\"migrationpolicyclassname\" type=\"string\">UARemoteDataMappingV2toV4</attribute>\n        <attribute name=\"sourcename\" type=\"string\">UARemoteDataStorePayload</attribute>\n        <attribute name=\"mappingtypename\" type=\"string\">Undefined</attribute>\n        <attribute name=\"mappingnumber\" type=\"int16\">1</attribute>\n        <attribute name=\"destinationname\" type=\"string\">UARemoteDataStorePayload</attribute>\n        <attribute name=\"autogenerateexpression\" type=\"bool\">1</attribute>\n        <relationship name=\"mappingmodel\" type=\"1/1\" destination=\"XDDEVMAPPINGMODEL\" idrefs=\"z107\"></relationship>\n        <relationship name=\"attributemappings\" type=\"0/0\" destination=\"XDDEVATTRIBUTEMAPPING\" idrefs=\"z103 z102 z104 z105\"></relationship>\n        <relationship name=\"relationshipmappings\" type=\"0/0\" destination=\"XDDEVRELATIONSHIPMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVMAPPINGMODEL\" id=\"z107\">\n        <attribute name=\"sourcemodelpath\" type=\"string\">AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 2.xcdatamodel</attribute>\n        <attribute name=\"sourcemodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCxAAsADAAZADUANgA3AD8AQABbAFwAXQBjAGQAcACGAIcAiACJAIoAiwCMAI0AjgCPAKgAqwCyALgAxwDWANkA6AD3APoAWgEKARkBHQEhATABNgE3AT8BTgFPAVgBZAFlAWYBZwFoAX0BfgGGAYcBiAGUAagBqQGqAasBrAGtAa4BrwGwAb8BzgHdAeEB8AH/AgACDwIeAi0COQJLAkwCTQJOAk8CUAJRAlICYQJwAn8CjgKPAp4CrQKuAr0CxQLaAtsC4wLvAwMDEgMhAzADNANDA1IDYQNwA38DiwOdA6wDuwPKA9kD6AP3A/gEBwQcBB0EJQQxBEUEVARjBHIEdgSFBJQEowSyBMEEzQTfBO4E/QUMBRsFHAUrBToFSQVeBV8FZwVzBYcFlgWlBbQFuAXHBdYF5QX0BgMGDwYhBjAGPwZOBl0GXgZtBnwGiwaMBo8GmAacBqAGpAasBq8Gswa0VSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgLCAAICtgK6Ar94AGgAbABwAHQAeAB8AIAAOACEAIgAjACQAJQAmACcAKAApAAkAJwAVAC0ALgAvADAAMQAnACcAFV8QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABICrgKmAAYAEgACAqoCsEACABYADgASABIAAUFNZRVPTADgAOQAOADoAPAA+V05TLmtleXNaTlMub2JqZWN0c6EAO4AGoQA9gAeAJV8QGFVBUmVtb3RlRGF0YVN0b3JlUGF5bG9hZN8QEABBAEIAQwBEAB8ARQBGACEARwBIAA4AIwBJAEoAJgBLAEwATQAnACcAEwBRAFIALwAnAEwAVQA7AEwAWABZAFpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAtgASABIACgAqApoAEgAmAqIAGgAmAp4AICBLHv9r2V29yZGVyZWTTADgAOQAOAF4AYAA+oQBfgAuhAGGADIAlXlhEX1BTdGVyZW90eXBl2QAfACMAZQAOACYAZgAhAEsAZwA9AF8ATABrABUAJwAvAFoAb18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAsgACABAiADdMAOAA5AA4AcQB7AD6pAHIAcwB0AHUAdgB3AHgAeQB6gA6AD4AQgBGAEoATgBSAFYAWqQB8AH0AfgB/AIAAgQCCAIMAhIAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAJsAFQBhAFoAWgBaAC8AWgCiAHIAWgBaABUAWlVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADkADgCpAKqggBnSAKwArQCuAK9aJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMArgCwALFXTlNBcnJheVhOU09iamVjdNIArACtALMAtF8QEFhEVU1MUHJvcGVydHlJbXCkALUAtgC3ALFfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogBzAFoAWgAVAFqAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDJABUAYQBaAFoAWgAvAFoAogB0AFoAWgAVAFqAAIAdgACADAgICAiAGoAQCAiAAAjSADkADgDXAKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogB1AFoAWgAVAFqAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDqABUAYQBaAFoAWgAvAFoAogB2AFoAWgAVAFqAAIAggACADAgICAiAGoASCAiAAAjSADkADgD4AKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUAYQBaAFoAWgAvAFoAogB3AFoAWgAVAFqAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBDAAVAGEAWgBaAFoALwBaAKIAeABaAFoAFQBagACAJIAAgAwICAgIgBqAFAgIgAAI0wA4ADkADgEaARsAPqCggCXSAKwArQEeAR9fEBNOU011dGFibGVEaWN0aW9uYXJ5owEeASAAsVxOU0RpY3Rpb25hcnnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEjABUAYQBaAFoAWgAvAFoAogB5AFoAWgAVAFqAAIAngACADAgICAiAGoAVCAiAAAjWACMADgAmAEsAHwAhATEBMgAVAFoAFQAvgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKwArQE4ATldWERVTUxDbGFzc0ltcKYBOgE7ATwBPQE+ALFdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQFBABUAYQBaAFoAWgAvAFoAogB6AFoAWgAVAFqAAIArgACADAgICAiAGoAWCAiAAAhfEBhVQVJlbW90ZURhdGFTdG9yZVBheWxvYWTSAKwArQFQAVFfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVIBUwFUAVUBVgFXALFfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOAVkBXgA+pAFaAVsBXAFdgC6AL4AwgDGkAV8BYAFhAWKAMoBegHaAjoAlWG1ldGFkYXRhVGRhdGFZdGltZXN0YW1wVHR5cGXfEBIAkACRAJIBaQAfAJQAlQFqACEAkwFrAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgFzAC8AWgBMAFoBdwFaAFoAWgF7AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiANAiACQiAXYAuCAiAMwgSXNfbWdMAOAA5AA4BfwGCAD6iAYABgYA1gDaiAYMBhIA3gEuAJV8QElhEX1BQcm9wU3RlcmVvdHlwZV8QElhEX1BBdHRfU3RlcmVvdHlwZdkAHwAjAYkADgAmAYoAIQBLAYsBXwGAAEwAawAVACcALwBaAZNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA1gAmALIAAgAQIgDjTADgAOQAOAZUBngA+qAGWAZcBmAGZAZoBmwGcAZ2AOYA6gDuAPIA9gD6AP4BAqAGfAaABoQGiAaMBpAGlAaaAQYBCgEOARYBGgEiASYBKgCVfEBtYRF9QUFNLX2lzU3RvcmVkSW5UcnV0aEZpbGVfEBtYRF9QUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBBYRF9QUFNLX3VzZXJJbmZvXxARWERfUFBTS19pc0luZGV4ZWRfEBJYRF9QUFNLX2lzT3B0aW9uYWxfEBpYRF9QUFNLX2lzU3BvdGxpZ2h0SW5kZXhlZF8QEVhEX1BQU0tfZWxlbWVudElEXxATWERfUFBTS19pc1RyYW5zaWVudN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIA3CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIA3CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAdAAFQGDAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgESAAIA3CAgICIAagDsICIAACNMAOAA5AA4B3gHfAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgDcICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVAYMAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgDcICAgIgBqAPQgIgAAICd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZsAWgBaABUAWoAAgCKAAIA3CAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZwAWgBaABUAWoAAgACAAIA3CAgICIAagD8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZ0AWgBaABUAWoAAgCKAAIA3CAgICIAagEAICIAACNkAHwAjAi4ADgAmAi8AIQBLAjABXwGBAEwAawAVACcALwBaAjhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA2gAmALIAAgAQIgEzTADgAOQAOAjoCQgA+pwI7AjwCPQI+Aj8CQAJBgE2AToBPgFCAUYBSgFOnAkMCRAJFAkYCRwJIAkmAVIBVgFaAV4BZgFqAXIAlXxAdWERfUEF0dEtfZGVmYXVsdFZhbHVlQXNTdHJpbmdfEChYRF9QQXR0S19hbGxvd3NFeHRlcm5hbEJpbmFyeURhdGFTdG9yYWdlXxAXWERfUEF0dEtfbWluVmFsdWVTdHJpbmdfEBZYRF9QQXR0S19hdHRyaWJ1dGVUeXBlXxAXWERfUEF0dEtfbWF4VmFsdWVTdHJpbmdfEB1YRF9QQXR0S192YWx1ZVRyYW5zZm9ybWVyTmFtZV8QIFhEX1BBdHRLX3JlZ3VsYXJFeHByZXNzaW9uU3RyaW5n3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICOwBaAFoAFQBagACAAIAAgEsICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYQAWgBaAFoALwBaAKICPABaAFoAFQBagACAIoAAgEsICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPQBaAFoAFQBagACAAIAAgEsICAgIgBqATwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCgQAVAYQAWgBaAFoALwBaAKICPgBaAFoAFQBagACAWIAAgEsICAgIgBqAUAgIgAAIEQcI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPwBaAFoAFQBagACAAIAAgEsICAgIgBqAUQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCoAAVAYQAWgBaAFoALwBaAKICQABaAFoAFQBagACAW4AAgEsICAgIgBqAUggIgAAIXxAeVUFOU0RpY3Rpb25hcnlWYWx1ZVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgEsICAgIgBqAUwgIgAAI0gCsAK0CvgK/XVhEUE1BdHRyaWJ1dGWmAsACwQLCAsMCxACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAsYAHwCUAJUCxwAhAJMCyACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC0AAvAFoATABaAXcBWwBaAFoC2ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGAIgAkIgF2ALwgIgF8IEkEheZjTADgAOQAOAtwC3wA+ogGAAYGANYA2ogLgAuGAYYBsgCXZAB8AIwLkAA4AJgLlACEASwLmAWABgABMAGsAFQAnAC8AWgLuXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANYAJgCyAAIAECIBi0wA4ADkADgLwAvkAPqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgC+gL7AvwC/QL+Av8DAAMBgGOAZIBlgGeAaIBpgGqAa4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgGEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAuAAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgGEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDIwAVAuAAWgBaAFoALwBaAKIBmABaAFoAFQBagACAZoAAgGEICAgIgBqAOwgIgAAI0wA4ADkADgMxAzIAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAYQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUC4ABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAYQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAYQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4ABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAYQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAYQgICAiAGoBACAiAAAjZAB8AIwOAAA4AJgOBACEASwOCAWABgQBMAGsAFQAnAC8AWgOKXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANoAJgCyAAIAECIBt0wA4ADkADgOMA5QAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwOVA5YDlwOYA5kDmgObgG6Ab4BwgHGAcoBzgHWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAIBsCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLhAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAIBsCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAIBsCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAoEAFQLhAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgFiAAIBsCAgICIAagFAICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAIBsCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA+oAFQLhAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgHSAAIBsCAgICIAagFIICIAACF8QFlVBSlNPTlZhbHVlVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4QBaAFoAWgAvAFoAogJBAFoAWgAVAFqAAIAAgACAbAgICAiAGoBTCAiAAAjfEBIAkACRAJIECAAfAJQAlQQJACEAkwQKAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgQSAC8AWgBMAFoBdwFcAFoAWgQaAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAeAiACQiAXYAwCAiAdwgTAAAAAQpFAUrTADgAOQAOBB4EIQA+ogGAAYGANYA2ogQiBCOAeYCEgCXZAB8AIwQmAA4AJgQnACEASwQoAWEBgABMAGsAFQAnAC8AWgQwXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgHaANYAJgCyAAIAECIB60wA4ADkADgQyBDsAPqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgEPAQ9BD4EPwRABEEEQgRDgHuAfIB9gH+AgICBgIKAg4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgHkICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCIAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgHkICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUEZQAVBCIAWgBaAFoALwBaAKIBmABaAFoAFQBagACAfoAAgHkICAgIgBqAOwgIgAAI0wA4ADkADgRzBHQAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIgBaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAeQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUEIgBaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAeQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIgBaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAeQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIgBaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAeQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIgBaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAeQgICAiAGoBACAiAAAjZAB8AIwTCAA4AJgTDACEASwTEAWEBgQBMAGsAFQAnAC8AWgTMXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgHaANoAJgCyAAIAECICF0wA4ADkADgTOBNYAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwTXBNgE2QTaBNsE3ATdgIaAh4CIgImAi4CMgI2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQjAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAICECAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQjAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAICECAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQjAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAICECAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBQ4AFQQjAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgIqAAICECAgICIAagFAICIAACBEDhN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQjAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAICECAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQjAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgACAAICECAgICIAagFIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQjAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAICECAgICIAagFMICIAACN8QEgCQAJEAkgVKAB8AlACVBUsAIQCTBUwAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBVQALwBaAEwAWgF3AV0AWgBaBVwAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICICQCIAJCIBdgDEICICPCBLq5+sV0wA4ADkADgVgBWMAPqIBgAGBgDWANqIFZAVlgJGAnIAl2QAfACMFaAAOACYFaQAhAEsFagFiAYAATABrABUAJwAvAFoFcl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCOgDWACYAsgACABAiAktMAOAA5AA4FdAV9AD6oAZYBlwGYAZkBmgGbAZwBnYA5gDqAO4A8gD2APoA/gECoBX4FfwWABYEFggWDBYQFhYCTgJSAlYCXgJiAmYCagJuAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVkAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAICRCAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVkAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAICRCAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBacAFQVkAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgJaAAICRCAgICIAagDsICIAACNMAOAA5AA4FtQW2AD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWQAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgJEICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVBWQAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgJEICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWQAWgBaAFoALwBaAKIBmwBaAFoAFQBagACAIoAAgJEICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWQAWgBaAFoALwBaAKIBnABaAFoAFQBagACAAIAAgJEICAgIgBqAPwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWQAWgBaAFoALwBaAKIBnQBaAFoAFQBagACAIoAAgJEICAgIgBqAQAgIgAAI2QAfACMGBAAOACYGBQAhAEsGBgFiAYEATABrABUAJwAvAFoGDl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCOgDaACYAsgACABAiAndMAOAA5AA4GEAYYAD6nAjsCPAI9Aj4CPwJAAkGATYBOgE+AUIBRgFKAU6cGGQYaBhsGHAYdBh4GH4CegJ+AoIChgKOApIClgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZQBaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIAAgACAnAgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZQBaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAigACAnAgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZQBaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACAnAgICAiAGoBPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQZQABUFZQBaAFoAWgAvAFoAogI+AFoAWgAVAFqAAICigACAnAgICAiAGoBQCAiAAAgRArzfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZQBaAFoAWgAvAFoAogI/AFoAWgAVAFqAAIAAgACAnAgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZQBaAFoAWgAvAFoAogJAAFoAWgAVAFqAAIAAgACAnAgICAiAGoBSCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZQBaAFoAWgAvAFoAogJBAFoAWgAVAFqAAIAAgACAnAgICAiAGoBTCAiAAAhaZHVwbGljYXRlc9IAOQAOBo0AqqCAGdIArACtBpAGkVpYRFBNRW50aXR5pwaSBpMGlAaVBpYGlwCxWlhEUE1FbnRpdHldWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOBpkGmgA+oKCAJdMAOAA5AA4GnQaeAD6goIAl0wA4ADkADgahBqIAPqCggCXSAKwArQalBqZeWERNb2RlbFBhY2thZ2WmBqcGqAapBqoGqwCxXlhETW9kZWxQYWNrYWdlXxAPWERVTUxQYWNrYWdlSW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNIAOQAOBq0AqqCAGdMAOAA5AA4GsAaxAD6goIAlUNIArACtBrUGtllYRFBNTW9kZWyjBrUGtwCxV1hETW9kZWwACAAZACIALAAxADoAPwBRAFYAWwBdAcIByAHhAfMB+gIIAhUCLQJHAkkCSwJNAk8CUQJTAowCqwLIAucC+QMZAyADPgNKA2YDbAOOA68DwgPEA8YDyAPKA8wDzgPQA9ID1APWA9gD2gPcA94D3wPjA/AD+AQDBAYECAQLBA0EDwQqBG0EkQS1BNgE/wUfBUYFbQWNBbEF1QXhBeMF5QXnBekF6wXtBe8F8QXzBfUF9wX5BfsF/QX+BgMGCwYYBhsGHQYgBiIGJAYzBlgGfAajBscGyQbLBs0GzwbRBtMG1AbWBuMG9gb4BvoG/Ab+BwAHAgcEBwYHCAcbBx0HHwchByMHJQcnBykHKwctBy8HRQdYB3QHkQetB8EH0wfpCAIIQQhHCFAIXQhpCHMIfQiICJMIoAioCKoIrAiuCLAIsQiyCLMItAi2CLgIuQi6CLwIvQjGCMcIyQjSCN0I5gj1CPwJBAkNCRYJKQkyCUUJXAluCa0JrwmxCbMJtQm2CbcJuAm5CbsJvQm+Cb8JwQnCCgEKAwoFCgcKCQoKCgsKDAoNCg8KEQoSChMKFQoWCh8KIAoiCmEKYwplCmcKaQpqCmsKbAptCm8KcQpyCnMKdQp2CrUKtwq5CrsKvQq+Cr8KwArBCsMKxQrGCscKyQrKCtMK1ArWCxULFwsZCxsLHQseCx8LIAshCyMLJQsmCycLKQsqCysLagtsC24LcAtyC3MLdAt1C3YLeAt6C3sLfAt+C38LjAuNC44LkAuZC68LtgvDDAIMBAwGDAgMCgwLDAwMDQwODBAMEgwTDBQMFgwXDDAMMgw0DDYMNww5DFAMWQxnDHQMggyXDKsMwgzUDRMNFQ0XDRkNGw0cDR0NHg0fDSENIw0kDSUNJw0oDUMNTA1hDXANhQ2TDagNvA3TDeUN8g37Df0N/w4BDgMODA4ODhAOEg4UDhYOHw4kDi4OMw5+DqEOwQ7hDuMO5Q7nDukO6w7sDu0O7w7wDvIO8w71DvcO+A75DvsO/A8BDw4PEw8VDxcPHA8eDyAPIg83D0wPcQ+VD7wP4A/iD+QP5g/oD+oP7A/tD+8P/BANEA8QERATEBUQFxAZEBsQHRAuEDAQMhA0EDYQOBA6EDwQPhBAEF4QfBCPEKMQuBDVEOkQ/xE+EUARQhFEEUYRRxFIEUkRShFMEU4RTxFQEVIRUxGSEZQRlhGYEZoRmxGcEZ0RnhGgEaIRoxGkEaYRpxHmEegR6hHsEe4R7xHwEfER8hH0EfYR9xH4EfoR+xIIEgkSChIMEksSTRJPElESUxJUElUSVhJXElkSWxJcEl0SXxJgEp8SoRKjEqUSpxKoEqkSqhKrEq0SrxKwErESsxK0ErUS9BL2EvgS+hL8Ev0S/hL/EwATAhMEEwUTBhMIEwkTSBNKE0wTThNQE1ETUhNTE1QTVhNYE1kTWhNcE10TnBOeE6ATohOkE6UTphOnE6gTqhOsE60TrhOwE7ET1hP6FCEURRRHFEkUSxRNFE8UURRSFFQUYRRwFHIUdBR2FHgUehR8FH4UjRSPFJEUkxSVFJcUmRSbFJ0UvRToFQIVGxU1FVUVeBW3FbkVuxW9Fb8VwBXBFcIVwxXFFccVyBXJFcsVzBYLFg0WDxYRFhMWFBYVFhYWFxYZFhsWHBYdFh8WIBZfFmEWYxZlFmcWaBZpFmoWaxZtFm8WcBZxFnMWdBazFrUWtxa5FrsWvBa9Fr4WvxbBFsMWxBbFFscWyBbLFwoXDBcOFxAXEhcTFxQXFRcWFxgXGhcbFxwXHhcfF14XYBdiF2QXZhdnF2gXaRdqF2wXbhdvF3AXchdzF5QX0xfVF9cX2RfbF9wX3RfeF98X4RfjF+QX5RfnF+gX8Rf/GAwYGhgnGDoYURhjGK4Y0RjxGREZExkVGRcZGRkbGRwZHRkfGSAZIhkjGSUZJxkoGSkZKxksGTEZPhlDGUUZRxlMGU4ZUBlSGXcZmxnCGeYZ6BnqGewZ7hnwGfIZ8xn1GgIaExoVGhcaGRobGh0aHxohGiMaNBo2GjgaOho8Gj4aQBpCGkQaRhqFGocaiRqLGo0ajhqPGpAakRqTGpUalhqXGpkamhrZGtsa3RrfGuEa4hrjGuQa5RrnGuka6hrrGu0a7hstGy8bMRszGzUbNhs3GzgbORs7Gz0bPhs/G0EbQhtPG1AbURtTG5IblBuWG5gbmhubG5wbnRueG6AbohujG6QbphunG+Yb6BvqG+wb7hvvG/Ab8RvyG/Qb9hv3G/gb+hv7HDocPBw+HEAcQhxDHEQcRRxGHEgcShxLHEwcThxPHI4ckBySHJQclhyXHJgcmRyaHJwcnhyfHKAcohyjHOIc5BzmHOgc6hzrHOwc7RzuHPAc8hzzHPQc9hz3HRwdQB1nHYsdjR2PHZEdkx2VHZcdmB2aHacdth24HbodvB2+HcAdwh3EHdMd1R3XHdkd2x3dHd8d4R3jHiIeJB4mHigeKh4rHiweLR4uHjAeMh4zHjQeNh43HnYeeB56Hnwefh5/HoAegR6CHoQehh6HHogeih6LHsoezB7OHtAe0h7THtQe1R7WHtge2h7bHtwe3h7fHx4fIB8iHyQfJh8nHygfKR8qHywfLh8vHzAfMh8zH3IfdB92H3gfeh97H3wffR9+H4Afgh+DH4Qfhh+HH8YfyB/KH8wfzh/PH9Af0R/SH9Qf1h/XH9gf2h/bH/QgMyA1IDcgOSA7IDwgPSA+ID8gQSBDIEQgRSBHIEggkyC2INYg9iD4IPog/CD+IQAhASECIQQhBSEHIQghCiEMIQ0hDiEQIREhGiEnISwhLiEwITUhNyE5ITshYCGEIashzyHRIdMh1SHXIdkh2yHcId4h6yH8If4iACICIgQiBiIIIgoiDCIdIh8iISIjIiUiJyIpIisiLSIvIm4icCJyInQidiJ3IngieSJ6InwifiJ/IoAigiKDIsIixCLGIsgiyiLLIswizSLOItAi0iLTItQi1iLXIxYjGCMaIxwjHiMfIyAjISMiIyQjJiMnIygjKiMrIzgjOSM6IzwjeyN9I38jgSODI4QjhSOGI4cjiSOLI4wjjSOPI5AjzyPRI9Mj1SPXI9gj2SPaI9sj3SPfI+Aj4SPjI+QkIyQlJCckKSQrJCwkLSQuJC8kMSQzJDQkNSQ3JDgkdyR5JHskfSR/JIAkgSSCJIMkhSSHJIgkiSSLJIwkyyTNJM8k0STTJNQk1STWJNck2STbJNwk3STfJOAlBSUpJVAldCV2JXgleiV8JX4lgCWBJYMlkCWfJaEloyWlJaclqSWrJa0lvCW+JcAlwiXEJcYlyCXKJcwmCyYNJg8mESYTJhQmFSYWJhcmGSYbJhwmHSYfJiAmXyZhJmMmZSZnJmgmaSZqJmsmbSZvJnAmcSZzJnQmsya1JrcmuSa7JrwmvSa+Jr8mwSbDJsQmxSbHJsgnBycJJwsnDScPJxAnEScSJxMnFScXJxgnGScbJxwnHydeJ2AnYidkJ2YnZydoJ2knaidsJ24nbydwJ3IncyeyJ7Qntie4J7onuye8J70nvifAJ8InwyfEJ8YnxygGKAgoCigMKA4oDygQKBEoEigUKBYoFygYKBooGyhmKIkoqSjJKMsozSjPKNEo0yjUKNUo1yjYKNoo2yjdKN8o4CjhKOMo5CjpKPYo+yj9KP8pBCkGKQgpCikvKVMpeimeKaApoimkKaYpqCmqKasprSm6KcspzSnPKdEp0ynVKdcp2SnbKewp7inwKfIp9Cn2Kfgp+in8Kf4qPSo/KkEqQypFKkYqRypIKkkqSypNKk4qTypRKlIqkSqTKpUqlyqZKpoqmyqcKp0qnyqhKqIqoyqlKqYq5SrnKukq6yrtKu4q7yrwKvEq8yr1KvYq9yr5KvorBysIKwkrCytKK0wrTitQK1IrUytUK1UrVitYK1orWytcK14rXyueK6AroiukK6YrpyuoK6krqiusK64rryuwK7IrsyvyK/Qr9iv4K/or+yv8K/0r/iwALAIsAywELAYsByxGLEgsSixMLE4sTyxQLFEsUixULFYsVyxYLFosWyyaLJwsniygLKIsoyykLKUspiyoLKosqyysLK4sryzULPgtHy1DLUUtRy1JLUstTS1PLVAtUi1fLW4tcC1yLXQtdi14LXotfC2LLY0tjy2RLZMtlS2XLZktmy3aLdwt3i3gLeIt4y3kLeUt5i3oLeot6y3sLe4t7y4uLjAuMi40LjYuNy44LjkuOi48Lj4uPy5ALkIuQy6CLoQuhi6ILoouiy6MLo0uji6QLpIuky6ULpYuly7WLtgu2i7cLt4u3y7gLuEu4i7kLuYu5y7oLuou6y7uLy0vLy8xLzMvNS82LzcvOC85LzsvPS8+Lz8vQS9CL4Evgy+FL4cviS+KL4svjC+NL48vkS+SL5MvlS+WL9Uv1y/ZL9sv3S/eL98v4C/hL+Mv5S/mL+cv6S/qL/Uv/i//MAEwCjAVMCQwLzA9MFIwZjB9MI8wnDCdMJ4woDCtMK4wrzCxML4wvzDAMMIwyzDaMOcw9jEIMRwxMzFFMU4xTzFRMV4xXzFgMWIxYzFsMXYxfQAAAAAAAAICAAAAAAAABrgAAAAAAAAAAAAAAAAAADGF\n</attribute>\n        <attribute name=\"destinationmodelpath\" type=\"string\">AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 4.xcdatamodel</attribute>\n        <attribute name=\"destinationmodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCxAAsADAAZADUANgA3AD8AQABbAFwAXQBjAGQAcACGAIcAiACJAIoAiwCMAI0AjgCPAKgAqwCyALgAxwDWANkA6AD3APoAWgEKARkBHQEhATABNgE3AT8BTgFPAVgBZAFlAWYBZwFoAX0BfgGGAYcBiAGUAagBqQGqAasBrAGtAa4BrwGwAb8BzgHdAeEB8AH/AgACDwIeAi0COQJLAkwCTQJOAk8CUAJRAlICYQJwAn8CjgKPAp4CrQKuAr0CxQLaAtsC4wLvAwMDEgMhAzADNANDA1IDYQNwA38DiwOdA6wDuwPKA9kD2gPpA/gEBwQcBB0EJQQxBEUEVARjBHIEdgSFBJQEowSyBMEEzQTfBO4E/QUMBRsFHAUrBToFSQVeBV8FZwVzBYcFlgWlBbQFuAXHBdYF5QX0BgMGDwYhBjAGPwZOBl0GbAZ7BnwGiwaMBo8GmAacBqAGpAasBq8Gswa0VSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgLCAAICtgK6Ar94AGgAbABwAHQAeAB8AIAAOACEAIgAjACQAJQAmACcAKAApAAkAJwAVAC0ALgAvADAAMQAnACcAFV8QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABICrgKmAAYAEgACAqoCsEACABYADgASABIAAUFNZRVPTADgAOQAOADoAPAA+V05TLmtleXNaTlMub2JqZWN0c6EAO4AGoQA9gAeAJV8QGFVBUmVtb3RlRGF0YVN0b3JlUGF5bG9hZN8QEABBAEIAQwBEAB8ARQBGACEARwBIAA4AIwBJAEoAJgBLAEwATQAnACcAEwBRAFIALwAnAEwAVQA7AEwAWABZAFpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAtgASABIACgAqApoAEgAmAqIAGgAmAp4AICBKZBEbMV29yZGVyZWTTADgAOQAOAF4AYAA+oQBfgAuhAGGADIAlXlhEX1BTdGVyZW90eXBl2QAfACMAZQAOACYAZgAhAEsAZwA9AF8ATABrABUAJwAvAFoAb18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAsgACABAiADdMAOAA5AA4AcQB7AD6pAHIAcwB0AHUAdgB3AHgAeQB6gA6AD4AQgBGAEoATgBSAFYAWqQB8AH0AfgB/AIAAgQCCAIMAhIAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAJsAFQBhAFoAWgBaAC8AWgCiAHIAWgBaABUAWlVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADkADgCpAKqggBnSAKwArQCuAK9aJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMArgCwALFXTlNBcnJheVhOU09iamVjdNIArACtALMAtF8QEFhEVU1MUHJvcGVydHlJbXCkALUAtgC3ALFfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogBzAFoAWgAVAFqAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDJABUAYQBaAFoAWgAvAFoAogB0AFoAWgAVAFqAAIAdgACADAgICAiAGoAQCAiAAAjSADkADgDXAKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogB1AFoAWgAVAFqAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDqABUAYQBaAFoAWgAvAFoAogB2AFoAWgAVAFqAAIAggACADAgICAiAGoASCAiAAAjSADkADgD4AKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUAYQBaAFoAWgAvAFoAogB3AFoAWgAVAFqAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBDAAVAGEAWgBaAFoALwBaAKIAeABaAFoAFQBagACAJIAAgAwICAgIgBqAFAgIgAAI0wA4ADkADgEaARsAPqCggCXSAKwArQEeAR9fEBNOU011dGFibGVEaWN0aW9uYXJ5owEeASAAsVxOU0RpY3Rpb25hcnnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEjABUAYQBaAFoAWgAvAFoAogB5AFoAWgAVAFqAAIAngACADAgICAiAGoAVCAiAAAjWACMADgAmAEsAHwAhATEBMgAVAFoAFQAvgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKwArQE4ATldWERVTUxDbGFzc0ltcKYBOgE7ATwBPQE+ALFdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQFBABUAYQBaAFoAWgAvAFoAogB6AFoAWgAVAFqAAIArgACADAgICAiAGoAWCAiAAAhfEBhVQVJlbW90ZURhdGFTdG9yZVBheWxvYWTSAKwArQFQAVFfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVIBUwFUAVUBVgFXALFfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOAVkBXgA+pAFaAVsBXAFdgC6AL4AwgDGkAV8BYAFhAWKAMoBegHaAjoAlVGRhdGFZdGltZXN0YW1wVHR5cGVecmVtb3RlRGF0YUluZm/fEBIAkACRAJIBaQAfAJQAlQFqACEAkwFrAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgFzAC8AWgBMAFoBdwFaAFoAWgF7AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiANAiACQiAXYAuCAiAMwgSQjblmNMAOAA5AA4BfwGCAD6iAYABgYA1gDaiAYMBhIA3gEuAJV8QElhEX1BQcm9wU3RlcmVvdHlwZV8QElhEX1BBdHRfU3RlcmVvdHlwZdkAHwAjAYkADgAmAYoAIQBLAYsBXwGAAEwAawAVACcALwBaAZNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA1gAmALIAAgAQIgDjTADgAOQAOAZUBngA+qAGWAZcBmAGZAZoBmwGcAZ2AOYA6gDuAPIA9gD6AP4BAqAGfAaABoQGiAaMBpAGlAaaAQYBCgEOARYBGgEiASYBKgCVfEBtYRF9QUFNLX2lzU3RvcmVkSW5UcnV0aEZpbGVfEBtYRF9QUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBBYRF9QUFNLX3VzZXJJbmZvXxARWERfUFBTS19pc0luZGV4ZWRfEBJYRF9QUFNLX2lzT3B0aW9uYWxfEBpYRF9QUFNLX2lzU3BvdGxpZ2h0SW5kZXhlZF8QEVhEX1BQU0tfZWxlbWVudElEXxATWERfUFBTS19pc1RyYW5zaWVudN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIA3CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIA3CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAdAAFQGDAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgESAAIA3CAgICIAagDsICIAACNMAOAA5AA4B3gHfAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgDcICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVAYMAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgDcICAgIgBqAPQgIgAAICd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZsAWgBaABUAWoAAgCKAAIA3CAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZwAWgBaABUAWoAAgACAAIA3CAgICIAagD8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZ0AWgBaABUAWoAAgCKAAIA3CAgICIAagEAICIAACNkAHwAjAi4ADgAmAi8AIQBLAjABXwGBAEwAawAVACcALwBaAjhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA2gAmALIAAgAQIgEzTADgAOQAOAjoCQgA+pwI7AjwCPQI+Aj8CQAJBgE2AToBPgFCAUYBSgFOnAkMCRAJFAkYCRwJIAkmAVIBVgFaAV4BZgFqAXIAlXxAdWERfUEF0dEtfZGVmYXVsdFZhbHVlQXNTdHJpbmdfEChYRF9QQXR0S19hbGxvd3NFeHRlcm5hbEJpbmFyeURhdGFTdG9yYWdlXxAXWERfUEF0dEtfbWluVmFsdWVTdHJpbmdfEBZYRF9QQXR0S19hdHRyaWJ1dGVUeXBlXxAXWERfUEF0dEtfbWF4VmFsdWVTdHJpbmdfEB1YRF9QQXR0S192YWx1ZVRyYW5zZm9ybWVyTmFtZV8QIFhEX1BBdHRLX3JlZ3VsYXJFeHByZXNzaW9uU3RyaW5n3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICOwBaAFoAFQBagACAAIAAgEsICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYQAWgBaAFoALwBaAKICPABaAFoAFQBagACAIoAAgEsICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPQBaAFoAFQBagACAAIAAgEsICAgIgBqATwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCgQAVAYQAWgBaAFoALwBaAKICPgBaAFoAFQBagACAWIAAgEsICAgIgBqAUAgIgAAIEQPo3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPwBaAFoAFQBagACAAIAAgEsICAgIgBqAUQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCoAAVAYQAWgBaAFoALwBaAKICQABaAFoAFQBagACAW4AAgEsICAgIgBqAUggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgEsICAgIgBqAUwgIgAAI0gCsAK0CvgK/XVhEUE1BdHRyaWJ1dGWmAsACwQLCAsMCxACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAsYAHwCUAJUCxwAhAJMCyACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC0AAvAFoATABaAXcBWwBaAFoC2ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGAIgAkIgF2ALwgIgF8IEwAAAAEo0uvZ0wA4ADkADgLcAt8APqIBgAGBgDWANqIC4ALhgGGAbIAl2QAfACMC5AAOACYC5QAhAEsC5gFgAYAATABrABUAJwAvAFoC7l8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBegDWACYAsgACABAiAYtMAOAA5AA4C8AL5AD6oAZYBlwGYAZkBmgGbAZwBnYA5gDqAO4A8gD2APoA/gECoAvoC+wL8Av0C/gL/AwADAYBjgGSAZYBngGiAaYBqgGuAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLgAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIBhCAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLgAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIBhCAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAyMAFQLgAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgGaAAIBhCAgICIAagDsICIAACNMAOAA5AA4DMQMyAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgGEICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVAuAAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgGEICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBmwBaAFoAFQBagACAIoAAgGEICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAuAAWgBaAFoALwBaAKIBnABaAFoAFQBagACAAIAAgGEICAgIgBqAPwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBnQBaAFoAFQBagACAIoAAgGEICAgIgBqAQAgIgAAI2QAfACMDgAAOACYDgQAhAEsDggFgAYEATABrABUAJwAvAFoDil8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBegDaACYAsgACABAiAbdMAOAA5AA4DjAOUAD6nAjsCPAI9Aj4CPwJAAkGATYBOgE+AUIBRgFKAU6cDlQOWA5cDmAOZA5oDm4BugG+AcIBxgHOAdIB1gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4QBaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIAAgACAbAgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4QBaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAigACAbAgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4QBaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACAbAgICAiAGoBPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQPMABUC4QBaAFoAWgAvAFoAogI+AFoAWgAVAFqAAIBygACAbAgICAiAGoBQCAiAAAgRA4TfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4QBaAFoAWgAvAFoAogI/AFoAWgAVAFqAAIAAgACAbAgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4QBaAFoAWgAvAFoAogJAAFoAWgAVAFqAAIAAgACAbAgICAiAGoBSCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4QBaAFoAWgAvAFoAogJBAFoAWgAVAFqAAIAAgACAbAgICAiAGoBTCAiAAAjfEBIAkACRAJIECAAfAJQAlQQJACEAkwQKAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgQSAC8AWgBMAFoBdwFcAFoAWgQaAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAeAiACQiAXYAwCAiAdwgS6edH+tMAOAA5AA4EHgQhAD6iAYABgYA1gDaiBCIEI4B5gISAJdkAHwAjBCYADgAmBCcAIQBLBCgBYQGAAEwAawAVACcALwBaBDBfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAdoA1gAmALIAAgAQIgHrTADgAOQAOBDIEOwA+qAGWAZcBmAGZAZoBmwGcAZ2AOYA6gDuAPIA9gD6AP4BAqAQ8BD0EPgQ/BEAEQQRCBEOAe4B8gH2Af4CAgIGAgoCDgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIgBaAFoAWgAvAFoAogGWAFoAWgAVAFqAAIAigACAeQgICAiAGoA5CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIgBaAFoAWgAvAFoAogGXAFoAWgAVAFqAAIAAgACAeQgICAiAGoA6CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQRlABUEIgBaAFoAWgAvAFoAogGYAFoAWgAVAFqAAIB+gACAeQgICAiAGoA7CAiAAAjTADgAOQAOBHMEdAA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQiAFoAWgBaAC8AWgCiAZkAWgBaABUAWoAAgCKAAIB5CAgICIAagDwICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAfIAFQQiAFoAWgBaAC8AWgCiAZoAWgBaABUAWoAAgEeAAIB5CAgICIAagD0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQiAFoAWgBaAC8AWgCiAZsAWgBaABUAWoAAgCKAAIB5CAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQiAFoAWgBaAC8AWgCiAZwAWgBaABUAWoAAgACAAIB5CAgICIAagD8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQiAFoAWgBaAC8AWgCiAZ0AWgBaABUAWoAAgCKAAIB5CAgICIAagEAICIAACNkAHwAjBMIADgAmBMMAIQBLBMQBYQGBAEwAawAVACcALwBaBMxfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAdoA2gAmALIAAgAQIgIXTADgAOQAOBM4E1gA+pwI7AjwCPQI+Aj8CQAJBgE2AToBPgFCAUYBSgFOnBNcE2ATZBNoE2wTcBN2AhoCHgIiAiYCLgIyAjYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCMAWgBaAFoALwBaAKICOwBaAFoAFQBagACAAIAAgIQICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCMAWgBaAFoALwBaAKICPABaAFoAFQBagACAIoAAgIQICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCMAWgBaAFoALwBaAKICPQBaAFoAFQBagACAAIAAgIQICAgIgBqATwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFDgAVBCMAWgBaAFoALwBaAKICPgBaAFoAFQBagACAioAAgIQICAgIgBqAUAgIgAAIEQK83xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCMAWgBaAFoALwBaAKICPwBaAFoAFQBagACAAIAAgIQICAgIgBqAUQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCMAWgBaAFoALwBaAKICQABaAFoAFQBagACAAIAAgIQICAgIgBqAUggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCMAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgIQICAgIgBqAUwgIgAAI3xASAJAAkQCSBUoAHwCUAJUFSwAhAJMFTACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoFVAAvAFoATABaAXcBXQBaAFoFXABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgJAIgAkIgF2AMQgIgI8IEpg/NzLTADgAOQAOBWAFYwA+ogGAAYGANYA2ogVkBWWAkYCcgCXZAB8AIwVoAA4AJgVpACEASwVqAWIBgABMAGsAFQAnAC8AWgVyXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgI6ANYAJgCyAAIAECICS0wA4ADkADgV0BX0APqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgFfgV/BYAFgQWCBYMFhAWFgJOAlICVgJeAmICZgJqAm4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWQAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgJEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWQAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgJEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFpwAVBWQAWgBaAFoALwBaAKIBmABaAFoAFQBagACAloAAgJEICAgIgBqAOwgIgAAI0wA4ADkADgW1BbYAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAkQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUFZABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAkQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAkQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAkQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAkQgICAiAGoBACAiAAAjZAB8AIwYEAA4AJgYFACEASwYGAWIBgQBMAGsAFQAnAC8AWgYOXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgI6ANoAJgCyAAIAECICd0wA4ADkADgYQBhgAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwYZBhoGGwYcBh0GHgYfgJ6An4CggKGAooCjgKWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAICcCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVlAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAICcCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAICcCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAoEAFQVlAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgFiAAICcCAgICIAagFAICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAICcCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBm4AFQVlAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgKSAAICcCAgICIAagFIICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAICcCAgICIAagFMICIAACFpkdXBsaWNhdGVz0gA5AA4GjQCqoIAZ0gCsAK0GkAaRWlhEUE1FbnRpdHmnBpIGkwaUBpUGlgaXALFaWERQTUVudGl0eV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4GmQaaAD6goIAl0wA4ADkADgadBp4APqCggCXTADgAOQAOBqEGogA+oKCAJdIArACtBqUGpl5YRE1vZGVsUGFja2FnZaYGpwaoBqkGqgarALFeWERNb2RlbFBhY2thZ2VfEA9YRFVNTFBhY2thZ2VJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0gA5AA4GrQCqoIAZ0wA4ADkADgawBrEAPqCggCVQ0gCsAK0GtQa2WVhEUE1Nb2RlbKMGtQa3ALFXWERNb2RlbAAIABkAIgAsADEAOgA/AFEAVgBbAF0BwgHIAeEB8wH6AggCFQItAkcCSQJLAk0CTwJRAlMCjAKrAsgC5wL5AxkDIAM+A0oDZgNsA44DrwPCA8QDxgPIA8oDzAPOA9AD0gPUA9YD2APaA9wD3gPfA+MD8AP4BAMEBgQIBAsEDQQPBCoEbQSRBLUE2AT/BR8FRgVtBY0FsQXVBeEF4wXlBecF6QXrBe0F7wXxBfMF9QX3BfkF+wX9Bf4GAwYLBhgGGwYdBiAGIgYkBjMGWAZ8BqMGxwbJBssGzQbPBtEG0wbUBtYG4wb2BvgG+gb8Bv4HAAcCBwQHBgcIBxsHHQcfByEHIwclBycHKQcrBy0HLwdFB1gHdAeRB60HwQfTB+kIAghBCEcIUAhdCGkIcwh9CIgIkwigCKgIqgisCK4IsAixCLIIswi0CLYIuAi5CLoIvAi9CMYIxwjJCNII3QjmCPUI/AkECQ0JFgkpCTIJRQlcCW4JrQmvCbEJswm1CbYJtwm4CbkJuwm9Cb4JvwnBCcIKAQoDCgUKBwoJCgoKCwoMCg0KDwoRChIKEwoVChYKHwogCiIKYQpjCmUKZwppCmoKawpsCm0KbwpxCnIKcwp1CnYKtQq3CrkKuwq9Cr4KvwrACsEKwwrFCsYKxwrJCsoK0wrUCtYLFQsXCxkLGwsdCx4LHwsgCyELIwslCyYLJwspCyoLKwtqC2wLbgtwC3ILcwt0C3ULdgt4C3oLewt8C34LfwuMC40LjguQC5kLrwu2C8MMAgwEDAYMCAwKDAsMDAwNDA4MEAwSDBMMFAwWDBcMMAwyDDQMNgw3DDkMUAxZDGcMdAyCDJcMqwzCDNQNEw0VDRcNGQ0bDRwNHQ0eDR8NIQ0jDSQNJQ0nDSgNQw1MDWENcA2FDZMNqA28DdMN5Q3yDfsN/Q3/DgEOAw4MDg4OEA4SDhQOFg4bDiUOKg45DoQOpw7HDucO6Q7rDu0O7w7xDvIO8w71DvYO+A75DvsO/Q7+Dv8PAQ8CDwcPFA8ZDxsPHQ8iDyQPJg8oDz0PUg93D5sPwg/mD+gP6g/sD+4P8A/yD/MP9RACEBMQFRAXEBkQGxAdEB8QIRAjEDQQNhA4EDoQPBA+EEAQQhBEEEYQZBCCEJUQqRC+ENsQ7xEFEUQRRhFIEUoRTBFNEU4RTxFQEVIRVBFVEVYRWBFZEZgRmhGcEZ4RoBGhEaIRoxGkEaYRqBGpEaoRrBGtEewR7hHwEfIR9BH1EfYR9xH4EfoR/BH9Ef4SABIBEg4SDxIQEhISURJTElUSVxJZEloSWxJcEl0SXxJhEmISYxJlEmYSpRKnEqkSqxKtEq4SrxKwErESsxK1ErYStxK5EroSuxL6EvwS/hMAEwITAxMEEwUTBhMIEwoTCxMMEw4TDxNOE1ATUhNUE1YTVxNYE1kTWhNcE14TXxNgE2ITYxOiE6QTphOoE6oTqxOsE60TrhOwE7ITsxO0E7YTtxPcFAAUJxRLFE0UTxRRFFMUVRRXFFgUWhRnFHYUeBR6FHwUfhSAFIIUhBSTFJUUlxSZFJsUnRSfFKEUoxTDFO4VCBUhFTsVWxV+Fb0VvxXBFcMVxRXGFccVyBXJFcsVzRXOFc8V0RXSFhEWExYVFhcWGRYaFhsWHBYdFh8WIRYiFiMWJRYmFmUWZxZpFmsWbRZuFm8WcBZxFnMWdRZ2FncWeRZ6FrkWuxa9Fr8WwRbCFsMWxBbFFscWyRbKFssWzRbOFtEXEBcSFxQXFhcYFxkXGhcbFxwXHhcgFyEXIhckFyUXZBdmF2gXahdsF20XbhdvF3AXchd0F3UXdhd4F3kXoBffF+EX4xflF+cX6BfpF+oX6xftF+8X8BfxF/MX9Bf9GAsYGBgmGDMYRhhdGG8YuhjdGP0ZHRkfGSEZIxklGScZKBkpGSsZLBkuGS8ZMRkzGTQZNRk3GTgZQRlOGVMZVRlXGVwZXhlgGWIZhxmrGdIZ9hn4GfoZ/Bn+GgAaAhoDGgUaEhojGiUaJxopGisaLRovGjEaMxpEGkYaSBpKGkwaThpQGlIaVBpWGpUalxqZGpsanRqeGp8aoBqhGqMapRqmGqcaqRqqGuka6xrtGu8a8RryGvMa9Br1Gvca+Rr6Gvsa/Rr+Gz0bPxtBG0MbRRtGG0cbSBtJG0sbTRtOG08bURtSG18bYBthG2MbohukG6YbqBuqG6sbrButG64bsBuyG7MbtBu2G7cb9hv4G/ob/Bv+G/8cABwBHAIcBBwGHAccCBwKHAscShxMHE4cUBxSHFMcVBxVHFYcWBxaHFscXBxeHF8cnhygHKIcpBymHKccqBypHKocrByuHK8csByyHLMc8hz0HPYc+Bz6HPsc/Bz9HP4dAB0CHQMdBB0GHQcdLB1QHXcdmx2dHZ8doR2jHaUdpx2oHaodtx3GHcgdyh3MHc4d0B3SHdQd4x3lHecd6R3rHe0d7x3xHfMeMh40HjYeOB46HjsePB49Hj4eQB5CHkMeRB5GHkcehh6IHooejB6OHo8ekB6RHpIelB6WHpcemB6aHpse2h7cHt4e4B7iHuMe5B7lHuYe6B7qHuse7B7uHu8fLh8wHzIfNB82HzcfOB85HzofPB8+Hz8fQB9CH0MfRh+FH4cfiR+LH40fjh+PH5AfkR+TH5Uflh+XH5kfmh/ZH9sf3R/fH+Ef4h/jH+Qf5R/nH+kf6h/rH+0f7iAtIC8gMSAzIDUgNiA3IDggOSA7ID0gPiA/IEEgQiCNILAg0CDwIPIg9CD2IPgg+iD7IPwg/iD/IQEhAiEEIQYhByEIIQohCyEQIR0hIiEkISYhKyEtIS8hMSFWIXohoSHFIcchySHLIc0hzyHRIdIh1CHhIfIh9CH2Ifgh+iH8If4iACICIhMiFSIXIhkiGyIdIh8iISIjIiUiZCJmImgiaiJsIm0ibiJvInAiciJ0InUidiJ4InkiuCK6IrwiviLAIsEiwiLDIsQixiLIIskiyiLMIs0jDCMOIxAjEiMUIxUjFiMXIxgjGiMcIx0jHiMgIyEjLiMvIzAjMiNxI3MjdSN3I3kjeiN7I3wjfSN/I4EjgiODI4UjhiPFI8cjySPLI80jziPPI9Aj0SPTI9Uj1iPXI9kj2iQZJBskHSQfJCEkIiQjJCQkJSQnJCkkKiQrJC0kLiRtJG8kcSRzJHUkdiR3JHgkeSR7JH0kfiR/JIEkgiTBJMMkxSTHJMkkyiTLJMwkzSTPJNEk0iTTJNUk1iT7JR8lRiVqJWwlbiVwJXIldCV2JXcleSWGJZUllyWZJZslnSWfJaEloyWyJbQltiW4JbolvCW+JcAlwiYBJgMmBSYHJgkmCiYLJgwmDSYPJhEmEiYTJhUmFiZVJlcmWSZbJl0mXiZfJmAmYSZjJmUmZiZnJmkmaiapJqsmrSavJrEmsiazJrQmtSa3Jrkmuia7Jr0mvib9Jv8nAScDJwUnBicHJwgnCScLJw0nDicPJxEnEicVJ1QnVidYJ1onXCddJ14nXydgJ2InZCdlJ2YnaCdpJ6gnqiesJ64nsCexJ7Insye0J7YnuCe5J7onvCe9J/wn/igAKAIoBCgFKAYoBygIKAooDCgNKA4oECgRKFwofyifKL8owSjDKMUoxyjJKMooyyjNKM4o0CjRKNMo1SjWKNco2SjaKN8o7CjxKPMo9Sj6KPwo/ikAKSUpSSlwKZQplimYKZopnCmeKaApoSmjKbApwSnDKcUpxynJKcspzSnPKdEp4inkKeYp6CnqKewp7inwKfIp9CozKjUqNyo5KjsqPCo9Kj4qPypBKkMqRCpFKkcqSCqHKokqiyqNKo8qkCqRKpIqkyqVKpcqmCqZKpsqnCrbKt0q3yrhKuMq5CrlKuYq5yrpKusq7CrtKu8q8Cr9Kv4q/ysBK0ArQitEK0YrSCtJK0orSytMK04rUCtRK1IrVCtVK5QrliuYK5ornCudK54rnyugK6IrpCulK6YrqCupK+gr6ivsK+4r8CvxK/Ir8yv0K/Yr+Cv5K/or/Cv9LDwsPixALEIsRCxFLEYsRyxILEosTCxNLE4sUCxRLJAskiyULJYsmCyZLJosmyycLJ4soCyhLKIspCylLMos7i0VLTktOy09LT8tQS1DLUUtRi1ILVUtZC1mLWgtai1sLW4tcC1yLYEtgy2FLYctiS2LLY0tjy2RLdAt0i3ULdYt2C3ZLdot2y3cLd4t4C3hLeIt5C3lLiQuJi4oLiouLC4tLi4uLy4wLjIuNC41LjYuOC45Lnguei58Ln4ugC6BLoIugy6ELoYuiC6JLooujC6NLswuzi7QLtIu1C7VLtYu1y7YLtou3C7dLt4u4C7hLyAvIi8kLyYvKC8pLyovKy8sLy4vMC8xLzIvNC81L3Qvdi94L3ovfC99L34vfy+AL4IvhC+FL4YviC+JL7Av7y/xL/Mv9S/3L/gv+S/6L/sv/S//MAAwATADMAQwDzAYMBkwGzAkMC8wPjBJMFcwbDCAMJcwqTC2MLcwuDC6MMcwyDDJMMsw2DDZMNow3DDlMPQxATEQMSIxNjFNMV8xaDFpMWsxeDF5MXoxfDF9MYYxkDGXAAAAAAAAAgIAAAAAAAAGuAAAAAAAAAAAAAAAAAAAMZ8=\n</attribute>\n        <relationship name=\"entitymappings\" type=\"0/0\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z106\"></relationship>\n    </object>\n</database>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UARemoteDataMappingV3toV4.xcmappingmodel/xcmapping.xml",
    "content": "<?xml version=\"1.0\" standalone=\"yes\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n    <databaseInfo>\n        <version>134481920</version>\n        <UUID>C445BE09-B38D-431E-87B7-5EDA490CBA9E</UUID>\n        <nextObjectID>107</nextObjectID>\n        <metadata>\n            <plist version=\"1.0\">\n                <dict>\n                    <key>NSPersistenceFrameworkVersion</key>\n                    <integer>1344</integer>\n                    <key>NSStoreModelVersionChecksumKey</key>\n                    <string>bMpud663vz0bXQE24C6Rh4MvJ5jVnzsD2sI3njZkKbc=</string>\n                    <key>NSStoreModelVersionHashes</key>\n                    <dict>\n                        <key>XDDevAttributeMapping</key>\n                        <data>\n\t\t0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=\n\t\t</data>\n                        <key>XDDevEntityMapping</key>\n                        <data>\n\t\tqeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=\n\t\t</data>\n                        <key>XDDevMappingModel</key>\n                        <data>\n\t\tEqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=\n\t\t</data>\n                        <key>XDDevPropertyMapping</key>\n                        <data>\n\t\tXN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA=\n\t\t</data>\n                        <key>XDDevRelationshipMapping</key>\n                        <data>\n\t\takYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs=\n\t\t</data>\n                    </dict>\n                    <key>NSStoreModelVersionHashesDigest</key>\n                    <string>+Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A==</string>\n                    <key>NSStoreModelVersionHashesVersion</key>\n                    <integer>3</integer>\n                    <key>NSStoreModelVersionIdentifiers</key>\n                    <array>\n                        <string></string>\n                    </array>\n                </dict>\n            </plist>\n        </metadata>\n    </databaseInfo>\n    <object type=\"XDDEVENTITYMAPPING\" id=\"z102\">\n        <attribute name=\"migrationpolicyclassname\" type=\"string\">UARemoteDataMappingV3toV4</attribute>\n        <attribute name=\"sourcename\" type=\"string\">UARemoteDataStorePayload</attribute>\n        <attribute name=\"mappingtypename\" type=\"string\">Undefined</attribute>\n        <attribute name=\"mappingnumber\" type=\"int16\">1</attribute>\n        <attribute name=\"destinationname\" type=\"string\">UARemoteDataStorePayload</attribute>\n        <attribute name=\"autogenerateexpression\" type=\"bool\">1</attribute>\n        <relationship name=\"mappingmodel\" type=\"1/1\" destination=\"XDDEVMAPPINGMODEL\" idrefs=\"z105\"></relationship>\n        <relationship name=\"attributemappings\" type=\"0/0\" destination=\"XDDEVATTRIBUTEMAPPING\" idrefs=\"z104 z103 z107\"></relationship>\n        <relationship name=\"relationshipmappings\" type=\"0/0\" destination=\"XDDEVRELATIONSHIPMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z103\">\n        <attribute name=\"name\" type=\"string\">type</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z102\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z104\">\n        <attribute name=\"name\" type=\"string\">timestamp</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z102\"></relationship>\n    </object>\n    <object type=\"XDDEVMAPPINGMODEL\" id=\"z105\">\n        <attribute name=\"sourcemodelpath\" type=\"string\">AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 3.xcdatamodel</attribute>\n        <attribute name=\"sourcemodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCyAAsADAAZADUANgA3AD8AQABbAFwAXQBjAGQAcACGAIcAiACJAIoAiwCMAI0AjgCPAKgAqwCyALgAxwDWANkA6AD3APoAWgEKARkBHQEhATABNgE3AT8BTgFPAVgBZAFlAWYBZwFoAX0BfgGGAYcBiAGUAagBqQGqAasBrAGtAa4BrwGwAb8BzgHdAeEB8AH/AgACDwIeAi0COQJLAkwCTQJOAk8CUAJRAlICYQJwAn8CjgKPAp4CrQKuAr0CxQLaAtsC4wLvAwMDEgMhAzADNANDA1IDYQNwA38DiwOdA6wDuwPKA9kD2gPpA/gEBwQcBB0EJQQxBEUEVARjBHIEdgSFBJQEowSyBMEEzQTfBO4E/QUMBRsFHAUrBToFSQVeBV8FZwVzBYcFlgWlBbQFuAXHBdYF5QX0BgMGDwYhBjAGPwZOBl0GXgZtBnwGfQaMBo0GkAaZBp0GoQalBq0GsAa0BrVVJG51bGzWAA0ADgAPABAAEQASABMAFAAVABYAFwAYXxAPX3hkX3Jvb3RQYWNrYWdlViRjbGFzc11feGRfbW9kZWxOYW1lXF94ZF9jb21tZW50c18QFV9jb25maWd1cmF0aW9uc0J5TmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKAsYAAgK6Ar4Cw3gAaABsAHAAdAB4AHwAgAA4AIQAiACMAJAAlACYAJwAoACkACQAnABUALQAuAC8AMAAxACcAJwAVXxAcWERCdWNrZXRGb3JDbGFzc2Vzd2FzRW5jb2RlZF8QGlhEQnVja2V0Rm9yUGFja2FnZXNzdG9yYWdlXxAcWERCdWNrZXRGb3JJbnRlcmZhY2Vzc3RvcmFnZV8QD194ZF9vd25pbmdNb2RlbF8QHVhEQnVja2V0Rm9yUGFja2FnZXN3YXNFbmNvZGVkVl9vd25lcl8QG1hEQnVja2V0Rm9yRGF0YVR5cGVzc3RvcmFnZVtfdmlzaWJpbGl0eV8QGVhEQnVja2V0Rm9yQ2xhc3Nlc3N0b3JhZ2VVX25hbWVfEB9YREJ1Y2tldEZvckludGVyZmFjZXN3YXNFbmNvZGVkXxAeWERCdWNrZXRGb3JEYXRhVHlwZXN3YXNFbmNvZGVkXxAQX3VuaXF1ZUVsZW1lbnRJRIAEgKyAqoABgASAAICrgK0QAIAFgAOABIAEgABQU1lFU9MAOAA5AA4AOgA8AD5XTlMua2V5c1pOUy5vYmplY3RzoQA7gAahAD2AB4AlXxAYVUFSZW1vdGVEYXRhU3RvcmVQYXlsb2Fk3xAQAEEAQgBDAEQAHwBFAEYAIQBHAEgADgAjAEkASgAmAEsATABNACcAJwATAFEAUgAvACcATABVADsATABYAFkAWl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgC2ABIAEgAKACoCngASACYCpgAaACYCogAgIElEqgV5Xb3JkZXJlZNMAOAA5AA4AXgBgAD6hAF+AC6EAYYAMgCVeWERfUFN0ZXJlb3R5cGXZAB8AIwBlAA4AJgBmACEASwBnAD0AXwBMAGsAFQAnAC8AWgBvXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCyAAIAECIAN0wA4ADkADgBxAHsAPqkAcgBzAHQAdQB2AHcAeAB5AHqADoAPgBCAEYASgBOAFIAVgBapAHwAfQB+AH8AgACBAIIAgwCEgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHNfEBpYRF9QU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QGVhEX1BTS19mZXRjaFJlcXVlc3RzQXJyYXlfEBFYRF9QU0tfaXNBYnN0cmFjdF8QD1hEX1BTS191c2VySW5mb18QE1hEX1BTS19jbGFzc01hcHBpbmdfEBZYRF9QU0tfZW50aXR5Q2xhc3NOYW1l3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAmwAVAGEAWgBaAFoALwBaAKIAcgBaAFoAFQBaVV90eXBlWF9kZWZhdWx0XF9hc3NvY2lhdGlvbltfaXNSZWFkT25seVlfaXNTdGF0aWNZX2lzVW5pcXVlWl9pc0Rlcml2ZWRaX2lzT3JkZXJlZFxfaXNDb21wb3NpdGVXX2lzTGVhZoAAgBiAAIAMCAgICIAagA4ICIAACNIAOQAOAKkAqqCAGdIArACtAK4Ar1okY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owCuALAAsVdOU0FycmF5WE5TT2JqZWN00gCsAK0AswC0XxAQWERVTUxQcm9wZXJ0eUltcKQAtQC2ALcAsV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHMAWgBaABUAWoAAgACAAIAMCAgICIAagA8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAMkAFQBhAFoAWgBaAC8AWgCiAHQAWgBaABUAWoAAgB2AAIAMCAgICIAagBAICIAACNIAOQAOANcAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHUAWgBaABUAWoAAgACAAIAMCAgICIAagBEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAOoAFQBhAFoAWgBaAC8AWgCiAHYAWgBaABUAWoAAgCCAAIAMCAgICIAagBIICIAACNIAOQAOAPgAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQBhAFoAWgBaAC8AWgCiAHcAWgBaABUAWoAAgCKAAIAMCAgICIAagBMICIAACAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEMABUAYQBaAFoAWgAvAFoAogB4AFoAWgAVAFqAAIAkgACADAgICAiAGoAUCAiAAAjTADgAOQAOARoBGwA+oKCAJdIArACtAR4BH18QE05TTXV0YWJsZURpY3Rpb25hcnmjAR4BIACxXE5TRGljdGlvbmFyed8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVASMAFQBhAFoAWgBaAC8AWgCiAHkAWgBaABUAWoAAgCeAAIAMCAgICIAagBUICIAACNYAIwAOACYASwAfACEBMQEyABUAWgAVAC+AKIApgAAIgABfEBRYREdlbmVyaWNSZWNvcmRDbGFzc9IArACtATgBOV1YRFVNTENsYXNzSW1wpgE6ATsBPAE9AT4AsV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAUEAFQBhAFoAWgBaAC8AWgCiAHoAWgBaABUAWoAAgCuAAIAMCAgICIAagBYICIAACF8QGFVBUmVtb3RlRGF0YVN0b3JlUGF5bG9hZNIArACtAVABUV8QElhEVU1MU3RlcmVvdHlwZUltcKcBUgFTAVQBVQFWAVcAsV8QElhEVU1MU3RlcmVvdHlwZUltcF1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4BWQFeAD6kAVoBWwFcAV2ALoAvgDCAMaQBXwFgAWEBYoAygF6AdoCOgCVUZGF0YVl0aW1lc3RhbXBUdHlwZV5yZW1vdGVEYXRhSW5mb98QEgCQAJEAkgFpAB8AlACVAWoAIQCTAWsAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAXMALwBaAEwAWgF3AVoAWgBaAXsAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIA0CIAJCIBdgC4ICIAzCBJf0eCy0wA4ADkADgF/AYIAPqIBgAGBgDWANqIBgwGEgDeAS4AlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAfACMBiQAOACYBigAhAEsBiwFfAYAATABrABUAJwAvAFoBk18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAygDWACYAsgACABAiAONMAOAA5AA4BlQGeAD6oAZYBlwGYAZkBmgGbAZwBnYA5gDqAO4A8gD2APoA/gECoAZ8BoAGhAaIBowGkAaUBpoBBgEKAQ4BFgEaASIBJgEqAJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFNLX2lzSW5kZXhlZF8QElhEX1BQU0tfaXNPcHRpb25hbF8QGlhEX1BQU0tfaXNTcG90bGlnaHRJbmRleGVkXxARWERfUFBTS19lbGVtZW50SURfEBNYRF9QUFNLX2lzVHJhbnNpZW503xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgDcICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYMAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgDcICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB0AAVAYMAWgBaAFoALwBaAKIBmABaAFoAFQBagACARIAAgDcICAgIgBqAOwgIgAAI0wA4ADkADgHeAd8APqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBgwBaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACANwgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUBgwBaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACANwgICAiAGoA9CAiAAAgJ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBmwBaAFoAFQBagACAIoAAgDcICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYMAWgBaAFoALwBaAKIBnABaAFoAFQBagACAAIAAgDcICAgIgBqAPwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBnQBaAFoAFQBagACAIoAAgDcICAgIgBqAQAgIgAAI2QAfACMCLgAOACYCLwAhAEsCMAFfAYEATABrABUAJwAvAFoCOF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAygDaACYAsgACABAiATNMAOAA5AA4COgJCAD6nAjsCPAI9Aj4CPwJAAkGATYBOgE+AUIBRgFKAU6cCQwJEAkUCRgJHAkgCSYBUgFWAVoBXgFmAWoBcgCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBhABaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIAAgACASwgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBhABaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAigACASwgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBhABaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACASwgICAiAGoBPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKBABUBhABaAFoAWgAvAFoAogI+AFoAWgAVAFqAAIBYgACASwgICAiAGoBQCAiAAAgRBwjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBhABaAFoAWgAvAFoAogI/AFoAWgAVAFqAAIAAgACASwgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKgABUBhABaAFoAWgAvAFoAogJAAFoAWgAVAFqAAIBbgACASwgICAiAGoBSCAiAAAhfEBZVQUpTT05WYWx1ZVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgEsICAgIgBqAUwgIgAAI0gCsAK0CvgK/XVhEUE1BdHRyaWJ1dGWmAsACwQLCAsMCxACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAsYAHwCUAJUCxwAhAJMCyACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC0AAvAFoATABaAXcBWwBaAFoC2ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGAIgAkIgF2ALwgIgF8IEsAq6njTADgAOQAOAtwC3wA+ogGAAYGANYA2ogLgAuGAYYBsgCXZAB8AIwLkAA4AJgLlACEASwLmAWABgABMAGsAFQAnAC8AWgLuXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANYAJgCyAAIAECIBi0wA4ADkADgLwAvkAPqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgC+gL7AvwC/QL+Av8DAAMBgGOAZIBlgGeAaIBpgGqAa4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgGEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAuAAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgGEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDIwAVAuAAWgBaAFoALwBaAKIBmABaAFoAFQBagACAZoAAgGEICAgIgBqAOwgIgAAI0wA4ADkADgMxAzIAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAYQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUC4ABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAYQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAYQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4ABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAYQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAYQgICAiAGoBACAiAAAjZAB8AIwOAAA4AJgOBACEASwOCAWABgQBMAGsAFQAnAC8AWgOKXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANoAJgCyAAIAECIBt0wA4ADkADgOMA5QAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwOVA5YDlwOYA5kDmgObgG6Ab4BwgHGAc4B0gHWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAIBsCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLhAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAIBsCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAIBsCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA8wAFQLhAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgHKAAIBsCAgICIAagFAICIAACBEDhN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAIBsCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgACAAIBsCAgICIAagFIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAIBsCAgICIAagFMICIAACN8QEgCQAJEAkgQIAB8AlACVBAkAIQCTBAoAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBBIALwBaAEwAWgF3AVwAWgBaBBoAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIB4CIAJCIBdgDAICIB3CBJPmzpt0wA4ADkADgQeBCEAPqIBgAGBgDWANqIEIgQjgHmAhIAl2QAfACMEJgAOACYEJwAhAEsEKAFhAYAATABrABUAJwAvAFoEMF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB2gDWACYAsgACABAiAetMAOAA5AA4EMgQ7AD6oAZYBlwGYAZkBmgGbAZwBnYA5gDqAO4A8gD2APoA/gECoBDwEPQQ+BD8EQARBBEIEQ4B7gHyAfYB/gICAgYCCgIOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQiAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIB5CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQiAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIB5CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBGUAFQQiAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgH6AAIB5CAgICIAagDsICIAACNMAOAA5AA4EcwR0AD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgHkICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVBCIAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgHkICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBmwBaAFoAFQBagACAIoAAgHkICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCIAWgBaAFoALwBaAKIBnABaAFoAFQBagACAAIAAgHkICAgIgBqAPwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBnQBaAFoAFQBagACAIoAAgHkICAgIgBqAQAgIgAAI2QAfACMEwgAOACYEwwAhAEsExAFhAYEATABrABUAJwAvAFoEzF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB2gDaACYAsgACABAiAhdMAOAA5AA4EzgTWAD6nAjsCPAI9Aj4CPwJAAkGATYBOgE+AUIBRgFKAU6cE1wTYBNkE2gTbBNwE3YCGgIeAiICJgIuAjICNgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIAAgACAhAgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIwBaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAigACAhAgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACAhAgICAiAGoBPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQUOABUEIwBaAFoAWgAvAFoAogI+AFoAWgAVAFqAAICKgACAhAgICAiAGoBQCAiAAAgRArzfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI/AFoAWgAVAFqAAIAAgACAhAgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogJAAFoAWgAVAFqAAIAAgACAhAgICAiAGoBSCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogJBAFoAWgAVAFqAAIAAgACAhAgICAiAGoBTCAiAAAjfEBIAkACRAJIFSgAfAJQAlQVLACEAkwVMAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgVUAC8AWgBMAFoBdwFdAFoAWgVcAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAkAiACQiAXYAxCAiAjwgTAAAAAQ6ISzDTADgAOQAOBWAFYwA+ogGAAYGANYA2ogVkBWWAkYCcgCXZAB8AIwVoAA4AJgVpACEASwVqAWIBgABMAGsAFQAnAC8AWgVyXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgI6ANYAJgCyAAIAECICS0wA4ADkADgV0BX0APqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgFfgV/BYAFgQWCBYMFhAWFgJOAlICVgJeAmICZgJqAm4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWQAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgJEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWQAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgJEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFpwAVBWQAWgBaAFoALwBaAKIBmABaAFoAFQBagACAloAAgJEICAgIgBqAOwgIgAAI0wA4ADkADgW1BbYAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAkQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUFZABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAkQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAkQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAkQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAkQgICAiAGoBACAiAAAjZAB8AIwYEAA4AJgYFACEASwYGAWIBgQBMAGsAFQAnAC8AWgYOXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgI6ANoAJgCyAAIAECICd0wA4ADkADgYQBhgAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwYZBhoGGwYcBh0GHgYfgJ6An4CggKGAo4CkgKaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAICcCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVlAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAICcCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAICcCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBlAAFQVlAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgKKAAICcCAgICIAagFAICIAACBED6N8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAICcCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBm8AFQVlAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgKWAAICcCAgICIAagFIICIAACF8QHlVBTlNEaWN0aW9uYXJ5VmFsdWVUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVlAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAICcCAgICIAagFMICIAACFpkdXBsaWNhdGVz0gA5AA4GjgCqoIAZ0gCsAK0GkQaSWlhEUE1FbnRpdHmnBpMGlAaVBpYGlwaYALFaWERQTUVudGl0eV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4GmgabAD6goIAl0wA4ADkADgaeBp8APqCggCXTADgAOQAOBqIGowA+oKCAJdIArACtBqYGp15YRE1vZGVsUGFja2FnZaYGqAapBqoGqwasALFeWERNb2RlbFBhY2thZ2VfEA9YRFVNTFBhY2thZ2VJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0gA5AA4GrgCqoIAZ0wA4ADkADgaxBrIAPqCggCVQ0gCsAK0Gtga3WVhEUE1Nb2RlbKMGtga4ALFXWERNb2RlbAAIABkAIgAsADEAOgA/AFEAVgBbAF0BxAHKAeMB9QH8AgoCFwIvAkkCSwJNAk8CUQJTAlUCjgKtAsoC6QL7AxsDIgNAA0wDaANuA5ADsQPEA8YDyAPKA8wDzgPQA9ID1APWA9gD2gPcA94D4APhA+UD8gP6BAUECAQKBA0EDwQRBCwEbwSTBLcE2gUBBSEFSAVvBY8FswXXBeMF5QXnBekF6wXtBe8F8QXzBfUF9wX5BfsF/QX/BgAGBQYNBhoGHQYfBiIGJAYmBjUGWgZ+BqUGyQbLBs0GzwbRBtMG1QbWBtgG5Qb4BvoG/Ab+BwAHAgcEBwYHCAcKBx0HHwchByMHJQcnBykHKwctBy8HMQdHB1oHdgeTB68HwwfVB+sIBAhDCEkIUghfCGsIdQh/CIoIlQiiCKoIrAiuCLAIsgizCLQItQi2CLgIugi7CLwIvgi/CMgIyQjLCNQI3wjoCPcI/gkGCQ8JGAkrCTQJRwleCXAJrwmxCbMJtQm3CbgJuQm6CbsJvQm/CcAJwQnDCcQKAwoFCgcKCQoLCgwKDQoOCg8KEQoTChQKFQoXChgKIQoiCiQKYwplCmcKaQprCmwKbQpuCm8KcQpzCnQKdQp3CngKtwq5CrsKvQq/CsAKwQrCCsMKxQrHCsgKyQrLCswK1QrWCtgLFwsZCxsLHQsfCyALIQsiCyMLJQsnCygLKQsrCywLLQtsC24LcAtyC3QLdQt2C3cLeAt6C3wLfQt+C4ALgQuOC48LkAuSC5sLsQu4C8UMBAwGDAgMCgwMDA0MDgwPDBAMEgwUDBUMFgwYDBkMMgw0DDYMOAw5DDsMUgxbDGkMdgyEDJkMrQzEDNYNFQ0XDRkNGw0dDR4NHw0gDSENIw0lDSYNJw0pDSoNRQ1ODWMNcg2HDZUNqg2+DdUN5w30Df0N/w4BDgMOBQ4ODhAOEg4UDhYOGA4dDicOLA47DoYOqQ7JDukO6w7tDu8O8Q7zDvQO9Q73DvgO+g77Dv0O/w8ADwEPAw8EDwkPFg8bDx0PHw8kDyYPKA8qDz8PVA95D50PxA/oD+oP7A/uD/AP8g/0D/UP9xAEEBUQFxAZEBsQHRAfECEQIxAlEDYQOBA6EDwQPhBAEEIQRBBGEEgQZhCEEJcQqxDAEN0Q8REHEUYRSBFKEUwRThFPEVARURFSEVQRVhFXEVgRWhFbEZoRnBGeEaARohGjEaQRpRGmEagRqhGrEawRrhGvEe4R8BHyEfQR9hH3EfgR+RH6EfwR/hH/EgASAhIDEhASERISEhQSUxJVElcSWRJbElwSXRJeEl8SYRJjEmQSZRJnEmgSpxKpEqsSrRKvErASsRKyErMStRK3ErgSuRK7ErwSvRL8Ev4TABMCEwQTBRMGEwcTCBMKEwwTDRMOExATERNQE1ITVBNWE1gTWRNaE1sTXBNeE2ATYRNiE2QTZROkE6YTqBOqE6wTrROuE68TsBOyE7QTtRO2E7gTuRPeFAIUKRRNFE8UURRTFFUUVxRZFFoUXBRpFHgUehR8FH4UgBSCFIQUhhSVFJcUmRSbFJ0UnxShFKMUpRTFFPAVChUjFT0VXRWAFb8VwRXDFcUVxxXIFckVyhXLFc0VzxXQFdEV0xXUFhMWFRYXFhkWGxYcFh0WHhYfFiEWIxYkFiUWJxYoFmcWaRZrFm0WbxZwFnEWchZzFnUWdxZ4FnkWexZ8FrsWvRa/FsEWwxbEFsUWxhbHFskWyxbMFs0WzxbQFtMXEhcUFxYXGBcaFxsXHBcdFx4XIBciFyMXJBcmFycXZhdoF2oXbBduF28XcBdxF3IXdBd2F3cXeBd6F3sXlBfTF9UX1xfZF9sX3BfdF94X3xfhF+MX5BflF+cX6BfxF/8YDBgaGCcYOhhRGGMYrhjRGPEZERkTGRUZFxkZGRsZHBkdGR8ZIBkiGSMZJRknGSgZKRkrGSwZMRk+GUMZRRlHGUwZThlQGVIZdxmbGcIZ5hnoGeoZ7BnuGfAZ8hnzGfUaAhoTGhUaFxoZGhsaHRofGiEaIxo0GjYaOBo6GjwaPhpAGkIaRBpGGoUahxqJGosajRqOGo8akBqRGpMalRqWGpcamRqaGtka2xrdGt8a4RriGuMa5BrlGuca6RrqGusa7RruGy0bLxsxGzMbNRs2GzcbOBs5GzsbPRs+Gz8bQRtCG08bUBtRG1MbkhuUG5YbmBuaG5sbnBudG54boBuiG6MbpBumG6cb5hvoG+ob7BvuG+8b8BvxG/Ib9Bv2G/cb+Bv6G/scOhw8HD4cQBxCHEMcRBxFHEYcSBxKHEscTBxOHE8cjhyQHJIclByWHJccmByZHJocnByeHJ8coByiHKMc4hzkHOYc6BzqHOsc7BztHO4c8BzyHPMc9Bz2HPcdHB1AHWcdix2NHY8dkR2THZUdlx2YHZodpx22Hbgduh28Hb4dwB3CHcQd0x3VHdcd2R3bHd0d3x3hHeMeIh4kHiYeKB4qHiseLB4tHi4eMB4yHjMeNB42Hjcedh54HnoefB5+Hn8egB6BHoIehB6GHoceiB6KHoseyh7MHs4e0B7SHtMe1B7VHtYe2B7aHtse3B7eHt8fHh8gHyIfJB8mHycfKB8pHyofLB8uHy8fMB8yHzMfNh91H3cfeR97H30ffh9/H4AfgR+DH4Ufhh+HH4kfih/JH8sfzR/PH9Ef0h/TH9Qf1R/XH9kf2h/bH90f3iAdIB8gISAjICUgJiAnICggKSArIC0gLiAvIDEgMiB9IKAgwCDgIOIg5CDmIOgg6iDrIOwg7iDvIPEg8iD0IPYg9yD4IPog+yEAIQ0hEiEUIRYhGyEdIR8hISFGIWohkSG1IbchuSG7Ib0hvyHBIcIhxCHRIeIh5CHmIegh6iHsIe4h8CHyIgMiBSIHIgkiCyINIg8iESITIhUiVCJWIlgiWiJcIl0iXiJfImAiYiJkImUiZiJoImkiqCKqIqwiriKwIrEisiKzIrQitiK4IrkiuiK8Ir0i/CL+IwAjAiMEIwUjBiMHIwgjCiMMIw0jDiMQIxEjHiMfIyAjIiNhI2MjZSNnI2kjaiNrI2wjbSNvI3EjciNzI3UjdiO1I7cjuSO7I70jviO/I8AjwSPDI8UjxiPHI8kjyiQJJAskDSQPJBEkEiQTJBQkFSQXJBkkGiQbJB0kHiRdJF8kYSRjJGUkZiRnJGgkaSRrJG0kbiRvJHEkciSxJLMktSS3JLkkuiS7JLwkvSS/JMEkwiTDJMUkxiTrJQ8lNiVaJVwlXiVgJWIlZCVmJWclaSV2JYUlhyWJJYsljSWPJZElkyWiJaQlpiWoJaolrCWuJbAlsiXxJfMl9SX3Jfkl+iX7Jfwl/SX/JgEmAiYDJgUmBiZFJkcmSSZLJk0mTiZPJlAmUSZTJlUmViZXJlkmWiaZJpsmnSafJqEmoiajJqQmpSanJqkmqiarJq0mribtJu8m8SbzJvUm9ib3Jvgm+Sb7Jv0m/ib/JwEnAicFJ0QnRidIJ0onTCdNJ04nTydQJ1InVCdVJ1YnWCdZJ5gnmiecJ54noCehJ6InoyekJ6YnqCepJ6onrCetJ+wn7ifwJ/In9Cf1J/Yn9yf4J/on/Cf9J/4oACgBKEwobyiPKK8osSizKLUotyi5KLoouyi9KL4owCjBKMMoxSjGKMcoySjKKNMo4CjlKOco6SjuKPAo8ij0KRkpPSlkKYgpiimMKY4pkCmSKZQplSmXKaQptSm3Kbkpuym9Kb8pwSnDKcUp1inYKdop3CneKeAp4inkKeYp6ConKikqKyotKi8qMCoxKjIqMyo1KjcqOCo5KjsqPCp7Kn0qfyqBKoMqhCqFKoYqhyqJKosqjCqNKo8qkCrPKtEq0yrVKtcq2CrZKtoq2yrdKt8q4CrhKuMq5CrxKvIq8yr1KzQrNis4KzorPCs9Kz4rPytAK0IrRCtFK0YrSCtJK4griiuMK44rkCuRK5IrkyuUK5YrmCuZK5ornCudK9wr3ivgK+Ir5CvlK+Yr5yvoK+or7CvtK+4r8CvxLDAsMiw0LDYsOCw5LDosOyw8LD4sQCxBLEIsRCxFLIQshiyILIosjCyNLI4sjyyQLJIslCyVLJYsmCyZLL4s4i0JLS0tLy0xLTMtNS03LTktOi08LUktWC1aLVwtXi1gLWItZC1mLXUtdy15LXstfS1/LYEtgy2FLcQtxi3ILcotzC3NLc4tzy3QLdIt1C3VLdYt2C3ZLhguGi4cLh4uIC4hLiIuIy4kLiYuKC4pLiouLC4tLmwubi5wLnIudC51LnYudy54LnoufC59Ln4ugC6BLsAuwi7ELsYuyC7JLsouyy7MLs4u0C7RLtIu1C7VLtgvFy8ZLxsvHS8fLyAvIS8iLyMvJS8nLygvKS8rLywvay9tL28vcS9zL3QvdS92L3cveS97L3wvfS9/L4AvoS/gL+Iv5C/mL+gv6S/qL+sv7C/uL/Av8S/yL/Qv9TAAMAkwCjAMMBUwIDAvMDowSDBdMHEwiDCaMKcwqDCpMKswuDC5MLowvDDJMMowyzDNMNYw5TDyMQExEzEnMT4xUDFZMVoxXDFpMWoxazFtMW4xdzGBMYgAAAAAAAACAgAAAAAAAAa5AAAAAAAAAAAAAAAAAAAxkA==\n</attribute>\n        <attribute name=\"destinationmodelpath\" type=\"string\">AirshipCore/Resources/UARemoteData.xcdatamodeld/UARemoteData 4.xcdatamodel</attribute>\n        <attribute name=\"destinationmodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxCxAAsADAAZADUANgA3AD8AQABbAFwAXQBjAGQAcACGAIcAiACJAIoAiwCMAI0AjgCPAKgAqwCyALgAxwDWANkA6AD3APoAWgEKARkBHQEhATABNgE3AT8BTgFPAVgBZAFlAWYBZwFoAX0BfgGGAYcBiAGUAagBqQGqAasBrAGtAa4BrwGwAb8BzgHdAeEB8AH/AgACDwIeAi0COQJLAkwCTQJOAk8CUAJRAlICYQJwAn8CjgKPAp4CrQKuAr0CxQLaAtsC4wLvAwMDEgMhAzADNANDA1IDYQNwA38DiwOdA6wDuwPKA9kD2gPpA/gEBwQcBB0EJQQxBEUEVARjBHIEdgSFBJQEowSyBMEEzQTfBO4E/QUMBRsFHAUrBToFSQVeBV8FZwVzBYcFlgWlBbQFuAXHBdYF5QX0BgMGDwYhBjAGPwZOBl0GbAZ7BnwGiwaMBo8GmAacBqAGpAasBq8Gswa0VSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgLCAAICtgK6Ar94AGgAbABwAHQAeAB8AIAAOACEAIgAjACQAJQAmACcAKAApAAkAJwAVAC0ALgAvADAAMQAnACcAFV8QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABICrgKmAAYAEgACAqoCsEACABYADgASABIAAUFNZRVPTADgAOQAOADoAPAA+V05TLmtleXNaTlMub2JqZWN0c6EAO4AGoQA9gAeAJV8QGFVBUmVtb3RlRGF0YVN0b3JlUGF5bG9hZN8QEABBAEIAQwBEAB8ARQBGACEARwBIAA4AIwBJAEoAJgBLAEwATQAnACcAEwBRAFIALwAnAEwAVQA7AEwAWABZAFpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAtgASABIACgAqApoAEgAmAqIAGgAmAp4AICBLFzDydV29yZGVyZWTTADgAOQAOAF4AYAA+oQBfgAuhAGGADIAlXlhEX1BTdGVyZW90eXBl2QAfACMAZQAOACYAZgAhAEsAZwA9AF8ATABrABUAJwAvAFoAb18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAsgACABAiADdMAOAA5AA4AcQB7AD6pAHIAcwB0AHUAdgB3AHgAeQB6gA6AD4AQgBGAEoATgBSAFYAWqQB8AH0AfgB/AIAAgQCCAIMAhIAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAJsAFQBhAFoAWgBaAC8AWgCiAHIAWgBaABUAWlVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADkADgCpAKqggBnSAKwArQCuAK9aJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMArgCwALFXTlNBcnJheVhOU09iamVjdNIArACtALMAtF8QEFhEVU1MUHJvcGVydHlJbXCkALUAtgC3ALFfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogBzAFoAWgAVAFqAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDJABUAYQBaAFoAWgAvAFoAogB0AFoAWgAVAFqAAIAdgACADAgICAiAGoAQCAiAAAjSADkADgDXAKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogB1AFoAWgAVAFqAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDqABUAYQBaAFoAWgAvAFoAogB2AFoAWgAVAFqAAIAggACADAgICAiAGoASCAiAAAjSADkADgD4AKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUAYQBaAFoAWgAvAFoAogB3AFoAWgAVAFqAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBDAAVAGEAWgBaAFoALwBaAKIAeABaAFoAFQBagACAJIAAgAwICAgIgBqAFAgIgAAI0wA4ADkADgEaARsAPqCggCXSAKwArQEeAR9fEBNOU011dGFibGVEaWN0aW9uYXJ5owEeASAAsVxOU0RpY3Rpb25hcnnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEjABUAYQBaAFoAWgAvAFoAogB5AFoAWgAVAFqAAIAngACADAgICAiAGoAVCAiAAAjWACMADgAmAEsAHwAhATEBMgAVAFoAFQAvgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKwArQE4ATldWERVTUxDbGFzc0ltcKYBOgE7ATwBPQE+ALFdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQFBABUAYQBaAFoAWgAvAFoAogB6AFoAWgAVAFqAAIArgACADAgICAiAGoAWCAiAAAhfEBhVQVJlbW90ZURhdGFTdG9yZVBheWxvYWTSAKwArQFQAVFfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVIBUwFUAVUBVgFXALFfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOAVkBXgA+pAFaAVsBXAFdgC6AL4AwgDGkAV8BYAFhAWKAMoBegHaAjoAlVGRhdGFZdGltZXN0YW1wVHR5cGVecmVtb3RlRGF0YUluZm/fEBIAkACRAJIBaQAfAJQAlQFqACEAkwFrAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgFzAC8AWgBMAFoBdwFaAFoAWgF7AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiANAiACQiAXYAuCAiAMwgSq8MRztMAOAA5AA4BfwGCAD6iAYABgYA1gDaiAYMBhIA3gEuAJV8QElhEX1BQcm9wU3RlcmVvdHlwZV8QElhEX1BBdHRfU3RlcmVvdHlwZdkAHwAjAYkADgAmAYoAIQBLAYsBXwGAAEwAawAVACcALwBaAZNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA1gAmALIAAgAQIgDjTADgAOQAOAZUBngA+qAGWAZcBmAGZAZoBmwGcAZ2AOYA6gDuAPIA9gD6AP4BAqAGfAaABoQGiAaMBpAGlAaaAQYBCgEOARYBGgEiASYBKgCVfEBtYRF9QUFNLX2lzU3RvcmVkSW5UcnV0aEZpbGVfEBtYRF9QUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBBYRF9QUFNLX3VzZXJJbmZvXxARWERfUFBTS19pc0luZGV4ZWRfEBJYRF9QUFNLX2lzT3B0aW9uYWxfEBpYRF9QUFNLX2lzU3BvdGxpZ2h0SW5kZXhlZF8QEVhEX1BQU0tfZWxlbWVudElEXxATWERfUFBTS19pc1RyYW5zaWVudN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIA3CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIA3CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAdAAFQGDAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgESAAIA3CAgICIAagDsICIAACNMAOAA5AA4B3gHfAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYMAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgDcICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVAYMAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgDcICAgIgBqAPQgIgAAICd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZsAWgBaABUAWoAAgCKAAIA3CAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGDAFoAWgBaAC8AWgCiAZwAWgBaABUAWoAAgACAAIA3CAgICIAagD8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGDAFoAWgBaAC8AWgCiAZ0AWgBaABUAWoAAgCKAAIA3CAgICIAagEAICIAACNkAHwAjAi4ADgAmAi8AIQBLAjABXwGBAEwAawAVACcALwBaAjhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAMoA2gAmALIAAgAQIgEzTADgAOQAOAjoCQgA+pwI7AjwCPQI+Aj8CQAJBgE2AToBPgFCAUYBSgFOnAkMCRAJFAkYCRwJIAkmAVIBVgFaAV4BZgFqAXIAlXxAdWERfUEF0dEtfZGVmYXVsdFZhbHVlQXNTdHJpbmdfEChYRF9QQXR0S19hbGxvd3NFeHRlcm5hbEJpbmFyeURhdGFTdG9yYWdlXxAXWERfUEF0dEtfbWluVmFsdWVTdHJpbmdfEBZYRF9QQXR0S19hdHRyaWJ1dGVUeXBlXxAXWERfUEF0dEtfbWF4VmFsdWVTdHJpbmdfEB1YRF9QQXR0S192YWx1ZVRyYW5zZm9ybWVyTmFtZV8QIFhEX1BBdHRLX3JlZ3VsYXJFeHByZXNzaW9uU3RyaW5n3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICOwBaAFoAFQBagACAAIAAgEsICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAYQAWgBaAFoALwBaAKICPABaAFoAFQBagACAIoAAgEsICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPQBaAFoAFQBagACAAIAAgEsICAgIgBqATwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCgQAVAYQAWgBaAFoALwBaAKICPgBaAFoAFQBagACAWIAAgEsICAgIgBqAUAgIgAAIEQPo3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICPwBaAFoAFQBagACAAIAAgEsICAgIgBqAUQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCoAAVAYQAWgBaAFoALwBaAKICQABaAFoAFQBagACAW4AAgEsICAgIgBqAUggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAYQAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgEsICAgIgBqAUwgIgAAI0gCsAK0CvgK/XVhEUE1BdHRyaWJ1dGWmAsACwQLCAsMCxACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAsYAHwCUAJUCxwAhAJMCyACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC0AAvAFoATABaAXcBWwBaAFoC2ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGAIgAkIgF2ALwgIgF8IEjHQDrvTADgAOQAOAtwC3wA+ogGAAYGANYA2ogLgAuGAYYBsgCXZAB8AIwLkAA4AJgLlACEASwLmAWABgABMAGsAFQAnAC8AWgLuXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANYAJgCyAAIAECIBi0wA4ADkADgLwAvkAPqgBlgGXAZgBmQGaAZsBnAGdgDmAOoA7gDyAPYA+gD+AQKgC+gL7AvwC/QL+Av8DAAMBgGOAZIBlgGeAaIBpgGqAa4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAuAAWgBaAFoALwBaAKIBlgBaAFoAFQBagACAIoAAgGEICAgIgBqAOQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAuAAWgBaAFoALwBaAKIBlwBaAFoAFQBagACAAIAAgGEICAgIgBqAOggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDIwAVAuAAWgBaAFoALwBaAKIBmABaAFoAFQBagACAZoAAgGEICAgIgBqAOwgIgAAI0wA4ADkADgMxAzIAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGZAFoAWgAVAFqAAIAigACAYQgICAiAGoA8CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHyABUC4ABaAFoAWgAvAFoAogGaAFoAWgAVAFqAAIBHgACAYQgICAiAGoA9CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGbAFoAWgAVAFqAAIAigACAYQgICAiAGoA+CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC4ABaAFoAWgAvAFoAogGcAFoAWgAVAFqAAIAAgACAYQgICAiAGoA/CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC4ABaAFoAWgAvAFoAogGdAFoAWgAVAFqAAIAigACAYQgICAiAGoBACAiAAAjZAB8AIwOAAA4AJgOBACEASwOCAWABgQBMAGsAFQAnAC8AWgOKXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgF6ANoAJgCyAAIAECIBt0wA4ADkADgOMA5QAPqcCOwI8Aj0CPgI/AkACQYBNgE6AT4BQgFGAUoBTpwOVA5YDlwOYA5kDmgObgG6Ab4BwgHGAc4B0gHWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAjsAWgBaABUAWoAAgACAAIBsCAgICIAagE0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQLhAFoAWgBaAC8AWgCiAjwAWgBaABUAWoAAgCKAAIBsCAgICIAagE4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj0AWgBaABUAWoAAgACAAIBsCAgICIAagE8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA8wAFQLhAFoAWgBaAC8AWgCiAj4AWgBaABUAWoAAgHKAAIBsCAgICIAagFAICIAACBEDhN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAj8AWgBaABUAWoAAgACAAIBsCAgICIAagFEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAkAAWgBaABUAWoAAgACAAIBsCAgICIAagFIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQLhAFoAWgBaAC8AWgCiAkEAWgBaABUAWoAAgACAAIBsCAgICIAagFMICIAACN8QEgCQAJEAkgQIAB8AlACVBAkAIQCTBAoAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBBIALwBaAEwAWgF3AVwAWgBaBBoAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIB4CIAJCIBdgDAICIB3CBJfEZy90wA4ADkADgQeBCEAPqIBgAGBgDWANqIEIgQjgHmAhIAl2QAfACMEJgAOACYEJwAhAEsEKAFhAYAATABrABUAJwAvAFoEMF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB2gDWACYAsgACABAiAetMAOAA5AA4EMgQ7AD6oAZYBlwGYAZkBmgGbAZwBnYA5gDqAO4A8gD2APoA/gECoBDwEPQQ+BD8EQARBBEIEQ4B7gHyAfYB/gICAgYCCgIOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQiAFoAWgBaAC8AWgCiAZYAWgBaABUAWoAAgCKAAIB5CAgICIAagDkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQiAFoAWgBaAC8AWgCiAZcAWgBaABUAWoAAgACAAIB5CAgICIAagDoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBGUAFQQiAFoAWgBaAC8AWgCiAZgAWgBaABUAWoAAgH6AAIB5CAgICIAagDsICIAACNMAOAA5AA4EcwR0AD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBmQBaAFoAFQBagACAIoAAgHkICAgIgBqAPAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB8gAVBCIAWgBaAFoALwBaAKIBmgBaAFoAFQBagACAR4AAgHkICAgIgBqAPQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBmwBaAFoAFQBagACAIoAAgHkICAgIgBqAPggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBCIAWgBaAFoALwBaAKIBnABaAFoAFQBagACAAIAAgHkICAgIgBqAPwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBCIAWgBaAFoALwBaAKIBnQBaAFoAFQBagACAIoAAgHkICAgIgBqAQAgIgAAI2QAfACMEwgAOACYEwwAhAEsExAFhAYEATABrABUAJwAvAFoEzF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYB2gDaACYAsgACABAiAhdMAOAA5AA4EzgTWAD6nAjsCPAI9Aj4CPwJAAkGATYBOgE+AUIBRgFKAU6cE1wTYBNkE2gTbBNwE3YCGgIeAiICJgIuAjICNgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI7AFoAWgAVAFqAAIAAgACAhAgICAiAGoBNCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEIwBaAFoAWgAvAFoAogI8AFoAWgAVAFqAAIAigACAhAgICAiAGoBOCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI9AFoAWgAVAFqAAIAAgACAhAgICAiAGoBPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQUOABUEIwBaAFoAWgAvAFoAogI+AFoAWgAVAFqAAICKgACAhAgICAiAGoBQCAiAAAgRArzfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogI/AFoAWgAVAFqAAIAAgACAhAgICAiAGoBRCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogJAAFoAWgAVAFqAAIAAgACAhAgICAiAGoBSCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEIwBaAFoAWgAvAFoAogJBAFoAWgAVAFqAAIAAgACAhAgICAiAGoBTCAiAAAjfEBIAkACRAJIFSgAfAJQAlQVLACEAkwVMAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgVUAC8AWgBMAFoBdwFdAFoAWgVcAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAkAiACQiAXYAxCAiAjwgSXEtGotMAOAA5AA4FYAVjAD6iAYABgYA1gDaiBWQFZYCRgJyAJdkAHwAjBWgADgAmBWkAIQBLBWoBYgGAAEwAawAVACcALwBaBXJfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAjoA1gAmALIAAgAQIgJLTADgAOQAOBXQFfQA+qAGWAZcBmAGZAZoBmwGcAZ2AOYA6gDuAPIA9gD6AP4BAqAV+BX8FgAWBBYIFgwWEBYWAk4CUgJWAl4CYgJmAmoCbgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFZABaAFoAWgAvAFoAogGWAFoAWgAVAFqAAIAigACAkQgICAiAGoA5CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFZABaAFoAWgAvAFoAogGXAFoAWgAVAFqAAIAAgACAkQgICAiAGoA6CAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQWnABUFZABaAFoAWgAvAFoAogGYAFoAWgAVAFqAAICWgACAkQgICAiAGoA7CAiAAAjTADgAOQAOBbUFtgA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVkAFoAWgBaAC8AWgCiAZkAWgBaABUAWoAAgCKAAICRCAgICIAagDwICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAfIAFQVkAFoAWgBaAC8AWgCiAZoAWgBaABUAWoAAgEeAAICRCAgICIAagD0ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVkAFoAWgBaAC8AWgCiAZsAWgBaABUAWoAAgCKAAICRCAgICIAagD4ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQVkAFoAWgBaAC8AWgCiAZwAWgBaABUAWoAAgACAAICRCAgICIAagD8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQVkAFoAWgBaAC8AWgCiAZ0AWgBaABUAWoAAgCKAAICRCAgICIAagEAICIAACNkAHwAjBgQADgAmBgUAIQBLBgYBYgGBAEwAawAVACcALwBaBg5fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAjoA2gAmALIAAgAQIgJ3TADgAOQAOBhAGGAA+pwI7AjwCPQI+Aj8CQAJBgE2AToBPgFCAUYBSgFOnBhkGGgYbBhwGHQYeBh+AnoCfgKCAoYCigKOApYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWUAWgBaAFoALwBaAKICOwBaAFoAFQBagACAAIAAgJwICAgIgBqATQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBWUAWgBaAFoALwBaAKICPABaAFoAFQBagACAIoAAgJwICAgIgBqATggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWUAWgBaAFoALwBaAKICPQBaAFoAFQBagACAAIAAgJwICAgIgBqATwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCgQAVBWUAWgBaAFoALwBaAKICPgBaAFoAFQBagACAWIAAgJwICAgIgBqAUAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWUAWgBaAFoALwBaAKICPwBaAFoAFQBagACAAIAAgJwICAgIgBqAUQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUGbgAVBWUAWgBaAFoALwBaAKICQABaAFoAFQBagACApIAAgJwICAgIgBqAUggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBWUAWgBaAFoALwBaAKICQQBaAFoAFQBagACAAIAAgJwICAgIgBqAUwgIgAAIWmR1cGxpY2F0ZXPSADkADgaNAKqggBnSAKwArQaQBpFaWERQTUVudGl0eacGkgaTBpQGlQaWBpcAsVpYRFBNRW50aXR5XVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADgaZBpoAPqCggCXTADgAOQAOBp0GngA+oKCAJdMAOAA5AA4GoQaiAD6goIAl0gCsAK0GpQamXlhETW9kZWxQYWNrYWdlpganBqgGqQaqBqsAsV5YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADkADgatAKqggBnTADgAOQAOBrAGsQA+oKCAJVDSAKwArQa1BrZZWERQTU1vZGVsowa1BrcAsVdYRE1vZGVsAAgAGQAiACwAMQA6AD8AUQBWAFsAXQHCAcgB4QHzAfoCCAIVAi0CRwJJAksCTQJPAlECUwKMAqsCyALnAvkDGQMgAz4DSgNmA2wDjgOvA8IDxAPGA8gDygPMA84D0APSA9QD1gPYA9oD3APeA98D4wPwA/gEAwQGBAgECwQNBA8EKgRtBJEEtQTYBP8FHwVGBW0FjQWxBdUF4QXjBeUF5wXpBesF7QXvBfEF8wX1BfcF+QX7Bf0F/gYDBgsGGAYbBh0GIAYiBiQGMwZYBnwGowbHBskGywbNBs8G0QbTBtQG1gbjBvYG+Ab6BvwG/gcABwIHBAcGBwgHGwcdBx8HIQcjByUHJwcpBysHLQcvB0UHWAd0B5EHrQfBB9MH6QgCCEEIRwhQCF0IaQhzCH0IiAiTCKAIqAiqCKwIrgiwCLEIsgizCLQItgi4CLkIugi8CL0IxgjHCMkI0gjdCOYI9Qj8CQQJDQkWCSkJMglFCVwJbgmtCa8JsQmzCbUJtgm3CbgJuQm7Cb0Jvgm/CcEJwgoBCgMKBQoHCgkKCgoLCgwKDQoPChEKEgoTChUKFgofCiAKIgphCmMKZQpnCmkKagprCmwKbQpvCnEKcgpzCnUKdgq1CrcKuQq7Cr0Kvgq/CsAKwQrDCsUKxgrHCskKygrTCtQK1gsVCxcLGQsbCx0LHgsfCyALIQsjCyULJgsnCykLKgsrC2oLbAtuC3ALcgtzC3QLdQt2C3gLegt7C3wLfgt/C4wLjQuOC5ALmQuvC7YLwwwCDAQMBgwIDAoMCwwMDA0MDgwQDBIMEwwUDBYMFwwwDDIMNAw2DDcMOQxQDFkMZwx0DIIMlwyrDMIM1A0TDRUNFw0ZDRsNHA0dDR4NHw0hDSMNJA0lDScNKA1DDUwNYQ1wDYUNkw2oDbwN0w3lDfIN+w39Df8OAQ4DDgwODg4QDhIOFA4WDhsOJQ4qDjkOhA6nDscO5w7pDusO7Q7vDvEO8g7zDvUO9g74DvkO+w79Dv4O/w8BDwIPBw8UDxkPGw8dDyIPJA8mDygPPQ9SD3cPmw/CD+YP6A/qD+wP7g/wD/IP8w/1EAIQExAVEBcQGRAbEB0QHxAhECMQNBA2EDgQOhA8ED4QQBBCEEQQRhBkEIIQlRCpEL4Q2xDvEQURRBFGEUgRShFMEU0RThFPEVARUhFUEVURVhFYEVkRmBGaEZwRnhGgEaERohGjEaQRphGoEakRqhGsEa0R7BHuEfAR8hH0EfUR9hH3EfgR+hH8Ef0R/hIAEgESDhIPEhASEhJRElMSVRJXElkSWhJbElwSXRJfEmESYhJjEmUSZhKlEqcSqRKrEq0SrhKvErASsRKzErUSthK3ErkSuhK7EvoS/BL+EwATAhMDEwQTBRMGEwgTChMLEwwTDhMPE04TUBNSE1QTVhNXE1gTWRNaE1wTXhNfE2ATYhNjE6ITpBOmE6gTqhOrE6wTrROuE7ATshOzE7QTthO3E9wUABQnFEsUTRRPFFEUUxRVFFcUWBRaFGcUdhR4FHoUfBR+FIAUghSEFJMUlRSXFJkUmxSdFJ8UoRSjFMMU7hUIFSEVOxVbFX4VvRW/FcEVwxXFFcYVxxXIFckVyxXNFc4VzxXRFdIWERYTFhUWFxYZFhoWGxYcFh0WHxYhFiIWIxYlFiYWZRZnFmkWaxZtFm4WbxZwFnEWcxZ1FnYWdxZ5FnoWuRa7Fr0WvxbBFsIWwxbEFsUWxxbJFsoWyxbNFs4W0RcQFxIXFBcWFxgXGRcaFxsXHBceFyAXIRciFyQXJRdkF2YXaBdqF2wXbRduF28XcBdyF3QXdRd2F3gXeRegF98X4RfjF+UX5xfoF+kX6hfrF+0X7xfwF/EX8xf0F/0YCxgYGCYYMxhGGF0Ybxi6GN0Y/RkdGR8ZIRkjGSUZJxkoGSkZKxksGS4ZLxkxGTMZNBk1GTcZOBk9GUoZTxlRGVMZWBlaGVwZXhmDGacZzhnyGfQZ9hn4GfoZ/Bn+Gf8aARoOGh8aIRojGiUaJxopGisaLRovGkAaQhpEGkYaSBpKGkwaThpQGlIakRqTGpUalxqZGpoamxqcGp0anxqhGqIaoxqlGqYa5RrnGuka6xrtGu4a7xrwGvEa8xr1GvYa9xr5GvobORs7Gz0bPxtBG0IbQxtEG0UbRxtJG0obSxtNG04bWxtcG10bXxueG6AbohukG6YbpxuoG6kbqhusG64brxuwG7IbsxvyG/Qb9hv4G/ob+xv8G/0b/hwAHAIcAxwEHAYcBxxGHEgcShxMHE4cTxxQHFEcUhxUHFYcVxxYHFocWxyaHJwcnhygHKIcoxykHKUcphyoHKocqxysHK4crxzuHPAc8hz0HPYc9xz4HPkc+hz8HP4c/x0AHQIdAx0oHUwdcx2XHZkdmx2dHZ8doR2jHaQdph2zHcIdxB3GHcgdyh3MHc4d0B3fHeEd4x3lHecd6R3rHe0d7x4uHjAeMh40HjYeNx44HjkeOh48Hj4ePx5AHkIeQx6CHoQehh6IHooeix6MHo0ejh6QHpIekx6UHpYelx7WHtge2h7cHt4e3x7gHuEe4h7kHuYe5x7oHuoe6x8qHywfLh8wHzIfMx80HzUfNh84HzofOx88Hz4fPx9CH4Efgx+FH4cfiR+KH4sfjB+NH48fkR+SH5MflR+WH9Uf1x/ZH9sf3R/eH98f4B/hH+Mf5R/mH+cf6R/qICkgKyAtIC8gMSAyIDMgNCA1IDcgOSA6IDsgPSA+IIkgrCDMIOwg7iDwIPIg9CD2IPcg+CD6IPsg/SD+IQAhAiEDIQQhBiEHIQwhGSEeISAhIiEnISkhKyEtIVIhdiGdIcEhwyHFIcchySHLIc0hziHQId0h7iHwIfIh9CH2Ifgh+iH8If4iDyIRIhMiFSIXIhkiGyIdIh8iISJgImIiZCJmImgiaSJqImsibCJuInAicSJyInQidSK0IrYiuCK6IrwivSK+Ir8iwCLCIsQixSLGIsgiySMIIwojDCMOIxAjESMSIxMjFCMWIxgjGSMaIxwjHSMqIysjLCMuI20jbyNxI3MjdSN2I3cjeCN5I3sjfSN+I38jgSOCI8EjwyPFI8cjySPKI8sjzCPNI88j0SPSI9Mj1SPWJBUkFyQZJBskHSQeJB8kICQhJCMkJSQmJCckKSQqJGkkayRtJG8kcSRyJHMkdCR1JHckeSR6JHskfSR+JL0kvyTBJMMkxSTGJMckyCTJJMskzSTOJM8k0STSJPclGyVCJWYlaCVqJWwlbiVwJXIlcyV1JYIlkSWTJZUllyWZJZslnSWfJa4lsCWyJbQltiW4JbolvCW+Jf0l/yYBJgMmBSYGJgcmCCYJJgsmDSYOJg8mESYSJlEmUyZVJlcmWSZaJlsmXCZdJl8mYSZiJmMmZSZmJqUmpyapJqsmrSauJq8msCaxJrMmtSa2JrcmuSa6Jvkm+yb9Jv8nAScCJwMnBCcFJwcnCScKJwsnDScOJxEnUCdSJ1QnVidYJ1knWidbJ1wnXidgJ2EnYidkJ2UnpCemJ6gnqiesJ60nrievJ7Ansie0J7Untie4J7kn+Cf6J/wn/igAKAEoAigDKAQoBigIKAkoCigMKA0oWCh7KJsouyi9KL8owSjDKMUoxijHKMkoyijMKM0ozyjRKNIo0yjVKNYo2yjoKO0o7yjxKPYo+Cj6KPwpISlFKWwpkCmSKZQplimYKZopnCmdKZ8prCm9Kb8pwSnDKcUpxynJKcspzSneKeAp4inkKeYp6CnqKewp7inwKi8qMSozKjUqNyo4KjkqOio7Kj0qPypAKkEqQypEKoMqhSqHKokqiyqMKo0qjiqPKpEqkyqUKpUqlyqYKtcq2SrbKt0q3yrgKuEq4irjKuUq5yroKukq6yrsKvkq+ir7Kv0rPCs+K0ArQitEK0UrRitHK0grSitMK00rTitQK1ErkCuSK5QrliuYK5krmiubK5wrniugK6EroiukK6Ur5CvmK+gr6ivsK+0r7ivvK/Ar8iv0K/Ur9iv4K/ksOCw6LDwsPixALEEsQixDLEQsRixILEksSixMLE0sjCyOLJAskiyULJUsliyXLJgsmiycLJ0sniygLKEsxizqLREtNS03LTktOy09LT8tQS1CLUQtUS1gLWItZC1mLWgtai1sLW4tfS1/LYEtgy2FLYctiS2LLY0tzC3OLdAt0i3ULdUt1i3XLdgt2i3cLd0t3i3gLeEuIC4iLiQuJi4oLikuKi4rLiwuLi4wLjEuMi40LjUudC52Lnguei58Ln0ufi5/LoAugi6ELoUuhi6ILokuyC7KLswuzi7QLtEu0i7TLtQu1i7YLtku2i7cLt0vHC8eLyAvIi8kLyUvJi8nLygvKi8sLy0vLi8wLzEvcC9yL3Qvdi94L3kvei97L3wvfi+AL4Evgi+EL4UvrC/rL+0v7y/xL/Mv9C/1L/Yv9y/5L/sv/C/9L/8wADALMBQwFTAXMCAwKzA6MEUwUzBoMHwwkzClMLIwszC0MLYwwzDEMMUwxzDUMNUw1jDYMOEw8DD9MQwxHjEyMUkxWzFkMWUxZzF0MXUxdjF4MXkxgjGMMZMAAAAAAAACAgAAAAAAAAa4AAAAAAAAAAAAAAAAAAAxmw==\n</attribute>\n        <relationship name=\"entitymappings\" type=\"0/0\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z102\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z106\">\n        <attribute name=\"name\" type=\"string\">remoteDataInfo</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z107\">\n        <attribute name=\"name\" type=\"string\">data</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z102\"></relationship>\n    </object>\n</database>"
  },
  {
    "path": "Airship/AirshipCore/Resources/UAirshipCache.xcdatamodeld/UAAirshipCache.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22222\" systemVersion=\"22G120\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Objective-C\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAirshipCacheData\" representedClassName=\"UAirshipCacheData\" syncable=\"YES\">\n        <attribute name=\"appVersion\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"expiry\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"key\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"sdkVersion\" optional=\"YES\" attributeType=\"String\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipCore/Resources/af.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Kanselleer\";\n\"ua_cancel_edit_messages_description\" = \"Kanselleer boodskapwysigings\";\n\"ua_connection_error\" = \"Konneksiefout\";\n\"ua_content_error\" = \"Inhoudfout\";\n\"ua_delete_message\" = \"Skrap boodskap\";\n\"ua_delete_message_description\" = \"Skrap geselekteerde boodskappe\";\n\"ua_delete_messages\" = \"Skrap boodskap\";\n\"ua_delete_messages_description\" = \"Skrap geselekteerde boodskappe\";\n\"ua_edit_messages\" = \"Wysig\";\n\"ua_edit_messages_description\" = \"Wysig boodskappe\";\n\"ua_empty_message_list\" = \"Geen boodskappe nie\";\n\"ua_mark_messages_read\" = \"Merk Lees\";\n\"ua_mark_messages_read_description\" = \"Merk geselekteerde boodskappe as gelees\";\n\"ua_mc_failed_to_load\" = \"Kan nie boodskap laai nie. Probeer asseblief weer later.\";\n\"ua_mc_no_longer_available\" = \"Die geselekteerde boodskap is nie meer beskikbaar nie.\";\n\"ua_message_cell_description\" = \"Wys volledige boodskap\";\n\"ua_message_cell_editing_description\" = \"Wissel seleksie\";\n\"ua_message_center_title\" = \"Boodskapsentrum\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Boodskap %@, gestuur om %@\";\n\"ua_message_not_selected\" = \"Geen boodskappe gekies nie\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Ongeleesde boodskap %@, gestuur om %@\";\n\"ua_notification_button_accept\" = \"Aanvaar\";\n\"ua_notification_button_add\" = \"Voeg by\";\n\"ua_notification_button_add_to_calendar\" = \"Voeg by Kalender\";\n\"ua_notification_button_book_now\" = \"Bespreek nou\";\n\"ua_notification_button_buy_now\" = \"Koop nou\";\n\"ua_notification_button_copy\" = \"Kopieer\";\n\"ua_notification_button_decline\" = \"Weier\";\n\"ua_notification_button_dislike\" = \"Hou nie van nie\";\n\"ua_notification_button_download\" = \"Aflaai\";\n\"ua_notification_button_follow\" = \"Volg\";\n\"ua_notification_button_less_like\" = \"Minder soos hierdie\";\n\"ua_notification_button_like\" = \"Hou van\";\n\"ua_notification_button_more_like\" = \"Meer soos hierdie\";\n\"ua_notification_button_no\" = \"Geen\";\n\"ua_notification_button_opt_in\" = \"Teken in\";\n\"ua_notification_button_opt_out\" = \"Onttrek\";\n\"ua_notification_button_rate_now\" = \"Beoordeel nou\";\n\"ua_notification_button_remind\" = \"Herinner my later\";\n\"ua_notification_button_save\" = \"Stoor\";\n\"ua_notification_button_search\" = \"Soek\";\n\"ua_notification_button_send_info\" = \"Stuur inligting\";\n\"ua_notification_button_share\" = \"Deel\";\n\"ua_notification_button_shop_now\" = \"Koop dit nou\";\n\"ua_notification_button_tell_me_more\" = \"Vertel my meer\";\n\"ua_notification_button_unfollow\" = \"Ontvolg\";\n\"ua_notification_button_yes\" = \"Ja\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Voorkeursentrum\";\n\"ua_retry_button\" = \"Probeer weer\";\n\"ua_select_all_messages\" = \"Kies Alles\";\n\"ua_select_all_messages_description\" = \"Kies alle boodskappe\";\n\"ua_select_none_messages\" = \"Kies Geen\";\n\"ua_select_none_messages_description\" = \"Stel boodskapkeuse terug\";\n\"ua_unread_message_description\" = \"Boodskap ongelees\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Laat gaan\";\n\"ua_escape\" = \"Ontsnap\";\n\"ua_next\" = \"Volgende\";\n\"ua_previous\" = \"Vorige\";\n\"ua_submit\" = \"Indien\";\n\"ua_loading\" = \"Laai\";\n\"ua_pager_progress\" = \"Bladsy %@ van %@\";\n\"ua_x_of_y\" = \"%@ van %@\";\n\"ua_play\" = \"Speel\";\n\"ua_pause\" = \"Laat wag\";\n\"ua_stop\" = \"Hou op\";\n\"ua_form_processing_error\" = \"Fout met die verwerking van vorm. Probeer asseblief weer\";\n\"ua_close\" = \"Sluit\";\n\"ua_mute\" = \"Demp\";\n\"ua_unmute\" = \"Ontdemp\";\n\"ua_required_field\" = \"* Verplig\";\n\"ua_invalid_form_message\" = \"Herstel asseblief die ongeldige velde om voort te gaan\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/am.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"ሰርዝ\";\n\"ua_cancel_edit_messages_description\" = \"የመልእክት አርትዖቶችን ሰርዝ\";\n\"ua_connection_error\" = \"የግንኙነት ስህተት\";\n\"ua_content_error\" = \"የይዘት ስህተት\";\n\"ua_delete_message\" = \"መልእክት ሰርዝ\";\n\"ua_delete_message_description\" = \"የተመረጡ መልዕክቶችን ሰርዝ\";\n\"ua_delete_messages\" = \"መልእክት ሰርዝ\";\n\"ua_delete_messages_description\" = \"የተመረጡ መልዕክቶችን ሰርዝ\";\n\"ua_edit_messages\" = \"አርትዕ\";\n\"ua_edit_messages_description\" = \"መልዕክቶችን ያርትዑ\";\n\"ua_empty_message_list\" = \"ምንም መልዕክቶች የሉም\";\n\"ua_mark_messages_read\" = \"መነበቡን አመልክት\";\n\"ua_mark_messages_read_description\" = \"የተመረጡትን መልዕክቶች እንደተነበቡ ምልክት ያድርጉ\";\n\"ua_mc_failed_to_load\" = \"መልእክት መጫን አልተቻለም። እባክዎ ቆየት ብለው ይሞክሩ.\";\n\"ua_mc_no_longer_available\" = \"የተመረጠው መልእክት ከአሁን በኋላ አይገኝም።\";\n\"ua_message_cell_description\" = \"ሙሉ መልእክት ያሳያል\";\n\"ua_message_cell_editing_description\" = \"ምርጫ ይቀያየራል።\";\n\"ua_message_center_title\" = \"የመልእክት ማእከል\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"መልዕክት %@፣ %@ ላይ ተልኳል\";\n\"ua_message_not_selected\" = \"ምንም መልዕክቶች አልተመረጡም።\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"ያልተነበበ መልዕክት %@፣ %@ ላይ ተልኳል\";\n\"ua_notification_button_accept\" = \"ተቀበል\";\n\"ua_notification_button_add\" = \"አክል\";\n\"ua_notification_button_add_to_calendar\" = \"ወደ ቀን መቁጠሪያ ያክሉ\";\n\"ua_notification_button_book_now\" = \"አሁን ያዝ\";\n\"ua_notification_button_buy_now\" = \"አሁን ይግዙ\";\n\"ua_notification_button_copy\" = \"ቅዳ\";\n\"ua_notification_button_decline\" = \"አትቀበል\";\n\"ua_notification_button_dislike\" = \"አለመውደድ\";\n\"ua_notification_button_download\" = \"አውርድ\";\n\"ua_notification_button_follow\" = \"ተከተል\";\n\"ua_notification_button_less_like\" = \"እንደዚህ ያነሰ\";\n\"ua_notification_button_like\" = \"መውደድ\";\n\"ua_notification_button_more_like\" = \"ተጨማሪ እንደዚህ\";\n\"ua_notification_button_no\" = \"አይ\";\n\"ua_notification_button_opt_in\" = \"መርጦ መግባት\";\n\"ua_notification_button_opt_out\" = \"መርጦ ውጣ\";\n\"ua_notification_button_rate_now\" = \"አሁን ደረጃ ይስጡ\";\n\"ua_notification_button_remind\" = \"በኋላ አስታውሰኝ\";\n\"ua_notification_button_save\" = \"አስቀምጥ\";\n\"ua_notification_button_search\" = \"ፈልግ\";\n\"ua_notification_button_send_info\" = \"መረጃ ላክ\";\n\"ua_notification_button_share\" = \"አጋራ\";\n\"ua_notification_button_shop_now\" = \"አሁን ይሸምቱ\";\n\"ua_notification_button_tell_me_more\" = \"ተጨማሪ ንገረኝ\";\n\"ua_notification_button_unfollow\" = \"አትከተል\";\n\"ua_notification_button_yes\" = \"አዎ\";\n\"ua_ok\" = \"እሺ\";\n\"ua_preference_center_title\" = \"ምርጫ ማዕከል\";\n\"ua_retry_button\" = \"እንደገና ይሞክሩ\";\n\"ua_select_all_messages\" = \"ሁሉንም ይምረጡ\";\n\"ua_select_all_messages_description\" = \"ሁሉንም መልዕክቶች ምረጥ\";\n\"ua_select_none_messages\" = \"ምንምን ይምረጡ\";\n\"ua_select_none_messages_description\" = \"የመልእክት ምርጫን ዳግም አስጀምር\";\n\"ua_unread_message_description\" = \"መልእክት አልተነበበም።\";\n\n// Generic localizations\n\"ua_dismiss\" = \"አሳርፍ\";\n\"ua_escape\" = \"መሻገሪያ\";\n\"ua_next\" = \"ሚቃኝ\";\n\"ua_previous\" = \"ቀዳሚ\";\n\"ua_submit\" = \"አስገባ\";\n\"ua_loading\" = \"በመጫን ላይ\";\n\"ua_pager_progress\" = \"ገጽ %@ ከ %@\";\n\"ua_x_of_y\" = \"%@ ከ %@\";\n\"ua_play\" = \"አጫውት\";\n\"ua_pause\" = \"ለአጭር ጊዜ ቆም\";\n\"ua_stop\" = \"አቁም\";\n\"ua_form_processing_error\" = \"ቅጹን በማስኬድ ላይ ስህተት። እባክዎ እንደገና ይሞክሩ\";\n\"ua_close\" = \"ዝጋ\";\n\"ua_mute\" = \"ድምፅ አጥፋ\";\n\"ua_unmute\" = \"ድምፅ አብራ\";\n\"ua_required_field\" = \"* አስፈላጊ\";\n\"ua_invalid_form_message\" = \"ለመቀጠል እባክዎን ልክ ያልሆኑ መስኮችን ያስተካክሉ\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ar.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"إلغاء\";\n\"ua_cancel_edit_messages_description\" = \"إلغاء تعديلات الرسائل\";\n\"ua_connection_error\" = \"خطأ في الإتصال\";\n\"ua_content_error\" = \"خطأ في المحتوى\";\n\"ua_delete_message\" = \"حذف رسالة\";\n\"ua_delete_message_description\" = \"حذف الرسائل المختارة\";\n\"ua_delete_messages\" = \"حذف رسالة\";\n\"ua_delete_messages_description\" = \"حذف الرسائل المختارة\";\n\"ua_edit_messages\" = \"يحرر\";\n\"ua_edit_messages_description\" = \"تحرير الرسائل\";\n\"ua_empty_message_list\" = \"لا توجد رسائل\";\n\"ua_mark_messages_read\" = \"اجعلها مقروءة\";\n\"ua_mark_messages_read_description\" = \"وضع علامة \\\"مقروءة\\\" على الرسائل المحددة\";\n\"ua_mc_failed_to_load\" = \"غير قادر على تحميل الرسالة. الرجاء معاودة المحاولة في وقت لاحق.\";\n\"ua_mc_no_longer_available\" = \"الرسالة المحددة لم تعد متوفرة.\";\n\"ua_message_cell_description\" = \"يعرض الرسالة كاملة\";\n\"ua_message_cell_editing_description\" = \"يبدل التحديد\";\n\"ua_message_center_title\" = \"مركز الرسائل\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"تم إرسال الرسالة %@ في %@\";\n\"ua_message_not_selected\" = \"لم يتم تحديد رسائل\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"رسالة غير مقروءة %@، أُرسلت في %@\";\n\"ua_notification_button_accept\" = \"قبول\";\n\"ua_notification_button_add\" = \"إضافة\";\n\"ua_notification_button_add_to_calendar\" = \"إضافة إلى التقويم\";\n\"ua_notification_button_book_now\" = \"احجز الآن\";\n\"ua_notification_button_buy_now\" = \"اشتري الآن\";\n\"ua_notification_button_copy\" = \"نسخ\";\n\"ua_notification_button_decline\" = \"رفض\";\n\"ua_notification_button_dislike\" = \"إلغاء إعجاب\";\n\"ua_notification_button_download\" = \"تحميل\";\n\"ua_notification_button_follow\" = \"تابِع\";\n\"ua_notification_button_less_like\" = \"القليل من هذا\";\n\"ua_notification_button_like\" = \"إعجاب\";\n\"ua_notification_button_more_like\" = \"المزيد من هذا\";\n\"ua_notification_button_no\" = \"لا\";\n\"ua_notification_button_opt_in\" = \"اشترك\";\n\"ua_notification_button_opt_out\" = \"انسحب\";\n\"ua_notification_button_rate_now\" = \"قيم الآن\";\n\"ua_notification_button_remind\" = \"ذكرني لاحقا\";\n\"ua_notification_button_save\" = \"حفظ\";\n\"ua_notification_button_search\" = \"بحث\";\n\"ua_notification_button_send_info\" = \"إرسال معلومات\";\n\"ua_notification_button_share\" = \"مشاركة\";\n\"ua_notification_button_shop_now\" = \"تسوق الآن\";\n\"ua_notification_button_tell_me_more\" = \"أخبرني أكثر\";\n\"ua_notification_button_unfollow\" = \"إلغاء متابعة\";\n\"ua_notification_button_yes\" = \"نعم\";\n\"ua_ok\" = \"موافق\";\n\"ua_preference_center_title\" = \"مركز التفضيلات\";\n\"ua_retry_button\" = \"إعادة المحاولة\";\n\"ua_select_all_messages\" = \"اختر الكل\";\n\"ua_select_all_messages_description\" = \"يختار كل الرسائل\";\n\"ua_select_none_messages\" = \"اختر لا شيء\";\n\"ua_select_none_messages_description\" = \"إعادة تعيين اختيار الرسالة\";\n\"ua_unread_message_description\" = \"الرسالة غير مقروءة\";\n\n// Generic localizations\n\"ua_dismiss\" = \"تجاهل\";\n\"ua_escape\" = \"خروج\";\n\"ua_next\" = \"التالي\";\n\"ua_previous\" = \"السابق\";\n\"ua_submit\" = \"إرسال\";\n\"ua_loading\" = \"تحميل\";\n\"ua_pager_progress\" = \"الصفحة %@ من %@\";\n\"ua_x_of_y\" = \"%@ من %@\";\n\"ua_play\" = \"تشغيل\";\n\"ua_pause\" = \"إيقاف مؤقت\";\n\"ua_stop\" = \"إيقاف\";\n\"ua_form_processing_error\" = \"خطأ في معالجة النموذج. يرجى المحاولة مرة أخرى\";\n\"ua_close\" = \"إغلاق\";\n\"ua_mute\" = \"كتم الصوت\";\n\"ua_unmute\" = \"إلغاء كتم الصوت\";\n\"ua_required_field\" = \"* مطلوب\";\n\"ua_invalid_form_message\" = \"يرجى تصحيح الحقول غير الصالحة للمتابعة\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/bg.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Отмяна\";\n\"ua_cancel_edit_messages_description\" = \"Отмени редакциите на съобщението\";\n\"ua_connection_error\" = \"Грешка при свързване\";\n\"ua_content_error\" = \"Грешка в съдържанието\";\n\"ua_delete_message\" = \"Изтрий съобщението\";\n\"ua_delete_message_description\" = \"Изтрий избраните съобщения\";\n\"ua_delete_messages\" = \"Изтрий съобщението\";\n\"ua_delete_messages_description\" = \"Изтрий избраните съобщения\";\n\"ua_edit_messages\" = \"Редактиране\";\n\"ua_edit_messages_description\" = \"Редактиране на съобщения\";\n\"ua_empty_message_list\" = \"Няма съобщения\";\n\"ua_mark_messages_read\" = \"Маркирай като прочетено\";\n\"ua_mark_messages_read_description\" = \"Маркирай избраните съобщения като прочетени\";\n\"ua_mc_failed_to_load\" = \"Съобщението не може да се зареди. Моля, опитайте отново по-късно.\";\n\"ua_mc_no_longer_available\" = \"Избраното съобщение вече не е налично.\";\n\"ua_message_cell_description\" = \"Показва пълно съобщение\";\n\"ua_message_cell_editing_description\" = \"Превключване на избора\";\n\"ua_message_center_title\" = \"Център за съобщения\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Съобщение %@, изпратено на %@\";\n\"ua_message_not_selected\" = \"Няма избрани съобщения\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Непрочетено съобщение %@, изпратено на %@\";\n\"ua_notification_button_accept\" = \"Приемам\";\n\"ua_notification_button_add\" = \"Добавяне\";\n\"ua_notification_button_add_to_calendar\" = \"Добавяне към календара\";\n\"ua_notification_button_book_now\" = \"Резервирай сега\";\n\"ua_notification_button_buy_now\" = \"Купи сега\";\n\"ua_notification_button_copy\" = \"Копиране\";\n\"ua_notification_button_decline\" = \"Отказвам се\";\n\"ua_notification_button_dislike\" = \"Не харесвам\";\n\"ua_notification_button_download\" = \"Изтегляне\";\n\"ua_notification_button_follow\" = \"Последвам\";\n\"ua_notification_button_less_like\" = \"По-малко като това\";\n\"ua_notification_button_like\" = \"Харесвам\";\n\"ua_notification_button_more_like\" = \"Още като това\";\n\"ua_notification_button_no\" = \"Не\";\n\"ua_notification_button_opt_in\" = \"Включвам се\";\n\"ua_notification_button_opt_out\" = \"Изключвам се\";\n\"ua_notification_button_rate_now\" = \"Оцени сега\";\n\"ua_notification_button_remind\" = \"Напомни ми по-късно\";\n\"ua_notification_button_save\" = \"Запазване\";\n\"ua_notification_button_search\" = \"Търси\";\n\"ua_notification_button_send_info\" = \"Изпрати информация\";\n\"ua_notification_button_share\" = \"Споделяне\";\n\"ua_notification_button_shop_now\" = \"Пазарувай сега\";\n\"ua_notification_button_tell_me_more\" = \"Кажи ми повече\";\n\"ua_notification_button_unfollow\" = \"Спирам да следвам\";\n\"ua_notification_button_yes\" = \"Да\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Център за предпочитания\";\n\"ua_retry_button\" = \"Опитайте отново\";\n\"ua_select_all_messages\" = \"Маркирай всичко\";\n\"ua_select_all_messages_description\" = \"Избери всички съобщения\";\n\"ua_select_none_messages\" = \"Изберете Няма\";\n\"ua_select_none_messages_description\" = \"Нулиране на избора на съобщение\";\n\"ua_unread_message_description\" = \"Съобщението е непрочетено\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Отхвърлям\";\n\"ua_escape\" = \"Излизам\";\n\"ua_next\" = \"Следващ\";\n\"ua_previous\" = \"Предишен\";\n\"ua_submit\" = \"Изпращам\";\n\"ua_loading\" = \"Зареждане\";\n\"ua_pager_progress\" = \"Страница %@ от %@\";\n\"ua_x_of_y\" = \"%@ от %@\";\n\"ua_play\" = \"Пуснете\";\n\"ua_pause\" = \"Пауза\";\n\"ua_stop\" = \"Стоп\";\n\"ua_form_processing_error\" = \"Грешка при обработка на формуляра. Моля, опитайте отново\";\n\"ua_close\" = \"Затвори\";\n\"ua_mute\" = \"Заглуши\";\n\"ua_unmute\" = \"Включи звука\";\n\"ua_required_field\" = \"* Задължително\";\n\"ua_invalid_form_message\" = \"Моля, коригирайте невалидните полета, за да продължите\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ca.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Cancel·lar\";\n\"ua_cancel_edit_messages_description\" = \"Cancel·lar les edicions del missatge\";\n\"ua_connection_error\" = \"Error de connexió\";\n\"ua_content_error\" = \"Error de contingut\";\n\"ua_delete_message\" = \"Missatge esborrat\";\n\"ua_delete_message_description\" = \"Suprimir els missatges seleccionats\";\n\"ua_delete_messages\" = \"Missatge esborrat\";\n\"ua_delete_messages_description\" = \"Suprimir els missatges seleccionats\";\n\"ua_edit_messages\" = \"Editar\";\n\"ua_edit_messages_description\" = \"Editar missatges\";\n\"ua_empty_message_list\" = \"No hi ha missatges\";\n\"ua_mark_messages_read\" = \"Marcar com a Llegit\";\n\"ua_mark_messages_read_description\" = \"Marcar els missatges seleccionats com a llegits\";\n\"ua_mc_failed_to_load\" = \"No es pot carregar el missatge. Si us plau, intenteu-ho més tard.\";\n\"ua_mc_no_longer_available\" = \"El missatge seleccionat ja no està disponible.\";\n\"ua_message_cell_description\" = \"Mostra el missatge complet\";\n\"ua_message_cell_editing_description\" = \"Selecció de toggles\";\n\"ua_message_center_title\" = \"Centre de missatges\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Missatge %@, enviat a les %@\";\n\"ua_message_not_selected\" = \"No s\\'ha seleccionat cap missatge\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Missatge no llegit %@, enviat a les %@\";\n\"ua_notification_button_accept\" = \"Acceptar\";\n\"ua_notification_button_add\" = \"Afegir\";\n\"ua_notification_button_add_to_calendar\" = \"Afegir al calendari\";\n\"ua_notification_button_book_now\" = \"Reservar ara\";\n\"ua_notification_button_buy_now\" = \"Comprar ara\";\n\"ua_notification_button_copy\" = \"Copiar\";\n\"ua_notification_button_decline\" = \"Refusar\";\n\"ua_notification_button_dislike\" = \"No m\\'agrada\";\n\"ua_notification_button_download\" = \"Descarregar\";\n\"ua_notification_button_follow\" = \"Seguir\";\n\"ua_notification_button_less_like\" = \"Menys com aquest\";\n\"ua_notification_button_like\" = \"M\\'agrada\";\n\"ua_notification_button_more_like\" = \"Més com aquest\";\n\"ua_notification_button_no\" = \"No\";\n\"ua_notification_button_opt_in\" = \"Activar\";\n\"ua_notification_button_opt_out\" = \"Desactivar\";\n\"ua_notification_button_rate_now\" = \"Valorar ara\";\n\"ua_notification_button_remind\" = \"Recordeu-m\\'ho més tard\";\n\"ua_notification_button_save\" = \"Desar\";\n\"ua_notification_button_search\" = \"Cercar\";\n\"ua_notification_button_send_info\" = \"Enviar informació\";\n\"ua_notification_button_share\" = \"Compartir\";\n\"ua_notification_button_shop_now\" = \"Anar a la botiga ara\";\n\"ua_notification_button_tell_me_more\" = \"Expliqueu-me més\";\n\"ua_notification_button_unfollow\" = \"Deixar de seguir\";\n\"ua_notification_button_yes\" = \"Sí\";\n\"ua_ok\" = \"D\\'acord\";\n\"ua_preference_center_title\" = \"Centre de preferències\";\n\"ua_retry_button\" = \"Torneu-ho a provar\";\n\"ua_select_all_messages\" = \"Seleccioneu Tot\";\n\"ua_select_all_messages_description\" = \"Selecciona tots els missatges\";\n\"ua_select_none_messages\" = \"Seleccioneu Cap\";\n\"ua_select_none_messages_description\" = \"Restablir la selecció de missatges\";\n\"ua_unread_message_description\" = \"Missatge no llegit\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Descartar\";\n\"ua_escape\" = \"Escapar\";\n\"ua_next\" = \"Següent\";\n\"ua_previous\" = \"Anterior\";\n\"ua_submit\" = \"Enviar\";\n\"ua_loading\" = \"Carregant\";\n\"ua_pager_progress\" = \"Pàgina %@ de %@\";\n\"ua_x_of_y\" = \"%@ de %@\";\n\"ua_play\" = \"Reproduir\";\n\"ua_pause\" = \"Pausar\";\n\"ua_stop\" = \"Aturar\";\n\"ua_form_processing_error\" = \"Error en processar el formulari. Si us plau, torneu-ho a provar\";\n\"ua_close\" = \"Tancar\";\n\"ua_mute\" = \"Silenciar\";\n\"ua_unmute\" = \"Activar so\";\n\"ua_required_field\" = \"* Obligatori\";\n\"ua_invalid_form_message\" = \"Si us plau, corregiu els camps no vàlids per continuar\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/cs.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Zrušit\";\n\"ua_cancel_edit_messages_description\" = \"Zrušit úpravy zpráv\";\n\"ua_connection_error\" = \"Chyba připojení\";\n\"ua_content_error\" = \"Chyba obsahu\";\n\"ua_delete_message\" = \"Smazat zprávu\";\n\"ua_delete_message_description\" = \"Smazat vybrané zprávy\";\n\"ua_delete_messages\" = \"Smazat zprávu\";\n\"ua_delete_messages_description\" = \"Smazat vybrané zprávy\";\n\"ua_edit_messages\" = \"Upravit\";\n\"ua_edit_messages_description\" = \"Upravit zprávy\";\n\"ua_empty_message_list\" = \"Žádné zprávy\";\n\"ua_mark_messages_read\" = \"Označit jako přečtené\";\n\"ua_mark_messages_read_description\" = \"Označit vybrané zprávy jako přečtené\";\n\"ua_mc_failed_to_load\" = \"Zprávu nelze načíst. Zkuste to prosím později znovu.\";\n\"ua_mc_no_longer_available\" = \"Vybraná zpráva již není dostupná.\";\n\"ua_message_cell_description\" = \"Zobrazí celou zprávu\";\n\"ua_message_cell_editing_description\" = \"Přepíná výběr\";\n\"ua_message_center_title\" = \"Centrum zpráv\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Zpráva %@, odeslána v %@\";\n\"ua_message_not_selected\" = \"Nejsou vybrány žádné zprávy\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Nepřečtená zpráva %@, odeslána v %@\";\n\"ua_notification_button_accept\" = \"Přijmout\";\n\"ua_notification_button_add\" = \"Přidat\";\n\"ua_notification_button_add_to_calendar\" = \"Přidat do kalendáře\";\n\"ua_notification_button_book_now\" = \"Zarezervovat hned\";\n\"ua_notification_button_buy_now\" = \"Nakupovat teď\";\n\"ua_notification_button_copy\" = \"Kopírovat\";\n\"ua_notification_button_decline\" = \"Odmítnout\";\n\"ua_notification_button_dislike\" = \"Nelíbí se mi\";\n\"ua_notification_button_download\" = \"Stáhnout\";\n\"ua_notification_button_follow\" = \"Sledovat\";\n\"ua_notification_button_less_like\" = \"Méně se mi líbí toto\";\n\"ua_notification_button_like\" = \"Líbí se mi\";\n\"ua_notification_button_more_like\" = \"Víc se mi líbí toto\";\n\"ua_notification_button_no\" = \"Ne\";\n\"ua_notification_button_opt_in\" = \"Přidat se\";\n\"ua_notification_button_opt_out\" = \"Odhlásit se\";\n\"ua_notification_button_rate_now\" = \"Hodnotit teď\";\n\"ua_notification_button_remind\" = \"Připomenout později\";\n\"ua_notification_button_save\" = \"Uložit\";\n\"ua_notification_button_search\" = \"Vyhledávání\";\n\"ua_notification_button_send_info\" = \"Odeslat informace\";\n\"ua_notification_button_share\" = \"Sdílet\";\n\"ua_notification_button_shop_now\" = \"Koupit teď\";\n\"ua_notification_button_tell_me_more\" = \"Chci vědět víc\";\n\"ua_notification_button_unfollow\" = \"Nesledovat\";\n\"ua_notification_button_yes\" = \"Ano\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centrum preferencí\";\n\"ua_retry_button\" = \"Zkusit znovu\";\n\"ua_select_all_messages\" = \"Vybrat Vše\";\n\"ua_select_all_messages_description\" = \"Vybere všechny zprávy\";\n\"ua_select_none_messages\" = \"Nevybírat nic\";\n\"ua_select_none_messages_description\" = \"Obnovit výběr zpráv\";\n\"ua_unread_message_description\" = \"Zpráva nepřečtena\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Zrušit\";\n\"ua_escape\" = \"Odejít\";\n\"ua_next\" = \"Další\";\n\"ua_previous\" = \"Předchozí\";\n\"ua_submit\" = \"Odeslat\";\n\"ua_loading\" = \"Načítání\";\n\"ua_pager_progress\" = \"Strana %@ z %@\";\n\"ua_x_of_y\" = \"%@ z %@\";\n\"ua_play\" = \"Přehrát\";\n\"ua_pause\" = \"Pozastavit\";\n\"ua_stop\" = \"Zastavit\";\n\"ua_form_processing_error\" = \"Chyba při zpracování formuláře. Zkuste to prosím znovu\";\n\"ua_close\" = \"Zavřít\";\n\"ua_mute\" = \"Ztlumit\";\n\"ua_unmute\" = \"Zrušit ztlumení\";\n\"ua_required_field\" = \"* Povinné\";\n\"ua_invalid_form_message\" = \"Opravte prosím neplatná pole a pokračujte\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/da.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Annullér\";\n\"ua_cancel_edit_messages_description\" = \"Annuller redigeringer af beskeder\";\n\"ua_connection_error\" = \"Forbindelsesfejl\";\n\"ua_content_error\" = \"Indholdsfejl\";\n\"ua_delete_message\" = \"Slet besked\";\n\"ua_delete_message_description\" = \"Slet valgte beskeder\";\n\"ua_delete_messages\" = \"Slet besked\";\n\"ua_delete_messages_description\" = \"Slet valgte beskeder\";\n\"ua_edit_messages\" = \"Rediger\";\n\"ua_edit_messages_description\" = \"Rediger beskeder\";\n\"ua_empty_message_list\" = \"Ingen meddelelser\";\n\"ua_mark_messages_read\" = \"Markeret som læst\";\n\"ua_mark_messages_read_description\" = \"Marker valgte beskeder læst\";\n\"ua_mc_failed_to_load\" = \"Kan ikke indlæse meddelelse. Prøv igen senere.\";\n\"ua_mc_no_longer_available\" = \"Den valgte besked er ikke længere tilgængelig.\";\n\"ua_message_cell_description\" = \"Viser hele meddelelsen\";\n\"ua_message_cell_editing_description\" = \"Skifte valg\";\n\"ua_message_center_title\" = \"Meddelelsescenter\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Besked %@, sendt kl. %@\";\n\"ua_message_not_selected\" = \"Der er ikke valgt nogen beskeder\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Ulæst besked %@, sendt kl. %@\";\n\"ua_notification_button_accept\" = \"Acceptér\";\n\"ua_notification_button_add\" = \"Tilføj\";\n\"ua_notification_button_add_to_calendar\" = \"Føj til kalender\";\n\"ua_notification_button_book_now\" = \"Bestil nu\";\n\"ua_notification_button_buy_now\" = \"Køb nu\";\n\"ua_notification_button_copy\" = \"Kopiér\";\n\"ua_notification_button_decline\" = \"Afvis\";\n\"ua_notification_button_dislike\" = \"Synes ikke om\";\n\"ua_notification_button_download\" = \"Hent\";\n\"ua_notification_button_follow\" = \"Følg\";\n\"ua_notification_button_less_like\" = \"Mindre som denne\";\n\"ua_notification_button_like\" = \"Synes om\";\n\"ua_notification_button_more_like\" = \"Mere som denne\";\n\"ua_notification_button_no\" = \"Nej\";\n\"ua_notification_button_opt_in\" = \"Tilmeld\";\n\"ua_notification_button_opt_out\" = \"Frameld\";\n\"ua_notification_button_rate_now\" = \"Bedøm nu\";\n\"ua_notification_button_remind\" = \"Mind mig om det senere\";\n\"ua_notification_button_save\" = \"Gem\";\n\"ua_notification_button_search\" = \"Søg\";\n\"ua_notification_button_send_info\" = \"Send info\";\n\"ua_notification_button_share\" = \"Del\";\n\"ua_notification_button_shop_now\" = \"Handl nu\";\n\"ua_notification_button_tell_me_more\" = \"Fortæl mig mere\";\n\"ua_notification_button_unfollow\" = \"Følg ikke længere\";\n\"ua_notification_button_yes\" = \"Ja\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Præferencecenter\";\n\"ua_retry_button\" = \"Prøv igen\";\n\"ua_select_all_messages\" = \"Vælg alle\";\n\"ua_select_all_messages_description\" = \"Vælger alle beskeder\";\n\"ua_select_none_messages\" = \"Vælg ingen\";\n\"ua_select_none_messages_description\" = \"Nulstil beskedvalg\";\n\"ua_unread_message_description\" = \"Besked er ulæst\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Afvis\";\n\"ua_escape\" = \"Undslip\";\n\"ua_next\" = \"Næste\";\n\"ua_previous\" = \"Forrige\";\n\"ua_submit\" = \"Indsend\";\n\"ua_loading\" = \"Indlæser\";\n\"ua_pager_progress\" = \"Side %@ af %@\";\n\"ua_x_of_y\" = \"%@ af %@\";\n\"ua_play\" = \"Afspil\";\n\"ua_pause\" = \"Pause\";\n\"ua_stop\" = \"Stop\";\n\"ua_form_processing_error\" = \"Fejl ved behandling af formular. Prøv venligst igen\";\n\"ua_close\" = \"Luk\";\n\"ua_mute\" = \"Slå lyd fra\";\n\"ua_unmute\" = \"Slå lyd til\";\n\"ua_required_field\" = \"* Påkrævet\";\n\"ua_invalid_form_message\" = \"Ret venligst de ugyldige felter for at fortsætte\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/de.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Abbrechen\";\n\"ua_cancel_edit_messages_description\" = \"Nachrichtenbearbeitung abbrechen\";\n\"ua_connection_error\" = \"Verbindungsfehler\";\n\"ua_content_error\" = \"Inhaltsfehler\";\n\"ua_delete_message\" = \"Nachricht löschen\";\n\"ua_delete_message_description\" = \"Ausgewählte Nachrichten löschen\";\n\"ua_delete_messages\" = \"Nachricht löschen\";\n\"ua_delete_messages_description\" = \"Ausgewählte Nachrichten löschen\";\n\"ua_edit_messages\" = \"Bearbeiten\";\n\"ua_edit_messages_description\" = \"Nachrichten bearbeiten\";\n\"ua_empty_message_list\" = \"Keine Nachrichten\";\n\"ua_mark_messages_read\" = \"Als gelesen markieren\";\n\"ua_mark_messages_read_description\" = \"Ausgewählte Nachrichten als gelesen markieren\";\n\"ua_mc_failed_to_load\" = \"Nachricht konnte nicht geladen werden. Bitte versuchen Sie es später noch einmal.\";\n\"ua_mc_no_longer_available\" = \"Die ausgewählte Nachricht ist nicht mehr verfügbar.\";\n\"ua_message_cell_description\" = \"Zeigt die vollständige Nachricht an\";\n\"ua_message_cell_editing_description\" = \"Schaltet die Auswahl um\";\n\"ua_message_center_title\" = \"Nachrichtenzentrum\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Nachricht %@, gesendet um %@\";\n\"ua_message_not_selected\" = \"Keine Nachrichten ausgewählt\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Ungelesene Nachricht %@, gesendet um %@\";\n\"ua_notification_button_accept\" = \"Akzeptieren\";\n\"ua_notification_button_add\" = \"Hinzufügen\";\n\"ua_notification_button_add_to_calendar\" = \"Zum Kalender hinzufügen\";\n\"ua_notification_button_book_now\" = \"Jetzt buchen\";\n\"ua_notification_button_buy_now\" = \"Jetzt kaufen\";\n\"ua_notification_button_copy\" = \"Kopieren\";\n\"ua_notification_button_decline\" = \"Ablehnen\";\n\"ua_notification_button_dislike\" = \"Nicht liken\";\n\"ua_notification_button_download\" = \"Herunterladen\";\n\"ua_notification_button_follow\" = \"Folgen\";\n\"ua_notification_button_less_like\" = \"Weniger wie dieses\";\n\"ua_notification_button_like\" = \"Liken\";\n\"ua_notification_button_more_like\" = \"Mehr wie dieses\";\n\"ua_notification_button_no\" = \"Nein\";\n\"ua_notification_button_opt_in\" = \"Anmelden\";\n\"ua_notification_button_opt_out\" = \"Abmelden\";\n\"ua_notification_button_rate_now\" = \"Jetzt bewerten\";\n\"ua_notification_button_remind\" = \"Später erinnern\";\n\"ua_notification_button_save\" = \"Speichern\";\n\"ua_notification_button_search\" = \"Suche\";\n\"ua_notification_button_send_info\" = \"Infos senden\";\n\"ua_notification_button_share\" = \"Teilen\";\n\"ua_notification_button_shop_now\" = \"Jetzt kaufen\";\n\"ua_notification_button_tell_me_more\" = \"Mehr erfahren\";\n\"ua_notification_button_unfollow\" = \"Nicht mehr folgen\";\n\"ua_notification_button_yes\" = \"Ja\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Präferenzzentrum\";\n\"ua_retry_button\" = \"Wiederholen\";\n\"ua_select_all_messages\" = \"Alles auswählen\";\n\"ua_select_all_messages_description\" = \"Wählt alle Nachrichten aus\";\n\"ua_select_none_messages\" = \"Nichts auswählen\";\n\"ua_select_none_messages_description\" = \"Nachrichtenauswahl zurücksetzen\";\n\"ua_unread_message_description\" = \"Nachricht ungelesen\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Verwerfen\";\n\"ua_escape\" = \"Verlassen\";\n\"ua_next\" = \"Weiter\";\n\"ua_previous\" = \"Zurück\";\n\"ua_submit\" = \"Senden\";\n\"ua_loading\" = \"Laden\";\n\"ua_pager_progress\" = \"Seite %@ von %@\";\n\"ua_x_of_y\" = \"%@ von %@\";\n\"ua_play\" = \"Abspielen\";\n\"ua_pause\" = \"Pausieren\";\n\"ua_stop\" = \"Stoppen\";\n\"ua_form_processing_error\" = \"Fehler bei der Verarbeitung des Formulars. Bitte versuchen Sie es erneut\";\n\"ua_close\" = \"Schließen\";\n\"ua_mute\" = \"Stumm schalten\";\n\"ua_unmute\" = \"Stummschaltung aufheben\";\n\"ua_required_field\" = \"* Erforderlich\";\n\"ua_invalid_form_message\" = \"Bitte beheben Sie die ungültigen Felder, um fortzufahren\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/el.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Ματαίωση\";\n\"ua_cancel_edit_messages_description\" = \"Ακύρωση επεξεργασιών μηνυμάτων\";\n\"ua_connection_error\" = \"Σφάλμα σύνδεσης\";\n\"ua_content_error\" = \"Σφάλμα περιεχομένου\";\n\"ua_delete_message\" = \"Διαγραφή μηνύματος\";\n\"ua_delete_message_description\" = \"Διαγραφή επιλεγμένων μηνυμάτων\";\n\"ua_delete_messages\" = \"Διαγραφή μηνύματος\";\n\"ua_delete_messages_description\" = \"Διαγραφή επιλεγμένων μηνυμάτων\";\n\"ua_edit_messages\" = \"Επεξεργασία\";\n\"ua_edit_messages_description\" = \"Επεξεργασία μηνυμάτων\";\n\"ua_empty_message_list\" = \"Χωρίς μηνύματα\";\n\"ua_mark_messages_read\" = \"Σημείωση ως διαβασμένο\";\n\"ua_mark_messages_read_description\" = \"Επισημάνετε επιλεγμένα μηνύματα ως αναγνωσμένα\";\n\"ua_mc_failed_to_load\" = \"Δεν είναι δυνατή η φόρτωση του μηνύματος. Παρακαλώ δοκιμάστε ξανά αργότερα.\";\n\"ua_mc_no_longer_available\" = \"Το επιλεγμένο μήνυμα δεν είναι πλέον διαθέσιμο.\";\n\"ua_message_cell_description\" = \"Εμφανίζει πλήρες μήνυμα\";\n\"ua_message_cell_editing_description\" = \"Εναλλάσσει την επιλογή\";\n\"ua_message_center_title\" = \"Κέντρο μηνυμάτων\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Μήνυμα %@, στάλθηκε στις %@\";\n\"ua_message_not_selected\" = \"Δεν επιλέχθηκαν μηνύματα\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Μη αναγνωσμένο μήνυμα %@, στάλθηκε στις %@\";\n\"ua_notification_button_accept\" = \"Αποδέχομαι\";\n\"ua_notification_button_add\" = \"Προσθήκη\";\n\"ua_notification_button_add_to_calendar\" = \"Προσθήκη στο Ημερολόγιο\";\n\"ua_notification_button_book_now\" = \"Κάνετε κράτηση τώρα\";\n\"ua_notification_button_buy_now\" = \"Αγόρασε τώρα\";\n\"ua_notification_button_copy\" = \"Αντίγραφο\";\n\"ua_notification_button_decline\" = \"Πτώση\";\n\"ua_notification_button_dislike\" = \"Δεν αρέσει\";\n\"ua_notification_button_download\" = \"Κατεβάστε\";\n\"ua_notification_button_follow\" = \"Ακολουθήστε\";\n\"ua_notification_button_less_like\" = \"Λιγότερο σαν αυτό\";\n\"ua_notification_button_like\" = \"Αρέσει\";\n\"ua_notification_button_more_like\" = \"Περισσότερα σαν αυτό\";\n\"ua_notification_button_no\" = \"Όχι\";\n\"ua_notification_button_opt_in\" = \"Συμμετοχή\";\n\"ua_notification_button_opt_out\" = \"Εξαίρεση\";\n\"ua_notification_button_rate_now\" = \"Βαθμολογήστε τώρα\";\n\"ua_notification_button_remind\" = \"Θύμισέ μου αργότερα\";\n\"ua_notification_button_save\" = \"Αποθηκεύσετε\";\n\"ua_notification_button_search\" = \"Αναζήτηση\";\n\"ua_notification_button_send_info\" = \"Αποστολή πληροφοριών\";\n\"ua_notification_button_share\" = \"Κοινοποίηση\";\n\"ua_notification_button_shop_now\" = \"Ψώνισε τώρα\";\n\"ua_notification_button_tell_me_more\" = \"Πες μου κι άλλα\";\n\"ua_notification_button_unfollow\" = \"Κατάργηση παρακολούθησης\";\n\"ua_notification_button_yes\" = \"Ναι\";\n\"ua_ok\" = \"Εντάξει\";\n\"ua_preference_center_title\" = \"Κέντρο προτιμήσεων\";\n\"ua_retry_button\" = \"Ξαναδοκιμάσετε\";\n\"ua_select_all_messages\" = \"Επιλογή όλων\";\n\"ua_select_all_messages_description\" = \"Επιλέγει όλα τα μηνύματα\";\n\"ua_select_none_messages\" = \"Επιλογή κανενός\";\n\"ua_select_none_messages_description\" = \"Επαναφορά της επιλογής μηνύματος\";\n\"ua_unread_message_description\" = \"Μη αναγνωσμένο μήνυμα\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Απόρριψη\";\n\"ua_escape\" = \"Διαφυγή\";\n\"ua_next\" = \"Επόμενο\";\n\"ua_previous\" = \"Προηγούμενο\";\n\"ua_submit\" = \"Υποβολή\";\n\"ua_loading\" = \"Φόρτωση\";\n\"ua_pager_progress\" = \"Σελίδα %@ από %@\";\n\"ua_x_of_y\" = \"%@ από %@\";\n\"ua_play\" = \"Αναπαραγωγή\";\n\"ua_pause\" = \"Παύση\";\n\"ua_stop\" = \"Διακοπή\";\n\"ua_form_processing_error\" = \"Σφάλμα επεξεργασίας φόρμας. Παρακαλώ δοκιμάστε ξανά\";\n\"ua_close\" = \"Κλείσιμο\";\n\"ua_mute\" = \"Σίγαση\";\n\"ua_unmute\" = \"Κατάργηση σίγασης\";\n\"ua_required_field\" = \"* Υποχρεωτικό\";\n\"ua_invalid_form_message\" = \"Παρακαλώ διορθώστε τα μη έγκυρα πεδία για να συνεχίσετε\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/en.lproj/UrbanAirship.strings",
    "content": "\"ua_connection_error\" = \"Connection Error\";\n\"ua_content_error\" = \"Content Error\";\n\n\"ua_delete_message\" = \"Delete\";\n\"ua_delete_message_description\" = \"Delete message\";\n\n\"ua_delete_messages\" = \"Delete\";\n\"ua_delete_messages_description\" = \"Delete selected messages\";\n\n\"ua_done\" = \"Done\";\n\"ua_done_description\" = \"Dismiss message center\";\n\n\"ua_message_cell_editing_description\" = \"Toggles selection\";\n\"ua_message_cell_description\" = \"Displays full message\";\n\n\"ua_empty_message_list\" = \"No messages\";\n\"ua_mark_messages_read\" = \"Mark Read\";\n\"ua_mark_messages_read_description\" = \"Mark selected messages read\";\n\n\"ua_cancel_edit_messages\" = \"Cancel\";\n\"ua_cancel_edit_messages_description\" = \"Cancel message edits\";\n\n\"ua_select_all_messages\" = \"Select All\";\n\"ua_select_all_messages_description\" = \"Selects all messages\";\n\n\"ua_select_none_messages\" = \"Select None\";\n\"ua_select_none_messages_description\" = \"Reset message selection\";\n\n\"ua_edit_messages\" = \"Edit\";\n\"ua_edit_messages_description\" = \"Edit messages\";\n\"ua_unread_message_description\" = \"Message unread\";\n\n// Message <TITLE>, sent at <DATE>\n\"ua_message_description\" = \"Message %@, sent at %@\";\n// Unread message <TITLE>, sent at <DATE>\n\"ua_message_unread_description\" = \"Unread message %@, sent at %@\";\n\n\"ua_mc_failed_to_load\" = \"Unable to load message. Please try again later.\";\n\"ua_mc_no_longer_available\" = \"The selected message is no longer available.\";\n\"ua_message_center_title\" = \"Message Center\";\n\"ua_message_not_selected\" = \"No messages selected\";\n\n\"ua_notification_button_accept\" = \"Accept\";\n\"ua_notification_button_add\" = \"Add\";\n\"ua_notification_button_add_to_calendar\" = \"Add to Calendar\";\n\"ua_notification_button_book_now\" = \"Book Now\";\n\"ua_notification_button_buy_now\" = \"Buy Now\";\n\"ua_notification_button_copy\" = \"Copy\";\n\"ua_notification_button_decline\" = \"Decline\";\n\"ua_notification_button_dislike\" = \"Dislike\";\n\"ua_notification_button_download\" = \"Download\";\n\"ua_notification_button_follow\" = \"Follow\";\n\"ua_notification_button_less_like\" = \"Less Like This\";\n\"ua_notification_button_like\" = \"Like\";\n\"ua_notification_button_more_like\" = \"More Like This\";\n\"ua_notification_button_no\" = \"No\";\n\"ua_notification_button_opt_in\" = \"Opt-in\";\n\"ua_notification_button_opt_out\" = \"Opt-out\";\n\"ua_notification_button_rate_now\" = \"Rate Now\";\n\"ua_notification_button_remind\" = \"Remind Me Later\";\n\"ua_notification_button_save\" = \"Save\";\n\"ua_notification_button_search\" = \"Search\";\n\"ua_notification_button_send_info\" = \"Send Info\";\n\"ua_notification_button_share\" = \"Share\";\n\"ua_notification_button_shop_now\" = \"Shop Now\";\n\"ua_notification_button_tell_me_more\" = \"Tell Me More\";\n\"ua_notification_button_unfollow\" = \"Unfollow\";\n\"ua_notification_button_yes\" = \"Yes\";\n\"ua_ok\" = \"OK\";\n\"ua_retry_button\" = \"Retry\";\n\n\"ua_chat_title\" = \"Chat\";\n\"ua_chat_send_button\" = \"Send\";\n\n\"ua_preference_center_title\" = \"Preference Center\";\n\"ua_preference_center_empty\" = \"Unable to load preferences. Please try again later.\";\n\n\n// Generic localizations\n\"ua_dismiss\" = \"Dismiss\";\n\"ua_escape\" = \"Escape\";\n\"ua_next\" = \"Next\";\n\"ua_previous\" = \"Previous\";\n\"ua_submit\" = \"Submit\";\n\"ua_loading\" = \"Loading\";\n\"ua_pager_progress\" = \"Page %@ of %@\";\n\"ua_x_of_y\" = \"%@ of %@\";\n\"ua_play\" = \"Play\";\n\"ua_pause\" = \"Pause\";\n\"ua_stop\" = \"Stop\";\n\"ua_form_processing_error\" = \"Error processing form. Please try again\";\n\"ua_close\" = \"Close\";\n\"ua_mute\" = \"Mute\";\n\"ua_unmute\" = \"Unmute\";\n\"ua_required_field\" = \"* Required\";\n\"ua_invalid_form_message\" = \"Please fix the invalid fields to continue\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/es-419.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Cancelar\";\n\"ua_cancel_edit_messages_description\" = \"Cancelar ediciones de mensajes\";\n\"ua_connection_error\" = \"Error de conexión\";\n\"ua_content_error\" = \"Error de contenido\";\n\"ua_delete_message\" = \"Borrar mensaje\";\n\"ua_delete_message_description\" = \"Eliminar mensajes seleccionados\";\n\"ua_delete_messages\" = \"Borrar mensaje\";\n\"ua_delete_messages_description\" = \"Eliminar mensajes seleccionados\";\n\"ua_edit_messages\" = \"Editar\";\n\"ua_edit_messages_description\" = \"Editar mensajes\";\n\"ua_empty_message_list\" = \"No hay mensajes\";\n\"ua_mark_messages_read\" = \"Marcar como leído\";\n\"ua_mark_messages_read_description\" = \"Marcar mensajes seleccionados como leídos\";\n\"ua_mc_failed_to_load\" = \"No se puede cargar el mensaje. Por favor inténtalo de nuevo más tarde.\";\n\"ua_mc_no_longer_available\" = \"El mensaje seleccionado ya no está disponible.\";\n\"ua_message_cell_description\" = \"Muestra el mensaje completo\";\n\"ua_message_cell_editing_description\" = \"Alterna la selección\";\n\"ua_message_center_title\" = \"Centro de mensajes\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Mensaje %@, enviado a las %@\";\n\"ua_message_not_selected\" = \"No hay mensajes seleccionados\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Mensaje no leído %@, enviado a las %@\";\n\"ua_notification_button_accept\" = \"Aceptar\";\n\"ua_notification_button_add\" = \"Anadir\";\n\"ua_notification_button_add_to_calendar\" = \"Añadir al calendario\";\n\"ua_notification_button_book_now\" = \"Reservar ahora\";\n\"ua_notification_button_buy_now\" = \"Comprar ahora\";\n\"ua_notification_button_copy\" = \"Copiar\";\n\"ua_notification_button_decline\" = \"Rechazar\";\n\"ua_notification_button_dislike\" = \"No me gusta\";\n\"ua_notification_button_download\" = \"Descargar\";\n\"ua_notification_button_follow\" = \"Seguir\";\n\"ua_notification_button_less_like\" = \"Menos como esto\";\n\"ua_notification_button_like\" = \"Me gusta\";\n\"ua_notification_button_more_like\" = \"Más como esto\";\n\"ua_notification_button_no\" = \"No\";\n\"ua_notification_button_opt_in\" = \"Participar\";\n\"ua_notification_button_opt_out\" = \"No participar\";\n\"ua_notification_button_rate_now\" = \"Calificar ahora\";\n\"ua_notification_button_remind\" = \"Recuérdame más tarde\";\n\"ua_notification_button_save\" = \"Guardar\";\n\"ua_notification_button_search\" = \"Buscar\";\n\"ua_notification_button_send_info\" = \"Enviar información\";\n\"ua_notification_button_share\" = \"Compartir\";\n\"ua_notification_button_shop_now\" = \"Ir  a comprar ahora\";\n\"ua_notification_button_tell_me_more\" = \"Díme más\";\n\"ua_notification_button_unfollow\" = \"Dejar de seguir\";\n\"ua_notification_button_yes\" = \"Sí\";\n\"ua_ok\" = \"Aceptar\";\n\"ua_preference_center_title\" = \"Centro de preferencias\";\n\"ua_retry_button\" = \"Reintentar\";\n\"ua_select_all_messages\" = \"Seleccionar todo\";\n\"ua_select_all_messages_description\" = \"Selecciona todos los mensajes\";\n\"ua_select_none_messages\" = \"Deseleccionar todo\";\n\"ua_select_none_messages_description\" = \"Restablecer selección de mensaje\";\n\"ua_unread_message_description\" = \"Mensaje no leído\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Descartar\";\n\"ua_escape\" = \"Escape\";\n\"ua_next\" = \"Siguiente\";\n\"ua_previous\" = \"Anterior\";\n\"ua_submit\" = \"Enviar\";\n\"ua_loading\" = \"Cargando\";\n\"ua_pager_progress\" = \"Página %@ de %@\";\n\"ua_x_of_y\" = \"%@ de %@\";\n\"ua_play\" = \"Reproducir\";\n\"ua_pause\" = \"Pausar\";\n\"ua_stop\" = \"Detener\";\n\"ua_form_processing_error\" = \"Error al procesar el formulario. Por favor intente nuevamente\";\n\"ua_close\" = \"Cerrar\";\n\"ua_mute\" = \"Silenciar\";\n\"ua_unmute\" = \"Activar sonido\";\n\"ua_required_field\" = \"* Obligatorio\";\n\"ua_invalid_form_message\" = \"Por favor, corrija los campos no válidos para continuar\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/es.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Cancelar\";\n\"ua_cancel_edit_messages_description\" = \"Cancelar ediciones de mensajes\";\n\"ua_connection_error\" = \"Error de conexión\";\n\"ua_content_error\" = \"Error de contenido\";\n\"ua_delete_message\" = \"Borrar mensaje\";\n\"ua_delete_message_description\" = \"Eliminar mensajes seleccionados\";\n\"ua_delete_messages\" = \"Borrar mensaje\";\n\"ua_delete_messages_description\" = \"Eliminar mensajes seleccionados\";\n\"ua_edit_messages\" = \"Editar\";\n\"ua_edit_messages_description\" = \"Editar mensajes\";\n\"ua_empty_message_list\" = \"No hay mensajes\";\n\"ua_mark_messages_read\" = \"Marcar como leído\";\n\"ua_mark_messages_read_description\" = \"Marcar mensajes seleccionados como leídos\";\n\"ua_mc_failed_to_load\" = \"No se pudo cargar el mensaje. Por favor intenta más tarde.\";\n\"ua_mc_no_longer_available\" = \"El mensaje seleccionado ya no está disponible.\";\n\"ua_message_cell_description\" = \"Muestra el mensaje completo\";\n\"ua_message_cell_editing_description\" = \"Alterna la selección\";\n\"ua_message_center_title\" = \"Centro de mensajes\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Mensaje %@, enviado a las %@\";\n\"ua_message_not_selected\" = \"No hay mensajes seleccionados\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Mensaje no leído %@, enviado a las %@\";\n\"ua_notification_button_accept\" = \"Aceptar\";\n\"ua_notification_button_add\" = \"Añadir\";\n\"ua_notification_button_add_to_calendar\" = \"Añadir al calendario\";\n\"ua_notification_button_book_now\" = \"Reservar ahora\";\n\"ua_notification_button_buy_now\" = \"Comprar ahora\";\n\"ua_notification_button_copy\" = \"Copiar\";\n\"ua_notification_button_decline\" = \"Rechazar\";\n\"ua_notification_button_dislike\" = \"No me gusta\";\n\"ua_notification_button_download\" = \"Descargar\";\n\"ua_notification_button_follow\" = \"Seguir\";\n\"ua_notification_button_less_like\" = \"Menos como éste\";\n\"ua_notification_button_like\" = \"Me gusta\";\n\"ua_notification_button_more_like\" = \"Más como éste\";\n\"ua_notification_button_no\" = \"No\";\n\"ua_notification_button_opt_in\" = \"Acepto\";\n\"ua_notification_button_opt_out\" = \"No acepto\";\n\"ua_notification_button_rate_now\" = \"Calificar ahora\";\n\"ua_notification_button_remind\" = \"Recordármelo más tarde\";\n\"ua_notification_button_save\" = \"Guardar\";\n\"ua_notification_button_search\" = \"Buscar\";\n\"ua_notification_button_send_info\" = \"Enviar información\";\n\"ua_notification_button_share\" = \"Compartir\";\n\"ua_notification_button_shop_now\" = \"Obtener ahora\";\n\"ua_notification_button_tell_me_more\" = \"Díme más\";\n\"ua_notification_button_unfollow\" = \"Dejar de seguir\";\n\"ua_notification_button_yes\" = \"Sí\";\n\"ua_ok\" = \"Aceptar\";\n\"ua_preference_center_title\" = \"Centro de preferencias\";\n\"ua_retry_button\" = \"Intentar de nuevo\";\n\"ua_select_all_messages\" = \"Seleccionar todo\";\n\"ua_select_all_messages_description\" = \"Selecciona todos los mensajes\";\n\"ua_select_none_messages\" = \"Deseleccionar todo\";\n\"ua_select_none_messages_description\" = \"Restablecer selección de mensaje\";\n\"ua_unread_message_description\" = \"Mensaje no leído\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Descartar\";\n\"ua_escape\" = \"Escapar\";\n\"ua_next\" = \"Siguiente\";\n\"ua_previous\" = \"Anterior\";\n\"ua_submit\" = \"Enviar\";\n\"ua_loading\" = \"Cargando\";\n\"ua_pager_progress\" = \"Página %@ de %@\";\n\"ua_x_of_y\" = \"%@ de %@\";\n\"ua_play\" = \"Reproducir\";\n\"ua_pause\" = \"Pausar\";\n\"ua_stop\" = \"Detener\";\n\"ua_form_processing_error\" = \"Error al procesar el formulario. Por favor, inténtelo de nuevo\";\n\"ua_close\" = \"Cerrar\";\n\"ua_mute\" = \"Silenciar\";\n\"ua_unmute\" = \"Activar sonido\";\n\"ua_required_field\" = \"* Obligatorio\";\n\"ua_invalid_form_message\" = \"Por favor, corrija los campos no válidos para continuar\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/et.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Tühista\";\n\"ua_cancel_edit_messages_description\" = \"Tühista sõnumi muudatused\";\n\"ua_connection_error\" = \"Ühenduse viga\";\n\"ua_content_error\" = \"Sisu viga\";\n\"ua_delete_message\" = \"Kustuta sõnum\";\n\"ua_delete_message_description\" = \"Kustutage valitud sõnumid\";\n\"ua_delete_messages\" = \"Kustuta sõnum\";\n\"ua_delete_messages_description\" = \"Kustutage valitud sõnumid\";\n\"ua_edit_messages\" = \"Muuda\";\n\"ua_edit_messages_description\" = \"Redigeeri sõnumeid\";\n\"ua_empty_message_list\" = \"Sõnumeid pole\";\n\"ua_mark_messages_read\" = \"Märgi loetuks\";\n\"ua_mark_messages_read_description\" = \"Märgi valitud sõnumid loetuks\";\n\"ua_mc_failed_to_load\" = \"Sõnumit ei saa laadida. Palun proovi hiljem uuesti.\";\n\"ua_mc_no_longer_available\" = \"Valitud sõnum pole enam saadaval.\";\n\"ua_message_cell_description\" = \"Kuvab kogu sõnumi\";\n\"ua_message_cell_editing_description\" = \"Lülitab valiku sisse\";\n\"ua_message_center_title\" = \"Sõnumikeskus\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Sõnum %@, saadetud aadressil %@\";\n\"ua_message_not_selected\" = \"Ühtegi sõnumit pole valitud\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Lugemata sõnum %@, saadetud kell %@\";\n\"ua_notification_button_accept\" = \"Nõustu\";\n\"ua_notification_button_add\" = \"Lisama\";\n\"ua_notification_button_add_to_calendar\" = \"Lisa kalendrisse\";\n\"ua_notification_button_book_now\" = \"Broneeri kohe\";\n\"ua_notification_button_buy_now\" = \"Osta kohe\";\n\"ua_notification_button_copy\" = \"Kopeeri\";\n\"ua_notification_button_decline\" = \"Keeldumine\";\n\"ua_notification_button_dislike\" = \"Ei meeldi\";\n\"ua_notification_button_download\" = \"Lae alla\";\n\"ua_notification_button_follow\" = \"Follow\";\n\"ua_notification_button_less_like\" = \"Vähem Sellist\";\n\"ua_notification_button_like\" = \"meeldib\";\n\"ua_notification_button_more_like\" = \"Rohkem sellist\";\n\"ua_notification_button_no\" = \"Ei\";\n\"ua_notification_button_opt_in\" = \"Lubatud\";\n\"ua_notification_button_opt_out\" = \"Loobumine\";\n\"ua_notification_button_rate_now\" = \"Hinda kohe\";\n\"ua_notification_button_remind\" = \"Tuleta mulle hiljem meelde\";\n\"ua_notification_button_save\" = \"Salvesta\";\n\"ua_notification_button_search\" = \"Otsing\";\n\"ua_notification_button_send_info\" = \"Saada Info\";\n\"ua_notification_button_share\" = \"Jaga\";\n\"ua_notification_button_shop_now\" = \"Osta nüüd\";\n\"ua_notification_button_tell_me_more\" = \"Räägi mulle rohkem\";\n\"ua_notification_button_unfollow\" = \"Jälgimise lõpetamine\";\n\"ua_notification_button_yes\" = \"Jah\";\n\"ua_ok\" = \"Okei\";\n\"ua_preference_center_title\" = \"Eelistuskeskus\";\n\"ua_retry_button\" = \"Uuesti proovima\";\n\"ua_select_all_messages\" = \"Vali kõik\";\n\"ua_select_all_messages_description\" = \"Valib kõik sõnumid\";\n\"ua_select_none_messages\" = \"Valige Puudub\";\n\"ua_select_none_messages_description\" = \"Lähtestage sõnumi valik\";\n\"ua_unread_message_description\" = \"Sõnum lugemata\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Lahkuda\";\n\"ua_escape\" = \"Põgenemine\";\n\"ua_next\" = \"Järgmine\";\n\"ua_previous\" = \"Eelmine\";\n\"ua_submit\" = \"Esitada\";\n\"ua_loading\" = \"Laadimine\";\n\"ua_pager_progress\" = \"Lehekülg %@ / %@\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"Mängima\";\n\"ua_pause\" = \"Paus\";\n\"ua_stop\" = \"Peatama\";\n\"ua_form_processing_error\" = \"Viga vormi töötlemisel. Palun proovige uuesti\";\n\"ua_close\" = \"Sulge\";\n\"ua_mute\" = \"Vaigista\";\n\"ua_unmute\" = \"Tühista vaigistus\";\n\"ua_required_field\" = \"* Kohustuslik\";\n\"ua_invalid_form_message\" = \"Palun parandage jätkamiseks kehtetud väljad\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/fa.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"لغو\";\n\"ua_cancel_edit_messages_description\" = \"لغو ویرایش پیام\";\n\"ua_connection_error\" = \"خطای اتصال\";\n\"ua_content_error\" = \"خطای محتوا\";\n\"ua_delete_message\" = \"حذف پیام\";\n\"ua_delete_message_description\" = \"پیام های انتخابی را حذف کنید\";\n\"ua_delete_messages\" = \"حذف پیام\";\n\"ua_delete_messages_description\" = \"پیام های انتخابی را حذف کنید\";\n\"ua_edit_messages\" = \"ویرایش\";\n\"ua_edit_messages_description\" = \"ویرایش پیام ها\";\n\"ua_empty_message_list\" = \"هیچ پیامی وجود ندارد\";\n\"ua_mark_messages_read\" = \"علامت گذاری به عنوان خوانده شده\";\n\"ua_mark_messages_read_description\" = \"علامت گذاری پیام های انتخاب شده به عنوان خوانده شده\";\n\"ua_mc_failed_to_load\" = \"بارگیری پیام ممکن نیست. لطفاً بعداً دوباره امتحان کنید.\";\n\"ua_mc_no_longer_available\" = \"پیام انتخاب شده دیگر در دسترس نیست.\";\n\"ua_message_cell_description\" = \"پیام کامل را نمایش می دهد\";\n\"ua_message_cell_editing_description\" = \"انتخاب را تغییر می دهد\";\n\"ua_message_center_title\" = \"مرکز پیام\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"پیام %@، ارسال شده در %@\";\n\"ua_message_not_selected\" = \"هیچ پیامی انتخاب نشد\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"پیام خوانده نشده %@، ارسال شده در %@\";\n\"ua_notification_button_accept\" = \"تایید\";\n\"ua_notification_button_add\" = \"اضافه\";\n\"ua_notification_button_add_to_calendar\" = \"به تقویم اضافه کنید\";\n\"ua_notification_button_book_now\" = \"اکنون رزرو کنید\";\n\"ua_notification_button_buy_now\" = \"هم اکنون خریداری کنید\";\n\"ua_notification_button_copy\" = \"کپی\";\n\"ua_notification_button_decline\" = \"رد کردن\";\n\"ua_notification_button_dislike\" = \"دوست نداشتن\";\n\"ua_notification_button_download\" = \"دانلود\";\n\"ua_notification_button_follow\" = \"دنبال کردن\";\n\"ua_notification_button_less_like\" = \"کمتر شبیه این\";\n\"ua_notification_button_like\" = \"پسندیدن\";\n\"ua_notification_button_more_like\" = \"بیشتر شبیه این\";\n\"ua_notification_button_no\" = \"خیر\";\n\"ua_notification_button_opt_in\" = \"شرکت کنید\";\n\"ua_notification_button_opt_out\" = \"انصراف دهید\";\n\"ua_notification_button_rate_now\" = \"اکنون بسنج\";\n\"ua_notification_button_remind\" = \"بعدا به من یادآوری کن\";\n\"ua_notification_button_save\" = \"ذخیره\";\n\"ua_notification_button_search\" = \"جستجو\";\n\"ua_notification_button_send_info\" = \"ارسال اطلاعات\";\n\"ua_notification_button_share\" = \"اشتراک گذاری\";\n\"ua_notification_button_shop_now\" = \"اکنون خرید کنید\";\n\"ua_notification_button_tell_me_more\" = \"بیشتر بگویید\";\n\"ua_notification_button_unfollow\" = \"لغو دنبال کردن\";\n\"ua_notification_button_yes\" = \"بله\";\n\"ua_ok\" = \"بسیارخوب\";\n\"ua_preference_center_title\" = \"مرکز ترجیحات\";\n\"ua_retry_button\" = \"دوباره امتحان کنید\";\n\"ua_select_all_messages\" = \"انتخاب همه\";\n\"ua_select_all_messages_description\" = \"انتخاب همه پیام ها\";\n\"ua_select_none_messages\" = \"هیچ کدام را انتخاب نکنید\";\n\"ua_select_none_messages_description\" = \"بازنشانی انتخاب پیام\";\n\"ua_unread_message_description\" = \"پیام خوانده نشده\";\n\n// Generic localizations\n\"ua_dismiss\" = \"رد کردن\";\n\"ua_escape\" = \"فرار\";\n\"ua_next\" = \"بعدی\";\n\"ua_previous\" = \"قبلی\";\n\"ua_submit\" = \"ارسال\";\n\"ua_loading\" = \"در حال بارگذاری\";\n\"ua_pager_progress\" = \"صفحه %@ از %@\";\n\"ua_x_of_y\" = \"%@ از %@\";\n\"ua_play\" = \"پخش\";\n\"ua_pause\" = \"مکث\";\n\"ua_stop\" = \"توقف\";\n\"ua_form_processing_error\" = \"خطا در پردازش فرم. لطفا دوباره تلاش کنید\";\n\"ua_close\" = \"بستن\";\n\"ua_mute\" = \"بی‌صدا کردن\";\n\"ua_unmute\" = \"صدادار کردن\";\n\"ua_required_field\" = \"* الزامی\";\n\"ua_invalid_form_message\" = \"لطفاً فیلدهای نامعتبر را برای ادامه اصلاح کنید\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/fi.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Peruuta\";\n\"ua_cancel_edit_messages_description\" = \"Peruuta viestien muokkaukset\";\n\"ua_connection_error\" = \"Yhteysvirhe\";\n\"ua_content_error\" = \"Sisältövirhe\";\n\"ua_delete_message\" = \"Poista viesti\";\n\"ua_delete_message_description\" = \"Poista valitut viestit\";\n\"ua_delete_messages\" = \"Poista viesti\";\n\"ua_delete_messages_description\" = \"Poista valitut viestit\";\n\"ua_edit_messages\" = \"Muokata\";\n\"ua_edit_messages_description\" = \"Muokkaa viestejä\";\n\"ua_empty_message_list\" = \"Ei viestejä\";\n\"ua_mark_messages_read\" = \"Merkitse luetuksi\";\n\"ua_mark_messages_read_description\" = \"Merkitse valitut viestit luetuiksi\";\n\"ua_mc_failed_to_load\" = \"Viestin lataaminen ei onnistu. Yritä uudelleen myöhemmin.\";\n\"ua_mc_no_longer_available\" = \"Valittu viesti ei ole enää saatavilla.\";\n\"ua_message_cell_description\" = \"Näyttää koko viestin\";\n\"ua_message_cell_editing_description\" = \"Vaihtaa valinnan\";\n\"ua_message_center_title\" = \"Viestikeskus\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Viesti %@, lähetetty numeroon %@\";\n\"ua_message_not_selected\" = \"Ei valittuja viestejä\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Lukematon viesti %@, lähetetty klo %@\";\n\"ua_notification_button_accept\" = \"Hyväksy\";\n\"ua_notification_button_add\" = \"Lisää\";\n\"ua_notification_button_add_to_calendar\" = \"Lisää kalenteriin\";\n\"ua_notification_button_book_now\" = \"Varaa nyt\";\n\"ua_notification_button_buy_now\" = \"Osta nyt\";\n\"ua_notification_button_copy\" = \"Kopioi\";\n\"ua_notification_button_decline\" = \"Hylkää\";\n\"ua_notification_button_dislike\" = \"Älä tykkää\";\n\"ua_notification_button_download\" = \"Lataa\";\n\"ua_notification_button_follow\" = \"Seuraa\";\n\"ua_notification_button_less_like\" = \"Vähemmän tämän kaltaisia\";\n\"ua_notification_button_like\" = \"Tykkää\";\n\"ua_notification_button_more_like\" = \"Lisää tämän kaltaisia\";\n\"ua_notification_button_no\" = \"Ei\";\n\"ua_notification_button_opt_in\" = \"Opt-in\";\n\"ua_notification_button_opt_out\" = \"Opt-out\";\n\"ua_notification_button_rate_now\" = \"Arvostele nyt\";\n\"ua_notification_button_remind\" = \"Muistuta minua myöhemmin\";\n\"ua_notification_button_save\" = \"Tallenna\";\n\"ua_notification_button_search\" = \"Hae\";\n\"ua_notification_button_send_info\" = \"Lähetä tietoa\";\n\"ua_notification_button_share\" = \"Jaa\";\n\"ua_notification_button_shop_now\" = \"Tee nyt ostoksia\";\n\"ua_notification_button_tell_me_more\" = \"Kerro minulle lisää\";\n\"ua_notification_button_unfollow\" = \"Älä seuraa\";\n\"ua_notification_button_yes\" = \"Kyllä\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Asetuskeskus\";\n\"ua_retry_button\" = \"Yritä uudelleen\";\n\"ua_select_all_messages\" = \"Valitse kaikki\";\n\"ua_select_all_messages_description\" = \"Valitsee kaikki viestit\";\n\"ua_select_none_messages\" = \"Älä valitse mitään\";\n\"ua_select_none_messages_description\" = \"Nollaa viestivalinta\";\n\"ua_unread_message_description\" = \"Viesti lukematon\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Hylkää\";\n\"ua_escape\" = \"Poistu\";\n\"ua_next\" = \"Seuraava\";\n\"ua_previous\" = \"Edellinen\";\n\"ua_submit\" = \"Lähetä\";\n\"ua_loading\" = \"Lataus\";\n\"ua_pager_progress\" = \"Sivu %@ / %@\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"Toista\";\n\"ua_pause\" = \"Tauko\";\n\"ua_stop\" = \"Pysäytä\";\n\"ua_form_processing_error\" = \"Virhe lomakkeen käsittelyssä. Yritä uudelleen\";\n\"ua_close\" = \"Sulje\";\n\"ua_mute\" = \"Mykistä\";\n\"ua_unmute\" = \"Poista mykistys\";\n\"ua_required_field\" = \"* Pakollinen\";\n\"ua_invalid_form_message\" = \"Korjaa virheelliset kentät jatkaaksesi\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/fr-CA.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Annuler\";\n\"ua_cancel_edit_messages_description\" = \"Annuler les modifications de message\";\n\"ua_connection_error\" = \"Erreur de connexion\";\n\"ua_content_error\" = \"Erreur de contenu\";\n\"ua_delete_message\" = \"Supprimer le message\";\n\"ua_delete_message_description\" = \"Supprimer les messages sélectionnés\";\n\"ua_delete_messages\" = \"Supprimer le message\";\n\"ua_delete_messages_description\" = \"Supprimer les messages sélectionnés\";\n\"ua_edit_messages\" = \"Éditer\";\n\"ua_edit_messages_description\" = \"Modifier les messages\";\n\"ua_empty_message_list\" = \"Pas de messages\";\n\"ua_mark_messages_read\" = \"Marquer comme lu\";\n\"ua_mark_messages_read_description\" = \"Marquer les messages sélectionnés comme lus\";\n\"ua_mc_failed_to_load\" = \"Impossible de charger le message. Veuillez réessayer plus tard.\";\n\"ua_mc_no_longer_available\" = \"Le message sélectionné n\\'est plus disponible.\";\n\"ua_message_cell_description\" = \"Affiche le message complet\";\n\"ua_message_cell_editing_description\" = \"Bascule la sélection\";\n\"ua_message_center_title\" = \"Centre de messagerie\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Message %@, envoyé à %@\";\n\"ua_message_not_selected\" = \"Aucun message sélectionné\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Message non lu %@, envoyé à %@\";\n\"ua_notification_button_accept\" = \"J\\'accepte\";\n\"ua_notification_button_add\" = \"Ajouter\";\n\"ua_notification_button_add_to_calendar\" = \"Ajouter au calendrier\";\n\"ua_notification_button_book_now\" = \"Reserve maintenant\";\n\"ua_notification_button_buy_now\" = \"Acheter maintenant\";\n\"ua_notification_button_copy\" = \"Copie\";\n\"ua_notification_button_decline\" = \"Déclin\";\n\"ua_notification_button_dislike\" = \"Ne pas aimer\";\n\"ua_notification_button_download\" = \"Télécharger\";\n\"ua_notification_button_follow\" = \"Suivre\";\n\"ua_notification_button_less_like\" = \"Moins comme ça\";\n\"ua_notification_button_like\" = \"Comme\";\n\"ua_notification_button_more_like\" = \"Plus comme ça\";\n\"ua_notification_button_no\" = \"Non\";\n\"ua_notification_button_opt_in\" = \"Adhésion\";\n\"ua_notification_button_opt_out\" = \"Se désengager\";\n\"ua_notification_button_rate_now\" = \"Évaluer maintenant\";\n\"ua_notification_button_remind\" = \"Rappelez-moi plus tard\";\n\"ua_notification_button_save\" = \"sauvegarder\";\n\"ua_notification_button_search\" = \"Rechercher\";\n\"ua_notification_button_send_info\" = \"Envoyer des informations\";\n\"ua_notification_button_share\" = \"Partager\";\n\"ua_notification_button_shop_now\" = \"Achetez maintenant\";\n\"ua_notification_button_tell_me_more\" = \"Dis m\\'en plus\";\n\"ua_notification_button_unfollow\" = \"Ne plus suivre\";\n\"ua_notification_button_yes\" = \"Oui\";\n\"ua_ok\" = \"d\\'accord\";\n\"ua_preference_center_title\" = \"Centre de préférences\";\n\"ua_retry_button\" = \"Réessayez\";\n\"ua_select_all_messages\" = \"Tout sélectionner\";\n\"ua_select_all_messages_description\" = \"Sélectionne tous les messages\";\n\"ua_select_none_messages\" = \"Ne rien sélectionner\";\n\"ua_select_none_messages_description\" = \"Réinitialiser la sélection des messages\";\n\"ua_unread_message_description\" = \"Message non lu\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Rejeter\";\n\"ua_escape\" = \"Échapper\";\n\"ua_next\" = \"Suivant\";\n\"ua_previous\" = \"Précédent\";\n\"ua_submit\" = \"Soumettre\";\n\"ua_loading\" = \"Chargement\";\n\"ua_pager_progress\" = \"Page %@ sur %@\";\n\"ua_x_of_y\" = \"%@ sur %@\";\n\"ua_play\" = \"Jouer\";\n\"ua_pause\" = \"Pause\";\n\"ua_stop\" = \"Arrêter\";\n\"ua_form_processing_error\" = \"Erreur lors du traitement du formulaire. Veuillez réessayer\";\n\"ua_close\" = \"Fermer\";\n\"ua_mute\" = \"Couper le son\";\n\"ua_unmute\" = \"Activer le son\";\n\"ua_required_field\" = \"* Obligatoire\";\n\"ua_invalid_form_message\" = \"Veuillez corriger les champs non valides pour continuer\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/fr.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Annuler\";\n\"ua_cancel_edit_messages_description\" = \"Annuler les modifications de message\";\n\"ua_connection_error\" = \"Erreur de connexion\";\n\"ua_content_error\" = \"Erreur de contenu\";\n\"ua_delete_message\" = \"Supprimer le message\";\n\"ua_delete_message_description\" = \"Supprimer le message sélectionné\";\n\"ua_delete_messages\" = \"Supprimer les messages\";\n\"ua_delete_messages_description\" = \"Supprimer les messages sélectionnés\";\n\"ua_edit_messages\" = \"Éditer\";\n\"ua_edit_messages_description\" = \"Modifier les messages\";\n\"ua_empty_message_list\" = \"Aucun message\";\n\"ua_mark_messages_read\" = \"Marquer comme lu\";\n\"ua_mark_messages_read_description\" = \"Marquer les messages sélectionnés comme lus\";\n\"ua_mc_failed_to_load\" = \"Impossible de charger le message. Veuillez réessayer plus tard.\";\n\"ua_mc_no_longer_available\" = \"Le message sélectionné n\\'est plus disponible.\";\n\"ua_message_cell_description\" = \"Affiche le message complet\";\n\"ua_message_cell_editing_description\" = \"Bascule la sélection\";\n\"ua_message_center_title\" = \"Centre de messagerie\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Message %@, envoyé à %@\";\n\"ua_message_not_selected\" = \"Aucun message sélectionné\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Message non lu %@, envoyé à %@\";\n\"ua_notification_button_accept\" = \"Accepter\";\n\"ua_notification_button_add\" = \"Ajouter\";\n\"ua_notification_button_add_to_calendar\" = \"Ajouter au calendrier\";\n\"ua_notification_button_book_now\" = \"Réserver maintenant\";\n\"ua_notification_button_buy_now\" = \"Acheter maintenant\";\n\"ua_notification_button_copy\" = \"Copier\";\n\"ua_notification_button_decline\" = \"Refuser\";\n\"ua_notification_button_dislike\" = \"Je n\\'aime pas\";\n\"ua_notification_button_download\" = \"Télécharger\";\n\"ua_notification_button_follow\" = \"Suivre\";\n\"ua_notification_button_less_like\" = \"Moins de contenus comme celui-là\";\n\"ua_notification_button_like\" = \"J\\'aime\";\n\"ua_notification_button_more_like\" = \"Plus de contenus comme celui-là\";\n\"ua_notification_button_no\" = \"Non\";\n\"ua_notification_button_opt_in\" = \"S\\'abonner\";\n\"ua_notification_button_opt_out\" = \"Se désabonner\";\n\"ua_notification_button_rate_now\" = \"Évaluer maintenant\";\n\"ua_notification_button_remind\" = \"Me le rappeler plus tard\";\n\"ua_notification_button_save\" = \"Enregistrer\";\n\"ua_notification_button_search\" = \"Rechercher\";\n\"ua_notification_button_send_info\" = \"Envoyer des infos\";\n\"ua_notification_button_share\" = \"Partager\";\n\"ua_notification_button_shop_now\" = \"Acheter maintenant\";\n\"ua_notification_button_tell_me_more\" = \"En savoir plus\";\n\"ua_notification_button_unfollow\" = \"Ne plus suivre\";\n\"ua_notification_button_yes\" = \"Oui\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Préférences\";\n\"ua_retry_button\" = \"Réessayer\";\n\"ua_select_all_messages\" = \"Tout sélectionner\";\n\"ua_select_all_messages_description\" = \"Sélectionne tous les messages\";\n\"ua_select_none_messages\" = \"Ne rien sélectionner\";\n\"ua_select_none_messages_description\" = \"Réinitialiser la sélection des messages\";\n\"ua_unread_message_description\" = \"Message non lu\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Rejeter\";\n\"ua_escape\" = \"Échapper\";\n\"ua_next\" = \"Suivant\";\n\"ua_previous\" = \"Précédent\";\n\"ua_submit\" = \"Soumettre\";\n\"ua_loading\" = \"Chargement\";\n\"ua_pager_progress\" = \"Page %@ sur %@\";\n\"ua_x_of_y\" = \"%@ sur %@\";\n\"ua_play\" = \"Jouer\";\n\"ua_pause\" = \"Pause\";\n\"ua_stop\" = \"Arrêter\";\n\"ua_form_processing_error\" = \"Erreur lors du traitement du formulaire. Veuillez réessayer\";\n\"ua_close\" = \"Fermer\";\n\"ua_mute\" = \"Couper le son\";\n\"ua_unmute\" = \"Activer le son\";\n\"ua_required_field\" = \"* Obligatoire\";\n\"ua_invalid_form_message\" = \"Veuillez corriger les champs non valides pour continuer\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/hi.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"रद्द करें\";\n\"ua_cancel_edit_messages_description\" = \"संदेश संपादन रद्द करें\";\n\"ua_connection_error\" = \"कनेक्शन त्रुटि\";\n\"ua_content_error\" = \"सामग्री त्रुटि\";\n\"ua_delete_message\" = \"संदेश को हटाएं\";\n\"ua_delete_message_description\" = \"चयनित संदेशों को हटाएं\";\n\"ua_delete_messages\" = \"संदेश को हटाएं\";\n\"ua_delete_messages_description\" = \"चयनित संदेशों को हटाएं\";\n\"ua_edit_messages\" = \"संपादित करें\";\n\"ua_edit_messages_description\" = \"संदेश संपादित करें\";\n\"ua_empty_message_list\" = \"कोई संदेश नहीं\";\n\"ua_mark_messages_read\" = \"पढ़ा गया चिह्नित करें\";\n\"ua_mark_messages_read_description\" = \"चयनित संदेशों को पढ़े के रूप में चिह्नित करें\";\n\"ua_mc_failed_to_load\" = \"संदेश लोड करने में असमर्थ। कृपया दोबारा कोशिश करें।\";\n\"ua_mc_no_longer_available\" = \"चयनित संदेश अब उपलब्ध नहीं है.\";\n\"ua_message_cell_description\" = \"पूरा संदेश प्रदर्शित करता है\";\n\"ua_message_cell_editing_description\" = \"चयन टॉगल करता है\";\n\"ua_message_center_title\" = \"संदेश केंद्र\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"संदेश %@, %@ पर भेजा गया\";\n\"ua_message_not_selected\" = \"कोई संदेश नहीं चुना गया\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"अपठित संदेश %@, %@ पर भेजा गया\";\n\"ua_notification_button_accept\" = \"स्वीकार करें\";\n\"ua_notification_button_add\" = \"जोड़ें\";\n\"ua_notification_button_add_to_calendar\" = \"कैलेंडर में जोड़ें\";\n\"ua_notification_button_book_now\" = \"अभी बुक करें\";\n\"ua_notification_button_buy_now\" = \"अभी खरीदें\";\n\"ua_notification_button_copy\" = \"कॉपी करें\";\n\"ua_notification_button_decline\" = \"अस्वीकार करें\";\n\"ua_notification_button_dislike\" = \"नापसंद करें\";\n\"ua_notification_button_download\" = \"डाउनलोड करें\";\n\"ua_notification_button_follow\" = \"फॉलो करें\";\n\"ua_notification_button_less_like\" = \"इसकी तरह कम\";\n\"ua_notification_button_like\" = \"पसंद करें\";\n\"ua_notification_button_more_like\" = \"ज्यादातर इस की तरह\";\n\"ua_notification_button_no\" = \"नहीं\";\n\"ua_notification_button_opt_in\" = \"ऑप्ट-इन\";\n\"ua_notification_button_opt_out\" = \"ऑप्ट-आउट\";\n\"ua_notification_button_rate_now\" = \"अभी रेट करें\";\n\"ua_notification_button_remind\" = \"मुझे बाद में याद दिलाएं\";\n\"ua_notification_button_save\" = \"सहेजें\";\n\"ua_notification_button_search\" = \"खोजें\";\n\"ua_notification_button_send_info\" = \"जानकारी भेजें\";\n\"ua_notification_button_share\" = \"साझा करें\";\n\"ua_notification_button_shop_now\" = \"अभी खरीददारी करें\";\n\"ua_notification_button_tell_me_more\" = \"मुझे और बताएं\";\n\"ua_notification_button_unfollow\" = \"अनफॉलो करें\";\n\"ua_notification_button_yes\" = \"हां\";\n\"ua_ok\" = \"ठीक है\";\n\"ua_preference_center_title\" = \"वरीयता केंद्र\";\n\"ua_retry_button\" = \"पुन: प्रयास करें\";\n\"ua_select_all_messages\" = \"सभी का चयन करें\";\n\"ua_select_all_messages_description\" = \"सभी संदेशों का चयन करता है\";\n\"ua_select_none_messages\" = \"किसी का चयन न करें\";\n\"ua_select_none_messages_description\" = \"संदेश चयन रीसेट करें\";\n\"ua_unread_message_description\" = \"संदेश अपठित करें\";\n\n// Generic localizations\n\"ua_dismiss\" = \"खारिज करें\";\n\"ua_escape\" = \"बचें\";\n\"ua_next\" = \"अगला\";\n\"ua_previous\" = \"पिछला\";\n\"ua_submit\" = \"जमा करें\";\n\"ua_loading\" = \"लोड हो रहा है\";\n\"ua_pager_progress\" = \"पृष्ठ %@ का %@\";\n\"ua_x_of_y\" = \"%@ में से %@\";\n\"ua_play\" = \"चलाएं\";\n\"ua_pause\" = \"विराम\";\n\"ua_stop\" = \"रोकें\";\n\"ua_form_processing_error\" = \"फॉर्म प्रोसेसिंग में त्रुटि। कृपया पुनः प्रयास करें\";\n\"ua_close\" = \"बंद करें\";\n\"ua_mute\" = \"म्यूट करें\";\n\"ua_unmute\" = \"अनम्यूट करें\";\n\"ua_required_field\" = \"* आवश्यक\";\n\"ua_invalid_form_message\" = \"कृपया जारी रखने के लिए अमान्य फ़ील्ड को ठीक करें\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/hr.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Otkaži\";\n\"ua_cancel_edit_messages_description\" = \"Otkažite uređivanje poruka\";\n\"ua_connection_error\" = \"Greška u povezivanju\";\n\"ua_content_error\" = \"Pogreška sadržaja\";\n\"ua_delete_message\" = \"Izbriši poruku\";\n\"ua_delete_message_description\" = \"Izbrišite odabrane poruke\";\n\"ua_delete_messages\" = \"Izbriši poruku\";\n\"ua_delete_messages_description\" = \"Izbrišite odabrane poruke\";\n\"ua_edit_messages\" = \"Uredi\";\n\"ua_edit_messages_description\" = \"Uredite poruke\";\n\"ua_empty_message_list\" = \"Nema poruka\";\n\"ua_mark_messages_read\" = \"Označi kao pročitano\";\n\"ua_mark_messages_read_description\" = \"Označite odabrane poruke kao pročitane\";\n\"ua_mc_failed_to_load\" = \"Nije moguće učitati poruku. Molimo pokušajte ponovno kasnije.\";\n\"ua_mc_no_longer_available\" = \"Odabrana poruka više nije dostupna.\";\n\"ua_message_cell_description\" = \"Prikazuje cijelu poruku\";\n\"ua_message_cell_editing_description\" = \"Prebacuje odabir\";\n\"ua_message_center_title\" = \"Centar za poruke\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Poruka %@, poslana na %@\";\n\"ua_message_not_selected\" = \"Nije odabrana nijedna poruka\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Nepročitana poruka %@, poslana u %@\";\n\"ua_notification_button_accept\" = \"Prihvati\";\n\"ua_notification_button_add\" = \"Dodaj\";\n\"ua_notification_button_add_to_calendar\" = \"Dodaj u kalendar\";\n\"ua_notification_button_book_now\" = \"Rezervirajte sada\";\n\"ua_notification_button_buy_now\" = \"Kupi odmah\";\n\"ua_notification_button_copy\" = \"Kopiraj\";\n\"ua_notification_button_decline\" = \"Odbij\";\n\"ua_notification_button_dislike\" = \"Ne sviđa mi se\";\n\"ua_notification_button_download\" = \"Preuzimanje datoteka\";\n\"ua_notification_button_follow\" = \"Slijedi\";\n\"ua_notification_button_less_like\" = \"Manje ovakvih\";\n\"ua_notification_button_like\" = \"Kao\";\n\"ua_notification_button_more_like\" = \"Više ovakvih\";\n\"ua_notification_button_no\" = \"Ne\";\n\"ua_notification_button_opt_in\" = \"Uključite se\";\n\"ua_notification_button_opt_out\" = \"Isključivanje\";\n\"ua_notification_button_rate_now\" = \"Ocijeni sada\";\n\"ua_notification_button_remind\" = \"Podsjeti me kasnije\";\n\"ua_notification_button_save\" = \"Sačuvaj\";\n\"ua_notification_button_search\" = \"Traži\";\n\"ua_notification_button_send_info\" = \"Pošaljite informacije\";\n\"ua_notification_button_share\" = \"Udio\";\n\"ua_notification_button_shop_now\" = \"Kupite sada\";\n\"ua_notification_button_tell_me_more\" = \"Reci mi više\";\n\"ua_notification_button_unfollow\" = \"Prestani da slijediš\";\n\"ua_notification_button_yes\" = \"Da\";\n\"ua_ok\" = \"U redu\";\n\"ua_preference_center_title\" = \"Centar za preferencije\";\n\"ua_retry_button\" = \"Pokušajte ponovno\";\n\"ua_select_all_messages\" = \"Odaberi sve\";\n\"ua_select_all_messages_description\" = \"Odabire sve poruke\";\n\"ua_select_none_messages\" = \"Ne odaberi ništa\";\n\"ua_select_none_messages_description\" = \"Poništi odabir poruke\";\n\"ua_unread_message_description\" = \"Poruka nepročitana\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Odbaciti\";\n\"ua_escape\" = \"Izlaz\";\n\"ua_next\" = \"Sljedeće\";\n\"ua_previous\" = \"Prethodno\";\n\"ua_submit\" = \"Podnijeti\";\n\"ua_loading\" = \"Učitavanje\";\n\"ua_pager_progress\" = \"Stranica %@ od %@\";\n\"ua_x_of_y\" = \"%@ od %@\";\n\"ua_play\" = \"Reproduciraj\";\n\"ua_pause\" = \"Pauziraj\";\n\"ua_stop\" = \"Zaustavi\";\n\"ua_form_processing_error\" = \"Pogreška u obradi obrasca. Molimo pokušajte ponovno\";\n\"ua_close\" = \"Zatvori\";\n\"ua_mute\" = \"Isključi zvuk\";\n\"ua_unmute\" = \"Uključi zvuk\";\n\"ua_required_field\" = \"* Obavezno\";\n\"ua_invalid_form_message\" = \"Molimo ispravite nevažeća polja za nastavak\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/hu.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Mégse\";\n\"ua_cancel_edit_messages_description\" = \"Üzenetszerkesztések visszavonása\";\n\"ua_connection_error\" = \"Kapcsolati hiba\";\n\"ua_content_error\" = \"Tartalmi hiba\";\n\"ua_delete_message\" = \"Üzenet törlése\";\n\"ua_delete_message_description\" = \"A kiválasztott üzenetek törlése\";\n\"ua_delete_messages\" = \"Üzenet törlése\";\n\"ua_delete_messages_description\" = \"A kiválasztott üzenetek törlése\";\n\"ua_edit_messages\" = \"Szerkesztés\";\n\"ua_edit_messages_description\" = \"Üzenetek szerkesztése\";\n\"ua_empty_message_list\" = \"Nincs üzenete\";\n\"ua_mark_messages_read\" = \"Jelöld olvasottnak\";\n\"ua_mark_messages_read_description\" = \"A kiválasztott üzenetek megjelölése olvasottként\";\n\"ua_mc_failed_to_load\" = \"Nem tudtam betölteni az üzenetet. Próbálja újra később.\";\n\"ua_mc_no_longer_available\" = \"A kiválasztott üzenet már nem érhető el.\";\n\"ua_message_cell_description\" = \"A teljes üzenetet jeleníti meg\";\n\"ua_message_cell_editing_description\" = \"Váltsa a kijelölést\";\n\"ua_message_center_title\" = \"Üzenetközpont\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Üzenet: %@, elküldve: %@\";\n\"ua_message_not_selected\" = \"Nincsenek kiválasztott üzenetek\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Olvasatlan üzenet: %@, elküldve: %@\";\n\"ua_notification_button_accept\" = \"Fogadd el\";\n\"ua_notification_button_add\" = \"Hozzáadás\";\n\"ua_notification_button_add_to_calendar\" = \"Hozzáadás naptárhoz\";\n\"ua_notification_button_book_now\" = \"Foglalja le most\";\n\"ua_notification_button_buy_now\" = \"Megvétel most\";\n\"ua_notification_button_copy\" = \"Másolás\";\n\"ua_notification_button_decline\" = \"Utasítsd el\";\n\"ua_notification_button_dislike\" = \"Nem tetszik\";\n\"ua_notification_button_download\" = \"Töltsd le\";\n\"ua_notification_button_follow\" = \"Követem\";\n\"ua_notification_button_less_like\" = \"Kevesebb ilyen\";\n\"ua_notification_button_like\" = \"Tetszik\";\n\"ua_notification_button_more_like\" = \"Még több ilyen\";\n\"ua_notification_button_no\" = \"Nem\";\n\"ua_notification_button_opt_in\" = \"Kérem\";\n\"ua_notification_button_opt_out\" = \"Nem kérem\";\n\"ua_notification_button_rate_now\" = \"Most értékelem\";\n\"ua_notification_button_remind\" = \"Emlékeztess később\";\n\"ua_notification_button_save\" = \"Mentés\";\n\"ua_notification_button_search\" = \"Keress\";\n\"ua_notification_button_send_info\" = \"Küldjön infót\";\n\"ua_notification_button_share\" = \"Oszd meg\";\n\"ua_notification_button_shop_now\" = \"Vásárlás most\";\n\"ua_notification_button_tell_me_more\" = \"Mondjon róla többet\";\n\"ua_notification_button_unfollow\" = \"Nem követem\";\n\"ua_notification_button_yes\" = \"Igen\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Preferencia központ\";\n\"ua_retry_button\" = \"Próbáld újra\";\n\"ua_select_all_messages\" = \"Válaszd ki mindet\";\n\"ua_select_all_messages_description\" = \"Kijelöli az összes üzenetet\";\n\"ua_select_none_messages\" = \"Ne válassz ki egyet sem\";\n\"ua_select_none_messages_description\" = \"Üzenetválasztás visszaállítása\";\n\"ua_unread_message_description\" = \"Az üzenet olvasatlan\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Elutasítás\";\n\"ua_escape\" = \"Kilépés\";\n\"ua_next\" = \"Következő\";\n\"ua_previous\" = \"Előző\";\n\"ua_submit\" = \"Beküldés\";\n\"ua_loading\" = \"Betöltés\";\n\"ua_pager_progress\" = \"%@. oldal a %@-ból\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"Lejátszás\";\n\"ua_pause\" = \"Szünet\";\n\"ua_stop\" = \"Leállítás\";\n\"ua_form_processing_error\" = \"Hiba az űrlap feldolgozása során. Kérjük, próbálja újra\";\n\"ua_close\" = \"Bezárás\";\n\"ua_mute\" = \"Némítás\";\n\"ua_unmute\" = \"Némítás feloldása\";\n\"ua_required_field\" = \"* Kötelező\";\n\"ua_invalid_form_message\" = \"Kérjük, javítsa ki az érvénytelen mezőket a folytatáshoz\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/id.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Batal\";\n\"ua_cancel_edit_messages_description\" = \"Batalkan pengeditan pesan\";\n\"ua_connection_error\" = \"Galat Koneksi\";\n\"ua_content_error\" = \"Kesalahan Konten\";\n\"ua_delete_message\" = \"Hapus pesan\";\n\"ua_delete_message_description\" = \"Hapus pesan yang dipilih\";\n\"ua_delete_messages\" = \"Hapus pesan\";\n\"ua_delete_messages_description\" = \"Hapus pesan yang dipilih\";\n\"ua_edit_messages\" = \"Edit\";\n\"ua_edit_messages_description\" = \"Edit pesan\";\n\"ua_empty_message_list\" = \"Tidak ada pesan\";\n\"ua_mark_messages_read\" = \"Tandai sebagai Sudah Dibaca\";\n\"ua_mark_messages_read_description\" = \"Tandai pesan yang dipilih telah dibaca\";\n\"ua_mc_failed_to_load\" = \"Tidak dapat memuat pesan. Silakan coba lagi nanti.\";\n\"ua_mc_no_longer_available\" = \"Pesan yang dipilih tidak lagi tersedia.\";\n\"ua_message_cell_description\" = \"Menampilkan pesan lengkap\";\n\"ua_message_cell_editing_description\" = \"Mengalihkan pilihan\";\n\"ua_message_center_title\" = \"Pusat Pesan\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Pesan %@, dikirim pada %@\";\n\"ua_message_not_selected\" = \"Tidak ada pesan yang dipilih\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Pesan yang belum dibaca %@, dikirim pada %@\";\n\"ua_notification_button_accept\" = \"Setujui\";\n\"ua_notification_button_add\" = \"Tambahkan\";\n\"ua_notification_button_add_to_calendar\" = \"Tambahkan ke Kalender\";\n\"ua_notification_button_book_now\" = \"Pesan Sekarang\";\n\"ua_notification_button_buy_now\" = \"Beli Sekarang\";\n\"ua_notification_button_copy\" = \"Salin\";\n\"ua_notification_button_decline\" = \"Tolak\";\n\"ua_notification_button_dislike\" = \"Tidak Suka\";\n\"ua_notification_button_download\" = \"Unduh\";\n\"ua_notification_button_follow\" = \"Ikuti\";\n\"ua_notification_button_less_like\" = \"Kurangi yang Seperti Ini\";\n\"ua_notification_button_like\" = \"Suka\";\n\"ua_notification_button_more_like\" = \"Lebih Banyak yang Seperti Ini\";\n\"ua_notification_button_no\" = \"Tidak\";\n\"ua_notification_button_opt_in\" = \"Turut serta\";\n\"ua_notification_button_opt_out\" = \"Batal turut serta\";\n\"ua_notification_button_rate_now\" = \"Beri Nilai Sekarang\";\n\"ua_notification_button_remind\" = \"Ingatkan Lagi Nanti\";\n\"ua_notification_button_save\" = \"Simpan\";\n\"ua_notification_button_search\" = \"Cari\";\n\"ua_notification_button_send_info\" = \"Kirim Info\";\n\"ua_notification_button_share\" = \"Bagikan\";\n\"ua_notification_button_shop_now\" = \"Belanja Sekarang\";\n\"ua_notification_button_tell_me_more\" = \"Beri Info Lebih Lengkap\";\n\"ua_notification_button_unfollow\" = \"Berhenti ikuti\";\n\"ua_notification_button_yes\" = \"Ya\";\n\"ua_ok\" = \"OKE\";\n\"ua_preference_center_title\" = \"Pusat Preferensi\";\n\"ua_retry_button\" = \"Coba lagi\";\n\"ua_select_all_messages\" = \"Pilih Semua\";\n\"ua_select_all_messages_description\" = \"Pilih semua pesan\";\n\"ua_select_none_messages\" = \"Batal Pilih Semua\";\n\"ua_select_none_messages_description\" = \"Atur ulang pemilihan pesan\";\n\"ua_unread_message_description\" = \"Pesan belum dibaca\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Tutup\";\n\"ua_escape\" = \"Keluar\";\n\"ua_next\" = \"Berikutnya\";\n\"ua_previous\" = \"Sebelumnya\";\n\"ua_submit\" = \"Kirim\";\n\"ua_loading\" = \"Memuat\";\n\"ua_pager_progress\" = \"Halaman %@ dari %@\";\n\"ua_x_of_y\" = \"%@ dari %@\";\n\"ua_play\" = \"Putar\";\n\"ua_pause\" = \"Jeda\";\n\"ua_stop\" = \"Berhenti\";\n\"ua_form_processing_error\" = \"Kesalahan memproses formulir. Silakan coba lagi\";\n\"ua_close\" = \"Tutup\";\n\"ua_mute\" = \"Bisukan\";\n\"ua_unmute\" = \"Bunyikan\";\n\"ua_required_field\" = \"* Wajib diisi\";\n\"ua_invalid_form_message\" = \"Silakan perbaiki kolom yang tidak valid untuk melanjutkan\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/it.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Annulla\";\n\"ua_cancel_edit_messages_description\" = \"Annulla le modifiche ai messaggi\";\n\"ua_connection_error\" = \"Errore di connessione\";\n\"ua_content_error\" = \"Errore di contenuto\";\n\"ua_delete_message\" = \"Cancella il messaggio\";\n\"ua_delete_message_description\" = \"Elimina i messaggi selezionati\";\n\"ua_delete_messages\" = \"Cancella il messaggio\";\n\"ua_delete_messages_description\" = \"Elimina i messaggi selezionati\";\n\"ua_edit_messages\" = \"Modificare\";\n\"ua_edit_messages_description\" = \"Modifica messaggi\";\n\"ua_empty_message_list\" = \"Nessun messaggio\";\n\"ua_mark_messages_read\" = \"Segna come letto\";\n\"ua_mark_messages_read_description\" = \"Segna i messaggi selezionati come letti\";\n\"ua_mc_failed_to_load\" = \"Impossibile caricare i messaggi. Riprovare più tardi\";\n\"ua_mc_no_longer_available\" = \"Il messaggio selezionato non è più disponibile.\";\n\"ua_message_cell_description\" = \"Visualizza il messaggio completo\";\n\"ua_message_cell_editing_description\" = \"Commuta la selezione\";\n\"ua_message_center_title\" = \"Centro messaggi\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Messaggio %@, inviato alle %@\";\n\"ua_message_not_selected\" = \"Nessun messaggio selezionato\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Messaggio non letto %@, inviato alle %@\";\n\"ua_notification_button_accept\" = \"Accetta\";\n\"ua_notification_button_add\" = \"Aggiungi\";\n\"ua_notification_button_add_to_calendar\" = \"Aggiungi al calendario\";\n\"ua_notification_button_book_now\" = \"Prenota adesso\";\n\"ua_notification_button_buy_now\" = \"Acquista ora\";\n\"ua_notification_button_copy\" = \"Copia\";\n\"ua_notification_button_decline\" = \"Rifiuta\";\n\"ua_notification_button_dislike\" = \"Non mi piace\";\n\"ua_notification_button_download\" = \"Scarica\";\n\"ua_notification_button_follow\" = \"Segui\";\n\"ua_notification_button_less_like\" = \"Di meno come questo\";\n\"ua_notification_button_like\" = \"Mi piace\";\n\"ua_notification_button_more_like\" = \"Di più come questo\";\n\"ua_notification_button_no\" = \"No\";\n\"ua_notification_button_opt_in\" = \"Opt-in\";\n\"ua_notification_button_opt_out\" = \"Opt-out\";\n\"ua_notification_button_rate_now\" = \"Valuta adesso\";\n\"ua_notification_button_remind\" = \"Ricordamelo più tardi\";\n\"ua_notification_button_save\" = \"Salva\";\n\"ua_notification_button_search\" = \"Cerca\";\n\"ua_notification_button_send_info\" = \"Invia informazioni\";\n\"ua_notification_button_share\" = \"Condividi\";\n\"ua_notification_button_shop_now\" = \"Fai shopping ora\";\n\"ua_notification_button_tell_me_more\" = \"Più informazioni\";\n\"ua_notification_button_unfollow\" = \"Smetti di seguire\";\n\"ua_notification_button_yes\" = \"Sì\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centro di preferenze\";\n\"ua_retry_button\" = \"Riprovare\";\n\"ua_select_all_messages\" = \"Seleziona tutti\";\n\"ua_select_all_messages_description\" = \"Seleziona tutti i messaggi\";\n\"ua_select_none_messages\" = \"Non selezionare\";\n\"ua_select_none_messages_description\" = \"Reimposta la selezione del messaggio\";\n\"ua_unread_message_description\" = \"Messaggio non letto\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Chiudere\";\n\"ua_escape\" = \"Sfuggire\";\n\"ua_next\" = \"Successivo\";\n\"ua_previous\" = \"Precedente\";\n\"ua_submit\" = \"Inviare\";\n\"ua_loading\" = \"Caricamento\";\n\"ua_pager_progress\" = \"Pagina %@ di %@\";\n\"ua_x_of_y\" = \"%@ di %@\";\n\"ua_play\" = \"Riprodurre\";\n\"ua_pause\" = \"Pausa\";\n\"ua_stop\" = \"Fermare\";\n\"ua_form_processing_error\" = \"Errore durante l'elaborazione del modulo. Per favore riprova\";\n\"ua_close\" = \"Chiudi\";\n\"ua_mute\" = \"Silenzia\";\n\"ua_unmute\" = \"Riattiva audio\";\n\"ua_required_field\" = \"* Obbligatorio\";\n\"ua_invalid_form_message\" = \"Si prega di correggere i campi non validi per continuare\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/iw.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"ביטול\";\n\"ua_cancel_edit_messages_description\" = \"בטל את עריכות ההודעה\";\n\"ua_connection_error\" = \"שגיאת התחברות\";\n\"ua_content_error\" = \"שגיאת תוכן\";\n\"ua_delete_message\" = \"למחוק הודעה\";\n\"ua_delete_message_description\" = \"מחק את ההודעות שנבחרו\";\n\"ua_delete_messages\" = \"למחוק הודעה\";\n\"ua_delete_messages_description\" = \"מחק את ההודעות שנבחרו\";\n\"ua_edit_messages\" = \"לַעֲרוֹך\";\n\"ua_edit_messages_description\" = \"ערוך הודעות\";\n\"ua_empty_message_list\" = \"אין הודעות\";\n\"ua_mark_messages_read\" = \"סמן כנקרא\";\n\"ua_mark_messages_read_description\" = \"סמן את ההודעות שנבחרו כנקראו\";\n\"ua_mc_failed_to_load\" = \"לא ניתן לטעון הודעה. נסה שוב מאוחר יותר.\";\n\"ua_mc_no_longer_available\" = \"ההודעה שנבחרה אינה זמינה יותר.\";\n\"ua_message_cell_description\" = \"מציג את ההודעה המלאה\";\n\"ua_message_cell_editing_description\" = \"מחליף בחירה\";\n\"ua_message_center_title\" = \"מרכז ההודעות\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"הודעה %@, נשלחה ב-%@\";\n\"ua_message_not_selected\" = \"לא נבחרו הודעות\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"הודעה שלא נקראה %@, נשלחה ב-%@\";\n\"ua_notification_button_accept\" = \"אשר\";\n\"ua_notification_button_add\" = \"הוסף\";\n\"ua_notification_button_add_to_calendar\" = \"הוסף ליומן\";\n\"ua_notification_button_book_now\" = \"הזמן עכשיו\";\n\"ua_notification_button_buy_now\" = \"קנה עכשיו\";\n\"ua_notification_button_copy\" = \"העתק\";\n\"ua_notification_button_decline\" = \"דחה\";\n\"ua_notification_button_dislike\" = \"לא אהבתי\";\n\"ua_notification_button_download\" = \"הורדה\";\n\"ua_notification_button_follow\" = \"עקוב\";\n\"ua_notification_button_less_like\" = \"פחות כמו זה\";\n\"ua_notification_button_like\" = \"אהבתי\";\n\"ua_notification_button_more_like\" = \"יותר כמו זה\";\n\"ua_notification_button_no\" = \"לא\";\n\"ua_notification_button_opt_in\" = \"הסכמת הרשמה\";\n\"ua_notification_button_opt_out\" = \"ביטול הסכמת הרשמה\";\n\"ua_notification_button_rate_now\" = \"דרג עכשיו\";\n\"ua_notification_button_remind\" = \"הזכר לי מאוחר יותר\";\n\"ua_notification_button_save\" = \"שמור\";\n\"ua_notification_button_search\" = \"חיפוש\";\n\"ua_notification_button_send_info\" = \"שלח פרטים\";\n\"ua_notification_button_share\" = \"שתף\";\n\"ua_notification_button_shop_now\" = \"קנה עכשיו\";\n\"ua_notification_button_tell_me_more\" = \"ספר לי עוד\";\n\"ua_notification_button_unfollow\" = \"הסר עקיבה\";\n\"ua_notification_button_yes\" = \"כן\";\n\"ua_ok\" = \"אישור\";\n\"ua_preference_center_title\" = \"מרכז העדפות\";\n\"ua_retry_button\" = \"נסה שוב\";\n\"ua_select_all_messages\" = \"בחר הכול\";\n\"ua_select_all_messages_description\" = \"בוחר את כל ההודעות\";\n\"ua_select_none_messages\" = \"ללא בחירה\";\n\"ua_select_none_messages_description\" = \"אפס את בחירת ההודעה\";\n\"ua_unread_message_description\" = \"הודעה שלא נקראה\";\n\n// Generic localizations\n\"ua_dismiss\" = \"להתעלם\";\n\"ua_escape\" = \"לצאת\";\n\"ua_next\" = \"הבא\";\n\"ua_previous\" = \"הקודם\";\n\"ua_submit\" = \"להגיש\";\n\"ua_loading\" = \"טוען\";\n\"ua_pager_progress\" = \"עמוד %@ מתוך %@\";\n\"ua_x_of_y\" = \"%@ מתוך %@\";\n\"ua_play\" = \"לנגן\";\n\"ua_pause\" = \"להפסיק\";\n\"ua_stop\" = \"לעצור\";\n\"ua_form_processing_error\" = \"שגיאה בעיבוד הטופס. אנא נסה שוב\";\n\"ua_close\" = \"סגור\";\n\"ua_mute\" = \"השתק\";\n\"ua_unmute\" = \"בטל השתקה\";\n\"ua_required_field\" = \"* נדרש\";\n\"ua_invalid_form_message\" = \"אנא תקן את השדות הלא תקינים כדי להמשיך\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ja.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"キャンセルする\";\n\"ua_cancel_edit_messages_description\" = \"メッセージ編集をキャンセル\";\n\"ua_connection_error\" = \"接続エラー\";\n\"ua_content_error\" = \"コンテンツエラー\";\n\"ua_delete_message\" = \"メッセージを削除\";\n\"ua_delete_message_description\" = \"選択したメッセージを削除\";\n\"ua_delete_messages\" = \"メッセージを削除\";\n\"ua_delete_messages_description\" = \"選択したメッセージを削除\";\n\"ua_edit_messages\" = \"編集\";\n\"ua_edit_messages_description\" = \"メッセージを編集\";\n\"ua_empty_message_list\" = \"メッセージはありません\";\n\"ua_mark_messages_read\" = \"既読にする\";\n\"ua_mark_messages_read_description\" = \"選択したメッセージに既読のマークを付ける\";\n\"ua_mc_failed_to_load\" = \"メッセージを読み込むことができません。後ほどもう一度お試しください。\";\n\"ua_mc_no_longer_available\" = \"選択したメッセージは使用できなくなります。\";\n\"ua_message_cell_description\" = \"完全なメッセージを表示\";\n\"ua_message_cell_editing_description\" = \"選択を切り替えます\";\n\"ua_message_center_title\" = \"メッセージ センター\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"メッセージ%@、%@で送信\";\n\"ua_message_not_selected\" = \"メッセージが選択されていません\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"未読メッセージ%@、%@で送信\";\n\"ua_notification_button_accept\" = \"同意\";\n\"ua_notification_button_add\" = \"追加する\";\n\"ua_notification_button_add_to_calendar\" = \"カレンダーに追加\";\n\"ua_notification_button_book_now\" = \"今すぐ予約する\";\n\"ua_notification_button_buy_now\" = \"今すぐ購入する\";\n\"ua_notification_button_copy\" = \"コピーする\";\n\"ua_notification_button_decline\" = \"拒否\";\n\"ua_notification_button_dislike\" = \"いいね！を取り消す\";\n\"ua_notification_button_download\" = \"ダウンロード\";\n\"ua_notification_button_follow\" = \"フォローする\";\n\"ua_notification_button_less_like\" = \"もっと違う項目\";\n\"ua_notification_button_like\" = \"いいね！\";\n\"ua_notification_button_more_like\" = \"さらに似ている項目\";\n\"ua_notification_button_no\" = \"いいえ\";\n\"ua_notification_button_opt_in\" = \"オプトイン\";\n\"ua_notification_button_opt_out\" = \"オプトアウト\";\n\"ua_notification_button_rate_now\" = \"今すぐ評価する\";\n\"ua_notification_button_remind\" = \"後で通知\";\n\"ua_notification_button_save\" = \"保存する\";\n\"ua_notification_button_search\" = \"検索する\";\n\"ua_notification_button_send_info\" = \"情報を送信する\";\n\"ua_notification_button_share\" = \"共有\";\n\"ua_notification_button_shop_now\" = \"今すぐお店へ移動する\";\n\"ua_notification_button_tell_me_more\" = \"詳しく教えてください\";\n\"ua_notification_button_unfollow\" = \"フォローしない\";\n\"ua_notification_button_yes\" = \"はい\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"プリファレンスセンター\";\n\"ua_retry_button\" = \"もう一度やり直す\";\n\"ua_select_all_messages\" = \"すべて選択する\";\n\"ua_select_all_messages_description\" = \"すべてのメッセージを選択\";\n\"ua_select_none_messages\" = \"何も選択しない\";\n\"ua_select_none_messages_description\" = \"メッセージ選択をリセット\";\n\"ua_unread_message_description\" = \"未読メッセージ\";\n\n// Generic localizations\n\"ua_dismiss\" = \"解散する\";\n\"ua_escape\" = \"脱出\";\n\"ua_next\" = \"次へ\";\n\"ua_previous\" = \"前\";\n\"ua_submit\" = \"は提出します\";\n\"ua_loading\" = \"ロード中\";\n\"ua_pager_progress\" = \"ページ%@/%@\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"再生\";\n\"ua_pause\" = \"一時停止\";\n\"ua_stop\" = \"止める\";\n\"ua_form_processing_error\" = \"フォームの処理中にエラーが発生しました。もう一度お試しください\";\n\"ua_close\" = \"閉じる\";\n\"ua_mute\" = \"ミュート\";\n\"ua_unmute\" = \"ミュート解除\";\n\"ua_required_field\" = \"* 必須\";\n\"ua_invalid_form_message\" = \"続行するには、無効なフィールドを修正してください\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ko.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"취소\";\n\"ua_cancel_edit_messages_description\" = \"메시지 수정 취소\";\n\"ua_connection_error\" = \"연결 오류\";\n\"ua_content_error\" = \"콘텐츠 오류\";\n\"ua_delete_message\" = \"메시지 삭제\";\n\"ua_delete_message_description\" = \"선택한 메시지 삭제\";\n\"ua_delete_messages\" = \"메시지 삭제\";\n\"ua_delete_messages_description\" = \"선택한 메시지 삭제\";\n\"ua_edit_messages\" = \"편집하다\";\n\"ua_edit_messages_description\" = \"메시지 수정\";\n\"ua_empty_message_list\" = \"메시지가 없습니다\";\n\"ua_mark_messages_read\" = \"읽음 표시\";\n\"ua_mark_messages_read_description\" = \"선택한 메시지를 읽은 상태로 표시\";\n\"ua_mc_failed_to_load\" = \"메시지를 로드할 수 없습니다. 나중에 다시 시도해주십시오.\";\n\"ua_mc_no_longer_available\" = \"선택한 메시지는 더 이상 사용할 수 없습니다.\";\n\"ua_message_cell_description\" = \"전체 메시지 표시\";\n\"ua_message_cell_editing_description\" = \"선택 토글\";\n\"ua_message_center_title\" = \"메시지 센터\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"메시지 %@, %@ 에 전송됨\";\n\"ua_message_not_selected\" = \"선택된 메시지 없음\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"읽지 않은 메시지 %@, %@ 에 전송됨\";\n\"ua_notification_button_accept\" = \"수락\";\n\"ua_notification_button_add\" = \"추가\";\n\"ua_notification_button_add_to_calendar\" = \"캘린더에 추가\";\n\"ua_notification_button_book_now\" = \"지금 예약\";\n\"ua_notification_button_buy_now\" = \"지금 구매\";\n\"ua_notification_button_copy\" = \"복사\";\n\"ua_notification_button_decline\" = \"거절\";\n\"ua_notification_button_dislike\" = \"싫어요\";\n\"ua_notification_button_download\" = \"다운로드\";\n\"ua_notification_button_follow\" = \"팔로우\";\n\"ua_notification_button_less_like\" = \"덜 좋아함\";\n\"ua_notification_button_like\" = \"좋아요\";\n\"ua_notification_button_more_like\" = \"더 좋아함\";\n\"ua_notification_button_no\" = \"아니요\";\n\"ua_notification_button_opt_in\" = \"구독\";\n\"ua_notification_button_opt_out\" = \"구독중지\";\n\"ua_notification_button_rate_now\" = \"지금 평가\";\n\"ua_notification_button_remind\" = \"나중에 알림\";\n\"ua_notification_button_save\" = \"저장\";\n\"ua_notification_button_search\" = \"검색\";\n\"ua_notification_button_send_info\" = \"정보 보내기\";\n\"ua_notification_button_share\" = \"공유\";\n\"ua_notification_button_shop_now\" = \"지금 쇼핑\";\n\"ua_notification_button_tell_me_more\" = \"더 자세히\";\n\"ua_notification_button_unfollow\" = \"언팔로우\";\n\"ua_notification_button_yes\" = \"예\";\n\"ua_ok\" = \"확인\";\n\"ua_preference_center_title\" = \"환경 설정 센터\";\n\"ua_retry_button\" = \"다시 시도\";\n\"ua_select_all_messages\" = \"모두 선택\";\n\"ua_select_all_messages_description\" = \"모든 메시지 선택\";\n\"ua_select_none_messages\" = \"없음 선택\";\n\"ua_select_none_messages_description\" = \"메시지 선택 재설정\";\n\"ua_unread_message_description\" = \"읽지 않은 메시지\";\n\n// Generic localizations\n\"ua_dismiss\" = \"해제\";\n\"ua_escape\" = \"탈출\";\n\"ua_next\" = \"다음\";\n\"ua_previous\" = \"이전\";\n\"ua_submit\" = \"제출\";\n\"ua_loading\" = \"불러오는 중\";\n\"ua_pager_progress\" = \"페이지 %@/%@\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"재생\";\n\"ua_pause\" = \"일시정지\";\n\"ua_stop\" = \"정지\";\n\"ua_form_processing_error\" = \"양식 처리 중 오류가 발생했습니다. 다시 시도해 주세요\";\n\"ua_close\" = \"닫기\";\n\"ua_mute\" = \"음소거\";\n\"ua_unmute\" = \"음소거 해제\";\n\"ua_required_field\" = \"* 필수\";\n\"ua_invalid_form_message\" = \"계속하려면 잘못된 필드를 수정하세요\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/lt.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Atšaukti\";\n\"ua_cancel_edit_messages_description\" = \"Atšaukti žinučių redagavimą\";\n\"ua_connection_error\" = \"Ryšio klaida\";\n\"ua_content_error\" = \"Turinio klaida\";\n\"ua_delete_message\" = \"Ištrinti žinutę\";\n\"ua_delete_message_description\" = \"Ištrinti pasirinktas žinutes\";\n\"ua_delete_messages\" = \"Ištrinti žinutę\";\n\"ua_delete_messages_description\" = \"Ištrinti pasirinktas žinutes\";\n\"ua_edit_messages\" = \"Redaguoti\";\n\"ua_edit_messages_description\" = \"Redaguoti žinutes\";\n\"ua_empty_message_list\" = \"Jokių žinučių\";\n\"ua_mark_messages_read\" = \"Pažymėti kaip skaitytą\";\n\"ua_mark_messages_read_description\" = \"Pažymėti pasirinktas žinutes kaip perskaitytas\";\n\"ua_mc_failed_to_load\" = \"Nepavyko įkelti žinutės. Pabandykite dar kartą vėliau.\";\n\"ua_mc_no_longer_available\" = \"Pasirinkta žinutė nebepasiekiama.\";\n\"ua_message_cell_description\" = \"Rodo visą žinutę\";\n\"ua_message_cell_editing_description\" = \"Perjungia pasirinkimą\";\n\"ua_message_center_title\" = \"Žinučių centras\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Žinutė %@, išsiųsta %@\";\n\"ua_message_not_selected\" = \"Jokių žinučių nepasirinkta\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Neskaityta žinutė %@, išsiųsta %@\";\n\"ua_notification_button_accept\" = \"Priimti\";\n\"ua_notification_button_add\" = \"Pridėti\";\n\"ua_notification_button_add_to_calendar\" = \"Pridėti prie kalendoriaus\";\n\"ua_notification_button_book_now\" = \"Užsakyti dabar\";\n\"ua_notification_button_buy_now\" = \"Pirkti dabar\";\n\"ua_notification_button_copy\" = \"Kopijuoti\";\n\"ua_notification_button_decline\" = \"Atmesti\";\n\"ua_notification_button_dislike\" = \"Nepatinka\";\n\"ua_notification_button_download\" = \"Atsisiųsti\";\n\"ua_notification_button_follow\" = \"Sekti\";\n\"ua_notification_button_less_like\" = \"Mažiau tokio\";\n\"ua_notification_button_like\" = \"Panašus\";\n\"ua_notification_button_more_like\" = \"Daugiau tokio\";\n\"ua_notification_button_no\" = \"Nr.\";\n\"ua_notification_button_opt_in\" = \"Sutikti\";\n\"ua_notification_button_opt_out\" = \"Atsisakyti\";\n\"ua_notification_button_rate_now\" = \"Įvertinti dabar\";\n\"ua_notification_button_remind\" = \"Priminti man vėliau\";\n\"ua_notification_button_save\" = \"Išsaugoti\";\n\"ua_notification_button_search\" = \"Paieška\";\n\"ua_notification_button_send_info\" = \"Siųsti informaciją\";\n\"ua_notification_button_share\" = \"Dalintis\";\n\"ua_notification_button_shop_now\" = \"Apsipirkti dabar\";\n\"ua_notification_button_tell_me_more\" = \"Noriu sužinoti daugiau\";\n\"ua_notification_button_unfollow\" = \"Nebesekti\";\n\"ua_notification_button_yes\" = \"Taip\";\n\"ua_ok\" = \"Gerai\";\n\"ua_preference_center_title\" = \"Pirmenybių centras\";\n\"ua_retry_button\" = \"Bandyti dar kartą\";\n\"ua_select_all_messages\" = \"Pasirinkti viską\";\n\"ua_select_all_messages_description\" = \"Parenka visas žinutes\";\n\"ua_select_none_messages\" = \"Pasirinkti jokio\";\n\"ua_select_none_messages_description\" = \"Iš naujo nustatyti žinutės pasirinkimą\";\n\"ua_unread_message_description\" = \"Žinutė neskaityta\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Atsisakyti\";\n\"ua_escape\" = \"Išeiti\";\n\"ua_next\" = \"Kitas\";\n\"ua_previous\" = \"Ankstesnis\";\n\"ua_submit\" = \"Pateikti\";\n\"ua_loading\" = \"Įkeliama\";\n\"ua_pager_progress\" = \"Puslapis %@ iš %@\";\n\"ua_x_of_y\" = \"%@ iš %@\";\n\"ua_play\" = \"Groti\";\n\"ua_pause\" = \"Pristabdyti\";\n\"ua_stop\" = \"Sustabdyti\";\n\"ua_form_processing_error\" = \"Klaida apdorojant formą. Bandykite dar kartą\";\n\"ua_close\" = \"Uždaryti\";\n\"ua_mute\" = \"Nutildyti\";\n\"ua_unmute\" = \"Įjungti garsą\";\n\"ua_required_field\" = \"* Privaloma\";\n\"ua_invalid_form_message\" = \"Prašome ištaisyti netinkamus laukus, kad galėtumėte tęsti\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/lv.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Atcelt\";\n\"ua_cancel_edit_messages_description\" = \"Atcelt ziņojumu labojumus\";\n\"ua_connection_error\" = \"Savienojuma kļūda\";\n\"ua_content_error\" = \"Satura kļūda\";\n\"ua_delete_message\" = \"Dzēst ziņojumu\";\n\"ua_delete_message_description\" = \"Dzēst atlasītos ziņojumus\";\n\"ua_delete_messages\" = \"Dzēst ziņojumu\";\n\"ua_delete_messages_description\" = \"Dzēst atlasītos ziņojumus\";\n\"ua_edit_messages\" = \"Rediģēt\";\n\"ua_edit_messages_description\" = \"Rediģēt ziņojumus\";\n\"ua_empty_message_list\" = \"Nav ziņojumu\";\n\"ua_mark_messages_read\" = \"Atzīmēt kā izlasītu\";\n\"ua_mark_messages_read_description\" = \"Atzīmēt atlasītās ziņojumus kā izlasītus\";\n\"ua_mc_failed_to_load\" = \"Nevar ielādēt ziņojumu. Lūdzu, pamēģiniet vēlreiz vēlāk.\";\n\"ua_mc_no_longer_available\" = \"Atlasītais ziņojums vairs nav pieejama.\";\n\"ua_message_cell_description\" = \"Parāda pilnu ziņojumu\";\n\"ua_message_cell_editing_description\" = \"Pārslēdz atlasi\";\n\"ua_message_center_title\" = \"Ziņojumu centrs\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Ziņojums %@, nosūtīts uz %@\";\n\"ua_message_not_selected\" = \"Nav atlasīts neviens ziņojums\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Nelasīts ziņojums %@, nosūtīts plkst. %@\";\n\"ua_notification_button_accept\" = \"Pieņemt\";\n\"ua_notification_button_add\" = \"Pievienot\";\n\"ua_notification_button_add_to_calendar\" = \"Pievienot kalendāram\";\n\"ua_notification_button_book_now\" = \"Rezervēt tagad\";\n\"ua_notification_button_buy_now\" = \"Pirkt tagad\";\n\"ua_notification_button_copy\" = \"Kopēt\";\n\"ua_notification_button_decline\" = \"Noraidīt\";\n\"ua_notification_button_dislike\" = \"Nepatīk\";\n\"ua_notification_button_download\" = \"Lejupielādēt\";\n\"ua_notification_button_follow\" = \"Sekot\";\n\"ua_notification_button_less_like\" = \"Mazāk kā šis\";\n\"ua_notification_button_like\" = \"Patīk\";\n\"ua_notification_button_more_like\" = \"Vairāk kā šis\";\n\"ua_notification_button_no\" = \"Nē\";\n\"ua_notification_button_opt_in\" = \"Pieteikties\";\n\"ua_notification_button_opt_out\" = \"Atteikties\";\n\"ua_notification_button_rate_now\" = \"Novērtēt tagad\";\n\"ua_notification_button_remind\" = \"Atgādināt man vēlāk\";\n\"ua_notification_button_save\" = \"Saglabāt\";\n\"ua_notification_button_search\" = \"Meklēt\";\n\"ua_notification_button_send_info\" = \"Nosūtīt Info\";\n\"ua_notification_button_share\" = \"Dalīties\";\n\"ua_notification_button_shop_now\" = \"Iepirkties tūlīt\";\n\"ua_notification_button_tell_me_more\" = \"Pastāstiet man vairāk\";\n\"ua_notification_button_unfollow\" = \"Pārtraukt sekošanu\";\n\"ua_notification_button_yes\" = \"Jā\";\n\"ua_ok\" = \"Labi\";\n\"ua_preference_center_title\" = \"Preferenču centrs\";\n\"ua_retry_button\" = \"Mēģiniet vēlreiz\";\n\"ua_select_all_messages\" = \"Atlasīt Visi\";\n\"ua_select_all_messages_description\" = \"Atlasa visus ziņojumus\";\n\"ua_select_none_messages\" = \"Atlasīt Neviens\";\n\"ua_select_none_messages_description\" = \"Atiestatīt ziņojumu atlasi\";\n\"ua_unread_message_description\" = \"Ziņojums nelasīts\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Noraidīt\";\n\"ua_escape\" = \"Izbēgt\";\n\"ua_next\" = \"Nākamais\";\n\"ua_previous\" = \"Iepriekšējais\";\n\"ua_submit\" = \"Iesniegt\";\n\"ua_loading\" = \"Ielādē\";\n\"ua_pager_progress\" = \"Lapa %@ no %@\";\n\"ua_x_of_y\" = \"%@ no %@\";\n\"ua_play\" = \"Atskaņot\";\n\"ua_pause\" = \"Pauze\";\n\"ua_stop\" = \"Apturēt\";\n\"ua_form_processing_error\" = \"Kļūda, apstrādājot veidlapu. Lūdzu, mēģiniet vēlreiz\";\n\"ua_close\" = \"Aizvērt\";\n\"ua_mute\" = \"Izslēgt skaņu\";\n\"ua_unmute\" = \"Ieslēgt skaņu\";\n\"ua_required_field\" = \"* Obligāts\";\n\"ua_invalid_form_message\" = \"Lūdzu, labojiet nederīgos laukus, lai turpinātu\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ms.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Batal\";\n\"ua_cancel_edit_messages_description\" = \"Batalkan suntingan mesej\";\n\"ua_connection_error\" = \"Ralat Sambungan\";\n\"ua_content_error\" = \"Ralat Kandungan\";\n\"ua_delete_message\" = \"Padamkan mesej\";\n\"ua_delete_message_description\" = \"Padamkan mesej yang dipilih\";\n\"ua_delete_messages\" = \"Padamkan mesej\";\n\"ua_delete_messages_description\" = \"Padamkan mesej yang dipilih\";\n\"ua_edit_messages\" = \"Suntingan\";\n\"ua_edit_messages_description\" = \"Sunting mesej\";\n\"ua_empty_message_list\" = \"Tiada Mesej\";\n\"ua_mark_messages_read\" = \"Tanda Dibaca\";\n\"ua_mark_messages_read_description\" = \"Tanda mesej yang dipilih telah dibaca\";\n\"ua_mc_failed_to_load\" = \"Tidak dapat memuatkan mesej. Sila cuba sebentar lagi.\";\n\"ua_mc_no_longer_available\" = \"Mesej yang dipilih tidak lagi tersedia.\";\n\"ua_message_cell_description\" = \"Memaparkan mesej penuh\";\n\"ua_message_cell_editing_description\" = \"Togol pemilihan\";\n\"ua_message_center_title\" = \"Pusat Mesej\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Mesej %@, dihantar pada %@\";\n\"ua_message_not_selected\" = \"Tiada mesej dipilih\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Mesej belum dibaca %@, dihantar pada %@\";\n\"ua_notification_button_accept\" = \"Terima\";\n\"ua_notification_button_add\" = \"Tambah\";\n\"ua_notification_button_add_to_calendar\" = \"Tambah Ke Kalendar\";\n\"ua_notification_button_book_now\" = \"Tempah Sekarang\";\n\"ua_notification_button_buy_now\" = \"Beli Sekarang\";\n\"ua_notification_button_copy\" = \"Salin\";\n\"ua_notification_button_decline\" = \"Tolak\";\n\"ua_notification_button_dislike\" = \"Tidak suka\";\n\"ua_notification_button_download\" = \"Muat turun\";\n\"ua_notification_button_follow\" = \"Ikut\";\n\"ua_notification_button_less_like\" = \"Kurang Seperti Ini\";\n\"ua_notification_button_like\" = \"Suka\";\n\"ua_notification_button_more_like\" = \"Lebih Seperti Ini\";\n\"ua_notification_button_no\" = \"Tidak\";\n\"ua_notification_button_opt_in\" = \"Ikut serta\";\n\"ua_notification_button_opt_out\" = \"Tarik diri\";\n\"ua_notification_button_rate_now\" = \"Nilaikan Sekarang\";\n\"ua_notification_button_remind\" = \"Ingatkan Saya Kemudian\";\n\"ua_notification_button_save\" = \"Simpan\";\n\"ua_notification_button_search\" = \"Carian\";\n\"ua_notification_button_send_info\" = \"Menghantar Info\";\n\"ua_notification_button_share\" = \"Kongsi\";\n\"ua_notification_button_shop_now\" = \"Berbelanjalah Sekarang\";\n\"ua_notification_button_tell_me_more\" = \"Beritahu Saya Lagi\";\n\"ua_notification_button_unfollow\" = \"Nyahikut\";\n\"ua_notification_button_yes\" = \"Ya\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Pusat Keutamaan\";\n\"ua_retry_button\" = \"Cuba semula\";\n\"ua_select_all_messages\" = \"Pilih Semua\";\n\"ua_select_all_messages_description\" = \"Memilih semua mesej\";\n\"ua_select_none_messages\" = \"Pilih Tiada\";\n\"ua_select_none_messages_description\" = \"Tetapkan semula pemilihan mesej\";\n\"ua_unread_message_description\" = \"Mesej belum dibaca\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Singkirkan\";\n\"ua_escape\" = \"Melarikan diri\";\n\"ua_next\" = \"Seterusnya\";\n\"ua_previous\" = \"Terdahulu\";\n\"ua_submit\" = \"Serahkan\";\n\"ua_loading\" = \"Memuat\";\n\"ua_pager_progress\" = \"Halaman %@ daripada %@\";\n\"ua_x_of_y\" = \"%@ daripada %@\";\n\"ua_play\" = \"Mainkan\";\n\"ua_pause\" = \"Jeda\";\n\"ua_stop\" = \"Berhenti\";\n\"ua_form_processing_error\" = \"Ralat memproses borang. Sila cuba lagi\";\n\"ua_close\" = \"Tutup\";\n\"ua_mute\" = \"Bisukan\";\n\"ua_unmute\" = \"Nyahbisu\";\n\"ua_required_field\" = \"* Wajib\";\n\"ua_invalid_form_message\" = \"Sila betulkan medan yang tidak sah untuk meneruskan\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/nl.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Annuleer\";\n\"ua_cancel_edit_messages_description\" = \"Berichtbewerkingen annuleren\";\n\"ua_connection_error\" = \"Verbindingsfout\";\n\"ua_content_error\" = \"Inhoudsfout\";\n\"ua_delete_message\" = \"Bericht verwijderen\";\n\"ua_delete_message_description\" = \"Geselecteerde berichten verwijderen\";\n\"ua_delete_messages\" = \"Bericht verwijderen\";\n\"ua_delete_messages_description\" = \"Geselecteerde berichten verwijderen\";\n\"ua_edit_messages\" = \"Bewerking\";\n\"ua_edit_messages_description\" = \"Berichten bewerken\";\n\"ua_empty_message_list\" = \"Geen berichten\";\n\"ua_mark_messages_read\" = \"Markeer als Gelezen\";\n\"ua_mark_messages_read_description\" = \"Markeer geselecteerde berichten als gelezen\";\n\"ua_mc_failed_to_load\" = \"Kan bericht niet laden. Probeer het later opnieuw.\";\n\"ua_mc_no_longer_available\" = \"Het geselecteerde bericht is niet meer beschikbaar.\";\n\"ua_message_cell_description\" = \"Geeft volledig bericht weer\";\n\"ua_message_cell_editing_description\" = \"Schakelt selectie in\";\n\"ua_message_center_title\" = \"Berichtencentrum\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Bericht %@, verzonden om %@\";\n\"ua_message_not_selected\" = \"Geen berichten geselecteerd\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Ongelezen bericht %@, verzonden om %@\";\n\"ua_notification_button_accept\" = \"Accepteer\";\n\"ua_notification_button_add\" = \"Voeg toe\";\n\"ua_notification_button_add_to_calendar\" = \"Aan Kalender toevoegen\";\n\"ua_notification_button_book_now\" = \"Boek Nu\";\n\"ua_notification_button_buy_now\" = \"Koop nu\";\n\"ua_notification_button_copy\" = \"Kopieer\";\n\"ua_notification_button_decline\" = \"Wijs af\";\n\"ua_notification_button_dislike\" = \"Vind ik niet leuk\";\n\"ua_notification_button_download\" = \"Download\";\n\"ua_notification_button_follow\" = \"Volg\";\n\"ua_notification_button_less_like\" = \"Minder \\\"Vind Dit Leuk\\\"\";\n\"ua_notification_button_like\" = \"Vind ik leuk\";\n\"ua_notification_button_more_like\" = \"Meer \\\"Vind Dit Leuk\\\"\";\n\"ua_notification_button_no\" = \"Nee\";\n\"ua_notification_button_opt_in\" = \"Aanmelden\";\n\"ua_notification_button_opt_out\" = \"Afmelden\";\n\"ua_notification_button_rate_now\" = \"Beoordeel Nu\";\n\"ua_notification_button_remind\" = \"Herinner mij later\";\n\"ua_notification_button_save\" = \"Sla op\";\n\"ua_notification_button_search\" = \"Zoek\";\n\"ua_notification_button_send_info\" = \"Stuur Info\";\n\"ua_notification_button_share\" = \"Deel\";\n\"ua_notification_button_shop_now\" = \"Winkel nu\";\n\"ua_notification_button_tell_me_more\" = \"Vertel Mij Meer\";\n\"ua_notification_button_unfollow\" = \"Ontvolg\";\n\"ua_notification_button_yes\" = \"Ja\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Voorkeurscentrum\";\n\"ua_retry_button\" = \"Probeer opnieuw\";\n\"ua_select_all_messages\" = \"Selecteer Alle\";\n\"ua_select_all_messages_description\" = \"Selecteert alle berichten\";\n\"ua_select_none_messages\" = \"Selecteer Geen\";\n\"ua_select_none_messages_description\" = \"Berichtselectie resetten\";\n\"ua_unread_message_description\" = \"Bericht ongelezen\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Afwijzen\";\n\"ua_escape\" = \"Ontsnappen\";\n\"ua_next\" = \"Volgende\";\n\"ua_previous\" = \"Vorige\";\n\"ua_submit\" = \"Indienen\";\n\"ua_loading\" = \"Laden\";\n\"ua_pager_progress\" = \"Pagina %@ van %@\";\n\"ua_x_of_y\" = \"%@ van %@\";\n\"ua_play\" = \"Spelen\";\n\"ua_pause\" = \"Pauze\";\n\"ua_stop\" = \"Stoppen\";\n\"ua_form_processing_error\" = \"Fout bij het verwerken van formulier. Probeer het opnieuw\";\n\"ua_close\" = \"Sluiten\";\n\"ua_mute\" = \"Dempen\";\n\"ua_unmute\" = \"Dempen opheffen\";\n\"ua_required_field\" = \"* Verplicht\";\n\"ua_invalid_form_message\" = \"Corrigeer de ongeldige velden om door te gaan\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/no.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Avbryt\";\n\"ua_cancel_edit_messages_description\" = \"Avbryt meldingsredigeringer\";\n\"ua_connection_error\" = \"Tilkoblingsfeil\";\n\"ua_content_error\" = \"Innholdsfeil\";\n\"ua_delete_message\" = \"Slett melding\";\n\"ua_delete_message_description\" = \"Slett valgte meldinger\";\n\"ua_delete_messages\" = \"Slett melding\";\n\"ua_delete_messages_description\" = \"Slett valgte meldinger\";\n\"ua_edit_messages\" = \"Rediger\";\n\"ua_edit_messages_description\" = \"Rediger meldinger\";\n\"ua_empty_message_list\" = \"Ingen meldinger\";\n\"ua_mark_messages_read\" = \"Merk som lest\";\n\"ua_mark_messages_read_description\" = \"Merk valgte meldinger som lest\";\n\"ua_mc_failed_to_load\" = \"Kan ikke laste inn meldingen. Prøv igjen senere.\";\n\"ua_mc_no_longer_available\" = \"Den valgte meldingen er ikke lenger tilgjengelig.\";\n\"ua_message_cell_description\" = \"Viser hele meldingen\";\n\"ua_message_cell_editing_description\" = \"Bytter valg\";\n\"ua_message_center_title\" = \"Meldingssenter\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Melding %@, sendt kl. %@\";\n\"ua_message_not_selected\" = \"Ingen meldinger er valgt\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Ulest melding %@, sendt kl. %@\";\n\"ua_notification_button_accept\" = \"Aksepter\";\n\"ua_notification_button_add\" = \"Legg til\";\n\"ua_notification_button_add_to_calendar\" = \"Legg til kalender\";\n\"ua_notification_button_book_now\" = \"Bestill nå\";\n\"ua_notification_button_buy_now\" = \"Kjøp nå\";\n\"ua_notification_button_copy\" = \"Kopier\";\n\"ua_notification_button_decline\" = \"Avslå\";\n\"ua_notification_button_dislike\" = \"Mislike\";\n\"ua_notification_button_download\" = \"Last ned\";\n\"ua_notification_button_follow\" = \"Følg\";\n\"ua_notification_button_less_like\" = \"Mindre som dette\";\n\"ua_notification_button_like\" = \"Like\";\n\"ua_notification_button_more_like\" = \"Mer som dette\";\n\"ua_notification_button_no\" = \"Nei\";\n\"ua_notification_button_opt_in\" = \"Meld deg på\";\n\"ua_notification_button_opt_out\" = \"Meld deg av\";\n\"ua_notification_button_rate_now\" = \"Gi karakter nå\";\n\"ua_notification_button_remind\" = \"Minn meg på det senere\";\n\"ua_notification_button_save\" = \"Lagre\";\n\"ua_notification_button_search\" = \"Søk\";\n\"ua_notification_button_send_info\" = \"Send info\";\n\"ua_notification_button_share\" = \"Del\";\n\"ua_notification_button_shop_now\" = \"Handle nå\";\n\"ua_notification_button_tell_me_more\" = \"Fortell meg mer\";\n\"ua_notification_button_unfollow\" = \"Slutt å følge\";\n\"ua_notification_button_yes\" = \"Ja\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Preferansesenter\";\n\"ua_retry_button\" = \"Prøv igjen\";\n\"ua_select_all_messages\" = \"Velg alle\";\n\"ua_select_all_messages_description\" = \"Velger alle meldinger\";\n\"ua_select_none_messages\" = \"Velg ingen\";\n\"ua_select_none_messages_description\" = \"Tilbakestill meldingsvalg\";\n\"ua_unread_message_description\" = \"Melding ulest\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Avvis\";\n\"ua_escape\" = \"Avbryt\";\n\"ua_next\" = \"Neste\";\n\"ua_previous\" = \"Forrige\";\n\"ua_submit\" = \"Send inn\";\n\"ua_loading\" = \"Laster\";\n\"ua_pager_progress\" = \"Side %@ av %@\";\n\"ua_x_of_y\" = \"%@ av %@\";\n\"ua_play\" = \"Spill av\";\n\"ua_pause\" = \"Pause\";\n\"ua_stop\" = \"Stopp\";\n\"ua_form_processing_error\" = \"Feil ved behandling av skjema. Vennligst prøv igjen\";\n\"ua_close\" = \"Lukk\";\n\"ua_mute\" = \"Demp\";\n\"ua_unmute\" = \"Opphev demping\";\n\"ua_required_field\" = \"* Påkrevd\";\n\"ua_invalid_form_message\" = \"Vennligst rett opp de ugyldige feltene for å fortsette\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/pl.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Anuluj\";\n\"ua_cancel_edit_messages_description\" = \"Anuluj edycję wiadomości\";\n\"ua_connection_error\" = \"Błąd połączenia\";\n\"ua_content_error\" = \"Błąd treści\";\n\"ua_delete_message\" = \"Usuń wiadomość\";\n\"ua_delete_message_description\" = \"Usuń wybrane wiadomości\";\n\"ua_delete_messages\" = \"Usuń wiadomość\";\n\"ua_delete_messages_description\" = \"Usuń wybrane wiadomości\";\n\"ua_edit_messages\" = \"Edytuj\";\n\"ua_edit_messages_description\" = \"Edytuj wiadomości\";\n\"ua_empty_message_list\" = \"Brak wiadomości\";\n\"ua_mark_messages_read\" = \"Zaznacz jako przeczytane\";\n\"ua_mark_messages_read_description\" = \"Zaznacz wybrane wiadomości jako przeczytane\";\n\"ua_mc_failed_to_load\" = \"Nie można załadować wiadomości. Spróbuj ponownie później.\";\n\"ua_mc_no_longer_available\" = \"Wybrana wiadomość nie jest już dostępna.\";\n\"ua_message_cell_description\" = \"Wyświetla pełną wiadomość\";\n\"ua_message_cell_editing_description\" = \"Przełącza wybór\";\n\"ua_message_center_title\" = \"Centrum wiadomości\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Wiadomość %@, wysłana o %@\";\n\"ua_message_not_selected\" = \"Nie wybrano wiadomości\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Nieprzeczytana wiadomość %@, wysłana o %@\";\n\"ua_notification_button_accept\" = \"Akceptuj\";\n\"ua_notification_button_add\" = \"Dodaj\";\n\"ua_notification_button_add_to_calendar\" = \"Dodaj do kalendarza\";\n\"ua_notification_button_book_now\" = \"Rezerwuj teraz\";\n\"ua_notification_button_buy_now\" = \"Kup teraz\";\n\"ua_notification_button_copy\" = \"Kopiuj\";\n\"ua_notification_button_decline\" = \"Odrzuć\";\n\"ua_notification_button_dislike\" = \"Cofnij polubienie\";\n\"ua_notification_button_download\" = \"Pobierz\";\n\"ua_notification_button_follow\" = \"Obserwuj\";\n\"ua_notification_button_less_like\" = \"Mniej podobnych treści\";\n\"ua_notification_button_like\" = \"Polub\";\n\"ua_notification_button_more_like\" = \"Więcej podobnych treści\";\n\"ua_notification_button_no\" = \"Nie\";\n\"ua_notification_button_opt_in\" = \"Subskrybuj\";\n\"ua_notification_button_opt_out\" = \"Zrezygnuj\";\n\"ua_notification_button_rate_now\" = \"Oceń teraz\";\n\"ua_notification_button_remind\" = \"Przypomnij mi później\";\n\"ua_notification_button_save\" = \"Zapisz\";\n\"ua_notification_button_search\" = \"Szukaj\";\n\"ua_notification_button_send_info\" = \"Wyślij informacje\";\n\"ua_notification_button_share\" = \"Udostępnij\";\n\"ua_notification_button_shop_now\" = \"Kupuj teraz\";\n\"ua_notification_button_tell_me_more\" = \"Więcej informacji\";\n\"ua_notification_button_unfollow\" = \"Przestań obserwować\";\n\"ua_notification_button_yes\" = \"Tak\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centrum preferencji\";\n\"ua_retry_button\" = \"Ponów próbę\";\n\"ua_select_all_messages\" = \"Zaznacz wszystko\";\n\"ua_select_all_messages_description\" = \"Zaznacza wszystkie wiadomości\";\n\"ua_select_none_messages\" = \"Nie zaznaczaj nic\";\n\"ua_select_none_messages_description\" = \"Zresetuj wybór wiadomości\";\n\"ua_unread_message_description\" = \"Wiadomość nieprzeczytana\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Odrzuć\";\n\"ua_escape\" = \"Uciec\";\n\"ua_next\" = \"Następny\";\n\"ua_previous\" = \"Poprzedni\";\n\"ua_submit\" = \"Złożyć\";\n\"ua_loading\" = \"Ładowanie\";\n\"ua_pager_progress\" = \"Strona %@ z %@\";\n\"ua_x_of_y\" = \"%@ z %@\";\n\"ua_play\" = \"Odtwarzać\";\n\"ua_pause\" = \"Pauza\";\n\"ua_stop\" = \"Zatrzymać\";\n\"ua_form_processing_error\" = \"Błąd przetwarzania formularza. Proszę spróbować ponownie\";\n\"ua_close\" = \"Zamknij\";\n\"ua_mute\" = \"Wycisz\";\n\"ua_unmute\" = \"Wyłącz wyciszenie\";\n\"ua_required_field\" = \"* Wymagane\";\n\"ua_invalid_form_message\" = \"Proszę poprawić nieprawidłowe pola, aby kontynuować\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/pt-PT.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Cancelar\";\n\"ua_cancel_edit_messages_description\" = \"Cancelar edições de mensagens\";\n\"ua_connection_error\" = \"Erro de Ligação\";\n\"ua_content_error\" = \"Erro de conteúdo\";\n\"ua_delete_message\" = \"Apagar mensagem\";\n\"ua_delete_message_description\" = \"Apagar mensagens selecionadas\";\n\"ua_delete_messages\" = \"Apagar mensagem\";\n\"ua_delete_messages_description\" = \"Apagar mensagens selecionadas\";\n\"ua_edit_messages\" = \"Editar\";\n\"ua_edit_messages_description\" = \"Editar mensagens\";\n\"ua_empty_message_list\" = \"Sem Mensagens\";\n\"ua_mark_messages_read\" = \"Marcar como Lido\";\n\"ua_mark_messages_read_description\" = \"Marcar mensagens selecionadas como lidas\";\n\"ua_mc_failed_to_load\" = \"Não é possível carregar mensagem. Por favor, tente novamente mais tarde.\";\n\"ua_mc_no_longer_available\" = \"A mensagem selecionada já não está disponível.\";\n\"ua_message_cell_description\" = \"Exibe mensagem completa\";\n\"ua_message_cell_editing_description\" = \"Alterna a seleção\";\n\"ua_message_center_title\" = \"Centro de Mensagens\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Mensagem %@, enviada em %@\";\n\"ua_message_not_selected\" = \"Nenhuma mensagem selecionada\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Mensagem não lida %@, enviada em %@\";\n\"ua_notification_button_accept\" = \"Aceitar\";\n\"ua_notification_button_add\" = \"Adicionar\";\n\"ua_notification_button_add_to_calendar\" = \"Adicionar ao Calendário\";\n\"ua_notification_button_book_now\" = \"Marcar Agora\";\n\"ua_notification_button_buy_now\" = \"Compre Agora\";\n\"ua_notification_button_copy\" = \"Cópia\";\n\"ua_notification_button_decline\" = \"Declinar\";\n\"ua_notification_button_dislike\" = \"Não Gostar\";\n\"ua_notification_button_download\" = \"Descarregar\";\n\"ua_notification_button_follow\" = \"Seguir\";\n\"ua_notification_button_less_like\" = \"Menos Como Isto\";\n\"ua_notification_button_like\" = \"Gostar\";\n\"ua_notification_button_more_like\" = \"Mais Como Isto\";\n\"ua_notification_button_no\" = \"Não\";\n\"ua_notification_button_opt_in\" = \"Opt-in\";\n\"ua_notification_button_opt_out\" = \"Opt-out\";\n\"ua_notification_button_rate_now\" = \"Classificar Agora\";\n\"ua_notification_button_remind\" = \"Lembrar-me depois\";\n\"ua_notification_button_save\" = \"Gravar\";\n\"ua_notification_button_search\" = \"Pesquisa\";\n\"ua_notification_button_send_info\" = \"Enviar Informações\";\n\"ua_notification_button_share\" = \"Partilhar\";\n\"ua_notification_button_shop_now\" = \"Comprar Agora\";\n\"ua_notification_button_tell_me_more\" = \"Diga-me Mais\";\n\"ua_notification_button_unfollow\" = \"Deixar de Seguir\";\n\"ua_notification_button_yes\" = \"Sim\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centro de Preferências\";\n\"ua_retry_button\" = \"Tentar novamente\";\n\"ua_select_all_messages\" = \"Selecionar Tudo\";\n\"ua_select_all_messages_description\" = \"Seleciona todas as mensagens\";\n\"ua_select_none_messages\" = \"Não Selecionar\";\n\"ua_select_none_messages_description\" = \"Redefinir seleção de mensagem\";\n\"ua_unread_message_description\" = \"Mensagem não lida\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Dispensar\";\n\"ua_escape\" = \"Escapar\";\n\"ua_next\" = \"Próximo\";\n\"ua_previous\" = \"Anterior\";\n\"ua_submit\" = \"Submeter\";\n\"ua_loading\" = \"Carregando\";\n\"ua_pager_progress\" = \"Página %@ de %@\";\n\"ua_x_of_y\" = \"%@ de %@\";\n\"ua_play\" = \"Reproduzir\";\n\"ua_pause\" = \"Pausar\";\n\"ua_stop\" = \"Parar\";\n\"ua_form_processing_error\" = \"Erro ao processar o formulário. Por favor, tente novamente\";\n\"ua_close\" = \"Fechar\";\n\"ua_mute\" = \"Silenciar\";\n\"ua_unmute\" = \"Ativar som\";\n\"ua_required_field\" = \"* Obrigatório\";\n\"ua_invalid_form_message\" = \"Por favor, corrija os campos inválidos para continuar\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/pt.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Cancelar\";\n\"ua_cancel_edit_messages_description\" = \"Cancelar edições de mensagens\";\n\"ua_connection_error\" = \"Erro de conexão\";\n\"ua_content_error\" = \"Erro de conteúdo\";\n\"ua_delete_message\" = \"Apagar mensagem\";\n\"ua_delete_message_description\" = \"Excluir mensagens selecionadas\";\n\"ua_delete_messages\" = \"Apagar mensagem\";\n\"ua_delete_messages_description\" = \"Excluir mensagens selecionadas\";\n\"ua_edit_messages\" = \"Editar\";\n\"ua_edit_messages_description\" = \"Editar mensagens\";\n\"ua_empty_message_list\" = \"Nenhuma mensagem\";\n\"ua_mark_messages_read\" = \"Marcar como lido\";\n\"ua_mark_messages_read_description\" = \"Marcar mensagens selecionadas como lidas\";\n\"ua_mc_failed_to_load\" = \"Não foi possível carregar a mensagem. Tente novamente mais tarde\";\n\"ua_mc_no_longer_available\" = \"A mensagem selecionada não está mais disponível.\";\n\"ua_message_cell_description\" = \"Exibe mensagem completa\";\n\"ua_message_cell_editing_description\" = \"Alterna a seleção\";\n\"ua_message_center_title\" = \"Central de mensagens\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Mensagem %@, enviada em %@\";\n\"ua_message_not_selected\" = \"Nenhuma mensagem selecionada\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Mensagem não lida %@, enviada em %@\";\n\"ua_notification_button_accept\" = \"Aceitar\";\n\"ua_notification_button_add\" = \"Adicionar\";\n\"ua_notification_button_add_to_calendar\" = \"Adicionar ao calendário\";\n\"ua_notification_button_book_now\" = \"Reservar agora\";\n\"ua_notification_button_buy_now\" = \"Comprar agora\";\n\"ua_notification_button_copy\" = \"Copiar\";\n\"ua_notification_button_decline\" = \"Recusar\";\n\"ua_notification_button_dislike\" = \"Descurtir\";\n\"ua_notification_button_download\" = \"Baixar\";\n\"ua_notification_button_follow\" = \"Seguir\";\n\"ua_notification_button_less_like\" = \"Menos como este\";\n\"ua_notification_button_like\" = \"Curtir\";\n\"ua_notification_button_more_like\" = \"Mais como este\";\n\"ua_notification_button_no\" = \"Não\";\n\"ua_notification_button_opt_in\" = \"Aceitar (Opt-in)\";\n\"ua_notification_button_opt_out\" = \"Não aceitar (Opt-out)\";\n\"ua_notification_button_rate_now\" = \"Avaliar agora\";\n\"ua_notification_button_remind\" = \"Lembrar mais tarde\";\n\"ua_notification_button_save\" = \"Salvar\";\n\"ua_notification_button_search\" = \"Pesquisar\";\n\"ua_notification_button_send_info\" = \"Enviar informações\";\n\"ua_notification_button_share\" = \"Compartilhar\";\n\"ua_notification_button_shop_now\" = \"Ir à loja\";\n\"ua_notification_button_tell_me_more\" = \"Conte-me mais\";\n\"ua_notification_button_unfollow\" = \"Deixar de seguir\";\n\"ua_notification_button_yes\" = \"Sim\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centro de preferências\";\n\"ua_retry_button\" = \"Tentar novamente\";\n\"ua_select_all_messages\" = \"Selecionar tudo\";\n\"ua_select_all_messages_description\" = \"Seleciona todas as mensagens\";\n\"ua_select_none_messages\" = \"Não selecionar\";\n\"ua_select_none_messages_description\" = \"Redefinir seleção de mensagem\";\n\"ua_unread_message_description\" = \"Mensagem não lida\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Dispensar\";\n\"ua_escape\" = \"Escapar\";\n\"ua_next\" = \"Próximo\";\n\"ua_previous\" = \"Anterior\";\n\"ua_submit\" = \"Enviar\";\n\"ua_loading\" = \"Carregando\";\n\"ua_pager_progress\" = \"Página %@ de %@\";\n\"ua_x_of_y\" = \"%@ de %@\";\n\"ua_play\" = \"Reproduzir\";\n\"ua_pause\" = \"Pausar\";\n\"ua_stop\" = \"Parar\";\n\"ua_form_processing_error\" = \"Erro ao processar formulário. Por favor, tente novamente\";\n\"ua_close\" = \"Fechar\";\n\"ua_mute\" = \"Silenciar\";\n\"ua_unmute\" = \"Ativar som\";\n\"ua_required_field\" = \"* Obrigatório\";\n\"ua_invalid_form_message\" = \"Por favor, corrija os campos inválidos para continuar\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ro.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Anulează\";\n\"ua_cancel_edit_messages_description\" = \"Anulați editările mesajelor\";\n\"ua_connection_error\" = \"Eroare de Conexiune\";\n\"ua_content_error\" = \"Eroare de conținut\";\n\"ua_delete_message\" = \"Stergeți mesajul\";\n\"ua_delete_message_description\" = \"Ștergeți mesajele selectate\";\n\"ua_delete_messages\" = \"Stergeți mesajul\";\n\"ua_delete_messages_description\" = \"Ștergeți mesajele selectate\";\n\"ua_edit_messages\" = \"Editați | ×\";\n\"ua_edit_messages_description\" = \"Editați mesajele\";\n\"ua_empty_message_list\" = \"Niciun mesaj\";\n\"ua_mark_messages_read\" = \"Marchează ca Citit\";\n\"ua_mark_messages_read_description\" = \"Marcați mesajele selectate ca citite\";\n\"ua_mc_failed_to_load\" = \"Imposibil de încărcat mesaje. Te rugăm să încerci mai târziu.\";\n\"ua_mc_no_longer_available\" = \"Mesajul selectat nu mai este disponibil.\";\n\"ua_message_cell_description\" = \"Afișează mesajul complet\";\n\"ua_message_cell_editing_description\" = \"Comută selecția\";\n\"ua_message_center_title\" = \"Centrul de Mesaje\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Mesaj %@, trimis la %@\";\n\"ua_message_not_selected\" = \"Niciun mesaj selectat\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Mesaj necitit %@, trimis la %@\";\n\"ua_notification_button_accept\" = \"Acceptă\";\n\"ua_notification_button_add\" = \"Adaugă\";\n\"ua_notification_button_add_to_calendar\" = \"Adaugă în Calendar\";\n\"ua_notification_button_book_now\" = \"Rezervă Acum\";\n\"ua_notification_button_buy_now\" = \"Cumpără Acum\";\n\"ua_notification_button_copy\" = \"Copiază\";\n\"ua_notification_button_decline\" = \"Refiză\";\n\"ua_notification_button_dislike\" = \"Nu îți place\";\n\"ua_notification_button_download\" = \"Descarcă\";\n\"ua_notification_button_follow\" = \"Urmărește\";\n\"ua_notification_button_less_like\" = \"Mai Puține Asemănătoare\";\n\"ua_notification_button_like\" = \"Îți place\";\n\"ua_notification_button_more_like\" = \"Mai Multe Asemănătoare\";\n\"ua_notification_button_no\" = \"Nu\";\n\"ua_notification_button_opt_in\" = \"Abonare\";\n\"ua_notification_button_opt_out\" = \"Dezabonare\";\n\"ua_notification_button_rate_now\" = \"Apreciază Acum\";\n\"ua_notification_button_remind\" = \"Amintește-mi mai târziu\";\n\"ua_notification_button_save\" = \"Salvează\";\n\"ua_notification_button_search\" = \"Caută\";\n\"ua_notification_button_send_info\" = \"Trimite Informații\";\n\"ua_notification_button_share\" = \"Distribuie\";\n\"ua_notification_button_shop_now\" = \"Cumpără acum\";\n\"ua_notification_button_tell_me_more\" = \"Mai multe informații\";\n\"ua_notification_button_unfollow\" = \"Nu mai urmări\";\n\"ua_notification_button_yes\" = \"Da\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centrul de preferințe\";\n\"ua_retry_button\" = \"Încearcă din nou\";\n\"ua_select_all_messages\" = \"Selectează Toate\";\n\"ua_select_all_messages_description\" = \"Selectați toate mesajele\";\n\"ua_select_none_messages\" = \"Selectează Niciunul\";\n\"ua_select_none_messages_description\" = \"Resetați selecția mesajului\";\n\"ua_unread_message_description\" = \"Mesaj necitit\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Renunță\";\n\"ua_escape\" = \"Ieși\";\n\"ua_next\" = \"Următorul\";\n\"ua_previous\" = \"Anterior\";\n\"ua_submit\" = \"Trimite\";\n\"ua_loading\" = \"Se încarcă\";\n\"ua_pager_progress\" = \"Pagina %@ din %@\";\n\"ua_x_of_y\" = \"%@ din %@\";\n\"ua_play\" = \"Redă\";\n\"ua_pause\" = \"Pauză\";\n\"ua_stop\" = \"Oprire\";\n\"ua_form_processing_error\" = \"Eroare la procesarea formularului. Vă rugăm să încercați din nou\";\n\"ua_close\" = \"Închide\";\n\"ua_mute\" = \"Oprește sunetul\";\n\"ua_unmute\" = \"Pornește sunetul\";\n\"ua_required_field\" = \"* Obligatoriu\";\n\"ua_invalid_form_message\" = \"Vă rugăm să corectați câmpurile nevalide pentru a continua\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/ru.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Отмена\";\n\"ua_cancel_edit_messages_description\" = \"Отменить редактирование сообщения\";\n\"ua_connection_error\" = \"Ошибка подключения\";\n\"ua_content_error\" = \"Ошибка содержимого\";\n\"ua_delete_message\" = \"Удаленное сообщение\";\n\"ua_delete_message_description\" = \"Удалить выбранные сообщения\";\n\"ua_delete_messages\" = \"Удаленное сообщение\";\n\"ua_delete_messages_description\" = \"Удалить выбранные сообщения\";\n\"ua_edit_messages\" = \"Редактировать\";\n\"ua_edit_messages_description\" = \"Редактировать сообщения\";\n\"ua_empty_message_list\" = \"Нет сообщений\";\n\"ua_mark_messages_read\" = \"Отметить как прочитанное\";\n\"ua_mark_messages_read_description\" = \"Отметить выбранные сообщения прочитанными\";\n\"ua_mc_failed_to_load\" = \"Невозможно загрузить сообщение. Пожалуйста, повторите попытку позже.\";\n\"ua_mc_no_longer_available\" = \"Выбранное сообщение больше недоступно.\";\n\"ua_message_cell_description\" = \"Отображает полное сообщение\";\n\"ua_message_cell_editing_description\" = \"Переключает выбор\";\n\"ua_message_center_title\" = \"Центр сообщений\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Сообщение %@, отправлено %@\";\n\"ua_message_not_selected\" = \"Сообщения не выбраны\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Непрочитанное сообщение %@, отправленное %@\";\n\"ua_notification_button_accept\" = \"Принять\";\n\"ua_notification_button_add\" = \"Добавить\";\n\"ua_notification_button_add_to_calendar\" = \"Добавить в календарь\";\n\"ua_notification_button_book_now\" = \"Забронировать сейчас\";\n\"ua_notification_button_buy_now\" = \"Купите сейчас\";\n\"ua_notification_button_copy\" = \"Копировать\";\n\"ua_notification_button_decline\" = \"Отклонить\";\n\"ua_notification_button_dislike\" = \"Не нравится\";\n\"ua_notification_button_download\" = \"Скачать\";\n\"ua_notification_button_follow\" = \"Подписаться\";\n\"ua_notification_button_less_like\" = \"Меньше подобного\";\n\"ua_notification_button_like\" = \"Нравится\";\n\"ua_notification_button_more_like\" = \"Больше подобного\";\n\"ua_notification_button_no\" = \"Нет\";\n\"ua_notification_button_opt_in\" = \"Подписаться на рассылку\";\n\"ua_notification_button_opt_out\" = \"Отписаться от рассылки\";\n\"ua_notification_button_rate_now\" = \"Оценить сейчас\";\n\"ua_notification_button_remind\" = \"Напомнить мне позже\";\n\"ua_notification_button_save\" = \"Сохранить\";\n\"ua_notification_button_search\" = \"Поиск\";\n\"ua_notification_button_send_info\" = \"Отправить информацию\";\n\"ua_notification_button_share\" = \"Поделиться\";\n\"ua_notification_button_shop_now\" = \"Купите сейчас\";\n\"ua_notification_button_tell_me_more\" = \"Сказать больше\";\n\"ua_notification_button_unfollow\" = \"Отписаться\";\n\"ua_notification_button_yes\" = \"Да\";\n\"ua_ok\" = \"ОК\";\n\"ua_preference_center_title\" = \"Центр предпочтений\";\n\"ua_retry_button\" = \"Повторить попытку\";\n\"ua_select_all_messages\" = \"Выбрать все\";\n\"ua_select_all_messages_description\" = \"Выбирает все сообщения\";\n\"ua_select_none_messages\" = \"Не выбирать ничего\";\n\"ua_select_none_messages_description\" = \"Сбросить выбор сообщения\";\n\"ua_unread_message_description\" = \"Сообщение непрочитано\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Отклонить\";\n\"ua_escape\" = \"Выход\";\n\"ua_next\" = \"Следующий\";\n\"ua_previous\" = \"Предыдущий\";\n\"ua_submit\" = \"Отправить\";\n\"ua_loading\" = \"Загрузка\";\n\"ua_pager_progress\" = \"Страница %@ из %@\";\n\"ua_x_of_y\" = \"%@ из %@\";\n\"ua_play\" = \"Воспроизвести\";\n\"ua_pause\" = \"Пауза\";\n\"ua_stop\" = \"Стоп\";\n\"ua_form_processing_error\" = \"Ошибка при обработке формы. Пожалуйста, попробуйте еще раз\";\n\"ua_close\" = \"Закрыть\";\n\"ua_mute\" = \"Отключить звук\";\n\"ua_unmute\" = \"Включить звук\";\n\"ua_required_field\" = \"* Обязательно\";\n\"ua_invalid_form_message\" = \"Пожалуйста, исправьте неверные поля, чтобы продолжить\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/sk.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Zrušiť\";\n\"ua_cancel_edit_messages_description\" = \"Zrušiť úpravy správ\";\n\"ua_connection_error\" = \"Chyba pripojenia\";\n\"ua_content_error\" = \"Chyba obsahu\";\n\"ua_delete_message\" = \"Odstrániť správu\";\n\"ua_delete_message_description\" = \"Vymazať vybrané správy\";\n\"ua_delete_messages\" = \"Odstrániť správu\";\n\"ua_delete_messages_description\" = \"Vymazať vybrané správy\";\n\"ua_edit_messages\" = \"Upraviť\";\n\"ua_edit_messages_description\" = \"Upraviť správy\";\n\"ua_empty_message_list\" = \"Žiadne správy\";\n\"ua_mark_messages_read\" = \"Označiť ako prečítané\";\n\"ua_mark_messages_read_description\" = \"Označiť vybrané správy ako prečítané\";\n\"ua_mc_failed_to_load\" = \"Nie je možné načítať správu. Skúste to neskôr, prosím.\";\n\"ua_mc_no_longer_available\" = \"Vybratá správa už nie je k dispozícii.\";\n\"ua_message_cell_description\" = \"Zobrazí celú správu\";\n\"ua_message_cell_editing_description\" = \"Prepína výber\";\n\"ua_message_center_title\" = \"Centrum správ\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Správa %@ odoslaná o %@\";\n\"ua_message_not_selected\" = \"Nie sú vybraté žiadne správy\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Neprečítaná správa %@, odoslaná o %@\";\n\"ua_notification_button_accept\" = \"Prijať\";\n\"ua_notification_button_add\" = \"Pridať\";\n\"ua_notification_button_add_to_calendar\" = \"Pridať do kalendára\";\n\"ua_notification_button_book_now\" = \"Rezervovať teraz\";\n\"ua_notification_button_buy_now\" = \"Kúpiť teraz\";\n\"ua_notification_button_copy\" = \"Kopírovať\";\n\"ua_notification_button_decline\" = \"Odmietnuť\";\n\"ua_notification_button_dislike\" = \"Nepáčiť sa\";\n\"ua_notification_button_download\" = \"Stiahnuť\";\n\"ua_notification_button_follow\" = \"Sledovať\";\n\"ua_notification_button_less_like\" = \"Menej ako je uvedené\";\n\"ua_notification_button_like\" = \"Páčiť sa\";\n\"ua_notification_button_more_like\" = \"Viac ako je uvedené\";\n\"ua_notification_button_no\" = \"Nie\";\n\"ua_notification_button_opt_in\" = \"Aktivovať možnosti výberu\";\n\"ua_notification_button_opt_out\" = \"Deaktivovať možnosti výberu\";\n\"ua_notification_button_rate_now\" = \"Hodnotiť teraz\";\n\"ua_notification_button_remind\" = \"Pripomenúť neskôr\";\n\"ua_notification_button_save\" = \"Uložiť\";\n\"ua_notification_button_search\" = \"Vyhľadať\";\n\"ua_notification_button_send_info\" = \"Poslať informáciu\";\n\"ua_notification_button_share\" = \"Zdieľať\";\n\"ua_notification_button_shop_now\" = \"Nakupovať teraz\";\n\"ua_notification_button_tell_me_more\" = \"Dajte viac informácií\";\n\"ua_notification_button_unfollow\" = \"Zrušiť sledovanie\";\n\"ua_notification_button_yes\" = \"Áno\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Centrum preferencií\";\n\"ua_retry_button\" = \"Opakovať pokus\";\n\"ua_select_all_messages\" = \"Vybrať všetko\";\n\"ua_select_all_messages_description\" = \"Vyberie všetky správy\";\n\"ua_select_none_messages\" = \"Nevybrať ani jednu\";\n\"ua_select_none_messages_description\" = \"Obnoviť výber správ\";\n\"ua_unread_message_description\" = \"Správa neprečítaná\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Zamietnuť\";\n\"ua_escape\" = \"Uniknúť\";\n\"ua_next\" = \"Ďalší\";\n\"ua_previous\" = \"Predchádzajúci\";\n\"ua_submit\" = \"Predložiť\";\n\"ua_loading\" = \"Načítavanie\";\n\"ua_pager_progress\" = \"Strana %@ z %@\";\n\"ua_x_of_y\" = \"%@ z %@\";\n\"ua_play\" = \"Hrať\";\n\"ua_pause\" = \"Pauza\";\n\"ua_stop\" = \"Zastaviť\";\n\"ua_form_processing_error\" = \"Chyba pri spracovaní formulára. Skúste to znova\";\n\"ua_close\" = \"Zavrieť\";\n\"ua_mute\" = \"Stlmiť\";\n\"ua_unmute\" = \"Zrušiť stlmenie\";\n\"ua_required_field\" = \"* Povinné\";\n\"ua_invalid_form_message\" = \"Opravte neplatné polia a pokračujte\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/sl.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Prekliči\";\n\"ua_cancel_edit_messages_description\" = \"Prekliči urejanje sporočil\";\n\"ua_connection_error\" = \"Napaka v povezavi\";\n\"ua_content_error\" = \"Napaka vsebine\";\n\"ua_delete_message\" = \"Izbriši sporočilo\";\n\"ua_delete_message_description\" = \"Izbriši izbrana sporočila\";\n\"ua_delete_messages\" = \"Izbriši sporočilo\";\n\"ua_delete_messages_description\" = \"Izbriši izbrana sporočila\";\n\"ua_edit_messages\" = \"Uredi\";\n\"ua_edit_messages_description\" = \"Uredite sporočila\";\n\"ua_empty_message_list\" = \"Brez sporočil\";\n\"ua_mark_messages_read\" = \"Označi kot prebrano\";\n\"ua_mark_messages_read_description\" = \"Označi izbrana sporočila kot prebrana\";\n\"ua_mc_failed_to_load\" = \"Sporočila ni mogoče naložiti. Prosim poskusite kasneje.\";\n\"ua_mc_no_longer_available\" = \"Izbrano sporočilo ni več na voljo.\";\n\"ua_message_cell_description\" = \"Prikaže celotno sporočilo\";\n\"ua_message_cell_editing_description\" = \"Preklopi izbiro\";\n\"ua_message_center_title\" = \"Center za sporočila\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Sporočilo %@, poslano ob %@\";\n\"ua_message_not_selected\" = \"Izbrano ni nobeno sporočilo\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Neprebrano sporočilo %@, poslano ob %@\";\n\"ua_notification_button_accept\" = \"Sprejmi\";\n\"ua_notification_button_add\" = \"Dodaj\";\n\"ua_notification_button_add_to_calendar\" = \"Dodaj v koledar\";\n\"ua_notification_button_book_now\" = \"Rezervirajte zdaj\";\n\"ua_notification_button_buy_now\" = \"Kupi zdaj\";\n\"ua_notification_button_copy\" = \"Kopirati\";\n\"ua_notification_button_decline\" = \"Zavrni\";\n\"ua_notification_button_dislike\" = \"Ne maram\";\n\"ua_notification_button_download\" = \"Prenesi\";\n\"ua_notification_button_follow\" = \"Sledite\";\n\"ua_notification_button_less_like\" = \"Manj takega\";\n\"ua_notification_button_like\" = \"Všeč mi je\";\n\"ua_notification_button_more_like\" = \"Več takega\";\n\"ua_notification_button_no\" = \"št\";\n\"ua_notification_button_opt_in\" = \"Privolitev\";\n\"ua_notification_button_opt_out\" = \"Odjava\";\n\"ua_notification_button_rate_now\" = \"Oceni zdaj\";\n\"ua_notification_button_remind\" = \"Opomni me kasneje\";\n\"ua_notification_button_save\" = \"Shrani\";\n\"ua_notification_button_search\" = \"Iskanje\";\n\"ua_notification_button_send_info\" = \"Pošlji informacije\";\n\"ua_notification_button_share\" = \"Deliti\";\n\"ua_notification_button_shop_now\" = \"Nakupujte zdaj\";\n\"ua_notification_button_tell_me_more\" = \"Povej mi več\";\n\"ua_notification_button_unfollow\" = \"Prekliči spremljanje\";\n\"ua_notification_button_yes\" = \"da\";\n\"ua_ok\" = \"v redu\";\n\"ua_preference_center_title\" = \"Preference Center\";\n\"ua_retry_button\" = \"Poskusi znova\";\n\"ua_select_all_messages\" = \"Izberi vse\";\n\"ua_select_all_messages_description\" = \"Izbere vsa sporočila\";\n\"ua_select_none_messages\" = \"Izberite Brez\";\n\"ua_select_none_messages_description\" = \"Ponastavi izbiro sporočila\";\n\"ua_unread_message_description\" = \"Sporočilo neprebrano\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Opusti\";\n\"ua_escape\" = \"Pobeg\";\n\"ua_next\" = \"Naslednji\";\n\"ua_previous\" = \"Prejšnji\";\n\"ua_submit\" = \"Predloži\";\n\"ua_loading\" = \"Nalaganje\";\n\"ua_pager_progress\" = \"Stran %@ od %@\";\n\"ua_x_of_y\" = \"%@ od %@\";\n\"ua_play\" = \"Igraj\";\n\"ua_pause\" = \"Premor\";\n\"ua_stop\" = \"Ustavi\";\n\"ua_form_processing_error\" = \"Napaka pri obdelavi obrazca. Poskusite znova\";\n\"ua_close\" = \"Zapri\";\n\"ua_mute\" = \"Utišaj\";\n\"ua_unmute\" = \"Prekliči utišanje\";\n\"ua_required_field\" = \"* Obvezno\";\n\"ua_invalid_form_message\" = \"Prosimo, popravite neveljavna polja za nadaljevanje\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/sr.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Откажи\";\n\"ua_cancel_edit_messages_description\" = \"Откажите измене порука\";\n\"ua_connection_error\" = \"Грешка везе\";\n\"ua_content_error\" = \"Грешка у садржају\";\n\"ua_delete_message\" = \"Избриши поруку\";\n\"ua_delete_message_description\" = \"Избришите изабране поруке\";\n\"ua_delete_messages\" = \"Избриши поруку\";\n\"ua_delete_messages_description\" = \"Избришите изабране поруке\";\n\"ua_edit_messages\" = \"Уреди\";\n\"ua_edit_messages_description\" = \"Уредите поруке\";\n\"ua_empty_message_list\" = \"Нема порука\";\n\"ua_mark_messages_read\" = \"Означи као прочитано\";\n\"ua_mark_messages_read_description\" = \"Означите изабране поруке као прочитане\";\n\"ua_mc_failed_to_load\" = \"Није могуће учитати поруку. Покушајте поново касније.\";\n\"ua_mc_no_longer_available\" = \"Изабрана порука више није доступна.\";\n\"ua_message_cell_description\" = \"Приказује целу поруку\";\n\"ua_message_cell_editing_description\" = \"Укључује избор\";\n\"ua_message_center_title\" = \"Центар за поруке\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Порука %@, послата у %@\";\n\"ua_message_not_selected\" = \"Није изабрана ниједна порука\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Непрочитана порука %@, послата у %@\";\n\"ua_notification_button_accept\" = \"Прихвати\";\n\"ua_notification_button_add\" = \"Додај\";\n\"ua_notification_button_add_to_calendar\" = \"Додај у календар\";\n\"ua_notification_button_book_now\" = \"Резервиши одмах\";\n\"ua_notification_button_buy_now\" = \"Купите одмах\";\n\"ua_notification_button_copy\" = \"Копирај\";\n\"ua_notification_button_decline\" = \"Одбиј\";\n\"ua_notification_button_dislike\" = \"Не свиђа ми се\";\n\"ua_notification_button_download\" = \"Преузимање\";\n\"ua_notification_button_follow\" = \"Прати\";\n\"ua_notification_button_less_like\" = \"Мање попут овога\";\n\"ua_notification_button_like\" = \"Као\";\n\"ua_notification_button_more_like\" = \"Више попут овога\";\n\"ua_notification_button_no\" = \"Не\";\n\"ua_notification_button_opt_in\" = \"Опт-ин\";\n\"ua_notification_button_opt_out\" = \"Одустани\";\n\"ua_notification_button_rate_now\" = \"Оцените сад\";\n\"ua_notification_button_remind\" = \"Подсети ме касније\";\n\"ua_notification_button_save\" = \"Сачувај\";\n\"ua_notification_button_search\" = \"Претрага\";\n\"ua_notification_button_send_info\" = \"Пошаљите информације\";\n\"ua_notification_button_share\" = \"Објави\";\n\"ua_notification_button_shop_now\" = \"Купујте сада\";\n\"ua_notification_button_tell_me_more\" = \"Реци ми више\";\n\"ua_notification_button_unfollow\" = \"Престани да пратиш\";\n\"ua_notification_button_yes\" = \"Да\";\n\"ua_ok\" = \"У реду\";\n\"ua_preference_center_title\" = \"Центар приоритета\";\n\"ua_retry_button\" = \"Покушај поново\";\n\"ua_select_all_messages\" = \"Изабери све\";\n\"ua_select_all_messages_description\" = \"Изабери све поруке\";\n\"ua_select_none_messages\" = \"Не изабери ниједан\";\n\"ua_select_none_messages_description\" = \"Ресетујте избор поруке\";\n\"ua_unread_message_description\" = \"Порука непрочитана\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Odbaci\";\n\"ua_escape\" = \"Izađi\";\n\"ua_next\" = \"Sledeće\";\n\"ua_previous\" = \"Prethodno\";\n\"ua_submit\" = \"Podnesi\";\n\"ua_loading\" = \"Učitavanje\";\n\"ua_pager_progress\" = \"Strana %@ od %@\";\n\"ua_x_of_y\" = \"%@ од %@\";\n\"ua_play\" = \"Pusti\";\n\"ua_pause\" = \"Pauziraj\";\n\"ua_stop\" = \"Zaustavi\";\n\"ua_form_processing_error\" = \"Грешка при обради обрасца. Молимо покушајте поново\";\n\"ua_close\" = \"Затвори\";\n\"ua_mute\" = \"Искључи звук\";\n\"ua_unmute\" = \"Укључи звук\";\n\"ua_required_field\" = \"* Обавезно\";\n\"ua_invalid_form_message\" = \"Молимо исправите неважећа поља да бисте наставили\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/sv.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Avbryt\";\n\"ua_cancel_edit_messages_description\" = \"Avbryt meddelanderedigeringar\";\n\"ua_connection_error\" = \"Anslutningsfel\";\n\"ua_content_error\" = \"Innehållsfel\";\n\"ua_delete_message\" = \"Radera meddelande\";\n\"ua_delete_message_description\" = \"Ta bort markerade meddelanden\";\n\"ua_delete_messages\" = \"Radera meddelande\";\n\"ua_delete_messages_description\" = \"Ta bort markerade meddelanden\";\n\"ua_edit_messages\" = \"Redigera\";\n\"ua_edit_messages_description\" = \"Redigera meddelanden\";\n\"ua_empty_message_list\" = \"Inga meddelanden\";\n\"ua_mark_messages_read\" = \"Markera som läst\";\n\"ua_mark_messages_read_description\" = \"Markera valda meddelanden som lästa\";\n\"ua_mc_failed_to_load\" = \"Det går inte att läsa in meddelande. Försök igen senare.\";\n\"ua_mc_no_longer_available\" = \"Det valda meddelandet är inte längre tillgängligt.\";\n\"ua_message_cell_description\" = \"Visar hela meddelandet\";\n\"ua_message_cell_editing_description\" = \"Växlar val\";\n\"ua_message_center_title\" = \"Meddelandecenter\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Meddelande %@, skickat kl. %@\";\n\"ua_message_not_selected\" = \"Inga meddelanden har valts\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Oläst meddelande %@, skickat kl. %@\";\n\"ua_notification_button_accept\" = \"Acceptera\";\n\"ua_notification_button_add\" = \"Lägg till\";\n\"ua_notification_button_add_to_calendar\" = \"Lägg till i kalender\";\n\"ua_notification_button_book_now\" = \"Boka nu\";\n\"ua_notification_button_buy_now\" = \"Köp nu\";\n\"ua_notification_button_copy\" = \"Kopiera\";\n\"ua_notification_button_decline\" = \"Neka\";\n\"ua_notification_button_dislike\" = \"Ogilla\";\n\"ua_notification_button_download\" = \"Ladda ner\";\n\"ua_notification_button_follow\" = \"Följ\";\n\"ua_notification_button_less_like\" = \"Mindre som detta\";\n\"ua_notification_button_like\" = \"Gilla\";\n\"ua_notification_button_more_like\" = \"Mer som detta\";\n\"ua_notification_button_no\" = \"Nej\";\n\"ua_notification_button_opt_in\" = \"Anmäl dig\";\n\"ua_notification_button_opt_out\" = \"Avanmäl dig\";\n\"ua_notification_button_rate_now\" = \"Betygsätt nu\";\n\"ua_notification_button_remind\" = \"Påminn mig senare\";\n\"ua_notification_button_save\" = \"Spara\";\n\"ua_notification_button_search\" = \"Sök\";\n\"ua_notification_button_send_info\" = \"Skicka info\";\n\"ua_notification_button_share\" = \"Dela\";\n\"ua_notification_button_shop_now\" = \"Handla nu\";\n\"ua_notification_button_tell_me_more\" = \"Berätta mer\";\n\"ua_notification_button_unfollow\" = \"Sluta följa\";\n\"ua_notification_button_yes\" = \"Ja\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Preferenscenter\";\n\"ua_retry_button\" = \"Försök igen\";\n\"ua_select_all_messages\" = \"Välj alla\";\n\"ua_select_all_messages_description\" = \"Väljer alla meddelanden\";\n\"ua_select_none_messages\" = \"Välj inget\";\n\"ua_select_none_messages_description\" = \"Återställ meddelandeval\";\n\"ua_unread_message_description\" = \"Meddelande oläst\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Avfärda\";\n\"ua_escape\" = \"Rymma\";\n\"ua_next\" = \"Nästa\";\n\"ua_previous\" = \"Föregående\";\n\"ua_submit\" = \"Skicka\";\n\"ua_loading\" = \"Laddar\";\n\"ua_pager_progress\" = \"Sida %@ av %@\";\n\"ua_x_of_y\" = \"%@ av %@\";\n\"ua_play\" = \"Spela\";\n\"ua_pause\" = \"Pausa\";\n\"ua_stop\" = \"Stoppa\";\n\"ua_form_processing_error\" = \"Fel vid bearbetning av formulär. Försök igen\";\n\"ua_close\" = \"Stäng\";\n\"ua_mute\" = \"Tysta\";\n\"ua_unmute\" = \"Avtysta\";\n\"ua_required_field\" = \"* Obligatoriskt\";\n\"ua_invalid_form_message\" = \"Vänligen åtgärda de ogiltiga fälten för att fortsätta\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/sw.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Ghairi\";\n\"ua_cancel_edit_messages_description\" = \"Ghairi uhariri wa ujumbe\";\n\"ua_connection_error\" = \"Hitilafu ya Muunganisho\";\n\"ua_content_error\" = \"Hitilafu ya Maudhui\";\n\"ua_delete_message\" = \"Futa ujumbe\";\n\"ua_delete_message_description\" = \"Futa ujumbe uliochaguliwa\";\n\"ua_delete_messages\" = \"Futa ujumbe\";\n\"ua_delete_messages_description\" = \"Futa ujumbe uliochaguliwa\";\n\"ua_edit_messages\" = \"Hariri\";\n\"ua_edit_messages_description\" = \"Hariri ujumbe\";\n\"ua_empty_message_list\" = \"Hakuna ujumbe\";\n\"ua_mark_messages_read\" = \"Alama kuwa Imesomwa\";\n\"ua_mark_messages_read_description\" = \"Tia alama kuwa ujumbe uliochaguliwa umesomwa\";\n\"ua_mc_failed_to_load\" = \"Imeshindwa kupakia ujumbe. Tafadhali jaribu tena baadae.\";\n\"ua_mc_no_longer_available\" = \"Ujumbe uliochaguliwa haupatikani tena.\";\n\"ua_message_cell_description\" = \"Inaonyesha ujumbe kamili\";\n\"ua_message_cell_editing_description\" = \"Hugeuza uteuzi\";\n\"ua_message_center_title\" = \"Kituo cha Ujumbe\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Ujumbe kwa %@, ulitumwa kwa %@\";\n\"ua_message_not_selected\" = \"Hakuna ujumbe uliochaguliwa\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Ujumbe ambao haujasomwa %@, ulitumwa kwa %@\";\n\"ua_notification_button_accept\" = \"Kubali\";\n\"ua_notification_button_add\" = \"Ongeza\";\n\"ua_notification_button_add_to_calendar\" = \"Ongeza kwenye Kalenda\";\n\"ua_notification_button_book_now\" = \"Weka Nafasi Sasa\";\n\"ua_notification_button_buy_now\" = \"Nunua Sasa\";\n\"ua_notification_button_copy\" = \"Nakili\";\n\"ua_notification_button_decline\" = \"Kataa\";\n\"ua_notification_button_dislike\" = \"Kutopenda\";\n\"ua_notification_button_download\" = \"Pakua\";\n\"ua_notification_button_follow\" = \"Fuata\";\n\"ua_notification_button_less_like\" = \"Kidogo Kama Hii\";\n\"ua_notification_button_like\" = \"Penda\";\n\"ua_notification_button_more_like\" = \"Zaidi Kama Hii\";\n\"ua_notification_button_no\" = \"Hapana\";\n\"ua_notification_button_opt_in\" = \"Jijumuishe\";\n\"ua_notification_button_opt_out\" = \"Chagua kutoka\";\n\"ua_notification_button_rate_now\" = \"Kadiria Sasa\";\n\"ua_notification_button_remind\" = \"Nikumbushe Baadaye\";\n\"ua_notification_button_save\" = \"Hifadhi\";\n\"ua_notification_button_search\" = \"Tafuta\";\n\"ua_notification_button_send_info\" = \"Tuma Taarifa\";\n\"ua_notification_button_share\" = \"Shiriki\";\n\"ua_notification_button_shop_now\" = \"Nunua Sasa\";\n\"ua_notification_button_tell_me_more\" = \"Niambie zaidi\";\n\"ua_notification_button_unfollow\" = \"Acha kufuata\";\n\"ua_notification_button_yes\" = \"Ndiyo\";\n\"ua_ok\" = \"SAWA\";\n\"ua_preference_center_title\" = \"Kituo cha Upendeleo\";\n\"ua_retry_button\" = \"Jaribu tena\";\n\"ua_select_all_messages\" = \"Chagua Zote\";\n\"ua_select_all_messages_description\" = \"Huchagua ujumbe wote\";\n\"ua_select_none_messages\" = \"Chagua Hakuna\";\n\"ua_select_none_messages_description\" = \"Weka upya uteuzi wa ujumbe\";\n\"ua_unread_message_description\" = \"Ujumbe haujasomwa\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Ondoa\";\n\"ua_escape\" = \"Kutoroka\";\n\"ua_next\" = \"Ifuatayo\";\n\"ua_previous\" = \"Iliyopita\";\n\"ua_submit\" = \"Wasilisha\";\n\"ua_loading\" = \"Inapakia\";\n\"ua_pager_progress\" = \"Ukurasa %@ ya %@\";\n\"ua_x_of_y\" = \"%@ kati ya %@\";\n\"ua_play\" = \"Cheza\";\n\"ua_pause\" = \"Sitisha\";\n\"ua_stop\" = \"Simamisha\";\n\"ua_form_processing_error\" = \"Hitilafu ya kusindika fomu. Tafadhali jaribu tena\";\n\"ua_close\" = \"Funga\";\n\"ua_mute\" = \"Nyamazisha\";\n\"ua_unmute\" = \"Ondoa unyamavishi\";\n\"ua_required_field\" = \"* Inahitajika\";\n\"ua_invalid_form_message\" = \"Tafadhali sahihisha sehemu zisizo sahihi ili kuendelea\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/th.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"ยกเลิก\";\n\"ua_cancel_edit_messages_description\" = \"ยกเลิกการแก้ไขข้อความ\";\n\"ua_connection_error\" = \"การเชื่อมต่อผิดพลาด\";\n\"ua_content_error\" = \"เนื้อหาผิดพลาด\";\n\"ua_delete_message\" = \"ลบข้อความ\";\n\"ua_delete_message_description\" = \"ลบข้อความที่เลือก\";\n\"ua_delete_messages\" = \"ลบข้อความ\";\n\"ua_delete_messages_description\" = \"ลบข้อความที่เลือก\";\n\"ua_edit_messages\" = \"แก้ไข\";\n\"ua_edit_messages_description\" = \"แก้ไขข้อความ\";\n\"ua_empty_message_list\" = \"ไม่มีข้อความ\";\n\"ua_mark_messages_read\" = \"ทำเครื่องหมายว่าอ่านแล้ว\";\n\"ua_mark_messages_read_description\" = \"ทำเครื่องหมายข้อความที่เลือกว่าอ่านแล้ว\";\n\"ua_mc_failed_to_load\" = \"ไม่สามารถโหลดข้อความ กรุณาลองใหม่อีกครั้งในภายหลัง\";\n\"ua_mc_no_longer_available\" = \"ข้อความที่เลือกไม่สามารถใช้ได้อีกต่อไป\";\n\"ua_message_cell_description\" = \"แสดงข้อความเต็ม\";\n\"ua_message_cell_editing_description\" = \"สลับการเลือก\";\n\"ua_message_center_title\" = \"ศูนย์ข้อความ\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"ข้อความ %@ ส่งเมื่อ %@\";\n\"ua_message_not_selected\" = \"ไม่ได้เลือกข้อความ\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"ข้อความที่ยังไม่ได้อ่าน %@ ส่งเมื่อ %@\";\n\"ua_notification_button_accept\" = \"ยอมรับ\";\n\"ua_notification_button_add\" = \"เพิ่ม\";\n\"ua_notification_button_add_to_calendar\" = \"เพิ่มไปยังปฏิทิน\";\n\"ua_notification_button_book_now\" = \"จองตอนนี้\";\n\"ua_notification_button_buy_now\" = \"ซื้อตอนนี้\";\n\"ua_notification_button_copy\" = \"คัดลอก\";\n\"ua_notification_button_decline\" = \"ปฏิเสธ\";\n\"ua_notification_button_dislike\" = \"ไม่ถูกใจ\";\n\"ua_notification_button_download\" = \"ดาวน์โหลด\";\n\"ua_notification_button_follow\" = \"ติดตาม\";\n\"ua_notification_button_less_like\" = \"ไม่เอาแบบนี้อีก\";\n\"ua_notification_button_like\" = \"ถูกใจ\";\n\"ua_notification_button_more_like\" = \"เอาแบบนี้อีก\";\n\"ua_notification_button_no\" = \"ไม่\";\n\"ua_notification_button_opt_in\" = \"เลือกเก็บ\";\n\"ua_notification_button_opt_out\" = \"เลือกทิ้ง\";\n\"ua_notification_button_rate_now\" = \"ให้คะแนนตอนนี้\";\n\"ua_notification_button_remind\" = \"เตือนฉันภายหลัง\";\n\"ua_notification_button_save\" = \"บันทึก\";\n\"ua_notification_button_search\" = \"ค้นหา\";\n\"ua_notification_button_send_info\" = \"ส่งข้อมูล\";\n\"ua_notification_button_share\" = \"แชร์\";\n\"ua_notification_button_shop_now\" = \"ช้อปตอนนี้\";\n\"ua_notification_button_tell_me_more\" = \"บอกรายละเอียดฉันเพิ่มเติม\";\n\"ua_notification_button_unfollow\" = \"เลิกติดตาม\";\n\"ua_notification_button_yes\" = \"ใช่\";\n\"ua_ok\" = \"ตกลง\";\n\"ua_preference_center_title\" = \"ศูนย์การตั้งค่า\";\n\"ua_retry_button\" = \"ลองใหม่\";\n\"ua_select_all_messages\" = \"เลือกทั้งหมด\";\n\"ua_select_all_messages_description\" = \"เลือกข้อความทั้งหมด\";\n\"ua_select_none_messages\" = \"ไม่เลือกเลย\";\n\"ua_select_none_messages_description\" = \"รีเซ็ตการเลือกข้อความ\";\n\"ua_unread_message_description\" = \"ยังไม่ได้อ่านข้อความ\";\n\n// Generic localizations\n\"ua_dismiss\" = \"ปฏิเสธ\";\n\"ua_escape\" = \"หนีออกจาก\";\n\"ua_next\" = \"ถัดไป\";\n\"ua_previous\" = \"ก่อนหน้า\";\n\"ua_submit\" = \"ส่ง\";\n\"ua_loading\" = \"กำลังโหลด\";\n\"ua_pager_progress\" = \"หน้า %@ จาก %@\";\n\"ua_x_of_y\" = \"%@ จาก %@\";\n\"ua_play\" = \"เล่น\";\n\"ua_pause\" = \"หยุดชั่วคราว\";\n\"ua_stop\" = \"หยุด\";\n\"ua_form_processing_error\" = \"เกิดข้อผิดพลาดในการประมวลผลแบบฟอร์ม โปรดลองอีกครั้ง\";\n\"ua_close\" = \"ปิด\";\n\"ua_mute\" = \"ปิดเสียง\";\n\"ua_unmute\" = \"เปิดเสียง\";\n\"ua_required_field\" = \"* จำเป็น\";\n\"ua_invalid_form_message\" = \"โปรดแก้ไขฟิลด์ที่ไม่ถูกต้องเพื่อดำเนินการต่อ\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/tr.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"İptal\";\n\"ua_cancel_edit_messages_description\" = \"Mesaj düzenlemelerini iptal et\";\n\"ua_connection_error\" = \"Bağlantı Hatası\";\n\"ua_content_error\" = \"İçerik Hatası\";\n\"ua_delete_message\" = \"Mesajı sil\";\n\"ua_delete_message_description\" = \"Seçili mesajları sil\";\n\"ua_delete_messages\" = \"Mesajı sil\";\n\"ua_delete_messages_description\" = \"Seçili mesajları sil\";\n\"ua_edit_messages\" = \"Düzenlemek\";\n\"ua_edit_messages_description\" = \"Mesajları düzenle\";\n\"ua_empty_message_list\" = \"Mesaj Yok\";\n\"ua_mark_messages_read\" = \"Okundu Olarak İşaretle\";\n\"ua_mark_messages_read_description\" = \"Seçilen mesajları okundu olarak işaretle\";\n\"ua_mc_failed_to_load\" = \"Mesaj yüklenemiyor. Lütfen daha sonra tekrar deneyin.\";\n\"ua_mc_no_longer_available\" = \"Seçilen mesaj artık mevcut değil.\";\n\"ua_message_cell_description\" = \"Tam mesajı görüntüler\";\n\"ua_message_cell_editing_description\" = \"Seçimi değiştirir\";\n\"ua_message_center_title\" = \"Mesaj Merkezi\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"%@ mesajı, %@ tarihinde gönderildi\";\n\"ua_message_not_selected\" = \"Hiçbir mesaj seçilmedi\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"%@ okunmamış iletisi, %@ adresine gönderildi\";\n\"ua_notification_button_accept\" = \"Kabul Et\";\n\"ua_notification_button_add\" = \"Ekle\";\n\"ua_notification_button_add_to_calendar\" = \"Takvime Ekle\";\n\"ua_notification_button_book_now\" = \"Hemen Ayırt\";\n\"ua_notification_button_buy_now\" = \"Şimdi Satın Al\";\n\"ua_notification_button_copy\" = \"Kopyala\";\n\"ua_notification_button_decline\" = \"Reddet\";\n\"ua_notification_button_dislike\" = \"Beğenme\";\n\"ua_notification_button_download\" = \"İndir\";\n\"ua_notification_button_follow\" = \"Takip Et\";\n\"ua_notification_button_less_like\" = \"Benzerlerinden Daha Az\";\n\"ua_notification_button_like\" = \"Beğen\";\n\"ua_notification_button_more_like\" = \"Benzerlerinden Daha Fazla\";\n\"ua_notification_button_no\" = \"Hayır\";\n\"ua_notification_button_opt_in\" = \"Ekle\";\n\"ua_notification_button_opt_out\" = \"Çıkar\";\n\"ua_notification_button_rate_now\" = \"Şimdi Oyla\";\n\"ua_notification_button_remind\" = \"Daha Sonra Hatırlat\";\n\"ua_notification_button_save\" = \"Kaydet\";\n\"ua_notification_button_search\" = \"Ara\";\n\"ua_notification_button_send_info\" = \"Bilgi Gönder\";\n\"ua_notification_button_share\" = \"Paylaş\";\n\"ua_notification_button_shop_now\" = \"Şimdi Alışveriş Yap\";\n\"ua_notification_button_tell_me_more\" = \"Daha Fazla Bilgi\";\n\"ua_notification_button_unfollow\" = \"Takibi Bırak\";\n\"ua_notification_button_yes\" = \"Evet\";\n\"ua_ok\" = \"Tamam\";\n\"ua_preference_center_title\" = \"Tercih Merkezi\";\n\"ua_retry_button\" = \"Tekrar dene\";\n\"ua_select_all_messages\" = \"Tümünü Seç\";\n\"ua_select_all_messages_description\" = \"Tüm mesajları seçer\";\n\"ua_select_none_messages\" = \"Hiçbirini Seçme\";\n\"ua_select_none_messages_description\" = \"Mesaj seçimini sıfırla\";\n\"ua_unread_message_description\" = \"Mesaj okunmadı\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Kapat\";\n\"ua_escape\" = \"Çıkış\";\n\"ua_next\" = \"Sonraki\";\n\"ua_previous\" = \"Önceki\";\n\"ua_submit\" = \"Gönder\";\n\"ua_loading\" = \"Yükleniyor\";\n\"ua_pager_progress\" = \"%@ / %@ Sayfa\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"Oynat\";\n\"ua_pause\" = \"Duraklat\";\n\"ua_stop\" = \"Durdur\";\n\"ua_form_processing_error\" = \"Form işlenirken hata oluştu. Lütfen tekrar deneyin\";\n\"ua_close\" = \"Kapat\";\n\"ua_mute\" = \"Sessiz\";\n\"ua_unmute\" = \"Sesi Aç\";\n\"ua_required_field\" = \"* Zorunlu\";\n\"ua_invalid_form_message\" = \"Devam etmek için lütfen geçersiz alanları düzeltin\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/uk.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Скасувати\";\n\"ua_cancel_edit_messages_description\" = \"Скасувати редагування повідомлення\";\n\"ua_connection_error\" = \"Помилка з\\'єднання\";\n\"ua_content_error\" = \"Помилка вмісту\";\n\"ua_delete_message\" = \"Видалити повідомлення\";\n\"ua_delete_message_description\" = \"Видалити вибрані повідомлення\";\n\"ua_delete_messages\" = \"Видалити повідомлення\";\n\"ua_delete_messages_description\" = \"Видалити вибрані повідомлення\";\n\"ua_edit_messages\" = \"Редагувати\";\n\"ua_edit_messages_description\" = \"Редагувати повідомлення\";\n\"ua_empty_message_list\" = \"Немає повідомлень\";\n\"ua_mark_messages_read\" = \"Позначити як прочитане\";\n\"ua_mark_messages_read_description\" = \"Позначити вибрані повідомлення як прочитані\";\n\"ua_mc_failed_to_load\" = \"Не вдається завантажити повідомлення. Будь-ласка, спробуйте пізніше.\";\n\"ua_mc_no_longer_available\" = \"Вибране повідомлення більше не доступне.\";\n\"ua_message_cell_description\" = \"Відображає повне повідомлення\";\n\"ua_message_cell_editing_description\" = \"Перемикає вибір\";\n\"ua_message_center_title\" = \"Центр повідомлень\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Повідомлення %@, надіслано о %@\";\n\"ua_message_not_selected\" = \"Не вибрано жодного повідомлення\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Непрочитане повідомлення %@, надіслано о %@\";\n\"ua_notification_button_accept\" = \"Прийняти\";\n\"ua_notification_button_add\" = \"Додати\";\n\"ua_notification_button_add_to_calendar\" = \"Додати до календаря\";\n\"ua_notification_button_book_now\" = \"Резервувати зараз\";\n\"ua_notification_button_buy_now\" = \"Придбайте зараз\";\n\"ua_notification_button_copy\" = \"Копіювати\";\n\"ua_notification_button_decline\" = \"Відхилити\";\n\"ua_notification_button_dislike\" = \"Не люблю\";\n\"ua_notification_button_download\" = \"Завантажити\";\n\"ua_notification_button_follow\" = \"Слідкувати\";\n\"ua_notification_button_less_like\" = \"Менше подібного\";\n\"ua_notification_button_like\" = \"Люблю\";\n\"ua_notification_button_more_like\" = \"Більше подібного\";\n\"ua_notification_button_no\" = \"Ні\";\n\"ua_notification_button_opt_in\" = \"Вибрати\";\n\"ua_notification_button_opt_out\" = \"Відмовитися\";\n\"ua_notification_button_rate_now\" = \"Оцінити зараз\";\n\"ua_notification_button_remind\" = \"Нагадай мені пізніше\";\n\"ua_notification_button_save\" = \"Зберегти\";\n\"ua_notification_button_search\" = \"Пошук\";\n\"ua_notification_button_send_info\" = \"Надіслати інформацію\";\n\"ua_notification_button_share\" = \"Поділіться\";\n\"ua_notification_button_shop_now\" = \"Здійснити покупку\";\n\"ua_notification_button_tell_me_more\" = \"Розкажи мені більше\";\n\"ua_notification_button_unfollow\" = \"Скасувати підписку\";\n\"ua_notification_button_yes\" = \"Так\";\n\"ua_ok\" = \"ОК\";\n\"ua_preference_center_title\" = \"Центр параметрів\";\n\"ua_retry_button\" = \"Повторіть спробу\";\n\"ua_select_all_messages\" = \"Вибрати все\";\n\"ua_select_all_messages_description\" = \"Вибрати всі повідомлення\";\n\"ua_select_none_messages\" = \"Скасувати вибір\";\n\"ua_select_none_messages_description\" = \"Скинути вибір повідомлення\";\n\"ua_unread_message_description\" = \"Повідомлення непрочитане\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Відхилити\";\n\"ua_escape\" = \"Вихід\";\n\"ua_next\" = \"Далі\";\n\"ua_previous\" = \"Попередній\";\n\"ua_submit\" = \"Подати\";\n\"ua_loading\" = \"Завантаження\";\n\"ua_pager_progress\" = \"Сторінка %@ з %@\";\n\"ua_x_of_y\" = \"%@ з %@\";\n\"ua_play\" = \"Грати\";\n\"ua_pause\" = \"Пауза\";\n\"ua_stop\" = \"Стоп\";\n\"ua_form_processing_error\" = \"Помилка обробки форми. Будь ласка, спробуйте ще раз\";\n\"ua_close\" = \"Закрити\";\n\"ua_mute\" = \"Вимкнути звук\";\n\"ua_unmute\" = \"Увімкнути звук\";\n\"ua_required_field\" = \"* Обов'язково\";\n\"ua_invalid_form_message\" = \"Будь ласка, виправте недійсні поля, щоб продовжити\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/vi.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Hủy bỏ\";\n\"ua_cancel_edit_messages_description\" = \"Hủy các chỉnh sửa tin nhắn\";\n\"ua_connection_error\" = \"Lỗi Kết nối\";\n\"ua_content_error\" = \"Lỗi nội dung\";\n\"ua_delete_message\" = \"Xóa tin nhắn\";\n\"ua_delete_message_description\" = \"Xóa các tin nhắn đã chọn\";\n\"ua_delete_messages\" = \"Xóa tin nhắn\";\n\"ua_delete_messages_description\" = \"Xóa các tin nhắn đã chọn\";\n\"ua_edit_messages\" = \"Chỉnh sửa\";\n\"ua_edit_messages_description\" = \"Chỉnh sửa tin nhắn\";\n\"ua_empty_message_list\" = \"Không có tin nhắn\";\n\"ua_mark_messages_read\" = \"Đánh dấu đã Đọc\";\n\"ua_mark_messages_read_description\" = \"Đánh dấu các tin nhắn đã chọn là đã đọc\";\n\"ua_mc_failed_to_load\" = \"Không thể tải tin nhắn. Vui lòng thử lại sau.\";\n\"ua_mc_no_longer_available\" = \"Tin nhắn đã chọn không còn nữa.\";\n\"ua_message_cell_description\" = \"Hiển thị thông báo đầy đủ\";\n\"ua_message_cell_editing_description\" = \"Chuyển đổi lựa chọn\";\n\"ua_message_center_title\" = \"Trung tâm Tin nhắn\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Tin nhắn %@, được gửi lúc %@\";\n\"ua_message_not_selected\" = \"Không có tin nhắn nào được chọn\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Tin nhắn chưa đọc %@, được gửi lúc %@\";\n\"ua_notification_button_accept\" = \"Chấp nhận\";\n\"ua_notification_button_add\" = \"Thêm\";\n\"ua_notification_button_add_to_calendar\" = \"Thêm vào Lịch\";\n\"ua_notification_button_book_now\" = \"Đặt Ngay\";\n\"ua_notification_button_buy_now\" = \"Mua Ngay\";\n\"ua_notification_button_copy\" = \"Sao chép\";\n\"ua_notification_button_decline\" = \"Từ chối\";\n\"ua_notification_button_dislike\" = \"Không thích\";\n\"ua_notification_button_download\" = \"Tải về\";\n\"ua_notification_button_follow\" = \"Theo dõi\";\n\"ua_notification_button_less_like\" = \"Ít Tương tự hơn\";\n\"ua_notification_button_like\" = \"Thích\";\n\"ua_notification_button_more_like\" = \"Thêm Tương Tự\";\n\"ua_notification_button_no\" = \"Không\";\n\"ua_notification_button_opt_in\" = \"Tham gia\";\n\"ua_notification_button_opt_out\" = \"Không tham gia\";\n\"ua_notification_button_rate_now\" = \"Đánh giá Ngay\";\n\"ua_notification_button_remind\" = \"Nhắc Tôi Sau\";\n\"ua_notification_button_save\" = \"Lưu\";\n\"ua_notification_button_search\" = \"Tìm kiếm\";\n\"ua_notification_button_send_info\" = \"Gửi Thông tin\";\n\"ua_notification_button_share\" = \"Chia sẻ\";\n\"ua_notification_button_shop_now\" = \"Mua hàng Ngay\";\n\"ua_notification_button_tell_me_more\" = \"Cho Tôi Biết Thêm\";\n\"ua_notification_button_unfollow\" = \"Hủy theo dõi\";\n\"ua_notification_button_yes\" = \"Có\";\n\"ua_ok\" = \"OK\";\n\"ua_preference_center_title\" = \"Trung tâm Ưu tiên\";\n\"ua_retry_button\" = \"Thử lại\";\n\"ua_select_all_messages\" = \"Chọn Tất cả\";\n\"ua_select_all_messages_description\" = \"Chọn tất cả các tin nhắn\";\n\"ua_select_none_messages\" = \"Chọn Không\";\n\"ua_select_none_messages_description\" = \"Đặt lại lựa chọn tin nhắn\";\n\"ua_unread_message_description\" = \"Tin nhắn chưa đọc\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Bỏ qua\";\n\"ua_escape\" = \"Thoát\";\n\"ua_next\" = \"Kế tiếp\";\n\"ua_previous\" = \"Trước đó\";\n\"ua_submit\" = \"Gửi\";\n\"ua_loading\" = \"Đang tải\";\n\"ua_pager_progress\" = \"Trang %@ của %@\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"Phát\";\n\"ua_pause\" = \"Tạm dừng\";\n\"ua_stop\" = \"Dừng\";\n\"ua_form_processing_error\" = \"Lỗi xử lý biểu mẫu. Vui lòng thử lại\";\n\"ua_close\" = \"Đóng\";\n\"ua_mute\" = \"Tắt tiếng\";\n\"ua_unmute\" = \"Bật tiếng\";\n\"ua_required_field\" = \"* Bắt buộc\";\n\"ua_invalid_form_message\" = \"Vui lòng sửa các trường không hợp lệ để tiếp tục\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/zh-HK.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"取消\";\n\"ua_cancel_edit_messages_description\" = \"取消消息編輯\";\n\"ua_connection_error\" = \"連接錯誤\";\n\"ua_content_error\" = \"內容錯誤\";\n\"ua_delete_message\" = \"刪除消息\";\n\"ua_delete_message_description\" = \"刪除選定的消息\";\n\"ua_delete_messages\" = \"刪除消息\";\n\"ua_delete_messages_description\" = \"刪除選定的消息\";\n\"ua_edit_messages\" = \"編輯\";\n\"ua_edit_messages_description\" = \"編輯消息\";\n\"ua_empty_message_list\" = \"沒有消息\";\n\"ua_mark_messages_read\" = \"標記閱讀\";\n\"ua_mark_messages_read_description\" = \"將所選消息標記為已讀\";\n\"ua_mc_failed_to_load\" = \"無法加載消息。請稍後再試。\";\n\"ua_mc_no_longer_available\" = \"所選消息不再可用。\";\n\"ua_message_cell_description\" = \"顯示完整消息\";\n\"ua_message_cell_editing_description\" = \"切換選擇\";\n\"ua_message_center_title\" = \"留言中心\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"消息 %@，發送於 %@\";\n\"ua_message_not_selected\" = \"未選擇任何消息\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"未讀消息 %@，發送於 %@\";\n\"ua_notification_button_accept\" = \"接受\";\n\"ua_notification_button_add\" = \"添加\";\n\"ua_notification_button_add_to_calendar\" = \"添加到日曆\";\n\"ua_notification_button_book_now\" = \"現在預訂\";\n\"ua_notification_button_buy_now\" = \"立即購買\";\n\"ua_notification_button_copy\" = \"複製\";\n\"ua_notification_button_decline\" = \"衰退\";\n\"ua_notification_button_dislike\" = \"不喜歡\";\n\"ua_notification_button_download\" = \"下載\";\n\"ua_notification_button_follow\" = \"跟隨\";\n\"ua_notification_button_less_like\" = \"少這樣\";\n\"ua_notification_button_like\" = \"喜歡\";\n\"ua_notification_button_more_like\" = \"更像這樣\";\n\"ua_notification_button_no\" = \"不\";\n\"ua_notification_button_opt_in\" = \"選擇參加\";\n\"ua_notification_button_opt_out\" = \"選擇退出\";\n\"ua_notification_button_rate_now\" = \"現在就評價吧\";\n\"ua_notification_button_remind\" = \"稍後提醒我\";\n\"ua_notification_button_save\" = \"保存\";\n\"ua_notification_button_search\" = \"搜索\";\n\"ua_notification_button_send_info\" = \"發送信息\";\n\"ua_notification_button_share\" = \"分享\";\n\"ua_notification_button_shop_now\" = \"現在去購物\";\n\"ua_notification_button_tell_me_more\" = \"告訴我更多\";\n\"ua_notification_button_unfollow\" = \"取消關注\";\n\"ua_notification_button_yes\" = \"是的\";\n\"ua_ok\" = \"好的\";\n\"ua_preference_center_title\" = \"偏好中心\";\n\"ua_retry_button\" = \"重試\";\n\"ua_select_all_messages\" = \"全選\";\n\"ua_select_all_messages_description\" = \"選擇所有消息\";\n\"ua_select_none_messages\" = \"選擇無\";\n\"ua_select_none_messages_description\" = \"重置消息選擇\";\n\"ua_unread_message_description\" = \"留言未讀\";\n\n// Generic localizations\n\"ua_dismiss\" = \"解除\";\n\"ua_escape\" = \"逃生\";\n\"ua_next\" = \"下一個\";\n\"ua_previous\" = \"上一個\";\n\"ua_submit\" = \"提交\";\n\"ua_loading\" = \"正在載入\";\n\"ua_pager_progress\" = \"第%@頁，共%@頁\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"播放\";\n\"ua_pause\" = \"暫停\";\n\"ua_stop\" = \"停止\";\n\"ua_form_processing_error\" = \"處理表單時出錯。請重試\";\n\"ua_close\" = \"關閉\";\n\"ua_mute\" = \"靜音\";\n\"ua_unmute\" = \"取消靜音\";\n\"ua_required_field\" = \"* 必填\";\n\"ua_invalid_form_message\" = \"請修正無效欄位以繼續\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/zh-Hans.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"取消\";\n\"ua_cancel_edit_messages_description\" = \"取消消息编辑\";\n\"ua_connection_error\" = \"连接错误\";\n\"ua_content_error\" = \"内容错误\";\n\"ua_delete_message\" = \"删除消息\";\n\"ua_delete_message_description\" = \"删除选定消息\";\n\"ua_delete_messages\" = \"删除消息\";\n\"ua_delete_messages_description\" = \"删除选定消息\";\n\"ua_edit_messages\" = \"编辑\";\n\"ua_edit_messages_description\" = \"编辑消息\";\n\"ua_empty_message_list\" = \"无消息\";\n\"ua_mark_messages_read\" = \"标记已读\";\n\"ua_mark_messages_read_description\" = \"将所选消息标记为已读\";\n\"ua_mc_failed_to_load\" = \"无法加载消息。请稍后再试。\";\n\"ua_mc_no_longer_available\" = \"所选消息不再可用。\";\n\"ua_message_cell_description\" = \"显示完整消息\";\n\"ua_message_cell_editing_description\" = \"切换选择\";\n\"ua_message_center_title\" = \"消息中心\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"消息 %@，发送于 %@\";\n\"ua_message_not_selected\" = \"未选择任何消息\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"未读消息 %@，发送于 %@\";\n\"ua_notification_button_accept\" = \"接受\";\n\"ua_notification_button_add\" = \"添加\";\n\"ua_notification_button_add_to_calendar\" = \"添加到日历\";\n\"ua_notification_button_book_now\" = \"现在预订\";\n\"ua_notification_button_buy_now\" = \"立刻购买\";\n\"ua_notification_button_copy\" = \"复制\";\n\"ua_notification_button_decline\" = \"拒绝\";\n\"ua_notification_button_dislike\" = \"不喜欢\";\n\"ua_notification_button_download\" = \"下载\";\n\"ua_notification_button_follow\" = \"关注\";\n\"ua_notification_button_less_like\" = \"更少类似\";\n\"ua_notification_button_like\" = \"喜欢\";\n\"ua_notification_button_more_like\" = \"更多类似\";\n\"ua_notification_button_no\" = \"否\";\n\"ua_notification_button_opt_in\" = \"参加\";\n\"ua_notification_button_opt_out\" = \"不参加\";\n\"ua_notification_button_rate_now\" = \"立即评价\";\n\"ua_notification_button_remind\" = \"稍后提醒我\";\n\"ua_notification_button_save\" = \"保存\";\n\"ua_notification_button_search\" = \"搜索\";\n\"ua_notification_button_send_info\" = \"发送信息\";\n\"ua_notification_button_share\" = \"分享\";\n\"ua_notification_button_shop_now\" = \"现在就选购吧！\";\n\"ua_notification_button_tell_me_more\" = \"告诉我更多\";\n\"ua_notification_button_unfollow\" = \"取消关注\";\n\"ua_notification_button_yes\" = \"是\";\n\"ua_ok\" = \"好\";\n\"ua_preference_center_title\" = \"偏好中心\";\n\"ua_retry_button\" = \"重试\";\n\"ua_select_all_messages\" = \"全选\";\n\"ua_select_all_messages_description\" = \"选择所有消息\";\n\"ua_select_none_messages\" = \"全不选\";\n\"ua_select_none_messages_description\" = \"重置消息选择\";\n\"ua_unread_message_description\" = \"未读消息\";\n\n// Generic localizations\n\"ua_dismiss\" = \"解雇\";\n\"ua_escape\" = \"逃脱\";\n\"ua_next\" = \"下一个\";\n\"ua_previous\" = \"以前\";\n\"ua_submit\" = \"提交\";\n\"ua_loading\" = \"加载中\";\n\"ua_pager_progress\" = \"第%@页，共%@页\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"播放\";\n\"ua_pause\" = \"暂停\";\n\"ua_stop\" = \"停止\";\n\"ua_form_processing_error\" = \"处理表单时出错。请重试\";\n\"ua_close\" = \"关闭\";\n\"ua_mute\" = \"静音\";\n\"ua_unmute\" = \"取消静音\";\n\"ua_required_field\" = \"* 必填\";\n\"ua_invalid_form_message\" = \"请修正无效字段以继续\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/zh-Hant.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"取消\";\n\"ua_cancel_edit_messages_description\" = \"取消消息編輯\";\n\"ua_connection_error\" = \"連接錯誤\";\n\"ua_content_error\" = \"內容錯誤\";\n\"ua_delete_message\" = \"刪除消息\";\n\"ua_delete_message_description\" = \"刪除選定的消息\";\n\"ua_delete_messages\" = \"刪除消息\";\n\"ua_delete_messages_description\" = \"刪除選定的消息\";\n\"ua_edit_messages\" = \"編輯\";\n\"ua_edit_messages_description\" = \"編輯消息\";\n\"ua_empty_message_list\" = \"無訊息\";\n\"ua_mark_messages_read\" = \"標記已讀\";\n\"ua_mark_messages_read_description\" = \"將所選消息標記為已讀\";\n\"ua_mc_failed_to_load\" = \"無法加載訊息。請稍後再試。\";\n\"ua_mc_no_longer_available\" = \"所選消息不再可用。\";\n\"ua_message_cell_description\" = \"顯示完整消息\";\n\"ua_message_cell_editing_description\" = \"切換選擇\";\n\"ua_message_center_title\" = \"訊息中心\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"消息 %@，發送於 %@\";\n\"ua_message_not_selected\" = \"未選擇任何消息\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"未讀消息 %@，發送於 %@\";\n\"ua_notification_button_accept\" = \"接受\";\n\"ua_notification_button_add\" = \"添加\";\n\"ua_notification_button_add_to_calendar\" = \"添加到日曆\";\n\"ua_notification_button_book_now\" = \"現在預訂\";\n\"ua_notification_button_buy_now\" = \"即刻下單\";\n\"ua_notification_button_copy\" = \"複製\";\n\"ua_notification_button_decline\" = \"拒絕\";\n\"ua_notification_button_dislike\" = \"不喜歡\";\n\"ua_notification_button_download\" = \"下載\";\n\"ua_notification_button_follow\" = \"關注\";\n\"ua_notification_button_less_like\" = \"更少類似\";\n\"ua_notification_button_like\" = \"喜歡\";\n\"ua_notification_button_more_like\" = \"更多類似\";\n\"ua_notification_button_no\" = \"否\";\n\"ua_notification_button_opt_in\" = \"參加\";\n\"ua_notification_button_opt_out\" = \"不參加\";\n\"ua_notification_button_rate_now\" = \"現在就評價吧\";\n\"ua_notification_button_remind\" = \"稍後提醒我\";\n\"ua_notification_button_save\" = \"保存\";\n\"ua_notification_button_search\" = \"搜索\";\n\"ua_notification_button_send_info\" = \"發送信息\";\n\"ua_notification_button_share\" = \"分享\";\n\"ua_notification_button_shop_now\" = \"即刻購買\";\n\"ua_notification_button_tell_me_more\" = \"告訴我更多\";\n\"ua_notification_button_unfollow\" = \"取消關注\";\n\"ua_notification_button_yes\" = \"是\";\n\"ua_ok\" = \"好\";\n\"ua_preference_center_title\" = \"偏好中心\";\n\"ua_retry_button\" = \"重試\";\n\"ua_select_all_messages\" = \"全選\";\n\"ua_select_all_messages_description\" = \"選擇所有消息\";\n\"ua_select_none_messages\" = \"全不選\";\n\"ua_select_none_messages_description\" = \"重置消息選擇\";\n\"ua_unread_message_description\" = \"留言未讀\";\n\n// Generic localizations\n\"ua_dismiss\" = \"解除\";\n\"ua_escape\" = \"逃離\";\n\"ua_next\" = \"下一個\";\n\"ua_previous\" = \"上一個\";\n\"ua_submit\" = \"提交\";\n\"ua_loading\" = \"正在載入\";\n\"ua_pager_progress\" = \"第%@頁，共%@頁\";\n\"ua_x_of_y\" = \"%@ / %@\";\n\"ua_play\" = \"播放\";\n\"ua_pause\" = \"暫停\";\n\"ua_stop\" = \"停止\";\n\"ua_form_processing_error\" = \"處理表單時出錯。請重試\";\n\"ua_close\" = \"關閉\";\n\"ua_mute\" = \"靜音\";\n\"ua_unmute\" = \"取消靜音\";\n\"ua_required_field\" = \"* 必填\";\n\"ua_invalid_form_message\" = \"請修正無效欄位以繼續\";\n"
  },
  {
    "path": "Airship/AirshipCore/Resources/zu.lproj/UrbanAirship.strings",
    "content": "\"ua_cancel_edit_messages\" = \"Khansela\";\n\"ua_cancel_edit_messages_description\" = \"Khansela ukuhlelwa komlayezo\";\n\"ua_connection_error\" = \"Iphutha Lokuxhuma\";\n\"ua_content_error\" = \"Iphutha Lokuqukethwe\";\n\"ua_delete_message\" = \"Susa umlayezo\";\n\"ua_delete_message_description\" = \"Susa imilayezo ekhethiwe\";\n\"ua_delete_messages\" = \"Susa umlayezo\";\n\"ua_delete_messages_description\" = \"Susa imilayezo ekhethiwe\";\n\"ua_edit_messages\" = \"Hlela\";\n\"ua_edit_messages_description\" = \"Hlela imilayezo\";\n\"ua_empty_message_list\" = \"Ayikho imilayezo\";\n\"ua_mark_messages_read\" = \"Maka Funda\";\n\"ua_mark_messages_read_description\" = \"Maka imilayezo ekhethiwe njengefundiwe\";\n\"ua_mc_failed_to_load\" = \"Ayikwazi ukulayisha umlayezo. Sicela uzame futhi emuva kwesikhathi.\";\n\"ua_mc_no_longer_available\" = \"Umlayezo okhethiwe awusatholakali.\";\n\"ua_message_cell_description\" = \"Ibonisa umlayezo ogcwele\";\n\"ua_message_cell_editing_description\" = \"Iguqula ukukhetha\";\n\"ua_message_center_title\" = \"Isikhungo Somlayezo\";\n/* Message <TITLE>, sent at <DATE> */\n\"ua_message_description\" = \"Umlayezo %@, uthunyelwe ngo-%@\";\n\"ua_message_not_selected\" = \"Ayikho imilayezo ekhethiwe\";\n/* Unread message <TITLE>, sent at <DATE> */\n\"ua_message_unread_description\" = \"Umlayezo ongafundiwe %@, uthunyelwe ngo-%@\";\n\"ua_notification_button_accept\" = \"Yamukela\";\n\"ua_notification_button_add\" = \"Engeza\";\n\"ua_notification_button_add_to_calendar\" = \"Engeza kukhalenda\";\n\"ua_notification_button_book_now\" = \"Bhukha Manje\";\n\"ua_notification_button_buy_now\" = \"Thenga Manje\";\n\"ua_notification_button_copy\" = \"Kopisha\";\n\"ua_notification_button_decline\" = \"Yenqaba\";\n\"ua_notification_button_dislike\" = \"Ukungathandi\";\n\"ua_notification_button_download\" = \"Landa\";\n\"ua_notification_button_follow\" = \"Landela\";\n\"ua_notification_button_less_like\" = \"Kancane Kanje\";\n\"ua_notification_button_like\" = \"Thanda\";\n\"ua_notification_button_more_like\" = \"Okuningi Okufana Nalokhu\";\n\"ua_notification_button_no\" = \"Cha\";\n\"ua_notification_button_opt_in\" = \"Khetha ukungena\";\n\"ua_notification_button_opt_out\" = \"Phuma\";\n\"ua_notification_button_rate_now\" = \"Linganisa Manje\";\n\"ua_notification_button_remind\" = \"Ngikhumbuze ngokuhamba kwesikhathi\";\n\"ua_notification_button_save\" = \"Londoloza\";\n\"ua_notification_button_search\" = \"Sesha\";\n\"ua_notification_button_send_info\" = \"Thumela Ulwazi\";\n\"ua_notification_button_share\" = \"Yabelana\";\n\"ua_notification_button_shop_now\" = \"Thenga Manje\";\n\"ua_notification_button_tell_me_more\" = \"Ngitshele Okuningi\";\n\"ua_notification_button_unfollow\" = \"Yekela ukulandela\";\n\"ua_notification_button_yes\" = \"Yebo\";\n\"ua_ok\" = \"KULUNGILE\";\n\"ua_preference_center_title\" = \"Isikhungo Esithandwayo\";\n\"ua_retry_button\" = \"Zama futhi\";\n\"ua_select_all_messages\" = \"Khetha konke\";\n\"ua_select_all_messages_description\" = \"Ikhetha yonke imilayezo\";\n\"ua_select_none_messages\" = \"Khetha Lutho\";\n\"ua_select_none_messages_description\" = \"Setha kabusha ukukhetha komlayezo\";\n\"ua_unread_message_description\" = \"Umlayezo awufundiwe\";\n\n// Generic localizations\n\"ua_dismiss\" = \"Khipha\";\n\"ua_escape\" = \"Phuma\";\n\"ua_next\" = \"Okulandelayo\";\n\"ua_previous\" = \"Okwangaphambili\";\n\"ua_submit\" = \"Faka\";\n\"ua_loading\" = \"Iyalayisha\";\n\"ua_pager_progress\" = \"Ikhasi %@ ye-%@\";\n\"ua_x_of_y\" = \"%@ kw %@\";\n\"ua_play\" = \"Dlala\";\n\"ua_pause\" = \"Misa\";\n\"ua_stop\" = \"Yeka\";\n\"ua_form_processing_error\" = \"Iphutha ekucubunguleni ifomu. Sicela uzame futhi\";\n\"ua_close\" = \"Vala\";\n\"ua_mute\" = \"Thulisa\";\n\"ua_unmute\" = \"Susa ukuthula\";\n\"ua_required_field\" = \"* Kuyadingeka\";\n\"ua_invalid_form_message\" = \"Sicela ulungise izinkambu ezingavumelekile ukuze uqhubeke\";\n"
  },
  {
    "path": "Airship/AirshipCore/Source/APNSEnvironment.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct APNSEnvironment {\n\n#if !targetEnvironment(macCatalyst)\n    private static let defaultProfilePath: String? = Bundle.main.path(\n            forResource: \"embedded\",\n            ofType: \"mobileprovision\"\n        )\n#else\n    private static let defaultProfilePath: String? = URL(\n            fileURLWithPath: URL(\n                fileURLWithPath: Bundle.main.resourcePath ?? \"\"\n            )\n            .deletingLastPathComponent().path\n        )\n        .appendingPathComponent(\"embedded.provisionprofile\").path\n#endif\n\n    public static func isProduction() throws -> Bool {\n        return try isProduction(self.defaultProfilePath)\n    }\n\n    public static func isProduction(\n        _ profilePath: String?\n    ) throws -> Bool {\n        guard\n            let path = profilePath,\n            let embeddedProfile: String = try? String(\n                contentsOfFile: path,\n                encoding: .isoLatin1\n            )\n        else {\n            throw AirshipErrors.error(\"No mobile provisioning profile found \\(profilePath ?? \"null\")\")\n        }\n\n        let scanner = Scanner(string: embeddedProfile)\n\n        _ = scanner.scanUpToString(\"<?xml version=\\\"1.0\\\" encoding=\\\"UTF-8\\\"?>\")\n\n        guard let extractedPlist = scanner.scanUpToString(\"</plist>\"),\n            let plistData = extractedPlist.appending(\"</plist>\")\n                .data(using: .utf8),\n            let plistDict = try? PropertyListSerialization.propertyList(\n                from: plistData,\n                options: [],\n                format: nil\n            ) as? [AnyHashable: Any]\n        else {\n            throw AirshipErrors.error(\"Unable to read provisioning profile \\(path)\")\n        }\n\n        guard\n            let entitlements = plistDict[\"Entitlements\"] as? [AnyHashable: Any]\n        else {\n            throw AirshipErrors.error(\"Unable to read provisioning profile \\(path). No entitlements.\")\n        }\n\n        guard let apsEnvironment = entitlements[\"aps-environment\"] as? String else  {\n            throw AirshipErrors.error(\"aps-environment value is not set \\(path), ensure that the app is properly provisioned for push.\")\n        }\n\n        switch(apsEnvironment) {\n        case \"production\": return true\n        case \"development\": return false\n        default: throw AirshipErrors.error(\"Unexpected aps-environment \\(apsEnvironment)\")\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/APNSRegistrar.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n\n@MainActor\nprotocol APNSRegistrar: Sendable {\n    var isRegisteredForRemoteNotifications: Bool { get }\n    func registerForRemoteNotifications()\n    var isRemoteNotificationBackgroundModeEnabled: Bool { get }\n    var isBackgroundRefreshStatusAvailable: Bool { get }\n}\n\n#if os(watchOS)\nimport WatchKit\n\nfinal class DefaultAPNSRegistrar: APNSRegistrar {\n    var isRegisteredForRemoteNotifications: Bool {\n        WKExtension.shared().isRegisteredForRemoteNotifications\n    }\n\n    func registerForRemoteNotifications() {\n        WKExtension.shared().registerForRemoteNotifications()\n    }\n\n    var isRemoteNotificationBackgroundModeEnabled: Bool {\n        return true\n    }\n\n    var isBackgroundRefreshStatusAvailable: Bool {\n        return true\n    }\n}\n\n\n#elseif os(macOS)\nimport AppKit\n\nfinal class DefaultAPNSRegistrar: APNSRegistrar {\n    var isRegisteredForRemoteNotifications: Bool {\n        return NSApplication.shared.isRegisteredForRemoteNotifications\n    }\n\n    func registerForRemoteNotifications() {\n        NSApplication.shared.registerForRemoteNotifications()\n    }\n\n    var isRemoteNotificationBackgroundModeEnabled: Bool {\n        return true\n    }\n\n    var isBackgroundRefreshStatusAvailable: Bool {\n        return true\n    }\n}\n\n#elseif canImport(UIKit)\nimport UIKit\n\nfinal class DefaultAPNSRegistrar: APNSRegistrar {\n    var isRegisteredForRemoteNotifications: Bool {\n        return UIApplication.shared.isRegisteredForRemoteNotifications\n    }\n\n    func registerForRemoteNotifications() {\n        UIApplication.shared.registerForRemoteNotifications()\n    }\n\n    static var _isRemoteNotificationBackgroundModeEnabled: Bool {\n        let backgroundModes =\n        Bundle.main.object(forInfoDictionaryKey: \"UIBackgroundModes\")\n        as? [Any]\n        return backgroundModes?\n            .contains(where: {\n                ($0 as? String) == \"remote-notification\"\n            }) == true\n    }\n\n    var isRemoteNotificationBackgroundModeEnabled: Bool {\n        return Self._isRemoteNotificationBackgroundModeEnabled\n    }\n\n    var isBackgroundRefreshStatusAvailable: Bool {\n#if os(tvOS)\n        return true\n#else\n        // This covers iOS and iPadOS\n        return UIApplication.shared.backgroundRefreshStatus == .available\n#endif\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/APNSRegistrationResult.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n\n/// The result of an APNs registration.\npublic enum APNSRegistrationResult: Sendable {\n    /// Registration was successful and a new device token was received.\n    case success(deviceToken: String)\n\n    /// Registration failed.\n    case failure(error: any Error)\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AccountEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\npublic extension CustomEvent {\n\n    /// Account template\n    enum AccountTemplate: Sendable {\n        /// Account registered\n        case registered\n\n        /// User logged in\n        case loggedIn\n\n        /// User logged out\n        case loggedOut\n\n        fileprivate static let templateName: String = \"account\"\n\n        fileprivate var eventName: String {\n            return switch self {\n            case .registered: \"registered_account\"\n            case .loggedIn: \"logged_in\"\n            case .loggedOut: \"logged_out\"\n            }\n        }\n    }\n\n    /// Additional acount template properties\n    struct AccountProperties: Encodable, Sendable {\n\n        /// User ID.\n        public var userID: String?\n\n        /// The event's category.\n        public var category: String?\n\n        /// The event's type.\n        public var type: String?\n\n        /// If the value is a lifetime value or not.\n        public var isLTV: Bool\n\n        public init(\n            category: String? = nil,\n            type: String? = nil,\n            isLTV: Bool = false,\n            userID: String? = nil\n        ) {\n            self.userID = userID\n            self.category = category\n            self.type = type\n            self.isLTV = isLTV\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case userID = \"user_id\"\n            case category\n            case type\n            case isLTV = \"ltv\"\n        }\n    }\n\n    /// Constructs a custom event using the account template.\n    /// - Parameters:\n    ///     - accountTemplate: The account template.\n    ///     - properties: Optional additional properties\n    ///     - encoder: Encoder used to encode the additional properties. Defaults to `CustomEvent.defaultEncoder`.\n    init(\n        accountTemplate: AccountTemplate,\n        properties: AccountProperties = AccountProperties(),\n        encoder: @autoclosure () -> JSONEncoder = CustomEvent.defaultEncoder()\n    ) {\n        self = .init(name: accountTemplate.eventName)\n        self.templateType = AccountTemplate.templateName\n\n        do {\n            try self.setProperties(properties, encoder: encoder())\n        } catch {\n            /// Should never happen so we are just catching the exception and logging\n            AirshipLogger.error(\"Failed to generate event \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ActionArguments.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Action situations\npublic enum ActionSituation: Int, Sendable {\n    /// Action invoked manually\n    case manualInvocation\n    /// Action invoked from the app being launched from a push notification\n    case launchedFromPush\n    /// Action invoked from a foreground push\n    case foregroundPush\n    /// Action invoked from a background push\n    case backgroundPush\n    /// Action invoked from a web view\n    case webViewInvocation\n    /// Action invoked from a foreground action button\n    case foregroundInteractiveButton\n    /// Action invoked from a background action button\n    case backgroundInteractiveButton\n    /// Action invoked from an automation\n    case automation\n}\n\n/// Contains the arguments passed into an action during execution.\npublic struct ActionArguments: Sendable {\n\n    /// Metadata key for the user notification action identifier. Available when an action is triggered from a\n    /// user notification action. The ID will be a String.\n    public static let userNotificationActionIDMetadataKey: String = \"com.urbanairship.user_notification_action_id\"\n\n    /// Metadata key for the push notification. Available when an action is triggered\n    /// from a push notification or user notification action. The payload will be an `AirshipJSON`.\n    public static let pushPayloadJSONMetadataKey: String = \"com.urbanairship.payload\"\n    public static let isForegroundPresentationMetadataKey: String = \"com.urbanairship.is_foreground_presentation\"\n\n    /// Metadata key for the inbox message's identifier. Available when an action is triggered from an\n    /// inbox message. The ID will be a String.\n    public static let inboxMessageIDMetadataKey: String = \"com.urbanairship.messageID\"\n\n\n    /// Metadata key for the user notification action response info text. Available when an action is triggered\n    /// from a user notification action with the behavior `UIUserNotificationActionBehaviorTextInput`.\n    public static let responseInfoMetadataKey: String = \"com.urbanairship.response_info\"\n\n    /// The action argument's value\n    public var value: AirshipJSON\n\n    /// The action argument's situation\n    public var situation: ActionSituation\n\n    /// The action argument's metadata\n    public var metadata: [String: any Sendable]\n\n    public init(\n        string: String,\n        situation: ActionSituation = .manualInvocation,\n        metadata: [String : any Sendable] = [:]\n    ) {\n        self.value = .string(string)\n        self.situation = situation\n        self.metadata = metadata\n    }\n\n    public init(\n        double: Double,\n        situation: ActionSituation = .manualInvocation,\n        metadata: [String : any Sendable] = [:]\n    ) {\n        self.value = AirshipJSON.number(double)\n        self.situation = situation\n        self.metadata = metadata\n    }\n\n    public init(\n        bool: Bool,\n        situation: ActionSituation = .manualInvocation,\n        metadata: [String : any Sendable] = [:]\n    ) {\n        self.value = .bool(bool)\n        self.situation = situation\n        self.metadata = metadata\n    }\n\n    public init(\n        value: AirshipJSON = AirshipJSON.null,\n        situation: ActionSituation = .manualInvocation,\n        metadata: [String : any Sendable] = [:]\n    ) {\n        self.value = value\n        self.situation = situation\n        self.metadata = metadata\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ActionRegistry.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// This protocol is responsible for runtime-persisting actions and associating\n/// them with names and predicates.\n@MainActor\npublic protocol AirshipActionRegistry: Sendable {\n\n    func registerEntry(\n        names: [String],\n        entry:  @escaping () -> ActionEntry\n    )\n\n    func registerEntry(\n        names: [String],\n        entry:  ActionEntry\n    )\n\n    @discardableResult\n    func removeEntry(name: String) -> Bool\n\n    @discardableResult\n    func updateEntry(name: String, action: any AirshipAction) -> Bool\n\n    @discardableResult\n    func updateEntry(name: String, predicate: (@Sendable (ActionArguments) async -> Bool)?) -> Bool\n\n    @discardableResult\n    func updateEntry(name: String, situation: ActionSituation, action: any AirshipAction) -> Bool\n\n    func entry(name: String) -> ActionEntry?\n\n    func registerActions(actionsManifests: [any ActionsManifest])\n}\n\n\n@MainActor\npublic class DefaultAirshipActionRegistry: AirshipActionRegistry {\n    private var entries: [String: EntryHolder] = [:]\n\n\n    public func registerEntry(\n        names: [String],\n        entry:  @escaping () -> ActionEntry\n    ) {\n        let entryHolder = EntryHolder(entryBlock: entry)\n        names.forEach { name in\n            entries[name] = entryHolder\n        }\n    }\n\n    public func registerEntry(\n        names: [String],\n        entry:  ActionEntry\n    ) {\n        let entryHolder = EntryHolder(entryBlock: { entry })\n        names.forEach { name in\n            entries[name] = entryHolder\n        }\n    }\n\n    @discardableResult\n    public func removeEntry(name: String) -> Bool {\n        guard let entryHolder = entries[name] else { return false }\n        entries.compactMap { (key, value) in\n            if (entryHolder === value) {\n                return key\n            }\n            return nil\n        }.forEach { name in\n            entries[name] = nil\n        }\n\n        return true\n    }\n\n    @discardableResult\n    public func updateEntry(name: String, action: any AirshipAction) -> Bool {\n        guard let entryHolder = entries[name] else { return false }\n        entryHolder.entry.action = action\n        return true\n    }\n\n    @discardableResult\n    public func updateEntry(name: String, predicate: (@Sendable (ActionArguments) async -> Bool)?) -> Bool {\n        guard let entryHolder = entries[name] else { return false }\n        entryHolder.entry.predicate = predicate\n        return true\n    }\n\n    @discardableResult\n    public func updateEntry(name: String, situation: ActionSituation, action: any AirshipAction) -> Bool {\n        guard let entryHolder = entries[name] else { return false }\n        entryHolder.entry.situationOverrides[situation] = action\n        return true\n    }\n\n    public func entry(name: String) -> ActionEntry? {\n        return self.entries[name]?.entry\n    }\n\n    public func registerActions(actionsManifests: [any ActionsManifest]) {\n        actionsManifests.forEach { actionsManifest in\n            actionsManifest.manifest.forEach { (names, entry) in\n                registerEntry(names: names, entry: entry)\n            }\n        }\n    }\n}\n\n/// Action registry entry\npublic struct ActionEntry: Sendable {\n    var situationOverrides: [ActionSituation: any AirshipAction] = [:]\n    var action: any AirshipAction\n    var predicate: (@Sendable (ActionArguments) async -> Bool)?\n\n    public init(\n        action: any AirshipAction,\n        situationOverrides: [ActionSituation: any AirshipAction] = [:],\n        predicate: (@Sendable (ActionArguments) async -> Bool)? = nil\n    ) {\n        self.action = action\n        self.predicate = predicate\n    }\n\n    func action(situation: ActionSituation) -> any AirshipAction {\n        return situationOverrides[situation] ?? action\n    }\n}\n\n\nfileprivate class EntryHolder {\n    private var _entry: ActionEntry?\n    var entry: ActionEntry {\n        get {\n            if let entry = _entry {\n                return entry\n            }\n            let resolved = entryBlock()\n            _entry = resolved\n            return resolved\n        }\n        set {\n            _entry = newValue\n        }\n    }\n\n    private let entryBlock:  () -> ActionEntry\n    init(entryBlock: @escaping () -> ActionEntry) {\n        self.entryBlock = entryBlock\n    }\n    \n}\n\n/// Airship action manifest.\n/// - Note: for internal use only.  :nodoc:\npublic protocol ActionsManifest {\n    var manifest: [[String]: () -> ActionEntry]  { get }\n}\n\nstruct DefaultActionsManifest: ActionsManifest {\n    let manifest:  [[String]: () -> ActionEntry] = {\n        var entries: [[String]: () -> ActionEntry] = [\n            OpenExternalURLAction.defaultNames: {\n                return ActionEntry(\n                    action: OpenExternalURLAction(),\n                    predicate: OpenExternalURLAction.defaultPredicate\n                )\n            },\n\n            AddTagsAction.defaultNames: {\n                return ActionEntry(\n                    action: AddTagsAction(),\n                    predicate: AddTagsAction.defaultPredicate\n                )\n            },\n\n            RemoveTagsAction.defaultNames: {\n                return ActionEntry(\n                    action: RemoveTagsAction(),\n                    predicate: RemoveTagsAction.defaultPredicate\n                )\n            },\n            \n            ModifyTagsAction.defaultNames: {\n                return ActionEntry(\n                    action: ModifyTagsAction(),\n                    predicate: ModifyTagsAction.defaultPredicate\n                )\n            },\n\n            DeepLinkAction.defaultNames: {\n                return ActionEntry(\n                    action: DeepLinkAction(),\n                    predicate: DeepLinkAction.defaultPredicate\n                )\n            },\n\n            AddCustomEventAction.defaultNames: {\n                return ActionEntry(\n                    action: AddCustomEventAction(),\n                    predicate: AddCustomEventAction.defaultPredicate\n                )\n            },\n\n            FetchDeviceInfoAction.defaultNames: {\n                return ActionEntry(\n                    action: FetchDeviceInfoAction(),\n                    predicate: FetchDeviceInfoAction.defaultPredicate\n                )\n            },\n\n            EnableFeatureAction.defaultNames: {\n                return ActionEntry(\n                    action: EnableFeatureAction(),\n                    predicate: EnableFeatureAction.defaultPredicate\n                )\n            },\n\n            ModifyAttributesAction.defaultNames: {\n                return ActionEntry(\n                    action: ModifyAttributesAction(),\n                    predicate: ModifyAttributesAction.defaultPredicate\n                )\n            },\n\n            SubscriptionListAction.defaultNames: {\n                return ActionEntry(\n                    action: SubscriptionListAction(),\n                    predicate: SubscriptionListAction.defaultPredicate\n                )\n            },\n\n            PromptPermissionAction.defaultNames: {\n                return ActionEntry(\n                    action: PromptPermissionAction(),\n                    predicate: PromptPermissionAction.defaultPredicate\n                )\n            }\n        ]\n\n        #if os(iOS) || os(visionOS)\n        entries[RateAppAction.defaultNames] = {\n            return ActionEntry(\n                action: RateAppAction(),\n                predicate: RateAppAction.defaultPredicate\n            )\n        }\n\n        entries[PasteboardAction.defaultNames] = {\n            return ActionEntry(\n                action: PasteboardAction()\n            )\n        }\n        #endif\n\n\n        #if os(iOS)\n        entries[ShareAction.defaultNames] = {\n            return ActionEntry(\n                action: ShareAction(),\n                predicate: ShareAction.defaultPredicate\n            )\n        }\n        #endif\n\n        return entries\n    }()\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ActionResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Action result\npublic enum ActionResult: Sendable {\n    /// Action ran and produced a result\n    case completed(AirshipJSON)\n    /// Action ran with an error\n    case error(any Error)\n    ///  Arguments rejected either by the action or predicate\n    case argumentsRejected\n    /// Action not found\n    case actionNotFound\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ActionRunner.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// A helper class for running actions by name or by reference.\npublic final class ActionRunner {\n\n    /// Runs an action\n    /// - Parameters:\n    ///     - action: The action to run\n    ///     - arguments: The action's arguments\n    /// - Returns An action result\n    public class func run(\n        action: any AirshipAction,\n        arguments: ActionArguments\n    ) async -> ActionResult {\n        guard await action.accepts(arguments: arguments) else {\n            AirshipLogger.debug(\n                \"Action \\(action) rejected arguments \\(arguments).\"\n            )\n            return .argumentsRejected\n        }\n\n        do {\n            let result = try await action.perform(arguments: arguments) ?? .null\n            AirshipLogger.debug(\n                \"Action \\(action) finished with result \\(result), argument: \\(arguments).\"\n            )\n            return .completed(result)\n        } catch {\n            AirshipLogger.debug(\n                \"Action \\(action) finished with error \\(error), argument: \\(arguments).\"\n            )\n            return .error(error)\n        }\n    }\n\n    /// Runs an action\n    /// - Parameters:\n    ///     - actionName: The name of the action\n    ///     - arguments: The action's arguments\n    /// - Returns An action result\n    public class func run(\n        actionName: String,\n        arguments: ActionArguments\n    ) async -> ActionResult {\n        guard\n            let entry = await Airship.actionRegistry.entry(name: actionName)\n        else {\n            return .actionNotFound\n        }\n\n        guard await entry.predicate?(arguments) != false else {\n            AirshipLogger.debug(\n                \"Action \\(actionName) predicate rejected argument: \\(arguments).\"\n            )\n            return .argumentsRejected\n        }\n\n        let action: any AirshipAction = entry.action(situation: arguments.situation)\n\n        return await self.run(\n            action: action,\n            arguments: arguments\n        )\n    }\n\n    /// Runs an action\n    /// - Parameters:\n    ///     - actionsPayload: A map of action name to action value.\n    ///     - situation: The action's situation\n    ///     - metadata: The action's metadata\n    /// - Returns A map of action name to action result\n    @discardableResult\n    public class func run(\n        actionsPayload: AirshipJSON,\n        situation: ActionSituation,\n        metadata: [String: any Sendable]\n    ) async -> [String: ActionResult] {\n        guard case .object(let payload) = actionsPayload else {\n            AirshipLogger.error(\"Invalid actions payload: \\(actionsPayload)\")\n            return [:]\n        }\n\n        var results: [String: ActionResult] = [:]\n        for (key, value) in payload {\n            results[key] = await run(\n                actionName: key,\n                arguments: ActionArguments(\n                    value: value,\n                    situation: situation,\n                    metadata: metadata\n                )\n            )\n        }\n\n        return results\n    }\n\n    public class func _run(\n        actionsPayload: [String: Any],\n        situation: ActionSituation\n    ) async {\n        guard let value = try? AirshipJSON.wrap(actionsPayload) else {\n            AirshipLogger.error(\"Invalid actions payload: \\(actionsPayload)\")\n            return\n        }\n        await run(\n            actionsPayload: value,\n            situation: situation,\n            metadata: [:]\n        )\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ActivityViewController.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if os(iOS)\nimport UIKit\n\nfinal class ActivityViewController: UIActivityViewController,\n    UIPopoverPresentationControllerDelegate, UIPopoverControllerDelegate\n{\n\n    @objc\n    public var dismissalBlock: (() -> Void)?\n\n    public override func viewDidDisappear(_ animated: Bool) {\n        super.viewDidDisappear(animated)\n        dismissalBlock?()\n    }\n\n    /**\n     * Returns the desired source rect dimensions for the popover.\n     * - Returns: popover dimensions.\n     */\n    @objc\n    public func sourceRect() -> CGRect {\n        let windowBounds = try? AirshipUtils.mainWindow()?.bounds\n\n        // Return a smaller rectangle by 25% on each axis, producing a 50% smaller rectangle inset.\n        return windowBounds?\n            .insetBy(\n                dx: (windowBounds?.width ?? 0.0) / 4.0,\n                dy: (windowBounds?.height ?? 0.0) / 4.0\n            ) ?? CGRect.zero\n    }\n\n    public func popoverPresentationController(\n        _ popoverPresentationController: UIPopoverPresentationController,\n        willRepositionPopoverTo rect: UnsafeMutablePointer<CGRect>,\n        in view: AutoreleasingUnsafeMutablePointer<UIView>\n    ) {\n        rect.pointee = sourceRect()\n    }\n    \n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AddCustomEventAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// An action that adds a custom event.\n///\n/// Expected argument values: A dictionary of keys for the custom event. When a\n/// custom event action is triggered from a Message Center Rich Push Message,\n/// the interaction type and ID will automatically be filled for the message if\n/// they are left blank.\n///\n/// Valid situations: All.\n///\n/// Result value: nil\npublic final class AddCustomEventAction: AirshipAction {\n    private static let eventNameKey: String = \"name\"\n    private static let eventValue: String = \"value\"\n\n    /// Default names - \"add_custom_event_action\", \"^+e\"\n    public static let defaultNames: [String] = [\"add_custom_event_action\", \"^+e\"]\n    \n    /// Default predicate - rejects foreground pushes with visible display options and `ActionSituation.backgroundPush`\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        if (args.situation == .backgroundPush) {\n            return false\n        }\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n\n    /// Metadata key for in-app context.\n    /// - Note: For internal use only. :nodoc:\n    public static let _inAppMetadata: String = \"in_app_metadata\"\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        return true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        guard\n            let dict = arguments.value.unWrap() as? [AnyHashable: Any],\n            let eventName = getEventName(dict)\n        else {\n            throw AirshipErrors\n                .error(\"Invalid custom event argument: \\(arguments.value)\")\n        }\n\n        let eventValue = getEventValue(dict)\n        let interactionID = parseString(\n            dict,\n            key: CustomEvent.eventInteractionIDKey\n        )\n        let interactionType = parseString(\n            dict,\n            key: CustomEvent.eventInteractionTypeKey\n        )\n        let transactionID = parseString(\n            dict,\n            key: CustomEvent.eventTransactionIDKey\n        )\n        let properties = dict[CustomEvent.eventPropertiesKey] as? [String: Any]\n\n        var event = CustomEvent(name: eventName, value: eventValue ?? 1.0)\n\n        if let inApp = arguments.metadata[Self._inAppMetadata] {\n            do {\n                event.inApp = try AirshipJSON.wrap(inApp)\n            } catch {\n                AirshipLogger.error(\"Failed to encode in-app info for custom event: \\(inApp), error: \\(error)\")\n            }\n        }\n\n        event.transactionID = transactionID\n        if let properties {\n            try event.setProperties(properties)\n        }\n\n        if interactionID != nil || interactionType != nil {\n            event.interactionType = interactionType\n            event.interactionID = interactionID\n        } else if let messageID =\n                    arguments.metadata[ActionArguments.inboxMessageIDMetadataKey] as? String\n        {\n            event.setInteractionFromMessageCenterMessage(messageID)\n        }\n\n        if\n            let json = arguments.metadata[ActionArguments.pushPayloadJSONMetadataKey] as? AirshipJSON,\n            let unwrapped = json.unWrap() as? [String: AnyHashable]\n        {\n            event.conversionSendID = unwrapped[\"_\"] as? String\n            event.conversionPushMetadata = unwrapped[\"com.urbanairship.metadata\"] as? String\n        }\n\n        guard event.isValid() else {\n            throw AirshipErrors.error(\"Invalid custom event: \\(arguments.value)\")\n        }\n\n        event.track()\n\n        return nil\n    }\n\n    func parseString(_ dict: [AnyHashable: Any], key: String) -> String? {\n        guard let value = dict[key] else {\n            return nil\n        }\n\n        guard value is String else {\n            return \"\\(value)\"\n        }\n        return value as? String\n    }\n\n    func parseDouble(_ dict: [AnyHashable: Any], key: String) -> Double? {\n        guard let value = dict[key] else {\n            return nil\n        }\n\n        guard let value = value as? Double else {\n            if let string = parseString(dict, key: key) {\n                return Double(string)\n            }\n            return nil\n        }\n        return value\n    }\n    \n    private func getEventName(_ dict: [AnyHashable: Any]) -> String? {\n        return parseString(dict, key: Self.eventNameKey)\n        ?? parseString(dict, key: CustomEvent.eventNameKey)\n    }\n    \n    private func getEventValue(_ dict: [AnyHashable: Any]) -> Double? {\n        return parseDouble(dict, key: Self.eventValue)\n        ?? parseDouble(dict, key: CustomEvent.eventValueKey)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AddTagsAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Adds tags.\n///\n/// Expected argument values: `String` (single tag), `[String]` (single or multiple tags), or an object.\n/// An example tag group JSON payload:\n/// {\n///     \"channel\": {\n///         \"channel_tag_group\": [\"channel_tag_1\", \"channel_tag_2\"],\n///         \"other_channel_tag_group\": [\"other_channel_tag_1\"]\n///     },\n///     \"named_user\": {\n///         \"named_user_tag_group\": [\"named_user_tag_1\", \"named_user_tag_2\"],\n///         \"other_named_user_tag_group\": [\"other_named_user_tag_1\"]\n///     },\n///     \"device\": [ \"tag\", \"another_tag\"]\n/// }\n///\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.backgroundInteractiveButton`, `ActionSituation.manualInvocation`, and\n/// `ActionSituation.automation`\npublic final class AddTagsAction: AirshipAction {\n\n    /// Default names - \"add_tags_action\", \"^+t\"\n    public static let defaultNames: [String] = [\"add_tags_action\", \"^+t\"]\n\n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n\n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n    \n    private let tagMutationsChannel: AirshipAsyncChannel<TagActionMutation> = AirshipAsyncChannel<TagActionMutation>()\n    \n    public var tagMutations: AsyncStream<TagActionMutation> {\n        get async {\n            return await tagMutationsChannel.makeStream()\n        }\n    }\n\n    \n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier()\n        )\n    }\n\n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact\n    ) {\n        self.channel = channel\n        self.contact = contact\n    }\n\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        guard arguments.situation != .backgroundPush else {\n            return false\n        }\n        return true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let unwrapped = arguments.value.unWrap()\n        if let tag = unwrapped as? String {\n            channel().editTags { editor in\n                editor.add(tag)\n            }\n            sendTagMutation(.channelTags([tag]))\n        } else if let tags = arguments.value.unWrap() as? [String] {\n            channel().editTags { editor in\n                editor.add(tags)\n            }\n            sendTagMutation(.channelTags(tags))\n        } else if let args: TagsActionsArgs = try arguments.value.decode() {\n            if let channelTagGroups = args.channel {\n                channel().editTagGroups { editor in\n                    channelTagGroups.forEach { group, tags in\n                        editor.add(tags, group: group)\n                    }\n                }\n                \n                sendTagMutation(.channelTagGroups(channelTagGroups))\n            }\n\n            if let contactTagGroups = args.namedUser {\n                contact().editTagGroups { editor in\n                    contactTagGroups.forEach { group, tags in\n                        editor.add(tags, group: group)\n                    }\n                }\n                sendTagMutation(.contactTagGroups(contactTagGroups))\n            }\n\n            if let deviceTags = args.device {\n                channel().editTags() { editor in\n                    editor.add(deviceTags)\n                }\n                sendTagMutation(.channelTags(deviceTags))\n            }\n        }\n        return nil\n    }\n    \n    private func sendTagMutation(_ mutation: TagActionMutation) {\n        Task { @MainActor in\n            await tagMutationsChannel.send(mutation)\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Airship.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AppKit) && os(macOS)\nimport AppKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n#if canImport(AirshipBasement)\n@_spi(AirshipInternal) import AirshipBasement\n#endif\n\n/// Main entry point for Airship. The application must call `takeOff` within `application(_:didFinishLaunchingWithOptions:)`\n/// before accessing any instances on Airship or Airship modules.\npublic final class Airship: Sendable {\n\n    /// Airship deep link scheme\n    /// - Note: For internal use only. :nodoc:\n    public static let deepLinkScheme: String = \"uairship\"\n\n    private static let appSettingsDeepLinkHost: String = \"app_settings\"\n\n    private static let appStoreDeepLinkHost: String = \"app_store\"\n\n    private static let itunesIDKey: String = \"itunesID\"\n\n    /// A flag that checks if the Airship instance is available. `true` if available, otherwise `false`.\n    public static var isFlying: Bool {\n        return Airship._shared != nil\n    }\n\n    private let _airshipInstanceHolder: AirshipAtomicValue<any AirshipInstance>\n    var airshipInstance: any AirshipInstance {\n        _airshipInstanceHolder.value\n    }\n\n    /// Airship config.\n    public static var config: RuntimeConfig { return shared.airshipInstance.config }\n\n    /// Action registry.\n    public static var actionRegistry: any AirshipActionRegistry {\n        return shared.airshipInstance.actionRegistry\n    }\n\n    /// The Airship permissions manager.\n    public static var permissionsManager: any AirshipPermissionsManager {\n        return shared.airshipInstance.permissionsManager\n    }\n\n#if !os(tvOS) && !os(watchOS)\n\n    /// A user configurable UAJavaScriptCommandDelegate\n    /// - NOTE: this delegate is not retained.\n    public static weak var javaScriptCommandDelegate: (any JavaScriptCommandDelegate)? {\n        get {\n            return shared.airshipInstance.javaScriptCommandDelegate\n        }\n        set {\n            shared._airshipInstanceHolder.value.javaScriptCommandDelegate = newValue\n        }\n    }\n\n    /// The channel capture utility.\n    public static var channelCapture: any AirshipChannelCapture {\n        return shared.airshipInstance.channelCapture\n    }\n#endif\n\n    /// A user configurable deep link delegate.\n    /// - NOTE: this delegate is not retained.\n    public static weak var deepLinkDelegate: (any DeepLinkDelegate)? {\n        get {\n            return shared.airshipInstance.deepLinkDelegate\n        }\n        set {\n            shared._airshipInstanceHolder.value.deepLinkDelegate = newValue\n        }\n    }\n\n    /// A user configurable deep link handler.\n    /// Takes precedence over `deepLinkDelegate` when set.\n    @MainActor\n    public static var onDeepLink: (@Sendable @MainActor (URL) async -> Void)? {\n        get {\n            return shared.airshipInstance.onDeepLink\n        }\n        set {\n            shared._airshipInstanceHolder.value.onDeepLink = newValue\n        }\n    }\n\n    /// The URL allow list used for validating URLs for landing pages,\n    /// wallet action, open external URL action, deep link\n    /// action (if delegate is not set), and HTML in-app messages.\n    public static var urlAllowList: any AirshipURLAllowList {\n        return shared.airshipInstance.urlAllowList\n    }\n\n    /// The locale manager.\n    public static var localeManager: any AirshipLocaleManager {\n        return shared.airshipInstance.localeManager\n    }\n\n    /// The privacy manager\n    public static var privacyManager: any AirshipPrivacyManager {\n        return shared.airshipInstance.privacyManager\n    }\n\n    static var inputValidator: any AirshipInputValidation.Validator {\n        return shared.airshipInstance.inputValidator\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    public var components: [any AirshipComponent] { return airshipInstance.components }\n\n    static let _sharedHolder: AirshipAtomicValue<Airship?> = AirshipAtomicValue<Airship?>(nil)\n    static var _shared: Airship? {\n        get { _sharedHolder.value }\n        set { _sharedHolder.value = newValue }\n    }\n\n    static var shared: Airship {\n        if !Airship.isFlying {\n            assertionFailure(\"TakeOff must be called before accessing Airship.\")\n        }\n        return _shared!\n    }\n\n    /// Shared Push instance.\n    public static var push: any AirshipPush {\n        return requireComponent(ofType: (any AirshipPush).self)\n    }\n\n    /// Shared Contact instance.\n    public static var contact: any AirshipContact {\n        return requireComponent(ofType: (any AirshipContact).self)\n    }\n\n    /// Shared Analytics instance.\n    public static var analytics: any AirshipAnalytics {\n        return requireComponent(ofType: (any AirshipAnalytics).self)\n    }\n\n    /// Shared Channel instance.\n    public static var channel: any AirshipChannel {\n        return requireComponent(ofType: (any AirshipChannel).self)\n    }\n\n    @MainActor\n    private static var onReadyCallbacks: [@MainActor @Sendable () -> Void] = []\n\n    init(instance: any AirshipInstance) {\n        self._airshipInstanceHolder = AirshipAtomicValue(instance)\n    }\n\n    /// Initializes Airship. If any errors are found with the config or if Airship is already intiialized it will throw with\n    /// the error.\n    /// - Parameters:\n    ///     - config: The Airship config. If nil, config will be loading from a plist.\n    @MainActor\n    public class func takeOff(\n        _ config: AirshipConfig? = nil\n    ) throws {\n        try commonTakeOff(config)\n    }\n\n#if !os(macOS) && !os(watchOS)\n    /// Initializes Airship. If any errors are found with the config or if Airship is already intiialized it will throw with\n    /// the error.\n    /// - Parameters:\n    ///     - config: The Airship config. If nil, config will be loading from a plist.\n    ///     - launchOptions: The launch options passed into `application:didFinishLaunchingWithOptions:`.\n    @MainActor\n    @available(*, deprecated, message: \"Use Airship.takeOff(_:) instead\")\n    public class func takeOff(\n        _ config: AirshipConfig? = nil,\n        launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n    ) throws {\n        try Self.takeOff(config)\n    }\n#endif\n\n\n    /// On ready callback gets called immediately when ready otherwise gets called immediately after takeoff\n    /// - Parameter callback: callback closure that's called when Airship is ready\n    @MainActor\n    public static func onReady(callback: @MainActor @Sendable @escaping () -> Void) {\n        onReadyCallbacks.append(callback)\n\n        if isFlying {\n            executeOnReady()\n        }\n    }\n\n    /// Helper method that executes any remaining onReady closures and resets the array\n    @MainActor\n    private static func executeOnReady() {\n        let toExecute = onReadyCallbacks\n        onReadyCallbacks.removeAll()\n        toExecute.forEach { $0() }\n    }\n\n\n    @MainActor\n    private class func configureLogger(_ config: AirshipConfig, inProduction: Bool) {\n        let handler = if let logHandler = config.logHandler {\n            logHandler\n        } else {\n            DefaultLogHandler(\n                privacyLevel: inProduction ? config.productionLogPrivacyLevel : config.developmentLogPrivacyLevel\n            )\n        }\n\n        AirshipLogger.configure(\n            logLevel: inProduction ? config.productionLogLevel : config.developmentLogLevel,\n            handler: handler\n        )\n    }\n\n    @MainActor\n    private class func commonTakeOff(_ config: AirshipConfig?, onReady: (() -> Void)? = nil) throws {\n        guard !Airship.isFlying else {\n            throw AirshipErrors.error(\"Airship already initalized. TakeOff can only be called once.\")\n        }\n\n        // Get the config\n        let resolvedConfig = try (config ?? AirshipConfig.default())\n\n        // Determine production flag and configure logger so we can log errors\n        var inProduction: Bool = true\n        do {\n            inProduction = try resolvedConfig.resolveInProduction()\n            configureLogger(resolvedConfig, inProduction: inProduction)\n        } catch {\n            configureLogger(resolvedConfig, inProduction: inProduction)\n            AirshipLogger.impError(\"Unable to determine AirshipConfig.inProduction \\(error), defaulting to true\")\n        }\n\n        let credentials = try resolvedConfig.resolveCredentails(inProduction)\n\n        // We have valid config, log issues\n        resolvedConfig.logIssues()\n\n        AirshipLogger.info(\n            \"Airship TakeOff! SDK Version \\(AirshipVersion.version), App Key: \\(credentials.appKey), inProduction: \\(inProduction)\"\n        )\n\n        ChallengeResolver.shared.resolver = resolvedConfig.connectionChallengeResolver\n\n        _shared = Airship(\n            instance: DefaultAirshipInstance(\n                airshipConfig: resolvedConfig,\n                appCredentials: credentials\n            )\n        )\n\n        let integrationDelegate = DefaultAppIntegrationDelegate(\n            push: requireComponent(ofType: (any InternalAirshipPush).self),\n            analytics: requireComponent(ofType: (any InternalAirshipAnalytics).self),\n            pushableComponents: _shared?.components.compactMap {\n                return $0 as? (any AirshipPushableComponent)\n            } ?? []\n        )\n\n        if resolvedConfig.isAutomaticSetupEnabled {\n            AirshipLogger.info(\"Automatic setup enabled.\")\n            AutoIntegration.shared.integrate(with: integrationDelegate)\n        } else {\n            AppIntegration.integrationDelegate = integrationDelegate\n        }\n\n        onReady?()\n\n        self.shared.airshipInstance.airshipReady()\n        executeOnReady()\n\n        if resolvedConfig.isExtendedBroadcastsEnabled {\n            var userInfo: [String: Any] = [:]\n            userInfo[AirshipNotifications.AirshipReady.channelIDKey] = self.channel.identifier\n            userInfo[AirshipNotifications.AirshipReady.appKey] = credentials.appKey\n            userInfo[AirshipNotifications.AirshipReady.payloadVersionKey] = 1\n            NotificationCenter.default.post(\n                name: AirshipNotifications.AirshipReady.name,\n                object: userInfo\n            )\n        } else {\n            NotificationCenter.default.post(\n                name: AirshipNotifications.AirshipReady.name,\n                object: nil\n            )\n        }\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    public class func component<E>(ofType componentType: E.Type) -> E? {\n        return shared.airshipInstance.component(ofType: componentType)\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    public class func requireComponent<E>(ofType componentType: E.Type) -> E {\n        let component = shared.airshipInstance.component(\n            ofType: componentType\n        )\n\n        if component == nil {\n            assertionFailure(\"Missing required component: \\(componentType)\")\n        }\n        return component!\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    public class func componentSupplier<E>() -> @Sendable () -> E {\n        return {\n            return requireComponent(ofType: E.self)\n        }\n    }\n\n    /// Processes a deep link.\n    /// For `uairship://` scheme URLs, Airship will handle the deep link internally.\n    /// For other URLs, Airship will forward the deep link to the deep link listener if set.\n    /// - Parameters:\n    ///     - url: The deep link.\n    /// - Returns `true` if the link was able to be processed, otherwise `false`.\n    @MainActor\n    public static func processDeepLink(_ url: URL) async -> Bool {\n        return await Airship.shared.deepLink(url)\n    }\n\n    @MainActor\n    private func deepLink(\n        _ deepLink: URL\n    ) async -> Bool {\n        guard deepLink.scheme != Airship.deepLinkScheme else {\n            guard await handleAirshipDeeplink(deepLink) else {\n                let component = self.airshipInstance.components.first(\n                    where: { $0.deepLink(deepLink) }\n                )\n\n                if component != nil {\n                    AirshipLogger.debug(\"Handling Airship deep link: \\(deepLink)\")\n                    return true\n                }\n\n                // Try handler first, then delegate\n                if let onDeepLink = self.airshipInstance.onDeepLink {\n                    AirshipLogger.debug(\"Handling deep link via onDeepLink closure: \\(deepLink)\")\n                    await onDeepLink(deepLink)\n                    return true\n                } else if let deepLinkDelegate = self.airshipInstance.deepLinkDelegate {\n                    AirshipLogger.debug(\"Handling deep link by receivedDeepLink: \\(deepLink) on delegate: \\(deepLinkDelegate)\")\n                    await deepLinkDelegate.receivedDeepLink(deepLink)\n                    return true\n                }\n\n                AirshipLogger.debug(\"Unhandled deep link \\(deepLink)\")\n                return true\n            }\n            return true\n        }\n\n        // Try handler first, then delegate\n        if let deepLinkHandler = self.airshipInstance.onDeepLink {\n            AirshipLogger.debug(\"Handling deep link via onDeepLink closure: \\(deepLink)\")\n            await deepLinkHandler(deepLink)\n            return true\n        } else if let deepLinkDelegate = self.airshipInstance.deepLinkDelegate {\n            AirshipLogger.debug(\"Handling deep link via receivedDeepLink: \\(deepLink) on delegate: \\(deepLinkDelegate)\")\n            await deepLinkDelegate.receivedDeepLink(deepLink)\n            return true\n        }\n\n        AirshipLogger.debug(\"Unhandled deep link \\(deepLink)\")\n        return false\n    }\n\n    /// Handle the Airship deep links for app_settings and app_store.\n    /// - Note: For internal use only. :nodoc:\n    /// `uairship://app_settings` and `uairship://app_store?itunesID=<ITUNES_ID>` deep links will be handled internally. If no itunesID provided, use the one in Airship Config.\n    /// - Parameters:\n    ///     - deepLink: The deep link.\n    /// - Returns: `true` if the deeplink is handled, `false` otherwise.\n    @MainActor\n    private func handleAirshipDeeplink(_ deeplink: URL) async -> Bool {\n        switch deeplink.host {\n        case Airship.appSettingsDeepLinkHost:\n            AirshipLogger.debug(\"Handling Settings deep link: \\(deeplink)\")\n            return await self.airshipInstance.urlOpener.openSettings()\n\n        case Airship.appStoreDeepLinkHost:\n            AirshipLogger.debug(\"Handling App Store deep link: \\(deeplink)\")\n\n            let appStoreUrl = \"itms-apps://itunes.apple.com/app/\"\n            guard let itunesID = getItunesID(deeplink) else {\n                return true\n            }\n            if let url = URL(string: appStoreUrl + itunesID) {\n                await self.airshipInstance.urlOpener.openURL(url)\n            }\n            return true\n\n        default:\n            return false\n        }\n    }\n\n    /// Gets the iTunes ID.\n    /// - Note: For internal use only. :nodoc:\n    /// - Parameters:\n    ///     - deepLink: The deep link.\n    /// - Returns: The iTunes ID or `nil` if it's not set.\n    private func getItunesID(_ deeplink: URL) -> String? {\n        let urlComponents = URLComponents(\n            url: deeplink,\n            resolvingAgainstBaseURL: false\n        )\n        let queryMap =\n            urlComponents?.queryItems?\n            .reduce(into: [String: String?]()) {\n                $0[$1.name] = $1.value\n            } ?? [:]\n        return queryMap[Airship.itunesIDKey] as? String ?? airshipInstance.config.airshipConfig.itunesID\n    }\n\n\n    // Taken from IAA so we can continue to use the existing value if set\n    private static let newUserCutOffDateKey: String = \"UAInAppRemoteDataClient.ScheduledNewUserCutoffTime\"\n\n    var installDate: Date {\n        if let date = self.airshipInstance.preferenceDataStore.value(forKey: Airship.newUserCutOffDateKey) as? Date {\n            return date\n        }\n\n        var date: Date!\n\n        if let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).last,\n           let attributes = try? FileManager.default.attributesOfItem(atPath: documentsURL.path),\n           let installDate = attributes[.creationDate] as? Date\n        {\n            date = installDate\n        } else {\n            date = self.airshipInstance.component(ofType: (any AirshipChannel).self)?.identifier != nil ? Date.distantPast : Date()\n        }\n\n        self.airshipInstance.preferenceDataStore.setObject(date, forKey: Airship.newUserCutOffDateKey)\n        return date\n    }\n}\n\n\n/// NSNotificationCenter keys event names\npublic final class AirshipNotifications {\n\n    /// Notification when Airship is ready.\n    public final class AirshipReady {\n        /// Notification name\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.airship_ready\"\n        )\n\n        /// Airship ready channel ID key. Only available if `extendedBroadcastEnabled` is true in config.\n        public static let channelIDKey: String = \"channel_id\"\n\n        /// Airship ready app key. Only available if `extendedBroadcastEnabled` is true in config.\n        public static let appKey: String = \"app_key\"\n\n        /// Airship ready payload version. Only available if `extendedBroadcastEnabled` is true in config.\n        public static let payloadVersionKey: String = \"payload_version\"\n    }\n}\n\n\npublic extension Airship {\n\n    /// Waits for Airship to be ready using async/await.\n    ///\n    /// This method provides a modern async/await interface for waiting until Airship\n    /// has finished initializing. It's particularly useful when you need to ensure\n    /// Airship is ready before performing operations that depend on it.\n    ///\n    /// ## Usage\n    ///\n    /// ```swift\n    /// // Wait for Airship to be ready\n    /// await Airship.waitForReady()\n    ///\n    /// // Now safe to use Airship components\n    /// Airship.push.enableUserNotifications()\n    /// ```\n    ///\n    /// ## Behavior\n    ///\n    /// - If Airship is already initialized (`isFlying` is `true`), this method returns immediately\n    /// - If Airship is not yet initialized, this method suspends until initialization completes\n    /// - The method will not throw or fail - it simply waits for the ready state\n    ///\n    /// - Note: This method must be called from the main thread.\n    /// - Important: This method assumes `Airship.takeOff` has been called. If `takeOff`\n    ///   is never called, this method will suspend indefinitely.\n    @MainActor\n    static func waitForReady() async {\n        guard !Airship.isFlying else { return }\n        await withCheckedContinuation { continuation in\n            Airship.onReady {\n                continuation.resume()\n            }\n        }\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Airship action. Actions can be registered in the `ActionRegistry` and ran through the `ActionRunner`.\npublic protocol AirshipAction: AnyObject, Sendable {\n\n     /// Called before an action is performed to determine if the\n     /// the action can accept the arguments.\n     /// This method can be used both to verify that an argument's value is an appropriate type,\n     /// as well as to limit the scope of execution of a desired range of values. Rejecting\n     /// arguments will result in the action not being performed when it is run.\n     /// - Parameters:\n     ///    -   ActionArgument A UAActionArgument value representing the arguments passed to the action.\n     ///  - Returns:  YES if the action can perform with the arguments, otherwise NO\n    func accepts(arguments: ActionArguments) async -> Bool\n    \n     /// Performs the action.\n     /// You should not ordinarily call this method directly. Instead, use the `ActionRunner`.\n     ///  - Parameters:\n     ///    - arguments Arguments value representing the arguments passed to the action.\n     ///  - Returns:An optional value.\n    func perform(arguments: ActionArguments) async throws -> AirshipJSON?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipActorValue.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@preconcurrency\nimport Combine\n\n/// NOTE: For internal use only. :nodoc:\npublic actor AirshipActorValue<T: Sendable> {\n\n    private let subject: PassthroughSubject<T, Never> = PassthroughSubject()\n\n    public private(set) var value: T {\n        didSet {\n            subject.send(value)\n        }\n    }\n\n    public var updates: AsyncStream<T> {\n        AsyncStream { [weak subject] continuation in\n            guard let subject = subject else {\n                continuation.finish()\n                return\n            }\n\n            let cancellable: AnyCancellable = subject.sink { value in\n                continuation.yield(value)\n            }\n\n            continuation.onTermination = { _ in\n                cancellable.cancel()\n            }\n        }\n    }\n\n    public init(_ value: T) {\n        self.value = value\n    }\n\n    public func set(_ value: T) {\n        self.value = value\n    }\n\n    public func getAndUpdate(block: @Sendable (inout T) -> Void) -> T {\n        block(&self.value)\n        return self.value\n    }\n\n    public func update(block: @Sendable (inout T) -> Void) {\n        block(&self.value)\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic final class AirshipMainActorValue<T: Sendable>: @unchecked Sendable {\n\n    private let subject: PassthroughSubject<T, Never> = PassthroughSubject()\n\n    @MainActor\n    public private(set) var value: T {\n        didSet {\n            subject.send(value)\n        }\n    }\n\n    @MainActor\n    public var updates: AsyncStream<T> {\n        AsyncStream { [weak subject] continuation in\n            continuation.yield(value)\n\n            guard let subject = subject else {\n                continuation.finish()\n                return\n            }\n\n            let cancellable: AnyCancellable? = subject.sink { value in\n                continuation.yield(value)\n            }\n\n            continuation.onTermination = { _ in\n                cancellable?.cancel()\n            }\n        }\n    }\n\n    public init(_ value: T) {\n        self.value = value\n    }\n\n    @MainActor\n    public func set(_ value: T) {\n        self.value = value\n    }\n\n    @MainActor\n    public func getAndUpdate(block: @Sendable (inout T) -> Void) -> T {\n        block(&self.value)\n        return value\n    }\n\n    @MainActor\n    public func update(block: @Sendable (inout T) -> Void) {\n        block(&self.value)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import Combine\n\n#if canImport(UserNotifications)\npublic import UserNotifications\n#endif\n\n/// Analytics protocol\npublic protocol AirshipAnalytics: AnyObject, Sendable {\n\n    /// The conversion send ID. :nodoc:\n    var conversionSendID: String? { get }\n\n    /// The conversion push metadata. :nodoc:\n    var conversionPushMetadata: String? { get }\n\n    /// The current session ID.\n    var sessionID: String { get }\n\n    /// Adds a custom event.\n    /// - Parameter event: The event.\n    func recordCustomEvent(_ event: CustomEvent)\n\n    /// Tracks a custom event.\n    /// - Parameter event: The event.\n    func recordRegionEvent(_ event: RegionEvent)\n\n    /// Tracks install attribution data.\n    /// - Parameters:\n    ///   - appPurchaseDate: The app purchase date.\n    ///   - iAdImpressionDate: The iAd impression date.\n    func trackInstallAttribution(appPurchaseDate: Date?, iAdImpressionDate: Date?)\n\n    /// Associates identifiers with the device. This call will add a special event\n    /// that will be batched and sent up with our other analytics events. Previous\n    /// associated identifiers will be replaced.\n    ///\n    ///\n    /// - Parameter associatedIdentifiers: The associated identifiers.\n    func associateDeviceIdentifiers(\n        _ associatedIdentifiers: AssociatedIdentifiers\n    )\n\n    /// The device's current associated identifiers.\n    /// - Returns: The device's current associated identifiers.\n    func currentAssociatedDeviceIdentifiers() -> AssociatedIdentifiers\n\n    /// Initiates screen tracking for a specific app screen, must be called once per tracked screen.\n    /// - Parameter screen: The screen's identifier.\n    @MainActor\n    func trackScreen(_ screen: String?)\n\n    /// Registers an SDK extension with the analytics module.\n    /// For internal use only. :nodoc:\n    ///\n    /// - Parameters:\n    ///   - ext: The SDK extension.\n    ///   - version: The version.\n    func registerSDKExtension(_ ext: AirshipSDKExtension, version: String)\n\n    /// A publisher of event data that is tracked through Airship.\n    var eventPublisher: AnyPublisher<AirshipEventData, Never> { get }\n}\n\n\n/// Internal Analytics protocol\n/// For internal use only. :nodoc:\npublic protocol InternalAirshipAnalytics: AirshipAnalytics {\n    var eventFeed: AirshipAnalyticsFeed { get }\n\n    @MainActor\n    var screenUpdates: AsyncStream<String?> { get }\n\n    @MainActor\n    var currentScreen: String? { get }\n\n    @MainActor\n    var regionUpdates: AsyncStream<Set<String>> { get }\n\n    @MainActor\n    var currentRegions: Set<String> { get }\n\n    func recordEvent(_ event: AirshipEvent)\n\n    #if !os(tvOS)\n    @MainActor\n    func onNotificationResponse(\n        response: UNNotificationResponse,\n        action: UNNotificationAction?\n    )\n    #endif\n\n\n    /// Called to notify analytics the app was launched from a push notification.\n    /// For internal use only. :nodoc:\n    /// - Parameter notification: The push notification.\n    @MainActor\n    func launched(fromNotification notification: [AnyHashable: Any])\n\n    @MainActor\n    func addHeaderProvider(_ headerProvider: @Sendable @escaping () async -> [String: String])\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAnalyticsFeed.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// For internal use only. :nodoc:\npublic final class AirshipAnalyticsFeed: Sendable {\n    private let isEnabled:  @Sendable () -> Bool\n\n    public init(isEnabled: @Sendable @escaping () -> Bool) {\n        self.isEnabled = isEnabled\n    }\n\n    public convenience init(privacyManager: any AirshipPrivacyManager, isAnalyticsEnabled: Bool) {\n        self.init(\n            isEnabled: { [weak privacyManager] in\n                return privacyManager?.isEnabled(.analytics) == true && isAnalyticsEnabled\n            }\n        )\n    }\n\n    public enum Event: Equatable, Sendable {\n        case screen(screen: String?)\n        case analytics(eventType: EventType, body: AirshipJSON, value: Double? = 1)\n    }\n\n    private let channel: AirshipAsyncChannel<Event> = AirshipAsyncChannel<Event>()\n\n    public var updates: AsyncStream<Event> {\n        get async {\n            return await channel.makeStream()\n        }\n    }\n\n    @discardableResult\n    func notifyEvent(_ event: Event) async -> Bool {\n        guard isEnabled() else {\n            return false\n        }\n\n        await channel.send(event)\n        return true \n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAppCredentials.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// App credentails\npublic struct AirshipAppCredentials: Sendable {\n    /// App key\n    public let appKey: String\n\n    /// App secret\n    public let appSecret: String\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipApptimizeIntegration.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/**\n * This class provides handy access for Airship method for the integration with Apptimize SDK\n */\n\n@objc(UAirshipApptimizeIntegration)\nfinal class AirshipApptimizeIntegration: NSObject {\n    \n    @objc\n    public static var airshipVersion: String {\n        return AirshipVersion.get()\n    }\n    \n    @objc\n    public static var isFlying: Bool {\n        return Airship.isFlying\n    }\n    \n    @objc(getUserID:)\n    public static func getUserID(completion: @Sendable @escaping (String?) -> Void) {\n        guard Airship.isFlying else { return }\n        \n        Task { @Sendable in\n            let id = await Airship.contact.namedUserID\n            completion(id)\n        }\n    }\n    \n    @objc\n    public static var channelID: String? {\n        guard Airship.isFlying else { return nil }\n        return Airship.channel.identifier\n    }\n    \n    @objc\n    public static var channelTags: [String]? {\n        guard Airship.isFlying else { return nil }\n        return Airship.channel.tags\n    }\n    \n    @objc\n    public static func addTags(_ tags: [String], group: String) {\n        guard Airship.isFlying else { return }\n        \n        Airship.channel.editTagGroups { $0.add(tags, group: group) }\n    }\n    \n    @objc\n    public static func setTags(_ tags: [String], group: String) {\n        guard Airship.isFlying else { return }\n        \n        Airship.channel.editTagGroups({ $0.set(tags, group: group) })\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAsyncChannel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import Combine\n\n/// Simple implementation of a `channel` that allows multiple AsyncStreams of the same data.\n/// - Note: for internal use only.  :nodoc:\npublic actor AirshipAsyncChannel<T: Sendable> {\n    public enum BufferPolicy: Sendable {\n        case unbounded\n        case bufferingNewest(Int)\n        case bufferingOldest(Int)\n\n        fileprivate func toStreamPolicy() -> AsyncStream<T>.Continuation.BufferingPolicy {\n            switch (self) {\n            case .unbounded: return .unbounded\n            case .bufferingOldest(let buffer): return .bufferingOldest(buffer)\n            case .bufferingNewest(let buffer): return .bufferingNewest(buffer)\n            }\n        }\n    }\n\n    private var nextID: Int = 0\n    private var listeners: [Int: Listener] = [:]\n\n    public func send(_ value: T) async {\n        listeners.values.forEach { listener in\n            listener.send(value: value)\n        }\n    }\n\n    public init() {}\n\n    public func makeStream(bufferPolicy: BufferPolicy = .unbounded) -> AsyncStream<T> {\n        let id = self.nextID\n        nextID += 1\n        \n        return AsyncStream(bufferingPolicy: bufferPolicy.toStreamPolicy()) { continuation in\n            let listener = Listener(continuation: continuation)\n            listeners[id] = listener\n\n            continuation.onTermination = { [weak self] _ in\n                Task { [weak self] in\n                    await self?.removeListener(id: id)\n                }\n            }\n        }\n    }\n\n    private func removeListener(id: Int) {\n        self.listeners[id] = nil\n    }\n\n    final class Listener: Sendable {\n        let continuation: AsyncStream<T>.Continuation\n\n        init(continuation: AsyncStream<T>.Continuation) {\n            self.continuation = continuation\n        }\n\n        func send(value: T) {\n            self.continuation.yield(value)\n        }\n    }\n}\n\npublic extension AirshipAsyncChannel {\n\n    /// Makes a stream that is nonisolated from the actor by wrapping the\n    /// actor stream in an async stream.\n    /// - Parameters:\n    ///     - bufferPolicy: The buffer policy.\n    ///     - initialValue: Optional initial value closure. If provided and the value is nil, it will finish the stream.\n    ///     - transform: Transforms the channel values to the stream value. If nil, it a value is mapped to nil it will finish the stream.\n    /// - Returns: An AsyncStream.\n    nonisolated func makeNonIsolatedStream<R: Sendable>(\n        bufferPolicy: BufferPolicy = .unbounded,\n        initialValue: (@Sendable () async -> R?)? = nil,\n        transform: @escaping @Sendable (T) async -> R?\n    ) -> AsyncStream<R> {\n        return AsyncStream<R> { [weak self] continuation in\n            let task = Task { [weak self] in\n                guard let stream = await self?.makeStream() else {\n                    return\n                }\n\n                if let initialValue {\n                    guard let value = await initialValue() else {\n                        continuation.finish()\n                        return\n                    }\n\n                    continuation.yield(value)\n                }\n\n                for await update in stream.map(transform) {\n                    if let update {\n                        continuation.yield(update)\n                    } else {\n                        continuation.finish()\n                    }\n                }\n                continuation.finish()\n            }\n\n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n\n    /// Makes a stream that is nonisolated from the actor by wrapping the\n    /// actor stream in an async stream.\n    /// - Parameters:\n    ///     - bufferPolicy: The buffer policy.\n    ///     - initialValue: Optional initial value closure. If provided and the value is nil, it will finish the stream.\n    ///     - transform: Transforms the channel values to the stream value. If nil, it a value is mapped to nil it will finish the stream.\n    /// - Returns: An AsyncStream.\n    nonisolated func makeNonIsolatedStream(\n        bufferPolicy: BufferPolicy = .unbounded,\n        initialValue: (@Sendable () async -> T)? = nil\n    ) -> AsyncStream<T> {\n        return makeNonIsolatedStream(\n            bufferPolicy: bufferPolicy,\n            initialValue: initialValue,\n            transform: { $0 }\n        )\n    }\n\n    /// Makes a stream that is nonisolated from the actor by wrapping the\n    /// actor stream in an async stream. Values will only be emitted if they are different than the previous value.\n    /// - Parameters:\n    ///     - bufferPolicy: The buffer policy.\n    ///     - initialValue: Optional initial value closure. If provided and the value is nil, it will finish the stream.\n    /// - Returns: An AsyncStream.\n    nonisolated func makeNonIsolatedDedupingStream<R: Sendable&Equatable>(\n        bufferPolicy: BufferPolicy = .unbounded,\n        initialValue: (@Sendable () async -> R?)? = nil,\n        transform: @escaping @Sendable (T) async -> R?\n    ) -> AsyncStream<R> {\n        return AsyncStream<R> { [weak self] continuation in\n            let task = Task { [weak self] in\n                guard let stream = await self?.makeStream() else {\n                    return\n                }\n\n                var last: R? = nil\n\n                if let initialValue {\n                    guard let value = await initialValue() else {\n                        continuation.finish()\n                        return\n                    }\n\n                    continuation.yield(value)\n                    last = value\n                }\n\n                for await update in stream.map(transform) {\n                    guard let update else {\n                        continuation.finish()\n                        return\n                    }\n\n                    if update != last {\n                        continuation.yield(update)\n                        last = update\n                    }\n                }\n            }\n\n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n\n    /// Makes a stream that is nonisolated from the actor by wrapping the\n    /// actor stream in an async stream. Values will only be emitted if they are different than the previous value.\n    /// - Parameters:\n    ///     - bufferPolicy: The buffer policy.\n    ///     - initialValue: Optional initial value closure. If provided and the value is nil, it will finish the stream.\n    /// - Returns: An AsyncStream.\n    nonisolated func makeNonIsolatedDedupingStream(\n        bufferPolicy: BufferPolicy = .unbounded,\n        initialValue: (@Sendable () async -> T?)? = nil\n    ) -> AsyncStream<T> where T: Equatable {\n        return makeNonIsolatedDedupingStream(\n            bufferPolicy: bufferPolicy,\n            initialValue: initialValue,\n            transform: { $0 }\n        )\n    }\n}\n\npublic extension AsyncStream where Element : Sendable {\n\n    /// Creates a combine publisher from an AsyncStream.\n    /// - Note: for internal use only.  :nodoc:\n    @MainActor\n    var airshipPublisher: AnyPublisher<Element?, Never>{\n        let subject = CurrentValueSubject<Element?, Never>(nil)\n        Task { @MainActor [weak subject] in\n            for await update in self {\n                guard let subject else { return }\n                subject.send(update)\n            }\n        }\n        return subject.eraseToAnyPublisher()\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAsyncImage.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n/// - Note: for internal use only.  :nodoc:\npublic struct AirshipAsyncImage<Placeholder: View, ImageView: View>: View {\n\n    private let url: String\n    private let imageLoader: AirshipImageLoader\n    private let image: (Image, CGSize) -> ImageView\n    private let placeholder: () -> Placeholder\n\n    public init(\n        url: String,\n        imageLoader: AirshipImageLoader = AirshipImageLoader(),\n        image: @escaping (Image, CGSize) -> ImageView,\n        placeholder: @escaping () -> Placeholder\n    ) {\n        self.url = url\n        self.imageLoader = imageLoader\n        self.image = image\n        self.placeholder = placeholder\n    }\n\n    @State private var loadedURL: String?\n    @State private var loadedImage: AirshipImageData?\n    @State private var currentImage: AirshipNativeImage?\n    @State private var imageIndex: Int = 0\n    @State private var animationTask: Task<Void, Never>?\n\n    public var body: some View {\n        content\n            .task(id: url) {\n                guard loadedURL != url else {\n                    startAnimation()\n                    return\n                }\n\n                self.loadedImage = nil\n                self.currentImage = nil\n\n                do {\n                    let image = try await imageLoader.load(url: url)\n                    self.loadedURL = url\n                    self.loadedImage = image\n                    startAnimation()\n                } catch is CancellationError {\n                } catch {\n                    AirshipLogger.error(\"Unable to load image \\(url): \\(error)\")\n                }\n            }\n    }\n\n    private var content: some View {\n        Group {\n            if let image = currentImage {\n                self.image(Image(airshipNativeImage: image), image.size)\n                    .animation(nil, value: self.imageIndex)\n                    .onDisappear {\n                        animationTask?.cancel()\n                    }\n            } else {\n                self.placeholder()\n            }\n        }\n    }\n\n    private func startAnimation() {\n        self.animationTask?.cancel()\n        self.animationTask = Task { @MainActor in\n            await animateImage()\n        }\n    }\n\n    @MainActor\n    private func animateImage() async {\n        guard let loadedImage = self.loadedImage else { return }\n        \n        guard loadedImage.isAnimated else {\n            self.currentImage = await loadedImage.loadFrames().first?.image\n            return\n        }\n        \n        let frameActor = loadedImage.getActor()\n\n        imageIndex = 0\n        var frame = await frameActor.loadFrame(at: imageIndex)\n\n        self.currentImage = frame?.image\n\n        while !Task.isCancelled {\n            let duration = frame?.duration ?? AirshipImageData.minFrameDuration\n            \n            async let delay: () = Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))\n            \n            let nextIndex = (imageIndex + 1) % loadedImage.imageFramesCount\n            \n            do {\n                let (_, nextFrame) = try await (delay, frameActor.loadFrame(at: nextIndex))\n                frame = nextFrame\n            } catch {} // most likely it's a task cancelled exception when animation is stopped\n\n            imageIndex = nextIndex\n\n            if !Task.isCancelled {\n                self.currentImage = frame?.image\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipAuthorizedNotificationSettings.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n\nimport UserNotifications\n\n// Authorized notification settings.\npublic struct AirshipAuthorizedNotificationSettings: OptionSet, Sendable {\n    public let rawValue: UInt\n\n    public init(rawValue: UInt) {\n        self.rawValue = rawValue\n    }\n\n    // Badge\n    public static let badge: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 0)\n\n    // Sound\n    public static let sound: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 1)\n\n    // Alert\n    public static let alert: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 2)\n\n    // Carplad\n    public static let carPlay: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 3)\n\n    // Lockscreen\n    public static let lockScreen: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 4)\n\n    // Notification Center\n    public static let notificationCenter: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 5)\n\n    // Critical alert\n    public static let criticalAlert: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 6)\n\n    // Announcement\n    public static let announcement: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 7)\n\n    // Scheduled delivery\n    public static let scheduledDelivery: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 8)\n\n    // Time sensitive\n    public static let timeSensitive: AirshipAuthorizedNotificationSettings = AirshipAuthorizedNotificationSettings(rawValue: 1 << 9)\n}\n\n\nextension AirshipAuthorizedNotificationSettings {\n\n    static func from(settings: UNNotificationSettings) -> AirshipAuthorizedNotificationSettings {\n        var authorizedSettings: AirshipAuthorizedNotificationSettings = []\n#if !os(watchOS)\n        if settings.badgeSetting == .enabled {\n            authorizedSettings.insert(.badge)\n        }\n#endif\n\n#if !os(tvOS)\n\n        if settings.soundSetting == .enabled {\n            authorizedSettings.insert(.sound)\n        }\n\n        if settings.alertSetting == .enabled {\n            authorizedSettings.insert(.alert)\n        }\n\n#if !os(watchOS) && !os(macOS)\n        if settings.carPlaySetting == .enabled {\n            authorizedSettings.insert(.carPlay)\n        }\n\n        if settings.lockScreenSetting == .enabled {\n            authorizedSettings.insert(.lockScreen)\n        }\n#endif\n\n        if settings.notificationCenterSetting == .enabled {\n            authorizedSettings.insert(.notificationCenter)\n        }\n\n        if settings.criticalAlertSetting == .enabled {\n            authorizedSettings.insert(.criticalAlert)\n        }\n\n#if !os(visionOS) && !os(macOS)\n        /// Announcement authorization is always included in visionOS\n        if settings.announcementSetting == .enabled {\n            authorizedSettings.insert(.announcement)\n        }\n#endif\n\n#endif\n\n#if !os(tvOS) && !targetEnvironment(macCatalyst)\n\n        if settings.timeSensitiveSetting == .enabled {\n            authorizedSettings.insert(.timeSensitive)\n        }\n\n        if settings.scheduledDeliverySetting == .enabled {\n            authorizedSettings.insert(.scheduledDelivery)\n        }\n\n#endif\n        return authorizedSettings\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipBase64.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: For internal use only. :nodoc:\npublic final class AirshipBase64 {\n\n    public class func data(from base64String: String) -> Data? {\n        var normalizedString = base64String.components(separatedBy: .newlines)\n            .joined(separator: \"\")\n            .replacingOccurrences(\n                of: \"=\",\n                with: \"\"\n            )\n\n        // Must be a multiple of 4 characters post padding\n        // For more information: https://tools.ietf.org/html/rfc4648#section-8\n        switch normalizedString.count % 4 {\n        case 2:\n            normalizedString += \"==\"\n        case 3:\n            normalizedString += \"=\"\n        default:\n            break\n        }\n\n        return Data(\n            base64Encoded: normalizedString,\n            options: .ignoreUnknownCharacters\n        )\n    }\n\n    public class func string(from data: Data) -> String? {\n        let base64 = data.base64EncodedData(options: .lineLength64Characters)\n        return String(data: base64, encoding: .ascii)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Button view.\nstruct AirshipButton<Label> : View  where Label : View {\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var pagerState: PagerState\n    @EnvironmentObject private var videoState: VideoState\n    @EnvironmentObject private var thomasState: ThomasState\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n    @Environment(\\.layoutState) private var layoutState\n    @Environment(\\.isButtonActionsEnabled) private var isButtonActionsEnabled\n\n    private let identifier: String\n    private let reportingMetadata: AirshipJSON?\n    private let description: String?\n    private let clickBehaviors: [ThomasButtonClickBehavior]?\n    private let eventHandlers: [ThomasEventHandler]?\n    private let actions: ThomasActionsPayload?\n    private let tapEffect: ThomasButtonTapEffect?\n    private let label: () -> Label\n\n    @State\n    private var isProcessing: Bool = false\n\n    init(\n        identifier: String,\n        reportingMetadata: AirshipJSON? = nil,\n        description: String?,\n        clickBehaviors: [ThomasButtonClickBehavior]? = nil,\n        eventHandlers: [ThomasEventHandler]? = nil,\n        actions: ThomasActionsPayload? = nil,\n        tapEffect: ThomasButtonTapEffect? = nil,\n        label: @escaping () -> Label\n    ) {\n        self.identifier = identifier\n        self.reportingMetadata = reportingMetadata\n        self.description = description\n        self.clickBehaviors = clickBehaviors\n        self.eventHandlers = eventHandlers\n        self.actions = actions\n        self.tapEffect = tapEffect\n        self.label = label\n    }\n\n    var body: some View {\n        Button(\n            action: {\n                if (isButtonActionsEnabled) {\n                    Task { @MainActor in\n                        isProcessing = true\n                        await doButtonActions()\n                        isProcessing = false\n                    }\n                }\n            },\n            label: self.label\n        )\n        .optionalAccessibilityLabel(self.description)\n        .buttonTapEffect(tapEffect ?? .default)\n        .disabled(isProcessing)\n    }\n\n    @MainActor\n    private func doButtonActions() async {\n        if clickBehaviors?.contains(.formSubmit) == true || clickBehaviors?.contains(.formValidate) == true {\n            guard await formState.validate() else { return }\n        }\n\n        let taps = self.eventHandlers?.filter { $0.type == .tap }\n        if let taps, !taps.isEmpty {\n            /// Tap handlers\n            taps.forEach { tap in\n                handleStateActions(tap.stateActions)\n            }\n\n            // WORKAROUND: SwiftUI state updates are not immediately available to child views.\n            // Yielding allows the state changes to propagate through the view hierarchy\n            // before executing behaviors that may depend on the updated state.\n            await Task.yield()\n        }\n\n        // Button reporting\n        thomasEnvironment.buttonTapped(\n            buttonIdentifier: self.identifier,\n            reportingMetadata: self.reportingMetadata,\n            layoutState: layoutState\n        )\n\n        // Buttons\n        await handleBehaviors(self.clickBehaviors ?? [])\n        handleActions(self.actions)\n    }\n\n    private func handleBehaviors(\n        _ behaviors: [ThomasButtonClickBehavior]?\n    ) async {\n        guard let behaviors else { return }\n\n        for behavior in behaviors {\n            switch(behavior) {\n            case .dismiss:\n                thomasEnvironment.dismiss(\n                    buttonIdentifier: self.identifier,\n                    buttonDescription: self.description ?? self.identifier,\n                    cancel: false,\n                    layoutState: layoutState\n                )\n\n            case .cancel:\n                  thomasEnvironment.dismiss(\n                    buttonIdentifier: self.identifier,\n                    buttonDescription: self.description ?? self.identifier,\n                    cancel: true,\n                    layoutState: layoutState\n                  )\n\n            case .pagerNext:\n                pagerState.process(request: .next)\n\n            case .pagerPrevious:\n                pagerState.process(request: .back)\n\n            case .pagerNextOrDismiss:\n                if pagerState.isLastPage {\n                    thomasEnvironment.dismiss(\n                        buttonIdentifier: self.identifier,\n                        buttonDescription: self.description ?? self.identifier,\n                        cancel: false,\n                        layoutState: layoutState\n                    )\n                } else {\n                    pagerState.process(request: .next)\n                }\n\n            case .pagerNextOrFirst:\n                if pagerState.isLastPage {\n                    pagerState.process(request: .first)\n                } else {\n                    pagerState.process(request: .next)\n                }\n\n            case .pagerPause:\n                pagerState.pause()\n\n            case .pagerResume:\n                pagerState.resume()\n\n            case .pagerPauseToggle:\n                pagerState.togglePause()\n\n            case .formValidate:\n                // Already handled above\n                break\n\n            case .formSubmit:\n                do {\n                    try await formState.submit(layoutState: layoutState)\n                } catch {\n                    AirshipLogger.error(\"Failed to submit \\(error)\")\n                }\n\n            case .videoPlay:\n                videoState.play()\n\n            case .videoPause:\n                videoState.pause()\n\n            case .videoTogglePlay:\n                videoState.togglePlay()\n\n            case .videoMute:\n                videoState.mute()\n\n            case .videoUnmute:\n                videoState.unmute()\n\n            case .videoToggleMute:\n                videoState.toggleMute()\n            }\n        }\n    }\n\n    private func handleActions(_ actionPayload: ThomasActionsPayload?) {\n        if let actionPayload {\n            thomasEnvironment.runActions(actionPayload, layoutState: layoutState)\n        }\n    }\n\n    private func handleStateActions(_ stateActions: [ThomasStateAction]) {\n        thomasState.processStateActions(stateActions)\n    }\n}\n\n\nfileprivate struct AirshipButtonEmptyStyle: ButtonStyle {\n    func makeBody(configuration: Configuration) -> some View {\n        configuration.label\n    }\n}\n\nfileprivate extension View {\n    @ViewBuilder\n    func buttonTapEffect(_ tapEffect: ThomasButtonTapEffect) -> some View {\n        switch(tapEffect) {\n        case .default:\n#if os(tvOS)\n            self.buttonStyle(TVButtonStyle())\n#else\n            self.buttonStyle(.plain)\n#endif\n        case .none:\n            self.buttonStyle(AirshipButtonEmptyStyle())\n        }\n    }\n\n    @ViewBuilder\n    func optionalAccessibilityLabel(_ label: String?) -> some View {\n        if let label {\n            self.accessibilityLabel(label)\n        } else {\n            self\n        }\n    }\n}\n\n#if os(tvOS)\nstruct TVButtonStyle: ButtonStyle {\n\n    func makeBody(configuration: Configuration) -> some View {\n        return ButtonView(configuration: configuration)\n    }\n\n    struct ButtonView: View {\n        @Environment(\\.isFocused) var isFocused\n        @Environment(\\.isEnabled) var isEnabled\n\n        let configuration: ButtonStyle.Configuration\n\n        var body: some View {\n            configuration.label\n                .hoverEffect(.highlight, isEnabled: isFocused)\n                .colorMultiply(isEnabled ? Color.white : ThomasConstants.disabledColor)\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipCache.swift",
    "content": "public import Foundation\nimport CoreData\n\npublic protocol AirshipCache: Actor {\n    func deleteCachedValue(key: String) async\n\n    func getCachedValue<T: Codable & Sendable>(key: String) async -> T?\n    func setCachedValue<T: Codable & Sendable>(_ value: T?, key: String, ttl: TimeInterval) async\n}\n\nactor CoreDataAirshipCache: AirshipCache {\n    private let coreData: UACoreData?\n    private let appVersion: String\n    private let sdkVersion: String\n    private let date: any AirshipDateProtocol\n    private let cleanUpTask: Task<Void, Never>\n\n\n\n    static func makeCoreData(appKey: String, inMemory: Bool = false) -> UACoreData? {\n        let modelURL = AirshipCoreResources.bundle.url(\n            forResource: \"UAirshipCache\",\n            withExtension: \"momd\"\n        )\n\n        if let modelURL = modelURL {\n            return UACoreData(\n                name: \"UAirshipCache\",\n                modelURL: modelURL,\n                inMemory: inMemory,\n                stores: [\"AirshipCache-\\(appKey).sqlite\"]\n            )\n        }\n\n        AirshipLogger.error(\"Failed to create AirshipCache\")\n        return nil\n    }\n\n    init(appKey: String) {\n        self.init(\n            coreData: CoreDataAirshipCache.makeCoreData(appKey: appKey)\n        )\n    }\n\n    init(\n        coreData: UACoreData?,\n        appVersion: String = AirshipUtils.bundleShortVersionString() ?? \"0.0.0\",\n        sdkVersion: String = AirshipVersion.version,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.coreData = coreData\n        self.appVersion = appVersion\n        self.sdkVersion = sdkVersion\n        self.date = date\n\n        self.cleanUpTask = Task { [appVersion, sdkVersion, date] in\n            guard let coreData = coreData else { return }\n\n            do {\n                let predicate = AirshipCoreDataPredicate(\n                    format: \"appVersion != %@ || sdkVersion != %@ || expiry <= %@\",\n                    args: [\n                        appVersion,\n                        sdkVersion,\n                        date.now\n                    ]\n                )\n                try await coreData.perform(skipIfStoreNotCreated: true) { context in\n                    try context.delete(\n                        predicate: predicate,\n                        useBatch: !coreData.inMemory\n                    )\n                }\n            } catch {\n                AirshipLogger.error(\"Failed to cleanup cache \\(error)\")\n            }\n        }\n    }\n\n\n    func getCachedValue<T>(\n        key: String\n    ) async -> T? where T : Decodable, T : Encodable, T : Sendable {\n        await self.cleanUpTask.value\n        do {\n            let result: T? = try await requireCoreData().performWithResult { context in\n                let entity = try context.getAirshipCacheEntity(key: key)\n\n                guard let data = entity?.data,\n                      let expiry = entity?.expiry,\n                      entity?.appVersion == self.appVersion,\n                      entity?.sdkVersion == self.sdkVersion\n                else {\n                    AirshipLogger.trace(\"Invalid cache data, deleting\")\n                    try? context.deleteCacheEntity(key: key)\n                    return nil\n                }\n\n                guard expiry > self.date.now else {\n                    AirshipLogger.trace(\"Value expired, deleting\")\n                    try? context.deleteCacheEntity(key: key)\n                    return nil\n                }\n\n                return try JSONDecoder().decode(T.self, from: data)\n            }\n            return result\n        } catch {\n            AirshipLogger.error(\"Failed to fetch cached value \\(key) \\(error)\")\n            return nil\n        }\n    }\n\n    func deleteCachedValue(key: String) async {\n        await self.cleanUpTask.value\n\n        do {\n            try await requireCoreData().perform { context in\n                try context.deleteCacheEntity(key: key)\n            }\n        } catch {\n            AirshipLogger.error(\"Failed to delete cached value for key \\(key) \\(error)\")\n        }\n    }\n\n    func setCachedValue<T>(\n        _ value: T?, key:\n        String, ttl: TimeInterval\n    ) async where T : Decodable, T : Encodable, T : Sendable {\n        await self.cleanUpTask.value\n\n        do {\n            try await requireCoreData().perform { context in\n                let entity = try context.getOrCreateAirshipCacheEntity(key: key)\n                entity.key = key\n                entity.sdkVersion = self.sdkVersion\n                entity.appVersion = self.appVersion\n                entity.expiry = self.date.now + ttl\n                entity.data = try JSONEncoder().encode(value)\n            }\n        } catch {\n            AirshipLogger.error(\"Failed to cache value for key \\(key) \\(error)\")\n        }\n    }\n\n    private func requireCoreData() throws -> UACoreData {\n        guard let coreData = self.coreData else {\n            throw AirshipErrors.error(\"Coredata does not exist\")\n        }\n        return coreData\n    }\n}\n\nfileprivate extension NSManagedObjectContext {\n    func getOrCreateAirshipCacheEntity(\n        key: String\n    ) throws -> AirshipCacheData {\n        return try getAirshipCacheEntity(key: key) ?? createAirshipCacheEntity()\n    }\n\n    func deleteCacheEntity(\n        key: String\n    ) throws {\n        let predicate = AirshipCoreDataPredicate(\n            format: \"key == %@\",\n            args: [key]\n        )\n        try? delete(predicate: predicate, useBatch: false)\n    }\n\n    func getAirshipCacheEntity(\n        key: String\n    ) throws -> AirshipCacheData? {\n        let request = NSFetchRequest<AirshipCacheData>(\n            entityName: AirshipCacheData.entityName\n        )\n\n        let predicate = AirshipCoreDataPredicate(\n            format: \"key == %@\",\n            args: [key]\n        )\n\n        request.fetchLimit = 1\n        request.predicate = predicate.toNSPredicate()\n\n        let fetchResult = try fetch(request)\n        return fetchResult.first\n    }\n\n    func createAirshipCacheEntity() throws -> AirshipCacheData {\n        let entity = NSEntityDescription.insertNewObject(\n           forEntityName: AirshipCacheData.entityName,\n           into: self\n       ) as? AirshipCacheData\n\n        guard let entity = entity else {\n            throw AirshipErrors.error(\"Failed to create AirshipCacheData\")\n        }\n\n        return entity\n    }\n\n    func delete(\n        predicate: AirshipCoreDataPredicate,\n        useBatch: Bool\n    ) throws {\n        if useBatch {\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: AirshipCacheData.entityName\n            )\n            request.predicate = predicate.toNSPredicate()\n            let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n            try execute(deleteRequest)\n        } else {\n            let request = NSFetchRequest<AirshipCacheData>(\n                entityName: AirshipCacheData.entityName\n            )\n            request.predicate = predicate.toNSPredicate()\n            request.includesPropertyValues = false\n            let fetched = try fetch(request)\n            fetched.forEach { entity in\n                delete(entity)\n            }\n        }\n    }\n}\n\n// Internal core data entity\n@objc(UAirshipCacheData)\nfileprivate class AirshipCacheData: NSManagedObject {\n    static let entityName: String = \"UAirshipCacheData\"\n\n    @NSManaged public dynamic var data: Data?\n\n    @objc\n    @NSManaged public dynamic var key: String?\n\n    @objc\n    @NSManaged public dynamic var appVersion: String?\n\n    @objc\n    @NSManaged public dynamic var sdkVersion: String?\n\n    @objc\n    @NSManaged public dynamic var expiry: Date?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipCancellable.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic protocol AirshipCancellable: Sendable {\n    func cancel()\n}\n\n/// - Note: for internal use only.  :nodoc:\npublic protocol AirshipMainActorCancellable: Sendable {\n    @MainActor\n    func cancel()\n}\n\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipMainActorCancellableBlock: AirshipMainActorCancellable, Sendable {\n    private let block: AirshipAtomicValue<(@Sendable @MainActor () -> Void)?> = AirshipAtomicValue<(@Sendable @MainActor () -> Void)?>(nil)\n\n    public init(block: @escaping @MainActor @Sendable () -> Void) {\n        self.block.value = block\n    }\n\n    @MainActor\n    public func cancel() {\n        self.block.value?()\n        self.block.value = nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipChannel.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\nimport Foundation\n\n#if canImport(ActivityKit) && !targetEnvironment(macCatalyst) && !os(macOS)\npublic import ActivityKit\n#endif\n\n/// Airship Channel protocol.\npublic protocol AirshipChannel: AnyObject, Sendable {\n    /**\n     * The Channel ID.\n     */\n    var identifier: String? { get }\n\n    /**\n     * Device tags\n     */\n    var tags: [String] { get set }\n\n    /**\n     * Allows setting tags from the device. Tags can be set from either the server or the device, but\n     * not both (without synchronizing the data), so use this flag to explicitly enable or disable\n     * the device-side flags.\n     *\n     * Set this to `false` to prevent the device from sending any tag information to the server when using\n     * server-side tagging. Defaults to `true`.\n     */\n    var isChannelTagRegistrationEnabled: Bool { get set }\n\n    /**\n     * Edits channel tags.\n     * - Returns: Tag editor.\n     */\n    func editTags() -> TagEditor\n\n    /**\n     * Edits channel tags.\n     * - Parameters:\n     *   - editorBlock: The editor block with the editor. The editor will `apply` will be called after the block is executed.\n     */\n    func editTags(_ editorBlock: (TagEditor) -> Void)\n\n    /**\n     * Edits channel tags groups.\n     * - Returns: Tag group editor.\n     */\n    func editTagGroups() -> TagGroupsEditor\n\n    /**\n     * Edits channel tag groups tags.\n     * - Parameters:\n     *   - editorBlock: The editor block with the editor. The editor will `apply` will be called after the block is executed.\n     */\n    func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void)\n\n    /**\n     * Edits channel subscription lists.\n     * - Returns: Subscription list editor.\n     */\n    func editSubscriptionLists() -> SubscriptionListEditor\n\n    /**\n     * Edits channel subscription lists.\n     * - Parameters:\n     *   - editorBlock: The editor block with the editor. The editor will `apply` will be called after the block is executed.\n     */\n    func editSubscriptionLists(_ editorBlock: (SubscriptionListEditor) -> Void)\n\n    /**\n     * Fetches current subscription lists.\n     * - Returns: The subscription lists\n     */\n    func fetchSubscriptionLists() async throws -> [String]\n\n    /**\n     * Edits channel attributes.\n     * - Returns: Attribute editor.\n     */\n    func editAttributes() -> AttributesEditor\n\n    /**\n     * Edits channel attributes.\n     * - Parameters:\n     *   - editorBlock: The editor block with the editor. The editor will `apply` will be called after the block is executed.\n     */\n    func editAttributes(_ editorBlock: (AttributesEditor) -> Void)\n\n    /**\n     * Enables channel creation if channelCreationDelayEnabled was set to `YES` in the config.\n     */\n    func enableChannelCreation()\n\n\n    /// Async stream of channel ID updates.\n    var identifierUpdates: AsyncStream<String> { get }\n\n    /// Publishes edits made to the subscription lists through the SDK\n    var subscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never> { get }\n\n#if canImport(ActivityKit) && !targetEnvironment(macCatalyst) && !os(macOS)\n\n    /// Gets an AsyncSequence of `LiveActivityRegistrationStatus` updates for a given live acitvity name.\n    /// - Parameters:\n    ///     - name: The live activity name\n    /// - Returns A `LiveActivityRegistrationStatusUpdates`\n    @available(iOS 16.1, *)\n    func liveActivityRegistrationStatusUpdates(\n        name: String\n    ) -> LiveActivityRegistrationStatusUpdates\n\n    /// Gets an AsyncSequence of `LiveActivityRegistrationStatus` updates for a given live acitvity ID.\n    /// - Parameters:\n    ///     - activity: The live activity\n    /// - Returns A `LiveActivityRegistrationStatusUpdates`\n    @available(iOS 16.1, *)\n    func liveActivityRegistrationStatusUpdates<T: ActivityAttributes>(\n        activity: Activity<T>\n    ) -> LiveActivityRegistrationStatusUpdates\n\n    /// Tracks a live activity with Airship for the given name.\n    /// Airship will monitor the push token and status and automatically\n    /// add and remove it from the channel for the App. If an activity is already\n    /// tracked with the given name it will be replaced with the new activity.\n    ///\n    /// The name will be used to send updates through Airship. It can be unique\n    /// for the device or shared across many devices.\n    ///\n    /// - Parameters:\n    ///     - activity: The live activity\n    ///     - name: The name of the activity\n    @available(iOS 16.1, *)\n    func trackLiveActivity<T: ActivityAttributes>(\n        _ activity: Activity<T>,\n        name: String\n    )\n\n    /// Called to restore live activity tracking. This method needs to be called exactly once\n    /// during `application(_:didFinishLaunchingWithOptions:)` right\n    /// after takeOff. Any activities not restored will stop being tracked by Airship.\n    /// - Parameters:\n    ///     - callback: Callback with the restorer.\n    @available(iOS 16.1, *)\n    func restoreLiveActivityTracking(\n        callback: @escaping @Sendable (any LiveActivityRestorer) async -> Void\n    )\n\n#endif\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol InternalAirshipChannel: AirshipChannel {\n    @MainActor\n    func addRegistrationExtender(\n        _ extender: @Sendable @escaping (inout ChannelRegistrationPayload) async -> Void\n    )\n\n    func updateRegistration()\n\n    func updateRegistration(forcefully: Bool)\n\n    func clearSubscriptionListsCache()\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipCheckboxToggleStyle.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct AirshipCheckboxToggleStyle: ToggleStyle {\n    let viewConstraints: ViewConstraints\n    let info: ThomasToggleStyleInfo.Checkbox\n\n    func makeBody(configuration: Self.Configuration) -> some View {\n        SwitchView(configuration: configuration, info: info, viewConstraints: viewConstraints)\n    }\n\n    struct SwitchView: View {\n        let configuration: ToggleStyle.Configuration\n        let info: ThomasToggleStyleInfo.Checkbox\n        let viewConstraints: ViewConstraints\n\n        @Environment(\\.isEnabled) var isEnabled\n        @Environment(\\.colorScheme) var colorScheme\n\n        var body: some View {\n            let binding = configuration.isOn ? info.bindings.selected : info.bindings.unselected\n            var constraints = self.viewConstraints\n            constraints.width = constraints.width ?? 24\n            constraints.height = constraints.height ?? 24\n            constraints.isVerticalFixedSize = true\n            constraints.isHorizontalFixedSize = true\n\n            return Button(action: { configuration.isOn.toggle() }) {\n                ZStack {\n                    if let shapes = binding.shapes {\n                        if binding == info.bindings.selected {\n                            ForEach(0..<shapes.count, id: \\.self) { index in\n                                Shapes.shape(\n                                    info: shapes[index],\n                                    constraints: constraints,\n                                    colorScheme: colorScheme\n                                )\n                            }\n                            .airshipApplyIf(!isEnabled) {  view in\n                                view.colorMultiply(ThomasConstants.disabledColor)\n                            }\n                        } else {\n                            ForEach(0..<shapes.count, id: \\.self) { index in\n                                Shapes.shape(\n                                    info: shapes[index],\n                                    constraints: constraints,\n                                    colorScheme: colorScheme\n                                )\n                            }\n                        }\n                    }\n\n                    if let iconModel = binding.icon {\n                        Icons.icon(info: iconModel, colorScheme: colorScheme)\n                    }\n                }\n                .constraints(constraints, fixedSize: true)\n                .animation(Animation.easeInOut(duration: 0.05), value: configuration.isOn)\n            }\n#if os(tvOS)\n            .buttonStyle(TVButtonStyle())\n#endif\n        }\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipColor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\n\n#if canImport(UIKit)\npublic import UIKit\n#elseif canImport(AppKit)\npublic import AppKit\n#endif\n\npublic enum AirshipColorError: Error {\n    /// Thrown when a color cannot be converted to an RGB space\n    case incompatibleColorSpace(AirshipNativeColor)\n}\n\n/// Airship Color utility.\npublic struct AirshipColor {\n\n    /// Resolves a SwiftUI Color from hex.\n    /// - Parameters:\n    ///   - string: The hex string.\n    /// - Returns: The resolved Color or nil.\n    public static func resolveHexColor(_ string: String) -> Color? {\n        if isHexString(string) {\n            if let native = resolveNativeColor(string) {\n                return Color(native)\n            }\n        }\n        return nil\n    }\n\n    /// Resolves a SwiftUI Color from hex or named string\n    /// - Parameters:\n    ///   - string: The hex or named string\n    ///   - bundle: The bundle to look for the named color in. Defaults to `.main`.\n    /// - Returns: The resolved Color\n    public static func resolveColor(_ string: String, bundle: Bundle = .main) -> Color {\n        if isHexString(string) {\n            if let native = resolveNativeColor(string) {\n                return Color(native)\n            }\n        }\n        return Color(string, bundle: bundle)\n    }\n\n    /// Resolves a Native Color (UIColor or NSColor) from hex\n    /// - Parameters:\n    ///   - hexString: The hex string, can be with or without #, 6 or 8 characters.\n    /// - Returns: The resolved native color, or nil if the string is invalid.\n    public static func resolveNativeColor(_ hexString: String) -> AirshipNativeColor? {\n        let string = normalize(hexString)\n        let width = 8 * (string.count / 2)\n\n        guard width == 32 || width == 24 else { return nil }\n\n        var component: UInt64 = 0\n        let scanner = Scanner(string: string)\n        guard scanner.scanHexInt64(&component) else { return nil }\n\n        let red = CGFloat((component & 0xFF0000) >> 16) / 255.0\n        let green = CGFloat((component & 0xFF00) >> 8) / 255.0\n        let blue = CGFloat((component & 0xFF)) / 255.0\n\n        let alpha: CGFloat = if width == 24 {\n            1.0\n        } else {\n            CGFloat((component & 0xFF00_0000) >> 24) / 255.0\n        }\n\n        return AirshipNativeColor(red: red, green: green, blue: blue, alpha: alpha)\n    }\n\n    /// Converts a Native Color back to an ARGB hex string (#AARRGGBB)\n    /// - Parameter color: The color to convert.\n    /// - Throws: `AirshipColorError.incompatibleColorSpace` if the color components cannot be extracted\n    /// - Returns: The hex string in #AARRGGBB format.\n    public static func hexString(_ color: AirshipNativeColor) throws -> String {\n#if os(macOS)\n        // Convert to sRGB to ensure getRed works safely.\n        // Colors in non-RGB spaces (CMYK, pattern, etc.) will crash getRed otherwise.\n        guard let convertedColor = color.usingColorSpace(.sRGB) else {\n            throw AirshipColorError.incompatibleColorSpace(color)\n        }\n#else\n        let convertedColor = color\n        // On iOS, getRed returns a Bool.\n        guard convertedColor.getRed(nil, green: nil, blue: nil, alpha: nil) else {\n            throw AirshipColorError.incompatibleColorSpace(color)\n        }\n#endif\n\n        var r: CGFloat = 0\n        var g: CGFloat = 0\n        var b: CGFloat = 0\n        var a: CGFloat = 0\n\n        convertedColor.getRed(&r, green: &g, blue: &b, alpha: &a)\n\n        return String(\n            format: \"#%02X%02X%02X%02X\",\n            Int(round(a * 255)),\n            Int(round(r * 255)),\n            Int(round(g * 255)),\n            Int(round(b * 255))\n        )\n    }\n\n    private static func isHexString(_ string: String) -> Bool {\n        let hexPattern = \"^#?([A-Fa-f0-9]{8}|[A-Fa-f0-9]{6})$\"\n        return string.range(of: hexPattern, options: .regularExpression) != nil\n    }\n\n    private static func normalize(_ hexString: String) -> String {\n        var string = hexString.trimmingCharacters(in: .whitespacesAndNewlines)\n        if string.hasPrefix(\"#\") { string.removeFirst() }\n        return string\n    }\n}\n\n\npublic extension String {\n    /// - Note: For internal use only. :nodoc:\n    func airshipToColor(_ bundle:Bundle = Bundle.main) -> Color {\n        return AirshipColor.resolveColor(self, bundle: bundle)\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    func airshipHexToNativeColor() -> AirshipNativeColor? {\n        return AirshipColor.resolveNativeColor(self)\n    }\n}\n\npublic extension ColorScheme {\n\n    /// Resolves a SwiftUI Color based on the scheme\n    /// - Parameters:\n    ///   - light: The light color\n    ///   - dark: The dark color\n    /// - Returns: The resolved color for the current scheme\n    /// - Note: For internal use only. :nodoc:\n    func airshipResolveColor(light: Color?, dark: Color?) -> Color? {\n        switch self {\n        case .dark:\n            return dark ?? light\n        case .light:\n            return light\n        @unknown default:\n            return light\n        }\n    }\n\n    /// Resolves the Native Color (NSColor or UIColor) based on the scheme\n    /// - Parameters:\n    ///   - light: The light color\n    ///   - dark: The dark color\n    /// - Returns: The resolved native color for the current scheme\n    /// - Note: For internal use only. :nodoc:\n    func airshipResolveNativeColor(\n        light: AirshipNativeColor?,\n        dark: AirshipNativeColor?\n    ) -> AirshipNativeColor? {\n        switch self {\n        case .dark:\n            return dark ?? light\n        case .light:\n            return light\n        @unknown default:\n            return light\n        }\n    }\n\n    /// Bridge helper to resolve Native colors as SwiftUI Colors\n    /// - Parameters:\n    ///   - light: The light color\n    ///   - dark: The dark color\n    /// - Returns: The resolved color for the current scheme\n    /// - Note: For internal use only. :nodoc:\n    func airshipResolveColor(\n        light: AirshipNativeColor?,\n        dark: AirshipNativeColor?\n    ) -> Color? {\n        let resolved = self.airshipResolveNativeColor(light: light, dark: dark)\n        return resolved.map { Color($0) }\n    }\n}\n\n\npublic extension AirshipColor {\n    static var systemBackground: Color {\n#if os(macOS)\n        return Color(NSColor.windowBackgroundColor)\n#elseif os(watchOS)\n        return .black\n#elseif os(tvOS)\n        return Color(UIColor { trait in\n            return trait.userInterfaceStyle == .dark ? .black : .white\n        })\n#else\n        return Color(UIColor.systemBackground)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Airship component.\n///  - Note: For internal use only. :nodoc:\npublic protocol AirshipComponent: Sendable {\n\n    /// Called once the Airship instance is ready.\n    @MainActor\n    func airshipReady()\n\n    /// Called to handle `uairship://` deep links. The first component that\n    /// returns true will prevent others from receiving the deep link.\n    /// - Parameters:\n    ///     - deepLink: The deep link.\n    /// - Returns: true if the deep link was handled, otherwise false.\n    @MainActor\n    func deepLink(_ deepLink: URL) -> Bool\n}\n\npublic extension AirshipComponent {\n    func airshipReady() {}\n    func deepLink(_ deepLink: URL) -> Bool { return false }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// The Config object provides an interface for passing common configurable values to `Airship`.\npublic struct AirshipConfig: Decodable, Sendable {\n\n    /// The default app key. Used as the default value for `developmentAppKey` or `productionAppKey`.\n    public var defaultAppKey: String?\n\n    /// The default app secret. Used as the default value for `developmentAppSecret` or `productionAppSecret`.\n    public var defaultAppSecret: String?\n\n    /// The  app key used when `inProduction` is `false`.\n    ///\n    /// The development credentials are generally used to point to a Test Airship project which will send to\n    /// the development  APNS sandbox.\n    public var developmentAppKey: String?\n\n    /// The  app secret used when `inProduction` is `false`.\n    public var developmentAppSecret: String?\n\n    /// The log level used when `inProduction` is `false`.\n    public var developmentLogLevel: AirshipLogLevel = .debug\n\n    /// The log privacy level used when `inProduction` is `false`.  Allows logging to public console.\n    /// Defaults to `private`.\n    public var developmentLogPrivacyLevel: AirshipLogPrivacyLevel = .private\n\n    /// The  app key used when `inProduction` is `true`.\n    ///\n    /// The production credentails are generally used to point to a Live Airship project which will send to\n    /// the production  APNS sandbox.\n    public var productionAppKey: String?\n\n    /// The  app secret used when `inProduction` is `true`.\n    public var productionAppSecret: String?\n\n    /// The log privacy level used when `inProduction` is `true`.  Allows logging to public console.\n    /// Defaults to `error`.\n    public var productionLogLevel: AirshipLogLevel = .error\n\n    /// The log privacy level used when `inProduction` is `true`.  Allows logging to public console.\n    /// Only used by the default log handler.\n    /// Defaults to `private`.\n    public var productionLogPrivacyLevel: AirshipLogPrivacyLevel = .private\n\n    /// Custom log handler to be used instead of the default Airship log handler.\n    public var logHandler: (any AirshipLogHandler)? = nil\n\n    /// Auto pause InAppAutomation on launch. Defaults to `false`\n    public var autoPauseInAppAutomationOnLaunch: Bool = false\n    \n    /// Flag to enable or disable web view inspection on Airship created  web views. Applies only to iOS 16.4+.\n    /// Defaults to `false`\n    public var isWebViewInspectionEnabled: Bool = false\n\n    // Overrides the input validation used by Preference Center and Scenes.\n    public var inputValidationOverrides: AirshipInputValidation.OverridesClosure?\n\n    /// Optional closure for auth challenge certificate validation.\n    public var connectionChallengeResolver: ChallengeResolveClosure?\n    \n    /// A closure that can be used to manually recover the channel ID instead of having\n    /// Airship recover or generate an ID automatically.\n    ///\n    /// This is a delicate API that should only be used if the application can ensure the channel ID was previously created and by recovering\n    /// it will only be used by a single device. Having multiple devices with the same channel ID will cause unpredictable behavior.\n    ///\n    /// When the method is set to `restore`, the user must provide a previously generated, unique\n    /// If the closure throws an error, Airship will delay channel registration until a successful execution.\n    public var restoreChannelID: AirshipChannelCreateOptionClosure?\n\n    /// The airship cloud site. Defaults to `us`.\n    public var site: CloudSite = .us\n   \n    /// Default enabled Airship features for the app. For more details, see `PrivacyManager`.\n    /// Defaults to `all`.\n    public var enabledFeatures: AirshipFeature = .all\n\n    /// Allows resetting enabled features to match the runtime config defaults on each takeOff\n    /// Defaults to `false`\n    public var resetEnabledFeatures: Bool = false\n\n    /// Used to select between the either production (`true`) or development (`false`) credentails\n    /// and logging.\n    ///\n    /// If not set, Airship will pick the credentials based on the APNS sandbox by inspecting the profile on the\n    /// device. If Airship fails to resolve the APNS environment `inProduction` will default to `true`.\n    public var inProduction: Bool?\n\n    /// If enabled, the Airship library automatically registers for remote notifications when push is enabled\n    /// and intercepts incoming notifications in both the foreground and upon launch.\n    ///\n    /// If disabled, the app needs to forward methods to Airship. See https://docs.airship.com/platform/mobile/setup/sdk/ios/#automatic-integration\n    /// for more details.\n    ///\n    /// Defaults to `true`.\n    public var isAutomaticSetupEnabled: Bool = true\n\n    /// An array of `UAURLAllowList` entry strings.\n    /// This url allow list is used for validating which URLs can be opened or load the JavaScript native bridge.\n    /// It affects landing pages, the open external URL and wallet actions,\n    /// deep link actions (if a delegate is not set), and HTML in-app messages.\n    ///\n    /// - NOTE: See `UAURLAllowList` for pattern entry syntax.\n    public var urlAllowList: [String]? = nil\n\n    /// An array of` UAURLAllowList` entry strings.\n    /// This url allow list is used for validating which URLs can load the JavaScript native bridge,\n    /// It affects Landing Pages, Message Center and HTML In-App Messages.\n    ///\n    /// - NOTE: See `UAURLAllowList` for pattern entry syntax.\n    public var urlAllowListScopeJavaScriptInterface: [String]? = nil\n\n    /// An array of UAURLAllowList entry strings.\n    /// This url allow list is used for validating which URLs can be opened.\n    /// It affects landing pages, the open external URL and wallet actions,\n    /// deep link actions (if a delegate is not set), and HTML in-app messages.\n    ///\n    /// - NOTE: See `UAURLAllowList` for pattern entry syntax.\n    public var urlAllowListScopeOpenURL: [String]? = nil\n\n    /// The iTunes ID used for Rate App Actions.\n    public var itunesID: String?\n\n    /// Toggles Airship analytics. Defaults to `true`. If set to `false`, many Airship features will not be\n    /// available to this application.\n    public var isAnalyticsEnabled: Bool = true\n\n    /// The Airship default message center style configuration file.\n    public var messageCenterStyleConfig: String?\n\n    /// If set to `true`, the Airship user will be cleared if the application is\n    /// restored on a different device from an encrypted backup.\n    ///\n    /// Defaults to `false`.\n    public var clearUserOnAppRestore: Bool = false\n\n    /// If set to `true`, the application will clear the previous named user ID on a\n    /// re-install. Defaults to `false`.\n    public var clearNamedUserOnAppRestore: Bool = false\n\n    /// Flag indicating whether channel capture feature is enabled or not.\n    ///\n    /// Defaults to `true`.\n    public var isChannelCaptureEnabled: Bool = true\n\n    /// Flag indicating whether delayed channel creation is enabled. If set to `true` channel\n    /// creation will not occur until channel creation is manually enabled.\n    ///\n    /// Defaults to `false`.\n    public var isChannelCreationDelayEnabled: Bool = false\n\n    /// Flag indicating whether extended broadcasts are enabled. If set to `true` the AirshipReady NSNotification\n    /// will contain additional data: the channel identifier and the app key.\n    ///\n    /// Defaults to `false`.\n    public var isExtendedBroadcastsEnabled: Bool = false\n\n    /// If set to `true`, the Airship SDK will request authorization to use\n    /// notifications from the user. Apps that set this flag to `false` are\n    /// required to request authorization themselves.\n    ///\n    /// Defaults to `true`.\n    public var requestAuthorizationToUseNotifications: Bool = true\n\n    /// If set to `true`, the SDK will wait for an initial remote config instead of falling back on default API URLs.\n    ///\n    /// Defaults to `true`.\n    public var requireInitialRemoteConfigEnabled: Bool = true\n\n    /// **For apps using the Swift 5 language mode:** It is **strongly recommended** to leave\n    /// this value as `false` (the default).\n    ///\n    /// A suspected compiler bug in **Xcode 16.1 and newer** can cause fatal runtime crashes\n    /// in this specific configuration. This flag disables the problematic code path.\n    /// For more details, see: https://github.com/urbanairship/ios-library/issues/434.\n    ///\n    /// ---\n    ///\n    /// **For apps using Swift 6 or newer:** You can set this to `true` to enable dynamic\n    /// background wait time calculation. This helps the SDK send off pending operations\n    /// before the app is fully backgrounded.\n    ///\n    /// If `false`, the SDK will wait a short, fixed amount of time.\n    ///\n    /// Defaults to `false`.\n    public var isDynamicBackgroundWaitTimeEnabled: Bool = false\n\n    /// The Airship URL used to pull the initial config. This should only be set if you are using custom domains\n    /// that forward to Airship.\n    public var initialConfigURL: String?\n\n    /// If set to `true`, the SDK will use the preferred locale. Otherwise it will use the current locale.\n    ///\n    /// Defaults to `false`.\n    public var useUserPreferredLocale: Bool = false\n\n    /// If set to `true`, Message Center will attempt to be restored between reinstalls. If `false`,\n    /// the Message Center user will be reset and the Channel will not be able to use the user\n    /// as an identity hint to recover the past Channel ID.\n    ///\n    /// Defaults to `true`.\n    public var restoreMessageCenterOnReinstall: Bool = true\n\n    /// Enables Airship Debug features. When enabled, the debug manager becomes available\n    /// for programmatic access to the Airship Debug view which provides insights into\n    /// channel information, events, and other debugging data.\n    ///\n    /// - Note: This flag alone does not enable shake-to-debug functionality. You must also\n    ///   add shake detection to your views using `.airshipDebugOnShake()` (SwiftUI) or\n    ///   implement shake gesture handling in UIKit.\n    /// - Note: This should typically be disabled in production builds for security reasons.\n    /// - Note: Requires the AirshipDebug module to be included in your app.\n    ///\n    /// Defaults to `false`.\n    public var isAirshipDebugEnabled: Bool = false\n\n    enum CodingKeys: String, CodingKey {\n        case defaultAppKey\n        case defaultAppSecret\n        case developmentAppKey\n        case developmentAppSecret\n        case productionAppKey\n        case productionAppSecret\n        case developmentLogLevel\n        case developmentLogPrivacyLevel\n        case productionLogLevel\n        case productionLogPrivacyLevel\n        case resetEnabledFeatures\n        case enabledFeatures\n        case site\n        case messageCenterStyleConfig\n        case isExtendedBroadcastsEnabled\n        case isChannelCreationDelayEnabled\n        case requireInitialRemoteConfigEnabled\n        case urlAllowListScopeOpenURL\n        case inProduction\n        case autoPauseInAppAutomationOnLaunch\n        case isWebViewInspectionEnabled\n        case isAutomaticSetupEnabled\n        case urlAllowList\n        case urlAllowListScopeJavaScriptInterface\n        case itunesID\n        case isAnalyticsEnabled\n        case clearUserOnAppRestore\n        case clearNamedUserOnAppRestore\n        case isChannelCaptureEnabled\n        case requestAuthorizationToUseNotifications\n        case initialConfigURL\n        case deviceAPIURL\n        case analyticsURL\n        case remoteDataAPIURL\n        case useUserPreferredLocale\n        case restoreMessageCenterOnReinstall\n        case isAirshipDebugEnabled\n        case isDynamicBackgroundWaitTimeEnabled\n\n        // legacy keys\n        \n        case LOG_LEVEL\n        case PRODUCTION_APP_KEY\n        case PRODUCTION_APP_SECRET\n        case DEVELOPMENT_APP_KEY\n        case DEVELOPMENT_APP_SECRET\n        case APP_STORE_OR_AD_HOC_BUILD\n        case isInProduction\n        case whitelist\n        case analyticsEnabled\n        case extendedBroadcastsEnabled\n        case channelCaptureEnabled\n        case channelCreationDelayEnabled\n        case automaticSetupEnabled\n    }\n\n    /// Creates an instance with empty values.\n    /// - Returns: A config with empty values.\n    public init() {\n    }\n\n    /// Creates an instance using the values set in the `AirshipConfig.plist` file.\n    /// - Returns: A config with values from `AirshipConfig.plist` file.\n    public static func `default`() throws -> AirshipConfig {\n        guard\n            let path = Bundle.main.path(\n                forResource: \"AirshipConfig\",\n                ofType: \"plist\"\n            )\n        else {\n            throw AirshipErrors.error(\"AirshipConfig.plist file is missing.\")\n        }\n\n        return try AirshipConfig(fromPlist: path)\n    }\n\n    /**\n     * Creates an instance using the values found in the specified `.plist` file.\n     * - Parameter fromPlist: The path of the specified plist file.\n     * - Returns: A config with values from the specified file.\n     */\n    public init(fromPlist path: String) throws {\n        guard let data = FileManager.default.contents(atPath: path) else {\n            throw AirshipErrors.error(\"Failed to load contents of the plist file \\(path)\")\n        }\n\n        let decoder = PropertyListDecoder()\n        self = try decoder.decode(AirshipConfig.self, from: data)\n    }\n    \n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        // Development\n        self.developmentAppKey = try container.decodeFirst(\n            String.self,\n            forKeys: [.developmentAppKey, .DEVELOPMENT_APP_KEY]\n        )\n\n        self.developmentAppSecret = try container.decodeFirst(\n            String.self,\n            forKeys: [.developmentAppSecret, .DEVELOPMENT_APP_SECRET]\n        )\n\n        self.developmentLogLevel = try container.decodeFirst(\n            AirshipLogLevel.self,\n            forKeys: [.developmentLogLevel, .LOG_LEVEL]\n        ) ?? self.developmentLogLevel\n\n        self.developmentLogPrivacyLevel = try container.decodeIfPresent(\n            AirshipLogPrivacyLevel.self,\n            forKey: .developmentLogPrivacyLevel\n        ) ?? self.developmentLogPrivacyLevel\n\n\n        // Production\n        self.productionAppKey = try container.decodeFirst(\n            String.self,\n            forKeys: [.productionAppKey, .PRODUCTION_APP_KEY]\n        )\n\n        self.productionAppSecret = try container.decodeFirst(\n            String.self,\n            forKeys: [.productionAppSecret, .PRODUCTION_APP_SECRET]\n        )\n\n        self.productionLogLevel = try container.decodeIfPresent(\n            AirshipLogLevel.self,\n            forKey: .productionLogLevel\n        ) ?? self.productionLogLevel\n\n        self.productionLogPrivacyLevel = try container.decodeIfPresent(\n            AirshipLogPrivacyLevel.self,\n            forKey: .productionLogPrivacyLevel\n        ) ?? self.productionLogPrivacyLevel\n\n\n        // In production\n        self.inProduction = try container.decodeFirst(\n            Bool.self,\n            forKeys: [.inProduction, .isInProduction, .APP_STORE_OR_AD_HOC_BUILD]\n        )\n\n        // Site\n        self.site = try container.decodeIfPresent(\n            CloudSite.self,\n            forKey: .site\n        ) ?? self.site\n\n        // Default credentials\n        self.defaultAppKey = try container.decodeIfPresent(\n            String.self,\n            forKey: .defaultAppKey\n        )\n        self.defaultAppSecret = try container.decodeIfPresent(\n            String.self,\n            forKey: .defaultAppSecret\n        )\n\n        // Allow lists\n        self.urlAllowList = try container.decodeFirst(\n            [String].self,\n            forKeys: [.urlAllowList, .whitelist]\n        )\n\n        self.urlAllowListScopeOpenURL = try container.decodeIfPresent(\n            [String].self,\n            forKey: .urlAllowListScopeOpenURL\n        )\n\n        self.urlAllowListScopeJavaScriptInterface = try container.decodeIfPresent(\n            [String].self,\n            forKey: .urlAllowListScopeJavaScriptInterface\n        )\n\n        // Features\n        self.resetEnabledFeatures = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .resetEnabledFeatures\n        ) ?? self.resetEnabledFeatures\n\n        self.enabledFeatures = try container.decodeIfPresent(\n            AirshipFeature.self,\n            forKey: .enabledFeatures\n        ) ?? self.enabledFeatures\n\n        self.isAnalyticsEnabled = try container.decodeFirst(\n            Bool.self,\n            forKeys: [.isAnalyticsEnabled, .analyticsEnabled]\n        ) ?? self.isAnalyticsEnabled\n\n\n        // Message Center\n        self.messageCenterStyleConfig = try container.decodeIfPresent(\n            String.self,\n            forKey: .messageCenterStyleConfig\n        )\n\n        self.restoreMessageCenterOnReinstall = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .restoreMessageCenterOnReinstall\n        ) ?? self.restoreMessageCenterOnReinstall\n\n\n        // Core\n        self.initialConfigURL = try container.decodeIfPresent(\n            String.self,\n            forKey: .initialConfigURL\n        )\n\n        self.itunesID = try container.decodeIfPresent(\n            String.self,\n            forKey: .itunesID\n        )\n\n        self.isExtendedBroadcastsEnabled = try container.decodeFirst(\n            Bool.self,\n            forKeys: [.isExtendedBroadcastsEnabled, .extendedBroadcastsEnabled]\n        ) ?? self.isExtendedBroadcastsEnabled\n\n        self.isChannelCreationDelayEnabled = try container.decodeFirst(\n            Bool.self,\n            forKeys: [.isChannelCreationDelayEnabled, .channelCreationDelayEnabled]\n        ) ?? self.isChannelCreationDelayEnabled\n\n        self.requireInitialRemoteConfigEnabled = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .requireInitialRemoteConfigEnabled\n        ) ?? self.requireInitialRemoteConfigEnabled\n\n        self.autoPauseInAppAutomationOnLaunch = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .autoPauseInAppAutomationOnLaunch\n        ) ?? self.autoPauseInAppAutomationOnLaunch\n\n        self.isWebViewInspectionEnabled = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .isWebViewInspectionEnabled\n        ) ?? self.isWebViewInspectionEnabled\n\n        self.isAutomaticSetupEnabled = try container.decodeFirst(\n            Bool.self,\n            forKeys: [.isAutomaticSetupEnabled, .automaticSetupEnabled]\n        ) ?? self.isAutomaticSetupEnabled\n\n        self.clearUserOnAppRestore = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .clearUserOnAppRestore\n        ) ?? false\n\n        self.clearNamedUserOnAppRestore = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .clearNamedUserOnAppRestore\n        ) ?? self.clearNamedUserOnAppRestore\n\n        self.isChannelCaptureEnabled = try container.decodeFirst(\n            Bool.self,\n            forKeys: [.isChannelCaptureEnabled, .channelCaptureEnabled]\n        ) ?? self.isChannelCaptureEnabled\n\n        self.requestAuthorizationToUseNotifications = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .requestAuthorizationToUseNotifications\n        ) ?? self.requestAuthorizationToUseNotifications\n\n        self.useUserPreferredLocale = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .useUserPreferredLocale\n        ) ?? self.useUserPreferredLocale\n\n        self.isAirshipDebugEnabled = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .isAirshipDebugEnabled\n        ) ?? self.isAirshipDebugEnabled\n\n        self.isDynamicBackgroundWaitTimeEnabled = try container.decodeIfPresent(\n            Bool.self,\n            forKey: .isDynamicBackgroundWaitTimeEnabled\n        ) ?? self.isDynamicBackgroundWaitTimeEnabled\n    }\n\n    /// Validates credentails\n    /// - Parameters:\n    ///     - inProduction: To validate production or development credentials\n    public func validateCredentials(inProduction: Bool) throws {\n        _ = try self.resolveCredentails(inProduction)\n    }\n\n    func logIssues() {\n        if (inProduction == nil) {\n            do {\n                try validateCredentials(inProduction: true)\n            } catch {\n                AirshipLogger.warn(\"Airship will automatically pick between production and development credentials, but production credentials are invalid \\(error)\")\n            }\n\n            do {\n                try validateCredentials(inProduction: false)\n            } catch {\n                AirshipLogger.warn(\"Airship will automatically pick between production and development credentials, but development credentials are invalid \\(error)\")\n            }\n\n            if productionAppKey == developmentAppKey {\n                AirshipLogger.warn(\"Production & Development app keys match\")\n            }\n\n            if productionAppSecret == developmentAppSecret {\n                AirshipLogger.warn(\"Production & Development app secrets match\")\n            }\n        }\n\n        if (urlAllowList == nil && urlAllowListScopeOpenURL == nil) {\n            AirshipLogger.impError(\n                \"The Airship config options is missing URL allow list rules for SCOPE_OPEN \" +\n                \"that controls what external URLs are able to be opened externally or loaded \" +\n                \"in a web view by Airship. By default, all URLs will be allowed. \" +\n                \"To suppress this error, specify the config urlAllowListScopeOpenURL = [*] \" +\n                \"to keep the defaults, or by providing a list of rules that your app expects. \" +\n                \"See https://docs.airship.com/platform/mobile/setup/sdk/ios/#url-allow-list \" +\n                \"for more information.\"\n            )\n        }\n    }\n}\n\npublic extension AirshipConfig {\n    /// Resolves the inProduction flag. The value will be resolved with:\n    /// - `inProduction` if set\n    /// - `false` if the target environment is a simulator\n    /// - by inspecting the `embedded.mobileprovision` file to look up the APNS environment.\n    ///\n    /// - returns  The resolved in production flag.\n    /// - throws If the APNS fails to resolve to an environment. Airship will fallback to assuming its inProduction during\n    /// takeOff.\n    func resolveInProduction() throws -> Bool {\n        if let inProduction {\n            return inProduction\n        }\n\n#if targetEnvironment(simulator)\n        return false\n#else\n        return try APNSEnvironment.isProduction()\n#endif\n    }\n}\n\n// The Channel generation method. In `automatic` mode Airship will generate a new channelID and create a new channel.\n// If the restore option is specified and `channelID` is a correct ID, Airship will try to restore a channel with the specified ID\npublic enum ChannelGenerationMethod: Sendable {\n    case automatic\n    case restore(channelID: String)\n}\n\npublic typealias AirshipChannelCreateOptionClosure = (@Sendable () async throws -> ChannelGenerationMethod)\n\nextension AirshipConfig {\n    func resolveCredentails(_ inProduction: Bool) throws -> AirshipAppCredentials {\n        let appKey = (inProduction ? productionAppKey : developmentAppKey) ?? defaultAppKey\n        let appSecret = (inProduction ? productionAppSecret : developmentAppSecret) ?? defaultAppSecret\n\n        let matchPred = NSPredicate(format: \"SELF MATCHES %@\", \"^\\\\S{22}+$\")\n\n        guard\n            let appKey,\n            matchPred.evaluate(with: appKey),\n            let appSecret,\n            matchPred.evaluate(with: appSecret),\n            appKey != appSecret\n        else {\n            throw AirshipErrors.error(\n                \"Invalid app credentials \\(appKey ?? \"\"):\\(appSecret ?? \"\")\"\n            )\n        }\n\n        return AirshipAppCredentials(\n            appKey: appKey,\n            appSecret: appSecret\n        )\n    }\n}\n\nfileprivate extension KeyedDecodingContainerProtocol {\n    func decodeFirst<T: Decodable>(_ type: T.Type, forKeys: [Self.Key]) throws -> T? where T : Decodable {\n        for (_, key) in forKeys.enumerated() {\n            if let value = try self.decodeIfPresent(type, forKey: key) {\n                return value\n            }\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipContact.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\n\n/// Airship contact. A contact is distinct from a channel and  represents a \"user\"\n/// within Airship. Contacts may be named and have channels associated with it.\npublic protocol AirshipContact: AnyObject, Sendable {\n\n    /// Current named user ID\n    var namedUserID: String? { get async }\n\n    /// The named user ID current value publisher.\n    var namedUserIDPublisher: AnyPublisher<String?, Never> { get }\n\n    /// Conflict event publisher.\n    var conflictEventPublisher: AnyPublisher<ContactConflictEvent, Never> { get }\n\n    /// Notifies any edits to the subscription lists.\n    var subscriptionListEdits: AnyPublisher<ScopedSubscriptionListEdit, Never> { get }\n\n    /// Fetches subscription lists.\n    /// - Returns: Subscriptions lists.\n    func fetchSubscriptionLists() async throws ->  [String: [ChannelScope]]\n\n    /**\n     * Re-sends the double opt in prompt via the pending or registered channel.\n     * - Parameters:\n     *   - channel: The pending or registered channel to resend the double opt-in prompt to.\n     */\n    func resend(_ channel: ContactChannel)\n\n    /**\n     * Opts out and disassociates channel\n     * - Parameters:\n     *   - channel: The channel to opt-out and disassociate\n     */\n    func disassociateChannel(_ channel: ContactChannel)\n\n    /// Contact channel updates stream.\n    var contactChannelUpdates: AsyncStream<ContactChannelsResult> { get }\n\n    /// Contact channel updates publisher.\n    var contactChannelPublisher: AnyPublisher<ContactChannelsResult, Never> { get }\n\n    /**\n     * Associates the contact with the given named user identifier.\n     * The named user ID must be between 1 and 128 characters\n     * - Parameters:\n     *   - namedUserID: The named user ID.\n     */\n    func identify(_ namedUserID: String)\n\n    /**\n     * Disassociate the channel from its current contact, and create a new\n     * un-named contact.\n     */\n    func reset()\n\n    /**\n     * Can be called after the app performs a remote named user association for the channel instead\n     * of using `identify` or `reset` through the SDK. When called, the SDK will refresh the contact\n     * data. Applications should only call this method when the user login has changed.\n     */\n    func notifyRemoteLogin()\n\n    /**\n     * Edits tags.\n     * - Returns: A tag groups editor.\n     */\n    func editTagGroups() -> TagGroupsEditor\n\n    /**\n     * Edits tags.\n     * - Parameters:\n     *   - editorBlock: The editor block with the editor. The editor will `apply` will be called after the block is executed.\n     */\n    func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void)\n\n    /**\n     * Edits attributes.\n     * - Returns: An attributes editor.\n     */\n    func editAttributes() -> AttributesEditor\n\n    /**\n     * Edits  attributes.\n     * - Parameters:\n     *   - editorBlock: The editor block with the editor. The editor will `apply` will be called after the block is executed.\n     */\n    func editAttributes(_ editorBlock: (AttributesEditor) -> Void)\n\n    /**\n     * Associates an Email channel to the contact.\n     * - Parameters:\n     *   - address: The email address.\n     *   - options: The email channel registration options.\n     */\n    func registerEmail(_ address: String, options: EmailRegistrationOptions)\n\n    /**\n     * Associates a SMS channel to the contact.\n     * - Parameters:\n     *   - msisdn: The SMS msisdn.\n     *   - options: The SMS channel registration options.\n     */\n    func registerSMS(_ msisdn: String, options: SMSRegistrationOptions)\n\n    /**\n     * Associates an Open channel to the contact.\n     * - Parameters:\n     *   - address: The open channel address.\n     *   - options: The open channel registration options.\n     */\n    func registerOpen(_ address: String, options: OpenRegistrationOptions)\n\n    /**\n     * Associates a channel to the contact.\n     * - Parameters:\n     *   - channelID: The channel ID.\n     *   - type: The channel type.\n     */\n    func associateChannel(_ channelID: String, type: ChannelType)\n\n    /// Begins a subscription list editing session\n    /// - Returns: A Scoped subscription list editor\n    func editSubscriptionLists() -> ScopedSubscriptionListEditor\n\n    /// Begins a subscription list editing session\n    /// - Parameter editorBlock: A scoped subscription list editor block.\n    func editSubscriptionLists(\n        _ editorBlock: (ScopedSubscriptionListEditor) -> Void\n    )\n}\n\n\nprotocol InternalAirshipContact: AirshipContact {\n    var contactID: String? { get async }\n    var authTokenProvider: any AuthTokenProvider { get }\n\n    func getStableContactID() async -> String\n\n    var contactIDInfo: ContactIDInfo? { get async }\n    var contactIDUpdates: AnyPublisher<ContactIDInfo, Never> { get }\n\n    func getStableContactInfo() async -> StableContactInfo\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipCoreDataPredicate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic struct AirshipCoreDataPredicate: Sendable {\n    private let format: String\n    private let args: [any Sendable]?\n    \n    public init(format: String, args: [any Sendable]? = nil) {\n        self.format = format\n        self.args = args\n    }\n    \n    public func toNSPredicate() -> NSPredicate {\n        guard let args = args else {\n            return NSPredicate(format: format)\n        }\n        return NSPredicate(format: format, argumentArray: args)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipCoreResources.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Resources for AirshipCore\npublic final class AirshipCoreResources {\n\n    /// Module bundle\n    public static let bundle: Bundle = resolveBundle()\n\n    private static func resolveBundle() -> Bundle {\n#if SWIFT_PACKAGE\n        AirshipLogger.trace(\"Using Bundle.module for AirshipCore\")\n        let bundle = Bundle.module\n#if DEBUG\n        if bundle.resourceURL == nil {\n            assertionFailure(\"\"\"\n            AirshipCore module was built with SWIFT_PACKAGE\n            but no resources were found. Check your build configuration.\n            \"\"\")\n        }\n#endif\n        return bundle\n#endif\n\n        return Bundle.airshipFindModule(\n            moduleName: \"AirshipCore\",\n            sourceBundle: Bundle(for: Self.self)\n        )\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipDate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: For internal use only. :nodoc:\npublic final class AirshipDate: AirshipDateProtocol {\n    public static let shared: AirshipDate = AirshipDate()\n    public init() {}\n    public var now: Date {\n        return Date()\n    }\n}\n\n/// - Note: For internal use only. :nodoc:\npublic protocol AirshipDateProtocol: Sendable {\n    var now: Date { get }\n}\n\n\nextension Date {\n    var millisecondsSince1970: Int64 {\n        Int64((self.timeIntervalSince1970 * 1000.0).rounded())\n    }\n\n    init(milliseconds: Int64) {\n        self = Date(timeIntervalSince1970: TimeInterval(milliseconds / 1000))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipDateFormatter.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipDateFormatter {\n\n    public enum Format: Int {\n        /// ISO 8601\n        case iso\n        /// ISO 8601 with delimitter\n        case isoDelimitter\n        /// Short date & time format\n        case relativeShort\n        /// Short date format\n        case relativeShortDate\n        /// Full date & time format\n        case relativeFull\n        /// Full date format\n        case relativeFullDate\n    }\n\n    private static let dateFormatterISO: DateFormatter = createDateFormatter {\n        formatter in\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.timeStyle = .full\n        formatter.dateFormat = \"yyyy-MM-dd HH:mm:ss\"\n        formatter.timeZone = TimeZone(secondsFromGMT: 0)\n    }\n\n    private static let dateFormatterISOWithDelimiter: DateFormatter =\n        createDateFormatter { formatter in\n            formatter.locale = Locale(identifier: \"en_US_POSIX\")\n            formatter.timeStyle = .full\n            formatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss\"\n            formatter.timeZone = TimeZone(secondsFromGMT: 0)\n        }\n\n    private static let dateFormatterRelativeFull: DateFormatter =\n        createDateFormatter { formatter in\n            formatter.timeStyle = .full\n            formatter.dateStyle = .full\n            formatter.doesRelativeDateFormatting = true\n        }\n\n    private static let dateFormatterRelativeShort: DateFormatter =\n        createDateFormatter { formatter in\n            formatter.timeStyle = .short\n            formatter.dateStyle = .short\n            formatter.doesRelativeDateFormatting = true\n        }\n\n    private static let dateFormatterRelativeShortDate: DateFormatter =\n        createDateFormatter { formatter in\n            formatter.timeStyle = .none\n            formatter.dateStyle = .short\n            formatter.doesRelativeDateFormatting = true\n        }\n\n    private static let dateFormatterRelativeFullDate: DateFormatter =\n        createDateFormatter { formatter in\n            formatter.timeStyle = .none\n            formatter.dateStyle = .full\n            formatter.doesRelativeDateFormatting = true\n        }\n\n    /// Parses ISO 8601 date strings.\n    ///\n    /// Supports timestamps with just year all the way up to seconds with and without the optional `T` delimiter.\n    ///\n    /// - Parameter from: The ISO 8601 timestamp.\n    ///\n    /// - Returns: A parsed Date object, or nil if the timestamp is not a valid format.\n    public class func date(fromISOString from: String) -> Date? {\n        if let date = dateFormatterISO.date(from: from) {\n            return date\n        }\n\n        if let date = dateFormatterISOWithDelimiter.date(from: from) {\n            return date\n        }\n\n        let formatter = DateFormatter()\n        formatter.locale = Locale(identifier: \"en_US_POSIX\")\n        formatter.timeZone = TimeZone(secondsFromGMT: 0)\n\n        // All the various formats\n        let formats = [\n            \"yyyy-MM-dd'T'HH:mm:ss.SSS\",\n            \"yyyy-MM-dd'T'HH:mm:ss\",\n            \"yyyy-MM-dd'T'HH:mm:ss'Z'\",\n            \"yyyy-MM-dd HH:mm:ss\",\n            \"yyyy-MM-dd'T'HH:mm\",\n            \"yyyy-MM-dd HH:mm\",\n            \"yyyy-MM-dd'T'HH\",\n            \"yyyy-MM-dd HH\",\n            \"yyyy-MM-dd\",\n            \"yyyy-MM\",\n            \"yyyy\",\n        ]\n\n        for format in formats {\n            formatter.dateFormat = format\n            if let date = formatter.date(from: from) {\n                return date\n            }\n        }\n\n        return nil\n    }\n\n    public static func string(fromDate date: Date, format: Format) -> String {\n        switch format {\n        case .iso:\n            return self.dateFormatterISO.string(from: date)\n        case .isoDelimitter:\n            return self.dateFormatterISOWithDelimiter.string(from: date)\n        case .relativeShortDate:\n            return self.dateFormatterRelativeShortDate.string(from: date)\n        case .relativeFullDate:\n            return self.dateFormatterRelativeFullDate.string(from: date)\n        case .relativeFull:\n            return self.dateFormatterRelativeFull.string(from: date)\n        case .relativeShort:\n            return self.dateFormatterRelativeShort.string(from: date)\n        }\n    }\n\n    private static func createDateFormatter(editBlock: (DateFormatter) -> Void)\n        -> DateFormatter\n    {\n        let formatter = DateFormatter()\n        editBlock(formatter)\n        return formatter\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipDevice.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n\n/// Internal helper for platform-specific device info.\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipDevice: Sendable {\n\n    /// Returns the device model name (e.g., \"iPhone14,3\" or \"MacBookPro18,1\")\n    public static let modelIdentifier: String = {\n#if targetEnvironment(macCatalyst)\n        return \"mac\"\n#elseif os(macOS)\n        // Native macOS\n        var size = 0\n        sysctlbyname(\"hw.model\", nil, &size, nil, 0)\n        var model = [CChar](repeating: 0, count: size)\n        sysctlbyname(\"hw.model\", &model, &size, nil, 0)\n\n        let bytes = model.map { UInt8(bitPattern: $0) }\n        return String(decoding: bytes.dropLast(), as: UTF8.self)\n#else\n        // iOS / tvOS / etc\n        var systemInfo = utsname()\n        uname(&systemInfo)\n\n        // Use withUnsafePointer to convert the C-char tuple to a String safely\n        return withUnsafePointer(to: &systemInfo.machine) {\n            $0.withMemoryRebound(to: CChar.self, capacity: Int(_SYS_NAMELEN)) {\n                String(cString: $0)\n            }\n        }\n#endif\n    }()\n\n    /// The generic device category (e.g., \"iPhone\", \"iPad\").\n    /// Matches the legacy UIDevice.current.model.\n    @MainActor\n    public static var model: String {\n#if os(iOS) || os(tvOS) || os(visionOS)\n        return UIDevice.current.model\n#elseif os(watchOS)\n        return WKInterfaceDevice.current().model\n#else\n        return \"Mac\"\n#endif\n    }\n\n    /// Returns the system name (e.g., \"iOS\", \"tvOS\", \"watchOS\").\n        /// This matches UIDevice.current.systemName on Apple platforms.\n        @MainActor\n    public static var deviceFamily: String {\n#if os(watchOS)\n        return WKInterfaceDevice.current().systemName\n#elseif canImport(UIKit)\n        return UIDevice.current.systemName\n#elseif os(macOS)\n        return \"macOS\"\n#else\n        return \"Unknown\"\n#endif\n    }\n\n    /// Returns the OS name\n    public static var osName: String {\n#if os(macOS)\n        return \"macOS\"\n#elseif targetEnvironment(macCatalyst)\n        // Catalyst returns true for os(iOS), so check this first\n        return \"macOS\"\n#elseif os(visionOS)\n        return \"visionOS\"\n#elseif os(tvOS)\n        return \"tvOS\"\n#elseif os(iOS)\n        return \"iOS\"\n#else\n        return \"Unknown\"\n#endif\n    }\n\n    /// Returns the OS Version string\n    @MainActor\n    public static var osVersion: String {\n#if os(watchOS)\n        return WKInterfaceDevice.current().systemVersion\n#elseif os(macOS)\n        let version = ProcessInfo.processInfo.operatingSystemVersion\n        return \"\\(version.majorVersion).\\(version.minorVersion).\\(version.patchVersion)\"\n#elseif canImport(UIKit)\n        return UIDevice.current.systemVersion\n#else\n        return \"0.0.0\"\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipDeviceAudienceResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipDeviceAudienceResult: Sendable, Codable, Equatable {\n    public var isMatch: Bool\n    public var reportingMetadata: [AirshipJSON]?\n\n    init(isMatch: Bool, reportingMetadata: [AirshipJSON]? = nil) {\n        self.isMatch = isMatch\n        self.reportingMetadata = reportingMetadata\n    }\n\n    mutating func negate() {\n        isMatch = !isMatch\n    }\n\n    public static let match: AirshipDeviceAudienceResult = .init(isMatch: true)\n    public static let miss: AirshipDeviceAudienceResult = .init(isMatch: false)\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipDeviceID.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol AirshipDeviceIDProtocol: Actor {\n    var value: String { get async }\n}\n\n/**\n * Access to a generated UUID stored in the keychain for this device only. Used to detect app restore only.\n */\nactor AirshipDeviceID: AirshipDeviceIDProtocol {\n    private static let deviceKeychainID: String = \"com.urbanairship.deviceID\"\n    private var cached: String? = nil\n    private let keychain: any AirshipKeychainAccessProtocol\n    private var queue: AirshipSerialQueue = AirshipSerialQueue()\n    private let appKey: String\n\n    init(\n        appKey: String,\n        keychain: any AirshipKeychainAccessProtocol = AirshipKeychainAccess.shared\n    ) {\n        self.appKey = appKey\n        self.keychain = keychain\n    }\n\n    var value: String {\n        get async {\n            return await queue.runSafe {\n                if let cached = await self.cached {\n                    return cached\n                } else if let fromKeychain = await self.keychain.readCredentails(\n                    identifier: AirshipDeviceID.deviceKeychainID,\n                    appKey: self.appKey\n                ) {\n                    await self.cacheDeviceId(fromKeychain.password)\n                    return fromKeychain.password\n                } else {\n                    let deviceID = UUID().uuidString\n                    await self.cacheDeviceId(deviceID)\n                    let result = await self.keychain.writeCredentials(\n                        AirshipKeychainCredentials(username: \"airship\", password: deviceID),\n                        identifier: AirshipDeviceID.deviceKeychainID,\n                        appKey: self.appKey\n                    )\n                    if (!result) {\n                        AirshipLogger.error(\"Failed to device ID to the keychain\")\n                    }\n                    return deviceID\n                }\n            }\n        }\n    }\n\n    private func cacheDeviceId(_ deviceID: String) {\n        self.cached = deviceID\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipDisplayTarget.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(watchOS)\n\nimport Foundation\npublic import SwiftUI\n\n/// A factory for creating display targets that manage the presentation of views in windows.\n///\n/// `AirshipDisplayTarget` provides a unified interface for displaying content as either\n/// banners or modals. It abstracts the window management logic and provides appropriate\n/// display implementations based on the requested display type.\n///\n/// ## Usage\n///\n/// ```swift\n/// let displayTarget = AirshipDisplayTarget { scene in\n///     return scene\n/// }\n///\n/// let displayable = displayTarget.prepareDisplay(for: .modal)\n/// try displayable.display { windowInfo in\n///     let viewController = MyViewController()\n///     return viewController\n/// }\n/// ```\n///\n///\n\n\n/// - Note: for internal use only.  :nodoc:\n@MainActor\npublic final class AirshipDisplayTarget {\n\n    /// The type of display presentation.\n    ///\n    /// Different display types have different behaviors:\n    /// - `.banner`: Displays content as an overlay on top of existing content\n    /// - `.modal`: Displays content in a new window that appears above all other content\n    public enum DisplayType {\n        /// Banner display that overlays on top of existing content.\n        case banner\n        /// Modal display that appears in a new window above all other content.\n        case modal\n    }\n\n    /// Information about the window where content will be displayed.\n    ///\n    /// This struct provides metadata about the target window, such as its size,\n    /// which can be used to configure the view controller appropriately.\n    public struct WindowInfo: Sendable {\n        /// The size of the window in points.\n        public var size: CGSize\n    }\n\n    /// A protocol for objects that can display and dismiss view controllers.\n    ///\n    /// Implementations of this protocol handle the lifecycle of displaying content\n    /// in a window, including creating the appropriate window hierarchy and managing\n    /// the view controller's presentation.\n    ///\n    @MainActor\n    public protocol Displayable: AnyObject, Sendable {\n\n#if os(macOS)\n        /// Displays a view controller provided by the given closure.\n        ///\n        /// - Parameter viewControllerProvider: A closure that creates and returns\n        ///   a view controller to display. The closure receives `WindowInfo` containing\n        ///   information about the target window.\n        /// - Throws: An error if the view controller cannot be displayed (e.g., if\n        ///   a window cannot be found or created).\n        func display(viewControllerProvider: @MainActor (WindowInfo) -> NSViewController) throws\n#else\n        /// Displays a view controller provided by the given closure.\n        ///\n        /// - Parameter viewControllerProvider: A closure that creates and returns\n        ///   a view controller to display. The closure receives `WindowInfo` containing\n        ///   information about the target window.\n        /// - Throws: An error if the view controller cannot be displayed (e.g., if\n        ///   a window cannot be found or created).\n        func display(viewControllerProvider: @MainActor (WindowInfo) -> UIViewController) throws\n#endif\n\n        /// Dismisses the currently displayed view controller and cleans up resources.\n        ///\n        /// This method should be called when the displayed content should be removed\n        /// from the screen. It handles animation and cleanup of the window hierarchy.\n        func dismiss()\n    }\n\n    #if os(macOS)\n\n    /// Creates a new display target\n    public init() {}\n\n    /// Prepares a displayable object for the specified display type.\n    ///\n    /// This method creates and returns an appropriate `Displayable` implementation\n    /// based on the requested display type. The returned object can then be used to\n    /// display view controllers.\n    ///\n    /// - Parameter displayType: The type of display to prepare (`.banner` or `.modal`).\n    /// - Returns: A `Displayable` instance configured for the specified display type.\n    public func prepareDisplay(for displayType: DisplayType) -> any Displayable {\n        return switch(displayType) {\n        case .banner:\n            BannerDisplayable()\n        case .modal:\n            ModalDisplayable()\n        }\n    }\n    #else\n\n    /// A closure that provides the `UIWindowScene` to use for displaying content.\n    ///\n    /// This closure is called when a display operation needs to determine which\n    /// window scene to use. It should return the appropriate scene or throw an error\n    /// if no scene is available.\n    public let sceneProvider: @MainActor () throws -> UIWindowScene\n\n\n    /// Creates a new display target with the given scene provider.\n    ///\n    /// - Parameter sceneProvider: A closure that returns the `UIWindowScene` to use\n    ///   for displaying content. The closure may throw if no scene is available.\n    public init(\n        sceneProvider: @escaping @MainActor () throws -> UIWindowScene = { try AirshipSceneManager.shared.lastActiveScene }\n    ) {\n        self.sceneProvider = sceneProvider\n    }\n\n    /// Prepares a displayable object for the specified display type.\n    ///\n    /// This method creates and returns an appropriate `Displayable` implementation\n    /// based on the requested display type. The returned object can then be used to\n    /// display view controllers.\n    ///\n    /// - Parameter displayType: The type of display to prepare (`.banner` or `.modal`).\n    /// - Returns: A `Displayable` instance configured for the specified display type.\n    public func prepareDisplay(for displayType: DisplayType) -> any Displayable {\n        return switch(displayType) {\n        case .banner:\n            BannerDisplayable(sceneProvider: sceneProvider)\n        case .modal:\n            ModalDisplayable(sceneProvider: sceneProvider)\n        }\n    }\n\n    #endif\n}\n\n#if os(macOS)\n@MainActor\nclass ModalDisplayable: AirshipDisplayTarget.Displayable {\n    private var window: NSWindow?\n    private var parentWindow: NSWindow?\n    private var frameObserver: (any NSObjectProtocol)?\n    private var moveObserver: (any NSObjectProtocol)?\n\n    func display(viewControllerProvider: @MainActor (AirshipDisplayTarget.WindowInfo) -> NSViewController) throws {\n        // Dismiss any existing modal first\n        self.dismiss()\n        \n        // Get the parent window (main window)\n        guard let parentWindow = NSApplication.shared.keyWindow ?? NSApplication.shared.mainWindow else {\n            throw AirshipErrors.error(\"Failed to find parent window\")\n        }\n        \n        self.parentWindow = parentWindow\n        \n        // Create a new window for the modal\n        let window = AirshipWindowFactory.shared.makeWindow()\n        \n        window.styleMask = [.titled]\n        window.isOpaque = false\n        window.backgroundColor = .clear\n        window.hasShadow = false\n        window.contentView?.alphaValue = 0\n        window.level = .modalPanel\n        window.ignoresMouseEvents = false  // Capture all mouse events\n        window.acceptsMouseMovedEvents = true\n        window.titleVisibility = .hidden           // Hides the title text\n        window.titlebarAppearsTransparent = true   // Transparent title bar\n        window.styleMask.insert(.fullSizeContentView) \n\n        self.window = window\n        \n        // Get view controller and set it as content view controller\n        let viewController = viewControllerProvider(window.airshipInfo)\n        window.contentViewController = viewController\n        \n        // Set frame to match parent window's frame (in screen coordinates)\n        // This must be done before adding as child window\n        let parentFrame = parentWindow.frame\n        window.setFrame(parentFrame, display: false)\n        \n        // Add as child window - this will make it move/resize with parent\n        parentWindow.addChildWindow(window, ordered: .above)\n        \n        // Update frame to match parent (after adding as child)\n        updateChildWindowFrame(window: window, parent: parentWindow)\n        \n        // Observe parent window frame and move changes to resize/reposition child window\n        frameObserver = NotificationCenter.default.addObserver(\n            forName: NSWindow.didResizeNotification,\n            object: parentWindow,\n            queue: .main\n        ) { [weak self] _ in\n            MainActor.assumeIsolated {\n                guard let self = self, let window = self.window, let parent = self.parentWindow else { return }\n                self.updateChildWindowFrame(window: window, parent: parent)\n            }\n        }\n        \n        moveObserver = NotificationCenter.default.addObserver(\n            forName: NSWindow.didMoveNotification,\n            object: parentWindow,\n            queue: .main\n        ) { [weak self] _ in\n            MainActor.assumeIsolated {\n                guard let self = self, let window = self.window, let parent = self.parentWindow else { return }\n                self.updateChildWindowFrame(window: window, parent: parent)\n            }\n        }\n        \n        // Make key and order front (after setting parent and frame)\n        window.makeKeyAndOrderFront(nil)\n        \n        // Animate in\n        window.airshipAnimateIn()\n    }\n    \n    private func updateChildWindowFrame(window: NSWindow, parent: NSWindow) {\n        // When using addChildWindow, set the frame to match the parent's frame in screen coordinates\n        // The child window will automatically move with the parent\n        let parentFrame = parent.frame\n        window.setFrame(parentFrame, display: false)\n    }\n\n    func dismiss() {\n        if let observer = frameObserver {\n            NotificationCenter.default.removeObserver(observer)\n            frameObserver = nil\n        }\n        if let observer = moveObserver {\n            NotificationCenter.default.removeObserver(observer)\n            moveObserver = nil\n        }\n        if let window = window, let parent = parentWindow {\n            parent.removeChildWindow(window)\n        }\n        window?.airshipAnimateOut()\n        window = nil\n        parentWindow = nil\n    }\n}\n\n@MainActor\nclass BannerDisplayable: AirshipDisplayTarget.Displayable {\n\n    private let holder: AirshipStrongValueHolder<NSViewController> = AirshipStrongValueHolder()\n\n    private var observers: [(any NSObjectProtocol)] = []\n\n    func display(viewControllerProvider: @MainActor (AirshipDisplayTarget.WindowInfo) -> NSViewController) throws {\n        // 1. Dismiss any existing banner/window first to prevent stacking\n        self.dismiss()\n\n        // 2. Get the main host window\n        guard let hostWindow = NSApplication.shared.keyWindow ?? NSApplication.shared.mainWindow else {\n            throw AirshipErrors.error(\"Failed to find window\")\n        }\n\n        // 3. Create the Satellite Window via factory so app customizations apply\n        let overlayWindow = AirshipWindowFactory.shared.makeWindow()\n        overlayWindow.setFrame(hostWindow.contentLayoutRect, display: false)\n        overlayWindow.styleMask = [.titled]\n        overlayWindow.backgroundColor = .clear\n        overlayWindow.isOpaque = false\n        overlayWindow.hasShadow = false\n        overlayWindow.isReleasedWhenClosed = false // AppKit holds a strong reference to the window, so it will manage its lifecycle\n        overlayWindow.acceptsMouseMovedEvents = true\n        \n        overlayWindow.titleVisibility = .hidden           // Hides the title text\n        overlayWindow.titlebarAppearsTransparent = true   // Transparent title bar\n        overlayWindow.styleMask.insert(.fullSizeContentView)\n\n        // 5. Load the View Controller\n        let viewController = viewControllerProvider(hostWindow.airshipInfo)\n        let wrapperViewController = FillWindowViewController(content: viewController)\n\n        // 6. Set Root (Standard AppKit)\n        // We set it as the contentViewController of our *new* window.\n        // This handles lifecycle methods (viewWillAppear) automatically.\n        overlayWindow.contentViewController = wrapperViewController\n        \n        self.updateChildWindowFrame(window: overlayWindow, parent: hostWindow)\n        \n\n        // 7. Attach to Host Window\n        // This ensures the banner moves, minimizes, and closes with the main app window.\n        hostWindow.addChildWindow(overlayWindow, ordered: .above)\n\n        // 8. Setup Auto-Resizing\n        // Child windows do not stick to parent size automatically. We must observe.\n        observers.append(NotificationCenter.default.addObserver(\n            forName: NSWindow.didResizeNotification,\n            object: hostWindow,\n            queue: .main\n        ) { [weak self] _ in\n            MainActor.assumeIsolated {\n                guard let self else { return }\n                self.updateChildWindowFrame(window: overlayWindow, parent: hostWindow)\n            }\n        })\n\n        observers.append(NotificationCenter.default.addObserver(\n            forName: NSWindow.didMoveNotification,\n            object: hostWindow,\n            queue: .main\n        ) { [weak self] _ in\n            MainActor.assumeIsolated {\n                guard let self else { return }\n                self.updateChildWindowFrame(window: overlayWindow, parent: hostWindow)\n            }\n        })\n        \n        // 9. Store Reference\n        holder.value = viewController\n    }\n\n    func dismiss() {\n        // Extract the window from the view controller before we dump the reference\n        if let viewController = holder.value, let overlayWindow = viewController.view.window {\n            // 1. Detach from parent (Critical to avoid memory leaks/crashes)\n            overlayWindow.parent?.removeChildWindow(overlayWindow)\n\n            // 2. Close the overlay\n            overlayWindow.close()\n        }\n\n        // 3. Cleanup references\n        cleanupObservers()\n        holder.value = nil\n    }\n\n    private func cleanupObservers() {\n        observers.forEach(NotificationCenter.default.removeObserver)\n        observers.removeAll()\n    }\n    \n    private func updateChildWindowFrame(window: NSWindow, parent: NSWindow) {\n        // When using addChildWindow, set the frame to match the parent's frame in screen coordinates\n        // The child window will automatically move with the parent\n        let parentFrame = parent.frame\n        window.setFrame(parentFrame, display: false)\n    }\n}\n\nclass FillWindowViewController: NSViewController {\n    private let contentViewController: NSViewController\n    \n    init(content: NSViewController) {\n        self.contentViewController = content\n        super.init(nibName: nil, bundle: nil)\n    }\n    \n    required init?(coder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n    \n    override func loadView() {\n        let containerView = NSView()\n        containerView.autoresizingMask = [.width, .height]\n        self.view = containerView\n    }\n    \n    override func viewDidLoad() {\n        super.viewDidLoad()\n        \n        contentViewController.view.translatesAutoresizingMaskIntoConstraints = false\n        \n        addChild(contentViewController)\n        view.addSubview(contentViewController.view)\n        \n        NSLayoutConstraint.activate([\n            contentViewController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor, constant: -10)\n        ])\n    }\n}\n\nextension NSWindow {\n    /// Returns window information suitable for use with `AirshipDisplayTarget`.\n    ///\n    /// This property provides a `WindowInfo` struct containing metadata about\n    /// the window, such as its size. The size is calculated based on the platform:\n    /// - iOS/tvOS: Uses the screen bounds\n    /// - visionOS: Uses a standard window size (1280x720) per Apple's guidelines\n    /// - watchOS: Uses the device's screen bounds\n    var airshipInfo: AirshipDisplayTarget.WindowInfo {\n        return .init(size: self.frame.size)\n    }\n    \n    /// Animates the window in with a fade effect.\n    @MainActor\n    func airshipAnimateIn() {\n        self.level = .modalPanel\n        self.makeKeyAndOrderFront(nil)\n        \n        NSAnimationContext.runAnimationGroup({ context in\n            context.duration = 0.3\n            self.contentView?.animator().alphaValue = 1\n        }, completionHandler: nil)\n    }\n    \n    /// Animates the window out with a fade effect and hides it.\n    @MainActor\n    func airshipAnimateOut() {\n        NSAnimationContext.runAnimationGroup({ context in\n            context.duration = 0.3\n            self.contentView?.animator().alphaValue = 0\n        }, completionHandler: {\n            Task { @MainActor in\n                self.orderOut(nil)\n            }\n        })\n    }\n}\n\n#else\n@MainActor\nclass ModalDisplayable: AirshipDisplayTarget.Displayable {\n\n    private let sceneProvider: @MainActor () throws -> UIWindowScene\n    private var window: UIWindow?\n\n    init(sceneProvider: @escaping @MainActor () throws -> UIWindowScene) {\n        self.sceneProvider = sceneProvider\n    }\n\n    func display(viewControllerProvider: @MainActor (AirshipDisplayTarget.WindowInfo) -> UIViewController) throws {\n        self.dismiss()\n        let scene = try sceneProvider()\n        let window: UIWindow = UIWindow.airshipMakeModalReadyWindow(scene: scene)\n        self.window = window\n\n        let viewController = viewControllerProvider(window.airshipInfo)\n        window.rootViewController = viewController\n        window.airshipAnimateIn()\n    }\n\n    func dismiss() {\n        window?.airshipAnimateOut()\n        window = nil\n    }\n}\n\n@MainActor\nclass BannerDisplayable: AirshipDisplayTarget.Displayable {\n    private let sceneProvider: @MainActor () throws -> UIWindowScene\n    private let holder: AirshipStrongValueHolder<UIViewController> = AirshipStrongValueHolder()\n\n    init(sceneProvider: @escaping @MainActor () throws -> UIWindowScene) {\n        self.sceneProvider = sceneProvider\n    }\n\n    func display(viewControllerProvider: @MainActor (AirshipDisplayTarget.WindowInfo) -> UIViewController) throws {\n        self.dismiss()\n        let scene = try sceneProvider()\n\n        guard let window = AirshipUtils.mainWindow(scene: scene) else {\n            throw AirshipErrors.error(\"Failed to find window\")\n        }\n\n        guard window.rootViewController != nil else {\n            throw AirshipErrors.error(\"Window missing rootViewController\")\n        }\n\n        let viewController = viewControllerProvider(window.airshipInfo)\n        holder.value = viewController\n\n        if let view = viewController.view {\n            view.willMove(toWindow: window)\n            window.addSubview(view)\n            view.didMoveToWindow()\n        }\n    }\n\n    func dismiss() {\n        holder.value?.view.removeFromSuperview()\n        holder.value?.removeFromParent()\n        holder.value = nil\n    }\n}\n\n\nextension UIWindow {\n    /// Returns window information suitable for use with `AirshipDisplayTarget`.\n    ///\n    /// This property provides a `WindowInfo` struct containing metadata about\n    /// the window, such as its size. The size is calculated based on the platform:\n    /// - iOS/tvOS: Uses the screen bounds\n    /// - visionOS: Uses a standard window size (1280x720) per Apple's guidelines\n    /// - watchOS: Uses the device's screen bounds\n    var airshipInfo: AirshipDisplayTarget.WindowInfo {\n        return .init(size: Self.windowSize(self))\n    }\n\n    /// Calculates the appropriate window size for the given window.\n    ///\n    /// The size calculation varies by platform to account for different\n    /// display characteristics and guidelines.\n    ///\n    /// - Parameter window: The window to calculate the size for.\n    /// - Returns: The size of the window in points.\n    @MainActor\n    private class func windowSize(_ window: UIWindow) -> CGSize {\n        #if os(iOS) || os(tvOS)\n        return window.screen.bounds.size\n        #elseif os(visionOS)\n        // https://developer.apple.com/design/human-interface-guidelines/windows#visionOS\n        return CGSize(\n            width: 1280,\n            height: 720\n        )\n        #elseif os(watchOS)\n        return CGSize(\n            width: WKInterfaceDevice.current().screenBounds.width,\n            height: WKInterfaceDevice.current().screenBounds.height\n        )\n        #endif\n    }\n\n    static func airshipMakeModalReadyWindow(\n        scene: UIWindowScene\n    ) -> UIWindow {\n        let window: UIWindow = AirshipWindowFactory.shared.makeWindow(windowScene: scene)\n        window.accessibilityViewIsModal = true\n        window.alpha = 0\n        window.makeKeyAndVisible()\n        window.isUserInteractionEnabled = false\n\n        return window\n    }\n\n    func airshipAnimateIn() {\n        self.makeKeyAndVisible()\n        self.isUserInteractionEnabled = true\n\n        UIView.animate(\n            withDuration: 0.3,\n            animations: {\n                self.alpha = 1\n            },\n            completion: { _ in\n            }\n        )\n    }\n\n    func airshipAnimateOut() {\n        UIView.animate(\n            withDuration: 0.3,\n            animations: {\n                self.alpha = 0\n            },\n            completion: { _ in\n                self.isHidden = true\n                self.isUserInteractionEnabled = false\n                self.removeFromSuperview()\n            }\n        )\n    }\n}\n\n#endif\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEmbeddedInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipEmbeddedInfo: Equatable, Hashable, Sendable {\n\n    /// A generated instance ID.\n    public let instanceID: String\n\n    /// Embedded ID. This is the ID used to place the embedded view.\n    public let embeddedID: String\n\n    /// The message extras\n    public let extras: AirshipJSON?\n\n    /// View priority. Lower is higher priority.\n    public let priority: Int\n\n    init(instanceID: String, embeddedID: String, extras: AirshipJSON?, priority: Int) {\n        self.instanceID = instanceID\n        self.embeddedID = embeddedID\n        self.extras = extras\n        self.priority = priority\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEmbeddedObserver.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\npublic import Combine\n\n/// Observable model for Airship embedded views\n@MainActor\npublic final class AirshipEmbeddedObserver : ObservableObject {\n    /// An array of embedded infos\n    @Published\n    public var embeddedInfos: [AirshipEmbeddedInfo] = []\n\n    private var subscription: AnyCancellable?\n\n    /// Creates a new view model for the given embedded ID .\n    ///\n    /// - Parameters:\n    ///   - embeddedID: The embedded ID to filter the embeddedInfos on.\n    public convenience init(embeddedID: String) {\n        self.init { info in\n            info.embeddedID == embeddedID\n        }\n    }\n\n    /// Creates a new view model for the given embedded IDs.\n    ///\n    /// - Parameters:\n    ///   - embeddedID: An array of embedded IDs to filter the embeddedInfos on.\n    public convenience init(embeddedIDs: [String]) {\n        let set: Set<String> = Set(embeddedIDs)\n        self.init { info in\n            set.contains(info.embeddedID)\n        }\n    }\n\n    /// Creates a new view model  for embedded infos.\n    public convenience init() {\n        self.init { info in\n            return true\n        }\n    }\n    /// Creates a new view model with the given predicate.\n    ///\n    /// - Parameters:\n    ///   - predicate: A predicate to filter out AirshipEmbeddedInfo.\n    public init(predicate: @escaping @MainActor (AirshipEmbeddedInfo) -> Bool) {\n        subscription = AirshipEmbeddedViewManager.shared.publisher\n            .map { array in\n                array.filter { predicate($0.embeddedInfo) }\n                    .map { $0.embeddedInfo }\n            }\n            .removeDuplicates()\n            .assign(to: \\.embeddedInfos, on: self)\n    }\n\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEmbeddedSize.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\npublic import Foundation\n\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipEmbeddedSize: Equatable, Hashable, Sendable {\n\n    /// The parent's width\n    public var parentWidth: CGFloat?\n\n    /// The parent's height\n    public var parentHeight: CGFloat?\n\n    /// Creates a new AirshipEmbeddedSize\n    /// - Parameters:\n    ///   - parentWidth: The parent's width in points.. This is required for horizontal scroll views to size correctly when using percent based sizing.\n    ///   - parentHeight: The parent's height in points.  This is required for vertical scroll views to size correctly when using percent based sizing.\n    public init(parentWidth: CGFloat? = nil, parentHeight: CGFloat? = nil) {\n        self.parentWidth = parentWidth\n        self.parentHeight = parentHeight\n    }\n\n    /// Creates a new AirshipEmbeddedSize\n    /// - Parameters:\n    ///   - maxSize: The max size that the view can grow to  in points.\n    public init(parentBounds: CGSize) {\n        self.parentWidth = parentBounds.width\n        self.parentHeight = parentBounds.height\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEmbeddedView.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\nimport Combine\n\n/// Airship embedded view - a scene that can be embedded in an app and managed remotely\npublic struct AirshipEmbeddedView<PlaceHolder: View>: View {\n\n    @Environment(\\.airshipEmbeddedViewStyle)\n    private var style\n\n    @StateObject\n    private var viewModel: EmbeddedViewModel\n\n    private let placeholder: () -> PlaceHolder\n    private let embeddedID: String\n    private let embeddedSize: AirshipEmbeddedSize?\n\n    /// Creates a new AirshipEmbeddedView.\n    ///\n    /// - Parameters:\n    ///   - embeddedID: The embedded ID.\n    ///   - size: The embedded size info. This is needed in a scroll view to determine proper percent based sizing.\n    ///   - placeholder: The place holder block.\n    public init(\n        embeddedID: String,\n        embeddedSize: AirshipEmbeddedSize? = nil,\n        @ViewBuilder placeholder: @escaping () -> PlaceHolder = { EmptyView()}\n    ) {\n        self.embeddedID = embeddedID\n        self.embeddedSize = embeddedSize\n        self.placeholder = placeholder\n        self._viewModel = StateObject(wrappedValue: EmbeddedViewModel(embeddedID: embeddedID))\n    }\n\n    /// Creates a new AirshipEmbeddedView.\n    ///\n    /// - Parameters:\n    ///   - embeddedID: The embedded ID.\n    ///   - size: The embedded size info. This is needed in a scroll view to determine proper percent based sizing.\n    public init(\n        embeddedID: String,\n        embeddedSize: AirshipEmbeddedSize? = nil\n    ) where PlaceHolder == EmptyView {\n        self.embeddedID = embeddedID\n        self.embeddedSize = embeddedSize\n        self.placeholder = { EmptyView() }\n        self._viewModel = StateObject(wrappedValue: EmbeddedViewModel(embeddedID: embeddedID))\n    }\n\n    public var body: some View {\n        let pending = viewModel.pending\n\n        let configuration = AirshipEmbeddedViewStyleConfiguration(\n            embeddedID: embeddedID,\n            pending: pending.map { item in\n                AirshipEmbeddedViewStyleConfiguration.Pending(\n                    content: AirshipEmbeddedContentView(\n                        embeddedInfo: item.embeddedInfo,\n                        view: {\n                            EmbeddedView(\n                                presentation: item.presentation,\n                                layout: item.layout,\n                                thomasEnvironment: item.environment,\n                                embeddedSize: embeddedSize\n                            )\n                        },\n                        dismissHandle: item.dismissHandle\n                    ),\n                    onDismiss: { item.dismissHandle.dismiss() }\n                )\n            },\n            placeHolder: AnyView(self.placeholder())\n        )\n\n        return self.style.makeBody(configuration: configuration)\n    }\n}\n\n\n@MainActor\nprivate class EmbeddedViewModel: ObservableObject {\n\n    @Published\n    var pending: [PendingEmbedded] = []\n\n    private var cancellable: AnyCancellable?\n    private var timer: AnyCancellable?\n    private var viewManager: AirshipEmbeddedViewManager\n    \n    init(embeddedID: String, manager: AirshipEmbeddedViewManager = AirshipEmbeddedViewManager.shared) {\n        self.viewManager = manager\n        cancellable = viewManager\n            .publisher(embeddedViewID: embeddedID)\n            .receive(on: DispatchQueue.main)\n            .sink(receiveValue: onNewViewReceived)\n    }\n\n    private func onNewViewReceived(_ pending: [PendingEmbedded]) {\n        withAnimation {\n            self.pending = pending\n        }\n    }\n}\n\n/**\n * Internal only\n * :nodoc:\n */\npublic struct AirshipEmbeddedContentView : View, Identifiable  {\n    public let embeddedInfo: AirshipEmbeddedInfo\n\n    nonisolated public var id: String {\n        embeddedInfo.instanceID\n    }\n\n    private let view: () -> EmbeddedView\n    private let dismissHandle: ThomasDismissHandle\n\n    internal init(\n        embeddedInfo: AirshipEmbeddedInfo,\n        view: @escaping () -> EmbeddedView,\n        dismissHandle: ThomasDismissHandle\n    ) {\n        self.embeddedInfo = embeddedInfo\n        self.view = view\n        self.dismissHandle = dismissHandle\n    }\n\n    public func dismiss() {\n        self.dismissHandle.dismiss()\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        view().onAppear {\n            EmbeddedViewSelector.shared.onViewDisplayed(embeddedInfo)\n        }\n        .id(embeddedInfo.instanceID)\n    }\n}\n\npublic struct AirshipEmbeddedViewStyleConfiguration {\n    public struct Pending: Identifiable {\n        public let content: AirshipEmbeddedContentView\n        public let onDismiss: @MainActor () -> Void\n        public var id: String { content.id }\n    }\n\n    public let embeddedID: String\n    public let pending: [Pending]\n    public let placeHolder: AnyView\n\n    /// Deprecated: Use `pending` instead.\n    @available(*, deprecated, message: \"Use `pending` which includes dismissal logic per-view.\")\n    public var views: [AirshipEmbeddedContentView] {\n        return pending.map { $0.content }\n    }\n\n    internal init(\n        embeddedID: String,\n        pending: [Pending],\n        placeHolder: AnyView\n    ) {\n        self.embeddedID = embeddedID\n        self.pending = pending\n        self.placeHolder = placeHolder\n    }\n}\n\n/// Protocol for customizing an Airship embedded view with a style\npublic protocol AirshipEmbeddedViewStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = AirshipEmbeddedViewStyleConfiguration\n    @preconcurrency @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension AirshipEmbeddedViewStyle where Self == DefaultAirshipEmbeddedViewStyle {\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// Default style for embedded views\npublic struct DefaultAirshipEmbeddedViewStyle: AirshipEmbeddedViewStyle {\n\n    @MainActor\n    private func nextView(configuration: Configuration) -> AirshipEmbeddedContentView? {\n        return EmbeddedViewSelector.shared.selectView(\n            embeddedID: configuration.embeddedID,\n            views: configuration.pending.map { $0.content }\n       )\n    }\n\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        if let view = nextView(configuration: configuration) {\n            view\n        } else {\n            configuration.placeHolder\n        }\n    }\n}\n\nstruct AnyAirshipEmbeddedViewStyle: AirshipEmbeddedViewStyle {\n    @ViewBuilder\n    private let _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: AirshipEmbeddedViewStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct AirshipEmbeddedViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyAirshipEmbeddedViewStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipEmbeddedViewStyle: AnyAirshipEmbeddedViewStyle {\n        get { self[AirshipEmbeddedViewStyleKey.self] }\n        set { self[AirshipEmbeddedViewStyleKey.self] = newValue }\n    }\n}\n\n\nextension View {\n    /// Setter for applying a style to an Airship embedded view\n    public func setAirshipEmbeddedStyle<S>(\n        _ style: S\n    ) -> some View where S: AirshipEmbeddedViewStyle {\n        self.environment(\n            \\.airshipEmbeddedViewStyle,\n            AnyAirshipEmbeddedViewStyle(style: style)\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEmbeddedViewManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@preconcurrency\nimport Combine\nimport SwiftUI\n\nprotocol AirshipEmbeddedViewManagerProtocol: Sendable {\n    @MainActor\n    func addPending(\n        presentation: ThomasPresentationInfo.Embedded,\n        layout: AirshipLayout,\n        extensions: ThomasExtensions?,\n        delegate: any ThomasDelegate,\n        extras: AirshipJSON?,\n        priority: Int\n    ) -> any AirshipMainActorCancellable\n\n    var publisher: AnyPublisher<[PendingEmbedded], Never> { get }\n    func publisher(embeddedViewID: String) -> AnyPublisher<[PendingEmbedded], Never>\n}\n\nfinal class AirshipEmbeddedViewManager: AirshipEmbeddedViewManagerProtocol {\n\n    public static let shared = AirshipEmbeddedViewManager()\n\n    @MainActor\n    private var pending: [PendingEmbedded] = []\n    private let viewSubject = CurrentValueSubject<[PendingEmbedded], Never>([])\n\n    var publisher: AnyPublisher<[PendingEmbedded], Never> {\n        viewSubject.eraseToAnyPublisher()\n    }\n\n    @MainActor\n    func addPending(\n        presentation: ThomasPresentationInfo.Embedded,\n        layout: AirshipLayout,\n        extensions: ThomasExtensions?,\n        delegate: any ThomasDelegate,\n        extras: AirshipJSON?,\n        priority: Int\n    ) -> any AirshipMainActorCancellable {\n        let id = UUID().uuidString\n\n        let dismissHandle = ThomasDismissHandle()\n\n        let environment = ThomasEnvironment(delegate: delegate, extensions: extensions, dismissHandle: dismissHandle) {\n            self.pending.removeAll { $0.id == id }\n            self.viewSubject.send(self.pending)\n        }\n\n\n        self.pending.append(\n            PendingEmbedded(\n                id: id,\n                presentation: presentation,\n                layout: layout,\n                environment: environment,\n                embeddedInfo: AirshipEmbeddedInfo(\n                    instanceID: id,\n                    embeddedID: presentation.embeddedID,\n                    extras: extras,\n                    priority: priority\n                ),\n                dismissHandle: dismissHandle\n            )\n        )\n\n        self.viewSubject.send(self.pending)\n\n        return AirshipMainActorCancellableBlock { [weak environment] in\n            environment?.dismiss()\n        }\n    }\n\n    func publisher(embeddedViewID: String) -> AnyPublisher<[PendingEmbedded], Never> {\n        return viewSubject\n            .map { array in\n                array.filter { value in value.presentation.embeddedID == embeddedViewID }\n            }\n            .eraseToAnyPublisher()\n    }\n}\n\nstruct PendingEmbedded: Sendable {\n    fileprivate let id: String\n    let presentation: ThomasPresentationInfo.Embedded\n    let layout: AirshipLayout\n    let environment: ThomasEnvironment\n    let embeddedInfo: AirshipEmbeddedInfo\n    let dismissHandle: ThomasDismissHandle\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipErrors.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipErrors {\n    public class func parseError(_ message: String) -> any Error {\n        return NSError(\n            domain: \"com.urbanairship.parse_error\",\n            code: 1,\n            userInfo: [\n                NSLocalizedDescriptionKey: message\n            ]\n        )\n    }\n\n    public class func error(_ message: String) -> any Error {\n        return NSError(\n            domain: \"com.urbanairship.error\",\n            code: 1,\n            userInfo: [\n                NSLocalizedDescriptionKey: message\n            ]\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: For Internal use only :nodoc:\npublic enum AirshipEventPriority: Sendable {\n    case normal\n    case high\n}\n\n\n/// - Note: For Internal use only :nodoc:\npublic struct AirshipEvent: Sendable {\n    public var priority: AirshipEventPriority\n    public var eventType: EventType\n    public var eventData: AirshipJSON\n\n    public init(\n        priority: AirshipEventPriority = .normal,\n        eventType: EventType,\n        eventData: AirshipJSON\n    ) {\n        self.priority = priority\n        self.eventType = eventType\n        self.eventData = eventData\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEventData.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Full airship event data\npublic struct AirshipEventData: Sendable, Equatable {\n\n    // The event body\n    public let body: AirshipJSON\n    \n    /// The event ID\n    public let id: String\n\n    /// The event date\n    public let date: Date\n\n    /// The SessionID\n    public let sessionID: String\n\n    /// The event type\n    public let type: EventType\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEventType.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/**\n * Airship event types\n */\npublic enum EventType: CaseIterable, Sendable, Equatable, Hashable {\n    case appInit\n    case appForeground\n    case appBackground\n    case screenTracking\n    case associateIdentifiers\n    case installAttribution\n    case interactiveNotificationAction\n    case regionEnter\n    case regionExit\n    case customEvent\n    case featureFlagInteraction\n    case inAppDisplay\n    case inAppResolution\n    case inAppButtonTap\n    case inAppPermissionResult\n    case inAppFormDisplay\n    case inAppFormResult\n    case inAppGesture\n    case inAppPagerCompleted\n    case inAppPagerSummary\n    case inAppPageSwipe\n    case inAppPageView\n    case inAppPageAction\n\n    /// NOTE: For internal use only. :nodoc:\n    public var reportingName: String {\n        switch self {\n        case .appInit:\n            return \"app_init\"\n        case .appForeground:\n            return \"app_foreground\"\n        case .appBackground:\n            return \"app_background\"\n        case .screenTracking:\n            return \"screen_tracking\"\n        case .associateIdentifiers:\n            return \"associate_identifiers\"\n        case .installAttribution:\n            return \"install_attribution\"\n        case .interactiveNotificationAction:\n            return \"interactive_notification_action\"\n        case .regionEnter, .regionExit:\n            return \"region_event\"\n        case .customEvent:\n            return \"enhanced_custom_event\"\n        case .featureFlagInteraction:\n            return \"feature_flag_interaction\"\n        case .inAppDisplay:\n            return \"in_app_display\"\n        case .inAppResolution:\n            return \"in_app_resolution\"\n        case .inAppButtonTap:\n            return \"in_app_button_tap\"\n        case .inAppPermissionResult:\n            return \"in_app_permission_result\"\n        case .inAppFormDisplay:\n            return \"in_app_form_display\"\n        case .inAppFormResult:\n            return \"in_app_form_result\"\n        case .inAppGesture:\n            return \"in_app_gesture\"\n        case .inAppPagerCompleted:\n            return \"in_app_pager_completed\"\n        case .inAppPagerSummary:\n            return \"in_app_pager_summary\"\n        case .inAppPageSwipe:\n            return \"in_app_page_swipe\"\n        case .inAppPageView:\n            return \"in_app_page_view\"\n        case .inAppPageAction:\n            return \"in_app_page_action\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipEvents.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\nstruct AirshipEvents {\n    \n#if !os(tvOS)\n    static func interactiveNotificationEvent(\n        action: UNNotificationAction,\n        category: String,\n        notification: [AnyHashable: Any],\n        responseText: String?\n    ) -> AirshipEvent {\n        let pushID = notification[\"_\"] as? String\n\n        return AirshipEvent(\n            priority: .high,\n            eventType: .interactiveNotificationAction,\n            eventData: AirshipJSON.makeObject { object in\n                object.set(string: category, key: \"button_group\")\n                object.set(string: action.identifier, key: \"button_id\")\n                object.set(string: action.title, key: \"button_description\")\n                object.set(string: action.isForeground.toString(), key: \"foreground\")\n                object.set(string: pushID, key: \"send_id\")\n                object.set(string: responseText?.truncate(maxCount: 255), key: \"user_input\")\n            }\n        )\n    }\n#endif\n\n    static func screenTrackingEvent(\n        screen: String,\n        previousScreen: String?,\n        startDate: Date,\n        duration: TimeInterval\n    ) throws -> AirshipEvent {\n        guard duration > 0 else {\n            throw AirshipErrors.error(\"Invalid screen event \\(screen), duration is zero.\")\n        }\n\n        guard screen.count >= 1, screen.count <= 255  else {\n            throw AirshipErrors.error(\"Invalid screen name \\(screen), Must be between 1 and 255 characters.\")\n        }\n\n        let startTime = startDate.timeIntervalSince1970\n        let endTime = startDate.advanced(by: duration).timeIntervalSince1970\n\n        return AirshipEvent(\n            priority: .normal,\n            eventType: .screenTracking,\n            eventData: AirshipJSON.makeObject { object in\n                object.set(string: screen, key: \"screen\")\n                object.set(string: previousScreen, key: \"previous_screen\")\n                object.set(string: startTime.toString(), key: \"entered_time\")\n                object.set(string: endTime.toString(), key: \"exited_time\")\n                object.set(string: duration.toString(), key: \"duration\")\n            }\n        )\n    }\n\n    static func associatedIdentifiersEvent(\n        identifiers: AssociatedIdentifiers?\n    ) throws -> AirshipEvent {\n        let identifiers = identifiers?.allIDs ?? [:]\n        guard identifiers.count <= AssociatedIdentifiers.maxCount else {\n            throw AirshipErrors.error(\n                \"Associated identifiers count exceed \\(AssociatedIdentifiers.maxCount)\"\n            )\n        }\n\n        try identifiers.forEach {\n            if $0.key.count > AssociatedIdentifiers.maxCharacterCount {\n                throw AirshipErrors.error(\n                    \"Associated identifier \\($0) key exceeds \\(AssociatedIdentifiers.maxCharacterCount) characters\"\n                )\n            }\n\n            if $0.value.count > AssociatedIdentifiers.maxCharacterCount {\n                throw AirshipErrors.error(\n                    \"Associated identifier \\($0) value exceeds \\(AssociatedIdentifiers.maxCharacterCount) characters\"\n                )\n            }\n        }\n\n        return AirshipEvent(\n            priority: .normal,\n            eventType: .associateIdentifiers,\n            eventData: try AirshipJSON.wrap(identifiers)\n        )\n    }\n\n    static func installAttirbutionEvent(\n        appPurchaseDate: Date? = nil,\n        iAdImpressionDate: Date? = nil\n    ) -> AirshipEvent {\n        return AirshipEvent(\n            priority: .normal,\n            eventType: .installAttribution,\n            eventData: AirshipJSON.makeObject { object in\n                object.set(\n                    string: appPurchaseDate?.timeIntervalSince1970.toString(),\n                    key: \"app_store_purchase_date\"\n                )\n                object.set(\n                    string: iAdImpressionDate?.timeIntervalSince1970.toString(),\n                    key: \"app_store_ad_impression_date\"\n                )\n            }\n        )\n    }\n\n    @MainActor\n    static func sessionEvent(\n        sessionEvent: SessionEvent,\n        push: any AirshipPush\n    ) -> AirshipEvent {\n        return AirshipEvent(\n            priority: .normal,\n            eventType: sessionEvent.eventType,\n            eventData: sessionEvent.eventData(push: push)\n        )\n    }\n}\n\nfileprivate extension TimeInterval {\n    func toString() -> String {\n        String(\n            format: \"%0.3f\",\n            self\n        )\n    }\n}\n\nfileprivate extension Bool {\n    func toString() -> String {\n        return self ? \"true\" : \"false\"\n    }\n}\n\n#if !os(tvOS)\nfileprivate extension UNNotificationAction {\n    var isForeground: Bool {\n        return (self.options.rawValue & UNNotificationActionOptions.foreground.rawValue) > 0\n    }\n}\n#endif\n\nfileprivate extension String {\n    func truncate(maxCount: Int) -> String {\n        return if self.count > maxCount {\n            String(self.prefix(maxCount))\n        } else {\n           self\n        }\n    }\n}\n\nfileprivate extension SessionEvent {\n    var isAppInit: Bool {\n        switch self.type {\n        case .foregroundInit, .backgroundInit: return true\n        case .background, .foreground: return false\n        }\n    }\n\n    var eventType: EventType {\n        switch self.type {\n        case .foregroundInit, .backgroundInit: return .appInit\n        case .background: return .appBackground\n        case .foreground: return .appForeground\n        }\n    }\n\n    @MainActor\n    func eventData(\n        push: any AirshipPush\n    ) -> AirshipJSON {\n        return AirshipJSON.makeObject { object in\n            /// Common\n            object.set(string: sessionState.conversionSendID, key: \"push_id\")\n            object.set(string: sessionState.conversionMetadata, key: \"metadata\")\n\n            /// App init\n            if self.isAppInit {\n                let isForeground = self.type == .foregroundInit\n                object.set(string: isForeground.toString(), key: \"foreground\")\n            }\n\n\n            /// App init or foreground\n            if self.isAppInit || self.type == .foreground {\n                object.set(string: AirshipVersion.version, key: \"lib_version\")\n                object.set(string: AirshipUtils.bundleShortVersionString() ?? \"\", key: \"package_version\")\n\n                object.set(string: AirshipDevice.osVersion, key:\"os_version\")\n\n\n                let localtz = TimeZone.current\n                object.set(double: Double(localtz.secondsFromGMT()), key:\"time_zone\")\n                object.set(string: localtz.isDaylightSavingTime().toString(), key: \"daylight_savings\")\n\n                let notificationTypes = EventUtils.notificationTypes(\n                    authorizedSettings: push.authorizedNotificationSettings\n                )?.map { AirshipJSON.string($0) }\n\n                let authroizedStatus = EventUtils.notificationAuthorization(\n                    authorizationStatus: push.authorizationStatus\n                )\n\n                object.set(array: notificationTypes, key: \"notification_types\")\n                object.set(string: authroizedStatus, key: \"notification_authorization\")\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipFont.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(UIKit)\npublic import UIKit\n#elseif canImport(AppKit)\npublic import AppKit\n#endif\n\npublic struct AirshipFont {\n\n    /// Resolves a SwiftUI Font\n    @MainActor\n    public static func resolveFont(\n        size: Double,\n        families: [String]? = nil,\n        weight: Double? = nil,\n        isItalic: Bool = false,\n        isBold: Bool = false\n    ) -> Font {\n        \n        let scaledSize = self.scaledSize(size)\n        let fontWeight = self.resolveSwiftWeight(weight: weight, isBold: isBold)\n\n        var font: Font\n        if let fontFamily = resolveFontFamily(families: families) {\n            font = Font.custom(fontFamily, fixedSize: scaledSize).weight(fontWeight)\n        } else {\n            font = Font.system(size: scaledSize, weight: fontWeight)\n        }\n\n        return isItalic ? font.italic() : font\n    }\n\n    /// Resolves a Native Font (UIFont or NSFont)\n    @MainActor\n    public static func resolveNativeFont(\n        size: Double,\n        families: [String]? = nil,\n        weight: Double? = nil,\n        isItalic: Bool = false,\n        isBold: Bool = false\n    ) -> AirshipNativeFont {\n        let scaledSize = CGFloat(self.scaledSize(size))\n        let nativeWeight = self.resolveNativeWeight(weight: weight, isBold: isBold)\n\n        var font: AirshipNativeFont\n        if let fontFamily = resolveFontFamily(families: families) {\n#if os(macOS)\n            font = NSFont(name: fontFamily, size: scaledSize) ?? NSFont.systemFont(ofSize: scaledSize, weight: nativeWeight)\n#else\n            font = UIFont(name: fontFamily, size: scaledSize) ?? UIFont.systemFont(ofSize: scaledSize, weight: nativeWeight)\n#endif\n        } else {\n#if os(macOS)\n            font = NSFont.systemFont(ofSize: scaledSize, weight: nativeWeight)\n#else\n            font = UIFont.systemFont(ofSize: scaledSize, weight: nativeWeight)\n#endif\n        }\n\n        if isItalic {\n#if os(macOS)\n            let descriptor = font.fontDescriptor.withSymbolicTraits(.italic)\n            font = NSFont(descriptor: descriptor, size: scaledSize) ?? font\n#else\n            let descriptor = font.fontDescriptor.withSymbolicTraits(.traitItalic)\n            font = UIFont(descriptor: descriptor ?? font.fontDescriptor, size: 0)\n#endif\n        }\n\n        return font\n    }\n\n    // MARK: - Scaling & Weights\n\n    public static func scaledSize(_ size: Double) -> Double {\n#if os(macOS)\n        return size\n#else\n        return UIFontMetrics.default.scaledValue(for: size)\n#endif\n    }\n\n    private static func resolveSwiftWeight(weight: Double?, isBold: Bool) -> Font.Weight {\n        if let weight = weight {\n            return swiftWeight(from: roundFontWeight(weight))\n        }\n        return isBold ? .bold : .regular\n    }\n\n#if os(macOS)\n    private static func resolveNativeWeight(weight: Double?, isBold: Bool) -> NSFont.Weight {\n        if let weight = weight {\n            return nativeWeight(from: roundFontWeight(weight))\n        }\n        return isBold ? .bold : .regular\n    }\n#else\n    private static func resolveNativeWeight(weight: Double?, isBold: Bool) -> UIFont.Weight {\n        if let weight = weight {\n            return nativeWeight(from: roundFontWeight(weight))\n        }\n        return isBold ? .bold : .regular\n    }\n#endif\n\n    // MARK: - Internal Mappers\n\n    private static func roundFontWeight(_ fontWeight: Double) -> Int {\n        let rounded = Int(round(fontWeight / 100.0) * 100.0)\n        return max(100, min(900, rounded))\n    }\n\n    private static func swiftWeight(from roundedWeight: Int) -> Font.Weight {\n        let map: [Int: Font.Weight] = [\n            100: .ultraLight, 200: .thin, 300: .light, 400: .regular,\n            500: .medium, 600: .semibold, 700: .bold, 800: .heavy, 900: .black\n        ]\n        return map[roundedWeight] ?? .regular\n    }\n\n#if os(macOS)\n    private static func nativeWeight(from roundedWeight: Int) -> NSFont.Weight {\n        let map: [Int: NSFont.Weight] = [\n            100: .ultraLight, 200: .thin, 300: .light, 400: .regular,\n            500: .medium, 600: .semibold, 700: .bold, 800: .heavy, 900: .black\n        ]\n        return map[roundedWeight] ?? .regular\n    }\n#else\n    private static func nativeWeight(from roundedWeight: Int) -> UIFont.Weight {\n        let map: [Int: UIFont.Weight] = [\n            100: .ultraLight, 200: .thin, 300: .light, 400: .regular,\n            500: .medium, 600: .semibold, 700: .bold, 800: .heavy, 900: .black\n        ]\n        return map[roundedWeight] ?? .regular\n    }\n#endif\n\n    public static func resolveFontFamily(families: [String]?) -> String? {\n        guard let families = families else { return nil }\n        for family in families {\n            let lower = family.lowercased()\n            if lower == \"serif\" { return \"Times New Roman\" }\n            if lower == \"sans-serif\" { return nil }\n\n#if os(macOS)\n            if NSFontManager.shared.availableFontFamilies.contains(family) { return family }\n#else\n            if !UIFont.fontNames(forFamilyName: family).isEmpty { return family }\n#endif\n        }\n        return nil\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipImageLoader.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\npublic actor AirshipImageLoader {\n    private static let retryDelay: UInt64 = 10 * 1_000_000_000\n    private static let maxRetries: Int = 10\n    \n    private let imageProvider: (any AirshipImageProvider)?\n    \n    public init(\n        imageProvider: (any AirshipImageProvider)? = nil\n    ) {\n        self.imageProvider = imageProvider\n    }\n    \n    func load(\n        url urlString: String\n    ) async throws -> AirshipImageData {\n        \n        guard let url = URL(string: urlString) else {\n            throw AirshipErrors.error(\"Invalid URL\")\n        }\n        \n        // Check Cache/Provider first\n        if let cachedData = imageProvider?.get(url: url) {\n            return cachedData\n        }\n        \n        // Route to appropriate loading logic\n        if url.isFileURL {\n            return try await loadImageFromFile(url: url)\n        } else {\n            return try await fetchImageWithRetry(url: url)\n        }\n    }\n    \n    private func loadImageFromFile(\n        url: URL\n    ) async throws -> AirshipImageData {\n        // Moving file I/O to a background task to avoid blocking the actor\n        return try await Task.detached(priority: .userInitiated) {\n            let data = try Data(contentsOf: url)\n            return try AirshipImageData(data: data)\n        }.value\n    }\n    \n    private func fetchImageWithRetry(\n        url: URL\n    ) async throws -> AirshipImageData {\n        var lastError: (any Error)?\n        \n        for attempt in 0..<Self.maxRetries {\n            do {\n                let (data, response) = try await URLSession.airshipSecureSession.data(from: url)\n                \n                guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200 else {\n                    throw AirshipErrors.error(\"Invalid server response\")\n                }\n                \n                return try AirshipImageData(data: data)\n            } catch {\n                lastError = error\n                AirshipLogger.debug(\"Failed to fetch image \\(url) attempt \\(attempt + 1)/\\(Self.maxRetries): \\(error)\")\n                \n                if attempt < Self.maxRetries - 1 {\n                    try await Task.sleep(nanoseconds: Self.retryDelay)\n                }\n            }\n        }\n        \n        AirshipLogger.debug(\"Failed to fetch image \\(url) after \\(Self.maxRetries) attempts\")\n        throw lastError ?? AirshipErrors.error(\"Failed to fetch after \\(Self.maxRetries) attempts\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipImageProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Image provider to extend image loading.\n/// - Note: for internal use only.  :nodoc:\npublic protocol AirshipImageProvider: Sendable {\n\n    /// Gets the an image.\n    /// - Parameters:\n    ///     - url: The image URL.\n    /// - Returns: The image or nil to let the image loader fetch it.\n    func get(url: URL) -> AirshipImageData?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipInputValidator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n/// A struct that encapsulates input validation logic for different request types such as email and SMS.\npublic struct AirshipInputValidation {\n\n    /// A closure type used for overriding validation logic.\n    public typealias OverridesClosure = (@Sendable (Request) async throws -> Override)\n\n    private init() {}\n\n    /// Enum representing the result of validation.\n    /// It indicates whether an input is valid or invalid.\n    public enum Result: Sendable, Equatable {\n        /// Indicates a valid input with the associated address (e.g., email or phone number).\n        case valid(address: String)\n        /// Indicates an invalid input.\n        case invalid\n    }\n\n    /// Enum representing the override options for input validation.\n    public enum Override: Sendable, Equatable {\n        /// Override the result of validation with a custom validation result.\n        case override(Result)\n        /// Skip the override and use the default validation method.\n        case useDefault\n    }\n\n    /// Enum representing the types of requests to be validated (e.g., Email or SMS).\n    public enum Request: Sendable, Equatable {\n        case email(Email)\n        case sms(SMS)\n\n        /// A struct representing an SMS request for validation.\n        public struct SMS: Sendable, Equatable {\n            public var rawInput: String\n            public var validationOptions: ValidationOptions\n            public var validationHints: ValidationHints?\n\n            /// Enum specifying the options for validating an SMS, such as sender ID or prefix.\n            public enum ValidationOptions: Sendable, Equatable {\n                case sender(senderID: String, prefix: String? = nil)\n                case prefix(prefix: String)\n            }\n\n            /// A struct for defining validation hints like min/max digit requirements.\n            public struct ValidationHints: Sendable, Equatable {\n                var minDigits: Int?\n                var maxDigits: Int?\n\n                public init(minDigits: Int? = nil, maxDigits: Int? = nil) {\n                    self.minDigits = minDigits\n                    self.maxDigits = maxDigits\n                }\n            }\n\n            /// Initializes the SMS validation request.\n            /// - Parameters:\n            ///   - rawInput: The raw input string to be validated.\n            ///   - validationOptions: The validation options to be applied.\n            ///   - validationHints: Optional validation hints such as min/max digit constraints.\n            public init(\n                rawInput: String,\n                validationOptions: ValidationOptions,\n                validationHints: ValidationHints? = nil\n            ) {\n                self.rawInput = rawInput\n                self.validationOptions = validationOptions\n                self.validationHints = validationHints\n            }\n        }\n\n        /// A struct representing an email request for validation.\n        public struct Email: Sendable, Equatable {\n            public var rawInput: String\n\n            /// Initializes the Email validation request.\n            /// - Parameter rawInput: The raw email input to be validated.\n            public init(rawInput: String) {\n                self.rawInput = rawInput\n            }\n        }\n    }\n\n    /// Protocol for validators that perform validation of input requests.\n    /// NOTE: For internal use only. :nodoc:\n    public protocol Validator: AnyObject, Sendable {\n        /// Validates the provided request and returns a result.\n        /// - Parameter request: The request to be validated (either SMS or Email).\n        /// - Throws: Can throw errors if validation fails.\n        /// - Returns: The validation result, either valid or invalid.\n        func validateRequest(_ request: Request) async throws -> Result\n    }\n}\n\nextension AirshipInputValidation {\n    /// A default implementation of the `Validator` protocol that uses a standard SMS validation API.\n    /// /// NOTE: For internal use only. :nodoc:\n    final class DefaultValidator: Validator {\n\n        // Regular expression for validating email addresses.\n        private static let emailRegex: String = #\"^[^@\\s]+@[^@\\s]+\\.[^@\\s.]+$\"#\n\n        private let overrides: OverridesClosure?\n        private let smsValidatorAPIClient: any SMSValidatorAPIClientProtocol\n\n        /// Initializes the validator with custom overrides and a SMS validation API client.\n        /// - Parameters:\n        ///   - smsValidatorAPIClient: The client used to validate SMS numbers.\n        ///   - overrides: An optional closure for overriding validation logic.\n        public init(\n            smsValidatorAPIClient: any SMSValidatorAPIClientProtocol,\n            overrides: OverridesClosure? = nil\n        ) {\n            self.overrides = overrides\n            self.smsValidatorAPIClient = smsValidatorAPIClient\n        }\n\n        /// Initializes the validator using a configuration object.\n        /// - Parameter config: The runtime configuration used for initializing the validator.\n        public convenience init(config: RuntimeConfig) {\n            self.init(\n                smsValidatorAPIClient: CachingSMSValidatorAPIClient(\n                    client: SMSValidatorAPIClient(config: config)\n                ),\n                overrides: config.airshipConfig.inputValidationOverrides\n            )\n        }\n\n        /// Validates the provided request asynchronously.\n        /// - Parameter request: The request to be validated (either SMS or Email).\n        /// - Throws: Can throw errors if validation fails or on cancellation.\n        /// - Returns: The validation result, either valid or invalid.\n        public func validateRequest(_ request: Request) async throws -> Result {\n            try Task.checkCancellation()\n\n            AirshipLogger.debug(\"Validating input request \\(request)\")\n\n            if let overrides {\n                AirshipLogger.trace(\"Attempting to use overrides for request \\(request)\")\n\n                switch(try await overrides(request)) {\n                case .override(let result):\n                    AirshipLogger.debug(\"Overrides result \\(result) for request \\(request)\")\n                    return result\n                case .useDefault:\n                    AirshipLogger.trace(\"Overrides skipped, using default method for request \\(request)\")\n                    break\n                }\n            }\n\n            try Task.checkCancellation()\n\n            let result = switch(request) {\n            case .sms(let sms):\n                try await validateSMS(sms, request: request)\n            case .email(let email):\n                try await validateEmail(email, request: request)\n            }\n\n            AirshipLogger.debug(\"Result \\(result) for request \\(request)\")\n            return result\n        }\n\n        /// Validates an email address.\n        /// - Parameter email: The email to be validated.\n        /// - Parameter request: The original request associated with the email.\n        /// - Throws: Can throw errors during validation or cancellation.\n        /// - Returns: The result of the email validation, either valid or invalid.\n        private func validateEmail(_ email: Request.Email, request: Request) async throws -> Result {\n            let address = email.rawInput.trimmingCharacters(in: .whitespacesAndNewlines)\n            let predicate = NSPredicate(format: \"SELF MATCHES %@\", Self.emailRegex)\n\n            guard predicate.evaluate(with: address) else {\n                return .invalid\n            }\n            return .valid(address: address)\n        }\n\n        /// Validates an SMS number.\n        /// - Parameter sms: The SMS object containing validation information.\n        /// - Parameter request: The original request associated with the SMS.\n        /// - Throws: Can throw errors during validation or cancellation.\n        /// - Returns: The result of the SMS validation, either valid or invalid.\n        private func validateSMS(_ sms: Request.SMS, request: Request) async throws -> Result {\n            guard sms.validationHints?.matches(sms.rawInput) != false else {\n                AirshipLogger.trace(\"SMS validation failed for \\(request), did not pass validation hints\")\n                return .invalid\n            }\n\n            // Airship SMS validation\n            let result = switch(sms.validationOptions) {\n            case .sender(let sender, _):\n                try await smsValidatorAPIClient.validateSMS(msisdn: sms.rawInput, sender: sender)\n            case .prefix(let prefix):\n                try await smsValidatorAPIClient.validateSMS(msisdn: sms.rawInput, prefix: prefix)\n            }\n\n            // Assume client errors are not valid\n            guard result.isClientError == false else { return .invalid }\n\n            // Make sure we have a result, if not throw an error\n            guard result.isSuccess, let value = result.result else {\n                throw AirshipErrors.error(\"Failed to validate SMS \\(result)\")\n            }\n\n            // Convert the result\n            return switch (value) {\n            case .invalid: .invalid\n            case .valid(let address): .valid(address: address)\n            }\n        }\n    }\n}\n/// Extension to add matching logic for SMS validation hints (e.g., minimum or maximum digits).\nfileprivate extension AirshipInputValidation.Request.SMS.ValidationHints {\n\n    func matches(_ rawInput: String) -> Bool {\n        let digits = rawInput.filter { $0.isNumber }\n\n        guard\n            digits.count >= (self.minDigits ?? 0),\n            digits.count <= (self.maxDigits ?? Int.max)\n        else {\n            return false\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipInstance.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol AirshipInstance: Sendable {\n    var config: RuntimeConfig { get }\n    var preferenceDataStore: PreferenceDataStore { get }\n    var permissionsManager: any AirshipPermissionsManager { get }\n    var actionRegistry: any AirshipActionRegistry { get }\n    var urlOpener: any URLOpenerProtocol { get }\n\n    #if !os(tvOS) && !os(watchOS)\n    var javaScriptCommandDelegate: (any JavaScriptCommandDelegate)? { get set }\n    var channelCapture: any AirshipChannelCapture { get }\n    #endif\n\n    var deepLinkDelegate: (any DeepLinkDelegate)? { get set }\n\n    @MainActor\n    var onDeepLink: (@MainActor @Sendable (URL) async -> Void)? { get set }\n\n    var urlAllowList: any AirshipURLAllowList { get }\n    var localeManager: any AirshipLocaleManager { get }\n    var inputValidator: any AirshipInputValidation.Validator { get }\n    var privacyManager: any InternalAirshipPrivacyManager { get }\n    var components: [any AirshipComponent] { get }\n\n    func component<E>(ofType componentType: E.Type) -> E?\n\n    @MainActor\n    func airshipReady()\n}\n\nfinal class DefaultAirshipInstance: AirshipInstance {\n\n    public let config: RuntimeConfig\n    public let preferenceDataStore: PreferenceDataStore\n\n    let inputValidator: any AirshipInputValidation.Validator\n    public let permissionsManager: any AirshipPermissionsManager\n    public let actionRegistry: any AirshipActionRegistry\n    \n#if !os(tvOS) && !os(watchOS)\n    private let _jsDelegateHolder: AirshipAtomicValue<(any JavaScriptCommandDelegate)?> = AirshipAtomicValue<(any JavaScriptCommandDelegate)?>(nil)\n    public var javaScriptCommandDelegate: (any JavaScriptCommandDelegate)? {\n        get { return _jsDelegateHolder.value }\n        set { _jsDelegateHolder.value = newValue }\n    }\n    public let channelCapture: any AirshipChannelCapture\n#endif\n\n\n    private let _deeplinkDelegateHolder: AirshipAtomicValue<(any DeepLinkDelegate)?> = AirshipAtomicValue<(any DeepLinkDelegate)?>(nil)\n    public var deepLinkDelegate: (any DeepLinkDelegate)? {\n        get { _deeplinkDelegateHolder.value }\n        set { _deeplinkDelegateHolder.value = newValue }\n    }\n\n    @MainActor\n    public var onDeepLink: (@MainActor @Sendable (URL) async -> Void)?\n\n    public let urlAllowList: any AirshipURLAllowList\n    public let localeManager: any AirshipLocaleManager\n    public let privacyManager: any InternalAirshipPrivacyManager\n    public let components: [any AirshipComponent]\n    private let remoteConfigManager: RemoteConfigManager\n    private let experimentManager: any ExperimentDataProvider\n    private let componentMap: AirshipAtomicValue<[String: any AirshipComponent]> = AirshipAtomicValue([String: any AirshipComponent]()) //it's accessed with the lock below\n    private let lock: AirshipLock = AirshipLock()\n    public let urlOpener: any URLOpenerProtocol = DefaultURLOpener()\n    \n    @MainActor\n    init(airshipConfig: AirshipConfig, appCredentials: AirshipAppCredentials) {\n        let requestSession = DefaultAirshipRequestSession(\n            appKey: appCredentials.appKey,\n            appSecret: appCredentials.appSecret\n        )\n\n        let dataStore = PreferenceDataStore(appKey: appCredentials.appKey)\n        self.preferenceDataStore = dataStore\n        self.permissionsManager = DefaultAirshipPermissionsManager()\n        self.config = RuntimeConfig(\n            airshipConfig: airshipConfig,\n            appCredentials: appCredentials,\n            dataStore: dataStore,\n            requestSession: requestSession\n        )\n\n        self.inputValidator = AirshipInputValidation.DefaultValidator(\n            config: config\n        )\n\n        self.privacyManager = DefaultAirshipPrivacyManager(\n            dataStore: dataStore,\n            config: self.config,\n            defaultEnabledFeatures: airshipConfig.enabledFeatures\n        )\n        \n        self.actionRegistry = DefaultAirshipActionRegistry()\n        self.urlAllowList = DefaultAirshipURLAllowList(airshipConfig: airshipConfig)\n        self.localeManager = DefaultAirshipLocaleManager(\n            dataStore: dataStore,\n            config: self.config\n        )\n        \n        let apnsRegistrar = DefaultAPNSRegistrar()\n        let audienceOverridesProvider = DefaultAudienceOverridesProvider()\n\n        let channel = DefaultAirshipChannel(\n            dataStore: dataStore,\n            config: self.config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceOverridesProvider: audienceOverridesProvider\n        )\n        \n        requestSession.channelAuthTokenProvider = ChannelAuthTokenProvider(\n            channel: channel,\n            runtimeConfig: self.config\n        )\n\n        let cache = CoreDataAirshipCache(appKey: appCredentials.appKey)\n        let audienceChecker = DefaultDeviceAudienceChecker(cache: cache)\n\n        \n        let analytics = DefaultAirshipAnalytics(\n            config: self.config,\n            dataStore: dataStore,\n            channel: channel,\n            localeManager: localeManager,\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager\n        )\n        \n        let push = DefaultAirshipPush(\n            config: self.config,\n            dataStore: dataStore,\n            channel: channel,\n            analytics: analytics,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            apnsRegistrar: apnsRegistrar,\n            badger: Badger.shared\n        )\n        \n        let contact = DefaultAirshipContact(\n            dataStore: dataStore,\n            config: self.config,\n            channel: channel,\n            privacyManager: self.privacyManager,\n            audienceOverridesProvider: audienceOverridesProvider,\n            localeManager: self.localeManager\n        )\n        requestSession.contactAuthTokenProvider = contact.authTokenProvider\n        \n        let remoteData = RemoteData(\n            config: self.config,\n            dataStore: dataStore,\n            localeManager: self.localeManager,\n            privacyManager: self.privacyManager,\n            contact: contact\n        )\n        \n        self.experimentManager = ExperimentManager(\n            dataStore: dataStore,\n            remoteData: remoteData,\n            audienceChecker: audienceChecker\n        )\n        \n        let meteredUsage = DefaultAirshipMeteredUsage(\n            config: self.config,\n            dataStore: dataStore,\n            channel: channel,\n            contact: contact,\n            privacyManager: privacyManager\n        )\n        \n#if !os(tvOS) && !os(watchOS)\n        self.channelCapture = DefaultAirshipChannelCapture(\n            config: self.config,\n            channel: channel\n        )\n#endif\n        \n        let deferredResolver = AirshipDeferredResolver(\n            config: self.config,\n            audienceOverrides: audienceOverridesProvider\n        )\n        \n        let moduleLoader = ModuleLoader(\n            config: self.config,\n            dataStore: dataStore,\n            channel: channel,\n            contact: contact,\n            push: push,\n            remoteData: remoteData,\n            analytics: analytics,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            audienceOverrides: audienceOverridesProvider,\n            experimentsManager: experimentManager,\n            meteredUsage: meteredUsage,\n            deferredResolver: deferredResolver,\n            cache: cache,\n            audienceChecker: audienceChecker,\n            inputValidator: inputValidator\n        )\n        \n        var components: [any AirshipComponent] = [\n            contact, channel, analytics, remoteData, push\n        ]\n        components.append(contentsOf: moduleLoader.components)\n        \n        self.components = components\n        \n        self.remoteConfigManager = RemoteConfigManager(\n            config: self.config,\n            remoteData: remoteData,\n            privacyManager: self.privacyManager\n        )\n        \n        self.actionRegistry.registerActions(\n            actionsManifests: moduleLoader.actionManifests + [DefaultActionsManifest()]\n        )\n    }\n    \n    public func component<E>(ofType componentType: E.Type) -> E? {\n        var component: E?\n        lock.sync {\n            let key = \"Type:\\(componentType)\"\n            var stored = componentMap.value[key]\n            \n            if stored == nil {\n                componentMap.update { current in\n                    var mutable = current\n                    stored = self.components.first { ($0 as? E) != nil }\n                    mutable[key] = stored\n                    return mutable\n                }\n            }\n            \n            component = stored as? E\n        }\n        return component\n    }\n    \n    @MainActor\n    func airshipReady() {\n        self.components.forEach { $0.airshipReady() }\n        self.remoteConfigManager.airshipReady()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipIvyVersionMatcher.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipIvyVersionMatcher: Sendable {\n\n    private static let exactVersionPattern: String = \"^([0-9]+)(\\\\.([0-9]+)((\\\\.([0-9]+))?(.*)))?$\"\n    private static let subVersionPattern: String = \"^(.*)\\\\+$\"\n    private static let startInclusive: String = \"[\"\n    private static let startExclusive: String = \"]\"\n    private static let startInfinite: String = \"(\"\n    private static let endInclusive: String = \"]\"\n    private static let endExclusive: String = \"[\"\n    private static let endInfinite: String = \")\"\n    private static let rangeSeparator: String = \",\"\n    private static let escapeChar: String = \"\\\\\"\n\n    private static let startTokens: String = escapeChar + startInclusive + escapeChar + startExclusive + escapeChar + startInfinite\n    private static let endTokens: String = escapeChar + endInclusive + escapeChar + endExclusive + escapeChar + endInfinite\n    private static let startEndTokens: String = startTokens + endTokens\n    private static let startPattern: String = \"([\" + startTokens + \"])\"\n    private static let endPattern: String = \"([\" + endTokens + \"])\"\n    private static let separatorPattern: String = \"(\" + rangeSeparator + \")\"\n    private static let versionPattern: String = \"([^\" + startEndTokens + rangeSeparator + \"]*)\"\n    private static let versionRangePattern: String = startPattern + versionPattern + separatorPattern + versionPattern + endPattern\n\n    private enum Constraint: Sendable {\n        case exactVersion(String)\n        case subVersion(String)\n        case versionRange(start: Boundary, end: Boundary)\n    }\n\n    private enum Boundary: Sendable {\n        case inclusive(String)\n        case exclusive(String)\n        case infinite\n\n        var isInfinite: Bool {\n            switch self {\n            case .infinite:\n                return true\n            default:\n                return false\n            }\n        }\n    }\n\n    private let contraint: Constraint\n\n    public init(versionConstraint: String) throws {\n        let strippedVersionConstraint = String(versionConstraint.filter { !$0.isWhitespace })\n\n        let parsed = Self.parseSubVersionConstraint(strippedVersionConstraint) ??\n        Self.parseExactVersionConstraint(strippedVersionConstraint) ??\n        Self.parseVersionRangeConstraint(strippedVersionConstraint)\n\n        guard let parsed else {\n            throw AirshipErrors.error(\"Invalid version matcher constraint \\(versionConstraint)\")\n        }\n\n        self.contraint = parsed\n\n    }\n\n    public func evaluate(version: String) -> Bool {\n        let checkVersion = version.filter { !$0.isWhitespace }\n\n        return switch self.contraint {\n        case .exactVersion(let exactVersion):\n            checkVersion.normalizeVersionString == exactVersion.normalizeVersionString\n        case .subVersion(let subVersion):\n            Self.evaluateSubversion(subVersion: subVersion, checkVersion: version)\n        case .versionRange(let start, let end):\n            Self.evaluateVersionRange(start: start, end: end, checkVersion: version)\n        }\n    }\n\n    private static func parseExactVersionConstraint(\n        _ versionConstraint: String\n    ) -> Constraint? {\n        guard\n            let matches = try? Self.getMatchesForPattern(\n                exactVersionPattern,\n                on: versionConstraint\n            ), matches.count == 1\n        else {\n            return nil\n        }\n\n        return .exactVersion(versionConstraint)\n    }\n\n\n    private static func parseSubVersionConstraint(\n        _ versionConstraint: String\n    ) -> Constraint? {\n        guard\n            let matches = try? self.getMatchesForPattern(\n                subVersionPattern,\n                on: versionConstraint\n            ),\n            matches.count == 1\n        else {\n            return nil\n        }\n\n        let nsRange = matches[0].range(at: 1)\n        guard let range = Range(nsRange, in: versionConstraint) else {\n            return nil\n        }\n        let versionNumberPart: String = String(versionConstraint[range])\n\n        return .subVersion(versionNumberPart)\n    }\n\n\n    private static func parseVersionRangeConstraint(\n        _ versionConstraint: String\n    ) -> Constraint? {\n        guard\n            let matches = try? Self.getMatchesForPattern(\n                versionRangePattern,\n                on: versionConstraint\n            ), matches.count == 1\n        else {\n            return nil\n        }\n\n        /// extract tokens from version constraint\n        let match = matches[0]\n        let numberOfTokens = (match.numberOfRanges) - 1\n        guard numberOfTokens == 5 else { return nil }\n\n        var tokens: [String] = []\n        for index in 1...numberOfTokens {\n            let nsRange = match.range(at: index)\n            guard let range = Range(nsRange, in: versionConstraint) else {\n                return nil\n            }\n            tokens.append(String(versionConstraint[range]))\n        }\n\n        guard\n            let start = parseStartRangeBoundary(tokens[0], constraint: tokens[1]),\n            let end = parseEndRangeBoundary(tokens[4], constraint: tokens[3]),\n            !(end.isInfinite && start.isInfinite)\n        else {\n            return nil\n        }\n\n        return .versionRange(start: start, end: end)\n\n    }\n\n    private static func evaluateSubversion(subVersion: String, checkVersion: String) -> Bool {\n        if subVersion == \"*\" {\n            return true\n        }\n\n        return checkVersion.hasPrefix(subVersion.normalizeVersionString)\n    }\n\n    private static func evaluateVersionRange(start: Boundary, end: Boundary, checkVersion: String) -> Bool {\n        switch(start) {\n        case .inclusive(let constraint):\n            let result = AirshipUtils.compareVersion(\n                constraint,\n                toVersion: checkVersion,\n                maxVersionParts: 3\n            )\n            if result != .orderedAscending && result != .orderedSame {\n                return false\n            }\n        case .exclusive(let constraint):\n            let result = AirshipUtils.compareVersion(\n                constraint,\n                toVersion: checkVersion,\n                maxVersionParts: 3\n            )\n            if result != .orderedAscending {\n                return false\n            }\n        case .infinite: break\n        }\n\n        switch(end) {\n        case .inclusive(let constraint):\n            let result = AirshipUtils.compareVersion(\n                checkVersion,\n                toVersion: constraint,\n                maxVersionParts: 3\n            )\n            if result != .orderedAscending && result != .orderedSame {\n                return false\n            }\n        case .exclusive(let constraint):\n            let result = AirshipUtils.compareVersion(\n                checkVersion,\n                toVersion: constraint,\n                maxVersionParts: 3\n            )\n            if result != .orderedAscending {\n                return false\n            }\n        case .infinite: break\n            \n        }\n\n        return true\n    }\n\n    private static func getMatchesForPattern(\n        _ pattern: String,\n        on string: String\n    ) throws -> [NSTextCheckingResult] {\n        let regex = try NSRegularExpression(\n            pattern: pattern,\n            options: .caseInsensitive\n        )\n\n        return regex.matches(\n            in: string,\n            options: [],\n            range: NSRange(location: 0, length: string.count)\n        )\n    }\n\n    private static func parseStartRangeBoundary(_ boundary: String, constraint: String) -> Boundary? {\n        return switch(boundary) {\n        case startInfinite: constraint.isEmpty ? .infinite : nil\n        case startExclusive: constraint.isEmpty ? nil : .exclusive(constraint)\n        case startInclusive: constraint.isEmpty ? nil : .inclusive(constraint)\n        default: nil\n        }\n    }\n\n    private static func parseEndRangeBoundary(_ boundary: String, constraint: String) -> Boundary? {\n        return switch(boundary) {\n        case endInfinite: constraint.isEmpty ? .infinite : nil\n        case endExclusive: constraint.isEmpty ? nil : .exclusive(constraint)\n        case endInclusive: constraint.isEmpty ? nil : .inclusive(constraint)\n        default: nil\n        }\n    }\n}\n\nfileprivate extension String {\n    var normalizeVersionString: String {\n        let trimmed = self.filter { !$0.isWhitespace }\n\n        // Find the first occurrence of \"-\"\n        if let index = trimmed.firstIndex(of: \"-\") {\n            // If the index is not the very first character...\n            if index > trimmed.startIndex {\n                // Get the substring before the \"-\"\n                let baseVersion = trimmed[..<index]\n\n                // Check for a \"+\" suffix and append it if it exists\n                if trimmed.hasSuffix(\"+\") {\n                    return String(baseVersion) + \"+\"\n                } else {\n                    return String(baseVersion)\n                }\n            }\n        }\n\n        return trimmed\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipJSON.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/**\n * Airship JSON.\n */\npublic enum AirshipJSON: Codable, Equatable, Sendable, Hashable {\n    public static var defaultEncoder: JSONEncoder {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        return encoder\n    }\n\n    public static var defaultDecoder: JSONDecoder { return JSONDecoder() }\n\n    case string(String)\n    case number(Double)\n    case object([String: AirshipJSON])\n    case array([AirshipJSON])\n    case bool(Bool)\n    case null\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n        switch self {\n        case .array(let array): try container.encode(array)\n        case .object(let object): try container.encode(object)\n        case .number(let number): try container.encode(number)\n        case .string(let string): try container.encode(string)\n        case .bool(let bool): try container.encode(bool)\n        case .null: try container.encodeNil()\n        }\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n\n        if let object = try? container.decode([String: AirshipJSON].self) {\n            self = .object(object)\n        } else if let array = try? container.decode([AirshipJSON].self) {\n            self = .array(array)\n        } else if let string = try? container.decode(String.self) {\n            self = .string(string)\n        } else if let bool = try? container.decode(Bool.self) {\n            self = .bool(bool)\n        } else if let number = try? container.decode(Double.self) {\n            self = .number(number)\n        } else if container.decodeNil() {\n            self = .null\n        } else {\n            throw AirshipErrors.error(\"Invalid JSON\")\n        }\n    }\n\n    public func unWrap() -> AnyHashable? {\n        switch self {\n        case .string(let value):\n            return value\n        case .number(let value):\n            return value\n        case .bool(let value):\n            return value\n        case .null:\n            return nil\n        case .object(let value):\n            var dict: [String: AnyHashable] = [:]\n            value.forEach {\n                dict[$0.key] = $0.value.unWrap()\n            }\n            return dict\n        case .array(let value):\n            var array: [AnyHashable] = []\n            value.forEach {\n                if let item = $0.unWrap() {\n                    array.append(item)\n                }\n            }\n            return array\n        }\n    }\n\n    public static func from(\n        json: String?,\n        decoder: JSONDecoder = AirshipJSON.defaultDecoder\n    ) throws -> AirshipJSON {\n        guard let json = json else {\n            return .null\n        }\n        \n        guard let data = json.data(using: .utf8) else {\n            throw AirshipErrors.error(\"Invalid encoding: \\(json)\")\n        }\n        \n        return try decoder.decode(AirshipJSON.self, from: data)\n    }\n    \n    public static func from(\n        data: Data?,\n        decoder: JSONDecoder = AirshipJSON.defaultDecoder\n    ) throws -> AirshipJSON {\n        guard let data = data else {\n            return .null\n        }\n        \n        return try decoder.decode(AirshipJSON.self, from: data)\n    }\n\n    public static func wrap(_ value: Any?, encoder: @autoclosure () -> JSONEncoder = AirshipJSON.defaultEncoder) throws -> AirshipJSON {\n        guard let value = value else {\n            return .null\n        }\n\n        if let json = value as? AirshipJSON {\n            return json\n        }\n\n        if let string = value as? String {\n            return .string(string)\n        }\n\n        if let url = value as? URL {\n            return .string(url.absoluteString)\n        }\n\n        if let number = value as? NSNumber {\n            guard CFBooleanGetTypeID() == CFGetTypeID(number) else {\n                return .number(number.doubleValue)\n            }\n            return .bool(number.boolValue)\n        }\n\n        if let bool = value as? Bool {\n            return .bool(bool)\n        }\n\n        if let number = value as? Double {\n            return .number(number)\n        }\n\n        if let array = value as? [Any?] {\n            let mapped: [AirshipJSON] = try array.map { child in\n                try wrap(child, encoder: encoder())\n            }\n\n            return .array(mapped)\n        }\n\n        if let object = value as? [String: Any?] {\n            let mapped: [String: AirshipJSON] = try object.mapValues { child in\n                try wrap(child, encoder: encoder())\n            }\n\n            return .object(mapped)\n        }\n\n        if let codable = value as? (any Encodable) {\n            let encoder = encoder()\n            return try wrap(\n                JSONSerialization.jsonObject(with: try encoder.encode(codable), options: .fragmentsAllowed),\n                encoder: encoder\n            )\n        }\n\n        throw AirshipErrors.error(\"Invalid JSON \\(value)\")\n    }\n    \n    public func toData(encoder: JSONEncoder = AirshipJSON.defaultEncoder) throws -> Data {\n        return try encoder.encode(self)\n    }\n    \n    public func toString(encoder: JSONEncoder = AirshipJSON.defaultEncoder) throws -> String {\n        return String(\n            decoding: try encoder.encode(self),\n            as: UTF8.self\n        )\n    }\n\n    public func decode<T: Decodable>(\n        decoder: JSONDecoder = AirshipJSON.defaultDecoder,\n        encoder: JSONEncoder = AirshipJSON.defaultEncoder\n    ) throws -> T {\n        let data = try toData(encoder: encoder)\n        return try decoder.decode(T.self, from: data)\n    }\n}\n\npublic extension AirshipJSON {\n    var isNull: Bool {\n        if case .null = self {\n            return true\n        }\n        return false\n    }\n\n    var isObject: Bool {\n        if case .object(_) = self {\n            return true\n        }\n        return false\n    }\n\n    var isArray: Bool {\n        if case .array(_) = self {\n            return true\n        }\n        return false\n    }\n\n    var isNumber: Bool {\n        if case .number(_) = self {\n            return true\n        }\n        return false\n    }\n\n    var isString: Bool {\n        if case .string(_) = self {\n            return true\n        }\n        return false\n    }\n\n    var isBool: Bool {\n        if case .bool(_) = self {\n            return true\n        }\n        return false\n    }\n\n    var string: String? {\n        guard case .string(let value) = self else { return nil }\n        return value\n    }\n\n    var number: Double? {\n        guard case .number(let value) = self else { return nil }\n        return value\n    }\n\n    var object: [String: AirshipJSON]? {\n        guard case .object(let value) = self else { return nil }\n        return value\n    }\n\n    var array: [AirshipJSON]? {\n        guard case .array(let value) = self else { return nil }\n        return value\n    }\n\n    var double: Double? {\n        guard case .number(let value) = self else { return nil }\n        return value\n    }\n\n    var bool: Bool? {\n        guard case .bool(let value) = self else { return nil }\n        return value\n    }\n\n    static func makeObject(builderBlock: (inout AirshipJSONObjectBuilder) -> Void) -> AirshipJSON {\n        var builder: AirshipJSONObjectBuilder = AirshipJSONObjectBuilder()\n        builderBlock(&builder)\n        return builder.build()\n    }\n}\n\nextension AirshipJSON {\n    /// Decodes an ``AirshipJSON`` value from raw JSON data, logging any decode error and returning `nil` on failure.\n    ///\n    /// Unlike ``from(data:decoder:)``, this method never throws. Decode errors are logged via `AirshipLogger`\n    /// and `nil` is returned instead. Passing `nil` data returns ``AirshipJSON/null``.\n    ///\n    /// - Parameters:\n    ///   - data: The raw JSON data to decode. Passing `nil` returns ``AirshipJSON/null``.\n    ///   - decoder: The decoder to use. Defaults to ``defaultDecoder``.\n    /// - Returns: The decoded value, or `nil` if decoding failed.\n    public static func fromDataLoggingError(data: Data?, decoder: JSONDecoder = AirshipJSON.defaultDecoder) -> AirshipJSON? {\n        do {\n            return try AirshipJSON.from(data: data, decoder: decoder)\n        } catch {\n            AirshipLogger.error(\"Failed to decode AirshipJSON: \\(error)\")\n            return nil\n        }\n    }\n\n    /// Encodes the value to JSON data, logging any encode error and returning `nil` on failure.\n    ///\n    /// Unlike ``toData(encoder:)``, this method never throws. Encode errors are logged via `AirshipLogger`\n    /// and `nil` is returned instead.\n    ///\n    /// - Returns: The encoded data, or `nil` if encoding failed.\n    public func toDataLoggingError(encoder: JSONEncoder = AirshipJSON.defaultEncoder) -> Data? {\n        do {\n            return try self.toData(encoder: encoder)\n        } catch {\n            AirshipLogger.error(\"Failed to encode AirshipJSON: \\(error)\")\n            return nil\n        }\n    }\n\n}\n\npublic struct AirshipJSONObjectBuilder {\n    var data: [String: AirshipJSON] = [:]\n\n    public mutating func set(string: String?, key: String) {\n        guard let string = string else {\n            data[key] = nil\n            return\n        }\n        data[key] = .string(string)\n    }\n\n    public mutating func set(array: [AirshipJSON]?, key: String) {\n        guard let array = array else {\n            data[key] = nil\n            return\n        }\n        data[key] = .array(array)\n    }\n\n    public mutating func set(object: [String: AirshipJSON]?, key: String) {\n        guard let object = object else {\n            data[key] = nil\n            return\n        }\n        data[key] = .object(object)\n    }\n\n    public mutating func set(json: AirshipJSON?, key: String) {\n        guard let json = json else {\n            data[key] = nil\n            return\n        }\n        data[key] = json\n    }\n\n    public mutating func set(double: Double?, key: String) {\n        guard let double = double else {\n            data[key] = nil\n            return\n        }\n        data[key] = .number(double)\n    }\n\n    public mutating func set(bool: Bool?, key: String) {\n        guard let bool = bool else {\n            data[key] = nil\n            return\n        }\n        data[key] = .bool(bool)\n    }\n\n    func build() -> AirshipJSON {\n        return .object(data)\n    }\n}\n\nextension AirshipJSON: ExpressibleByStringLiteral {\n    public init(stringLiteral value: StringLiteralType) {\n        self = .string(value)\n    }\n}\n\nextension AirshipJSON: ExpressibleByBooleanLiteral {\n    public init(booleanLiteral value: Bool) {\n        self = .bool(value)\n    }\n}\n\nextension AirshipJSON: ExpressibleByIntegerLiteral {\n    public init(integerLiteral value: IntegerLiteralType) {\n        self = .number(Double(value))\n    }\n}\n\nextension AirshipJSON: ExpressibleByFloatLiteral {\n    public init(floatLiteral value: FloatLiteralType) {\n        self = .number(Double(value))\n    }\n}\n\nextension AirshipJSON: ExpressibleByArrayLiteral {\n    public init(arrayLiteral elements: AirshipJSON...) {\n        self = .array(elements)\n    }\n}\n\nextension AirshipJSON: ExpressibleByDictionaryLiteral {\n    public init(dictionaryLiteral elements: (String, AirshipJSON)...) {\n        var dict: [String: AirshipJSON] = [:]\n        for (key, value) in elements {\n            dict[key] = value\n        }\n        self = .object(dict)\n    }\n}\n\nextension AirshipJSON: ExpressibleByNilLiteral {\n    public init(nilLiteral: Void) {\n        self = .null\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipJSONUtils.swift",
    "content": "// Copyright Airship and Contributors\n\npublic import Foundation\n\n/// - NOTE: Internal use only :nodoc:\npublic class AirshipJSONUtils: NSObject {\n\n    public class func data(\n        _ obj: Any,\n        options: JSONSerialization.WritingOptions = []\n    ) throws -> Data {\n        try validateJSONObject(obj, options: options)\n        return try JSONSerialization.data(withJSONObject: obj, options: options)\n    }\n    \n    public class func toData(\n        _ obj: Any?\n    ) -> Data? {\n        guard let obj = obj else {\n            return nil\n        }\n        \n        do {\n            return try AirshipJSONUtils.data(\n                obj,\n                options: JSONSerialization.WritingOptions.prettyPrinted\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to transform value: \\(obj), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n    \n    public class func json(_ data: Data?) -> Any? {\n        \n        guard let data = data, !data.isEmpty else {\n            return nil\n        }\n        \n        do {\n            return try JSONSerialization.jsonObject(\n                with: data,\n                options: .mutableContainers\n            )\n        } catch {\n            AirshipLogger.error(\"Converting data \\(data) failed with error \\(error)\")\n            return nil\n        }\n        \n    }\n    \n\n    public class func string(\n        _ obj: Any,\n        options: JSONSerialization.WritingOptions\n    ) throws -> String {\n        try validateJSONObject(obj, options: options)\n        let data = try self.data(obj, options: options)\n        guard let string = String(data: data, encoding: .utf8) else {\n            throw AirshipErrors.error(\"Invalid JSON \\(obj)\")\n        }\n\n        return string\n    }\n\n    public class func string(_ obj: Any) -> String? {\n        return try? self.string(obj, options: [])\n    }\n\n    public class func object(_ string: String) -> Any? {\n        return try? self.object(string, options: [])\n    }\n\n    public class func object(\n        _ string: String,\n        options: JSONSerialization.ReadingOptions\n    ) throws -> Any {\n        guard let data = string.data(using: .utf8) else {\n            throw AirshipErrors.error(\"Invalid JSON \\(string)\")\n        }\n\n        return try JSONSerialization.jsonObject(with: data, options: options)\n    }\n\n    public class func decode<T: Decodable>(\n        data: Data?\n    ) throws -> T {\n        guard let data = data else {\n            throw AirshipErrors.parseError(\"data missing response body.\")\n        }\n\n        return try JSONDecoder()\n            .decode(\n                T.self,\n                from: data\n            )\n    }\n\n    public class func encode<T: Encodable>(\n        object: T?\n    ) throws -> Data {\n        guard let object = object else {\n            throw AirshipErrors.parseError(\"data missing.\")\n        }\n\n        return try JSONEncoder().encode(object)\n    }\n\n    private class func validateJSONObject(\n        _ object: Any,\n        options: JSONSerialization.WritingOptions\n    ) throws {\n\n        var valid = false\n        if options.contains(.fragmentsAllowed) {\n            valid = JSONSerialization.isValidJSONObject([object])\n        } else {\n            valid = JSONSerialization.isValidJSONObject(object)\n        }\n\n        guard valid else {\n            throw AirshipErrors.error(\"Invalid JSON: \\(object)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipKeychainAccess.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Keychain credentials\n/// - Note: for internal use only.  :nodoc:\npublic struct AirshipKeychainCredentials: Sendable {\n\n    /// The username\n    public let username: String\n\n    /// The password\n    public let password: String\n\n    /// Constructor\n    /// - Parameters:\n    ///     - username: The username\n    ///     - password: The password\n    public init(username: String, password: String) {\n        self.username = username\n        self.password = password\n    }\n}\n\n/// Keychain access\n/// - Note: for internal use only.  :nodoc:\npublic protocol AirshipKeychainAccessProtocol: Sendable {\n    /// Writes credentials to the keychain for the given identifier.\n    /// - Parameters:\n    ///     - credentials: The credentials to save\n    ///     - identifier: The credential's identifier\n    ///     - appKey: The app key\n    /// - Returns: `true` if the data was written, otherwise `false`.\n    func writeCredentials(\n        _ credentials: AirshipKeychainCredentials,\n        identifier: String,\n        appKey: String\n    ) async -> Bool\n\n    /// Deletes credentials for the given identifier.\n    /// - Parameters:\n    ///     - identifier: The credential's identifier\n    ///     - appKey: The app key\n    func deleteCredentials(\n        identifier: String,\n        appKey: String\n    ) async\n\n    /// Reads credentials from the keychain synchronously.\n    ///\n    /// - NOTE: This method could take a long time to call, it should not\n    /// be called on the main queue.\n    ///\n    /// - Parameters:\n    ///     - identifier: The credential's identifier\n    ///     - appKey: The app key\n    /// - Returns: The credentials if found.\n    func readCredentails(\n        identifier: String,\n        appKey: String\n    ) async -> AirshipKeychainCredentials?\n}\n\n/// Keychain access\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipKeychainAccess: AirshipKeychainAccessProtocol {\n\n    public static let shared: AirshipKeychainAccess = AirshipKeychainAccess()\n\n    // Dispatch queue to prevent blocking any tasks\n    private let dispatchQueue: AirshipUnsafeSendableWrapper<DispatchQueue> = AirshipUnsafeSendableWrapper(\n        DispatchQueue(\n            label: \"com.urbanairship.dispatcher.keychain\",\n            qos: .utility\n        )\n    )\n\n    public func writeCredentials(\n        _ credentials: AirshipKeychainCredentials,\n        identifier: String,\n        appKey: String\n    ) async -> Bool {\n        let service = service(appKey: appKey)\n        return await self.dispatch { [service] in\n            let result = Keychain.writeCredentials(\n                credentials,\n                identifier: identifier,\n                service: service\n            )\n\n            // Write to old location in case of a downgrade\n            if let bundleID = Bundle.main.bundleIdentifier {\n                let _ = Keychain.writeCredentials(\n                    credentials,\n                    identifier: identifier,\n                    service: bundleID\n                )\n            }\n\n\n            return result\n        }\n    }\n\n    public func deleteCredentials(identifier: String, appKey: String) async {\n        let service = service(appKey: appKey)\n\n        await self.dispatch { [service] in\n            Keychain.deleteCredentials(\n                identifier: identifier,\n                service: service\n            )\n\n            // Delete old\n            if let bundleID = Bundle.main.bundleIdentifier {\n                Keychain.deleteCredentials(\n                    identifier: identifier,\n                    service: bundleID\n                )\n            }\n        }\n    }\n\n    public func readCredentails(\n        identifier: String,\n        appKey: String\n    ) async -> AirshipKeychainCredentials? {\n\n        let service = service(appKey: appKey)\n\n        return await self.dispatch { [service] in\n            if let credentials = Keychain.readCredentials(\n                identifier: identifier,\n                service: service\n            ) {\n                return credentials\n            }\n\n            // If we do not have a new value, check\n            // the old service location\n            if let bundleID = Bundle.main.bundleIdentifier {\n\n                let old = Keychain.readCredentials(\n                    identifier: identifier,\n                    service: bundleID\n                )\n\n                if let old = old {\n                    // Migrate old data to new service location\n                    let _ = Keychain.writeCredentials(\n                        old,\n                        identifier: identifier,\n                        service: service\n                    )\n                    return old\n                }\n            }\n            return nil\n        }\n\n    }\n\n    private func dispatch<T>(block: @escaping @Sendable () -> T) async -> T {\n        return await withCheckedContinuation { continuation in\n            dispatchQueue.value.async {\n                continuation.resume(returning: block())\n            }\n        }\n    }\n\n    private func service(appKey: String) -> String {\n        return \"\\(Bundle.main.bundleIdentifier ?? \"\").airship.\\(appKey)\"\n    }\n}\n\n\n/// Helper that wraps the actual keychain calls\nprivate struct Keychain {\n    static func writeCredentials(\n        _ credentials: AirshipKeychainCredentials,\n        identifier: String,\n        service: String\n    ) -> Bool {\n        guard \n            let identifierData = identifier.data(using: .utf8),\n            let passwordData = credentials.password.data(using: .utf8)\n        else {\n            return false\n        }\n\n        deleteCredentials(identifier: identifier, service: service)\n\n        let addquery: [String: Any] = [\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: service,\n            kSecAttrGeneric as String: identifierData,\n            kSecAttrAccount as String: credentials.username,\n            kSecValueData as String: passwordData,\n        ]\n\n        let status = SecItemAdd(addquery as CFDictionary, nil)\n        return status == errSecSuccess\n    }\n\n    static func deleteCredentials(\n        identifier: String,\n        service: String\n    ) {\n        guard let identifierData = identifier.data(using: .utf8)\n        else {\n            return\n        }\n\n        let deleteQuery: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: service,\n            kSecAttrGeneric as String: identifierData,\n        ]\n\n        SecItemDelete(deleteQuery as CFDictionary)\n    }\n\n    static func readCredentials(\n        identifier: String,\n        service: String\n    ) -> AirshipKeychainCredentials? {\n        guard let identifierData = identifier.data(using: .utf8) else {\n            return nil\n        }\n\n        let searchQuery: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecMatchLimit as String: kSecMatchLimitOne,\n            kSecAttrService as String: service,\n            kSecAttrGeneric as String: identifierData,\n            kSecReturnAttributes as String: true,\n            kSecReturnData as String: true,\n        ]\n        \n\n        var item: CFTypeRef?\n        let status = SecItemCopyMatching(searchQuery as CFDictionary, &item)\n        guard status == errSecSuccess else {\n            return nil\n        }\n\n        guard let existingItem = item as? [String: Any] else {\n            return nil\n        }\n\n        guard let passwordData = existingItem[kSecValueData as String] as? Data,\n              let password = String(\n                data: passwordData,\n                encoding: String.Encoding.utf8\n              ),\n              let username = existingItem[kSecAttrAccount as String] as? String\n        else {\n            return nil\n        }\n\n        let credentials = AirshipKeychainCredentials(\n            username: username,\n            password: password\n        )\n\n        let attrAccessible = existingItem[kSecAttrAccessible as String] as? String\n        if attrAccessible != (kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly as String) {\n            updateThisDeviceOnly(credentials: credentials, identifier: identifier, service: service)\n        }\n\n        return credentials\n    }\n\n    static func updateThisDeviceOnly(credentials: AirshipKeychainCredentials, identifier: String, service: String) {\n        guard\n            let identifierData = identifier.data(using: .utf8),\n            let passwordData = credentials.password.data(using: .utf8)\n        else {\n            return\n        }\n\n        let updateQuery: [String: Any] = [\n            kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,\n            kSecValueData as String: passwordData\n        ]\n\n        let searchQuery: [String: Any] = [\n            kSecClass as String: kSecClassGenericPassword,\n            kSecAttrService as String: service,\n            kSecAttrGeneric as String: identifierData,\n            kSecAttrAccount as String: credentials.username\n        ]\n\n        let updateStatus = SecItemUpdate(searchQuery as CFDictionary, updateQuery as CFDictionary)\n\n        if (updateStatus == errSecSuccess) {\n            AirshipLogger.trace(\"Updated keychain value \\(identifier) to this device only\")\n        } else {\n            AirshipLogger.debug(\"Failed to update keychain value \\(identifier) status:\\(updateStatus)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n/// AirshipLayout\npublic struct AirshipLayout: ThomasSerializable {\n    /// The view DSL\n    let view: ThomasViewInfo\n\n    /// Layout DSL version\n    let version: Int\n\n    /// Presentation configuration\n    let presentation: ThomasPresentationInfo\n\n    public var isEmbedded: Bool {\n        guard case .embedded(_) = presentation else {\n            return false\n        }\n\n        return true\n    }\n}\n\nextension ThomasViewInfo {\n    func extractDescendants<T>(extractor: (ThomasViewInfo) -> T?) -> [T] {\n        var infos: [ThomasViewInfo] = [self]\n        var result: [T] = []\n        while (!infos.isEmpty) {\n            let info = infos.removeFirst()\n            if let children = info.immediateChildren {\n                infos.append(contentsOf: children)\n            }\n\n            if let value = extractor(info) {\n                result.append(value)\n            }\n        }\n\n        return result\n    }\n\n    var immediateChildren: [ThomasViewInfo]? {\n        return switch self {\n        case .container(let info): info.properties.items.map { $0.view }\n        case .linearLayout(let info): info.properties.items.map { $0.view }\n        case .pager(let info): info.properties.items.map { $0.view }\n        case .scrollLayout(let info): [info.properties.view]\n        case .checkboxController(let info): [info.properties.view]\n        case .radioInputController(let info): [info.properties.view]\n        case .formController(let info): [info.properties.view]\n        case .npsController(let info): [info.properties.view]\n        case .pagerController(let info): [info.properties.view]\n        case .media: nil\n        case .imageButton: nil\n        case .stackImageButton: nil\n        #if !os(tvOS) && !os(watchOS)\n        case .webView: nil\n        #endif\n        case .label: nil\n        case .labelButton(let info): [.label(info.properties.label)]\n        case .emptyView: nil\n        case .pagerIndicator(_): nil\n        case .storyIndicator(_): nil\n        case .checkbox(_): nil\n        case .radioInput(_): nil\n        case .textInput(_): nil\n        case .score(_): nil\n        case .toggle(_): nil\n        case .stateController(let info): [info.properties.view]\n        case .customView: nil\n        case .buttonLayout(let info): [info.properties.view]\n        case .basicToggleLayout(let info): [info.properties.view]\n        case .checkboxToggleLayout(let info): [info.properties.view]\n        case .radioInputToggleLayout(let info): [info.properties.view]\n        case .iconView: nil\n        case .scoreController(let info): [info.properties.view]\n        case .scoreToggleLayout(let info): [info.properties.view]\n        case .videoController(let info): [info.properties.view]\n        }\n    }\n}\n\nextension AirshipLayout {\n    static let minLayoutVersion = 1\n    static let maxLayoutVersion = 2\n\n    public func validate() -> Bool\n    {\n        guard\n            self.version >= Self.minLayoutVersion\n                && self.version <= Self.maxLayoutVersion else {\n            return false\n        }\n\n        return true\n    }\n\n    func extract<T>(extractor: (ThomasViewInfo) -> T?) -> [T] {\n        return self.view.extractDescendants(extractor: extractor)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipLocalizationUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - NOTE: Internal use only :nodoc:\npublic final class AirshipLocalizationUtils {\n\n    private static func sanitizedLocalizedString(\n        _ localizedString: String,\n        withTable table: String?,\n        primaryBundle: Bundle,\n        secondaryBundle: Bundle,\n        tertiaryBundle: Bundle?\n    ) -> String? {\n\n        var string: String?\n\n        /// This \"empty\" string has a space in it, so as not to be treated as equivalent to nil by the NSBundle method\n        let missing = \" \"\n\n        string = NSLocalizedString(\n            localizedString,\n            tableName: table,\n            bundle: primaryBundle,\n            value: missing,\n            comment: \"\"\n        )\n\n        if string == nil || (string == missing) {\n            string = NSLocalizedString(\n                localizedString,\n                tableName: table,\n                bundle: secondaryBundle,\n                value: missing,\n                comment: \"\"\n            )\n        }\n\n        if string == nil || (string == missing) {\n            string = NSLocalizedString(\n                localizedString,\n                tableName: table,\n                bundle: tertiaryBundle!,\n                value: missing,\n                comment: \"\"\n            )\n        }\n\n        if string == nil || (string == missing) {\n            return nil\n        }\n\n        return string\n    }\n\n    public static func localizedString(\n        _ string: String,\n        withTable table: String,\n        moduleBundle: Bundle?\n    ) -> String? {\n\n        let mainBundle = Bundle.main\n        let coreBundle = AirshipCoreResources.bundle\n\n        return sanitizedLocalizedString(\n            string,\n            withTable: table,\n            primaryBundle: mainBundle,\n            secondaryBundle: coreBundle,\n            tertiaryBundle: moduleBundle\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipLock.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: For internal use only. :nodoc:\npublic final class AirshipLock: Sendable {\n    private let _lock: NSRecursiveLock = NSRecursiveLock()\n\n    public init() {}\n\n    public func sync(closure: () -> Void) {\n        self._lock.withLock {\n            closure()\n        }\n    }\n    \n    public func sync<T>(closure: () -> T) -> T {\n        return self._lock.withLock {\n            return closure()\n        }\n    }\n\n    public func lock() {\n        self._lock.lock()\n    }\n\n    public func unlock() {\n        self._lock.unlock()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipMeteredUsage.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AirshipMeteredUsage: Sendable {\n    func addEvent(_ event: AirshipMeteredUsageEvent) async throws\n}\n\n/// NOTE: For internal use only. :nodoc:\nfinal class DefaultAirshipMeteredUsage: AirshipMeteredUsage {\n\n    private static let workID: String = \"MeteredUsage.upload\"\n    private static let configKey: String = \"MeteredUsage.config\"\n    private static let rateLimitID: String = \"MeteredUsage.rateLimit\"\n    private static let defaultRateLimit: TimeInterval = 30.0\n    private static let defaultInitialDelay: TimeInterval = 15.0\n\n    private let config: RuntimeConfig\n\n    private let dataStore: PreferenceDataStore\n    private let channel: any AirshipChannel\n    private let contact: any InternalAirshipContact\n    private let client: any MeteredUsageAPIClientProtocol\n    private let workManager: any AirshipWorkManagerProtocol\n    private let store: MeteredUsageStore\n    private let privacyManager: any AirshipPrivacyManager\n\n    @MainActor\n    convenience init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel,\n        contact: any InternalAirshipContact,\n        privacyManager: any AirshipPrivacyManager\n    ) {\n        self.init(\n            config: config,\n            dataStore: dataStore,\n            channel: channel,\n            contact: contact,\n            privacyManager: privacyManager,\n            client: MeteredUsageAPIClient(config: config),\n            store: MeteredUsageStore(appKey: config.appCredentials.appKey)\n        )\n    }\n\n    @MainActor\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel,\n        contact: any InternalAirshipContact,\n        privacyManager: any AirshipPrivacyManager,\n        client: any MeteredUsageAPIClientProtocol,\n        store: MeteredUsageStore,\n        workManager: any AirshipWorkManagerProtocol = AirshipWorkManager.shared,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared\n    ) {\n        self.config = config\n        self.dataStore = dataStore\n        self.channel = channel\n        self.contact = contact\n        self.privacyManager = privacyManager\n        self.client = client\n        self.store = store\n        self.workManager = workManager\n        \n        self.workManager.registerWorker(\n            Self.workID\n        ) { [weak self] _ in\n            guard let self else {\n                return .success\n            }\n            return try await self.performWork()\n        }\n\n        self.workManager.autoDispatchWorkRequestOnBackground(\n            AirshipWorkRequest(\n                workID: Self.workID,\n                requiresNetwork: true,\n                conflictPolicy: .replace\n            )\n        )\n\n        self.config.addRemoteConfigListener { [weak self] old, new in\n            self?.updateConfig(\n                old: old?.meteredUsageConfig,\n                new: new.meteredUsageConfig\n            )\n        }\n    }\n\n    @MainActor\n    private func updateConfig(old: RemoteConfig.MeteredUsageConfig?, new: RemoteConfig.MeteredUsageConfig?) {\n        self.workManager.setRateLimit(\n            Self.rateLimitID,\n            rate: 1,\n            timeInterval: new?.interval ?? Self.defaultRateLimit\n        )\n\n        if old?.isEnabled != true && new?.isEnabled == true {\n            self.scheduleWork(\n                initialDelay: new?.intialDelay ?? Self.defaultInitialDelay\n            )\n        }\n    }\n\n    private func performWork() async throws -> AirshipWorkResult {\n        guard self.isEnabled else { return .success }\n        \n        var events = try await self.store.getEvents()\n        guard events.count != 0 else { return .success }\n\n        var channelID: String? = nil\n        if (privacyManager.isEnabled(.analytics)) {\n            channelID = self.channel.identifier\n        } else {\n            events = events.map( { $0.withDisabledAnalytics() })\n        }\n\n        let result = try await self.client.uploadEvents(\n            events,\n            channelID: channelID\n        )\n\n        guard result.isSuccess else {\n            return .failure\n        }\n\n        try await self.store.deleteEvents(events)\n        return .success\n    }\n\n    func addEvent(_ event: AirshipMeteredUsageEvent) async throws {\n        guard self.isEnabled else { return }\n\n        var eventToStore = event\n        if (privacyManager.isEnabled(.analytics)) {\n            if eventToStore.contactID == nil {\n                eventToStore.contactID = await contact.contactID\n            }\n        } else {\n            eventToStore = event.withDisabledAnalytics()\n        }\n        try await self.store.saveEvent(eventToStore)\n        scheduleWork()\n    }\n\n    func scheduleWork(initialDelay: TimeInterval = 0.0) {\n        guard self.isEnabled else { return }\n\n        self.workManager.dispatchWorkRequest(\n            AirshipWorkRequest(\n                workID: Self.workID,\n                initialDelay: initialDelay,\n                requiresNetwork: true,\n                conflictPolicy: .keepIfNotStarted\n            )\n        )\n    }\n    \n    private var isEnabled: Bool {\n        return self.config.remoteConfig.meteredUsageConfig?.isEnabled ?? false\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipMeteredUsageEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/**\n * Internal only\n * :nodoc:\n */\npublic enum AirshipMeteredUsageType: String, Codable, Sendable {\n    case inAppExperienceImpression = \"iax_impression\"\n}\n\n/**\n * Internal only\n * :nodoc:\n */\npublic struct AirshipMeteredUsageEvent: Codable, Sendable, Equatable {\n    var eventID: String\n    var entityID: String?\n    var usageType: AirshipMeteredUsageType\n    var product: String\n    var reportingContext: AirshipJSON?\n    var timestamp: Date?\n    var contactID: String?\n\n    public init(\n        eventID: String,\n        entityID: String?,\n        usageType: AirshipMeteredUsageType,\n        product: String,\n        reportingContext: AirshipJSON?,\n        timestamp: Date?,\n        contactID: String?\n    ) {\n        self.eventID = eventID\n        self.entityID = entityID\n        self.usageType = usageType\n        self.product = product\n        self.reportingContext = reportingContext\n        self.timestamp = timestamp\n        self.contactID = contactID\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case eventID = \"event_id\"\n        case usageType = \"usage_type\"\n        case product\n        case reportingContext = \"reporting_context\"\n        case timestamp = \"occurred\"\n        case entityID = \"entity_id\"\n        case contactID = \"contact_id\"\n    }\n    \n    func withDisabledAnalytics() -> AirshipMeteredUsageEvent {\n        return AirshipMeteredUsageEvent(\n            eventID: eventID,\n            entityID: nil,\n            usageType: usageType,\n            product: product,\n            reportingContext: nil,\n            timestamp: nil,\n            contactID: nil\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipNativePlatform.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\n\n#if canImport(UIKit)\npublic import UIKit\npublic typealias AirshipNativeFont = UIFont\npublic typealias AirshipNativeColor = UIColor\n#if !os(watchOS)\npublic typealias AirshipNativeViewController = UIViewController\npublic typealias AirshipNativeHostingController = UIHostingController\npublic typealias AirshipNativeViewRepresentable = UIViewRepresentable\n#endif\n#elseif canImport(AppKit)\npublic import AppKit\npublic typealias AirshipNativeFont = NSFont\npublic typealias AirshipNativeColor = NSColor\npublic typealias AirshipNativeViewController = NSViewController\npublic typealias AirshipNativeHostingController = NSHostingController\npublic typealias AirshipNativeViewRepresentable = NSViewRepresentable\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipNetworkChecker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Network\n\n/// - Note: For internal use only. :nodoc:\npublic protocol AirshipNetworkCheckerProtocol: Sendable {\n    @MainActor\n    var isConnected: Bool { get }\n\n    @MainActor\n    var connectionUpdates: AsyncStream<Bool> { get }\n}\n\n#if os(watchOS)\n\nimport WatchConnectivity\n\n/// - Note: For internal use only. :nodoc:\npublic final class AirshipNetworkChecker: AirshipNetworkCheckerProtocol, Sendable {\n    private let _isConnected: AirshipMainActorValue<Bool>\n\n    @MainActor\n    public var connectionUpdates: AsyncStream<Bool> {\n        _isConnected.updates\n    }\n\n    @MainActor\n    public var isConnected: Bool {\n        return _isConnected.value\n    }\n\n    public init() {\n        self._isConnected = AirshipMainActorValue(true)\n    }\n}\n#else\n/// - Note: For internal use only. :nodoc:\npublic final class AirshipNetworkChecker: AirshipNetworkCheckerProtocol, Sendable {\n    private let pathMonitor: NWPathMonitor\n    private let _isConnected: AirshipMainActorValue<Bool>\n    private let updateQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n\n    @MainActor\n    public var connectionUpdates: AsyncStream<Bool> {\n        _isConnected.updates\n    }\n\n    public static let shared: AirshipNetworkChecker = AirshipNetworkChecker()\n\n    @MainActor\n    public var isConnected: Bool {\n        return _isConnected.value\n    }\n\n    public init() {\n        self._isConnected = AirshipMainActorValue(\n            AirshipUtils.hasNetworkConnection()\n        )\n\n        let monitor = NWPathMonitor()\n        self.pathMonitor = monitor\n\n        monitor.pathUpdateHandler = { [updateQueue, _isConnected] path in\n            updateQueue.enqueue {\n                let connected = path.status == .satisfied\n                if await (_isConnected.value != connected) {\n                    await _isConnected.set(path.status == .satisfied)\n                }\n            }\n        }\n\n        monitor.start(queue: DispatchQueue.global(qos: .utility))\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipNotificationCenter.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: For internal use only. :nodoc:\npublic struct AirshipNotificationCenter: Sendable {\n\n    public static let shared: AirshipNotificationCenter = AirshipNotificationCenter()\n    \n    private let notificationCenter: NotificationCenter\n    \n    public init(notificationCenter: NotificationCenter = NotificationCenter.default) {\n        self.notificationCenter = notificationCenter\n    }\n    \n    public func post(name: NSNotification.Name, object: Any? = nil, userInfo: [AnyHashable: Any]? = nil){\n        self.notificationCenter.post(\n            name: name,\n            object: object,\n            userInfo: userInfo\n        )\n    }\n    \n    @discardableResult\n    public func addObserver(\n        forName: NSNotification.Name,\n        object: (any Sendable)? = nil,\n        queue: OperationQueue? = nil,\n        using: @Sendable @escaping (Notification) -> Void\n    ) -> AnyObject {\n        return self.notificationCenter.addObserver(\n            forName: forName,\n            object: object,\n            queue: queue,\n            using: using\n        )\n    }\n    \n    public func postOnMain(name: NSNotification.Name, object: (any Sendable)? = nil, userInfo: [AnyHashable: Any]? = nil){\n        let wrapped = try? AirshipJSON.wrap(userInfo)\n        \n        DefaultDispatcher.main.dispatchAsyncIfNecessary {\n            self.post(\n                name: name,\n                object: object,\n                userInfo: wrapped?.unWrap() as? [AnyHashable: Any]\n            )\n        }\n    }\n    \n    public func addObserver(_ observer: Any, selector: Selector, name: NSNotification.Name, object: Any? = nil) {\n        notificationCenter.addObserver(observer, selector: selector, name: name, object: object)\n    }\n    \n    public func removeObserver(_ observer: Any) {\n        notificationCenter.removeObserver(observer)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipNotificationStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n\n/// Airship push notification status\npublic struct AirshipNotificationStatus: Sendable, Equatable {\n    /// If user notifications are enabled on AirshipPush.\n    public let isUserNotificationsEnabled: Bool\n\n    /// If notifications are either  ephemeral or granted and has at least one authorized type.\n    public let areNotificationsAllowed: Bool\n\n    /// If the push feature is enabled on `AirshipPrivacyManager`.\n    public let isPushPrivacyFeatureEnabled: Bool\n\n    /// If a push token is generated.\n    public let isPushTokenRegistered: Bool\n    \n    /// Display notification status\n    public let displayNotificationStatus: AirshipPermissionStatus\n\n\n    /// If isUserNotificationsEnabled, isPushPrivacyFeatureEnabled, and areNotificationsAllowed are all true..\n    public var isUserOptedIn: Bool {\n        return isUserNotificationsEnabled && isPushPrivacyFeatureEnabled && areNotificationsAllowed && displayNotificationStatus == .granted\n    }\n\n    /// If isUserOptedIn and isPushTokenRegistered are both true.\n    public var isOptedIn: Bool {\n        isUserOptedIn && isPushTokenRegistered\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipPasteboard.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n@available(tvOS, unavailable)\n@available(watchOS, unavailable)\nprotocol AirshipPasteboardProtocol: Sendable {\n    func copy(value: String, expiry: TimeInterval)\n    func copy(value: String)\n}\n\n@available(tvOS, unavailable)\n@available(watchOS, unavailable)\nstruct DefaultAirshipPasteboard: AirshipPasteboardProtocol {\n\n    func copy(value: String, expiry: TimeInterval) {\n        #if os(macOS)\n        // macOS pasteboard doesn't support expiration dates natively for simple strings\n        self.copy(value: value)\n        #else\n        // iOS, visionOS\n        let expirationDate = Date().advanced(by: expiry)\n        UIPasteboard.general.setItems(\n            [[UIPasteboard.typeAutomatic: value]],\n            options: [\n                UIPasteboard.OptionsKey.expirationDate: expirationDate\n            ]\n        )\n        #endif\n    }\n\n    func copy(value: String) {\n        #if os(macOS)\n        let pasteboard = NSPasteboard.general\n        pasteboard.declareTypes([.string], owner: nil)\n        pasteboard.setString(value, forType: .string)\n        #else\n        // iOS, visionOS\n        UIPasteboard.general.string = value\n        #endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipPrivacyManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// The privacy manager allow enabling/disabling features in the SDK.\n/// The SDK will not make any network requests or collect data if all features are disabled, with\n/// a few exceptions when going from enabled -> disabled. To have the SDK opt-out of all features on startup,\n/// set the default enabled features in the Config to an empty option set, or in the\n/// airshipconfig.plist file with `enabledFeatures = none`.\n/// If any feature is enabled, the SDK will collect and send the following data:\n/// - Channel ID\n/// - Contact ID\n/// - Locale\n/// - TimeZone\n/// - Platform\n/// - Opt in state (push and notifications)\n/// - SDK version\npublic protocol AirshipPrivacyManager: AnyObject, Sendable {\n    /// The current set of enabled features.\n    var enabledFeatures: AirshipFeature { get set }\n\n    /// Enables features.\n    /// This will append any features to the `enabledFeatures` property.\n    /// - Parameter features: The features to enable.\n    func enableFeatures(_ features: AirshipFeature)\n\n    /// Disables features.\n    /// This will remove any features to the `enabledFeatures` property.\n    /// - Parameter features: The features to disable.\n    func disableFeatures(_ features: AirshipFeature)\n\n    /// Checks if a given feature is enabled.\n    ///\n    /// - Parameter feature: The features to check.\n    /// - Returns: True if the provided features are enabled, otherwise false.\n    func isEnabled(_ feature: AirshipFeature) -> Bool\n\n    /// Checks if any feature is enabled.\n    /// - Returns: `true` if a feature is enabled, otherwise `false`.\n    func isAnyFeatureEnabled() -> Bool\n}\n\n\nprotocol InternalAirshipPrivacyManager: AirshipPrivacyManager {\n    /// Checks if any feature is enabled.\n    /// - Parameters:\n    ///     - ignoringRemoteConfig: true to ignore any remotely disable features, false to include them.\n    /// - Returns: `true` if a feature is enabled, otherwise `false`.\n    /// * - Note: For internal use only. :nodoc:\n    func isAnyFeatureEnabled(ignoringRemoteConfig: Bool) -> Bool\n}\n\nfinal class DefaultAirshipPrivacyManager: InternalAirshipPrivacyManager {\n    private static let enabledFeaturesKey: String = \"com.urbanairship.privacymanager.enabledfeatures\"\n\n    private let legacyIAAEnableFlag: String = \"UAInAppMessageManagerEnabled\"\n    private let legacyChatEnableFlag: String = \"AirshipChat.enabled\"\n    private let legacyLocationEnableFlag: String = \"UALocationUpdatesEnabled\"\n    private let legacyAnalyticsEnableFlag: String = \"UAAnalyticsEnabled\"\n    private let legacyPushTokenRegistrationEnableFlag: String = \"UAPushTokenRegistrationEnabled\"\n    private let legacyDataCollectionEnableEnableFlag: String = \"com.urbanairship.data_collection_enabled\"\n\n    private let dataStore: PreferenceDataStore\n    private let config: RuntimeConfig\n    private let defaultEnabledFeatures: AirshipFeature\n    private let notificationCenter: AirshipNotificationCenter\n\n    private let lock: AirshipLock = AirshipLock()\n\n    private let lastUpdated: AirshipAtomicValue<AirshipFeature> = AirshipAtomicValue<AirshipFeature>([])\n\n    private var localEnabledFeatures: AirshipFeature {\n        get {\n            guard let fromStore = self.dataStore.unsignedInteger(forKey: DefaultAirshipPrivacyManager.enabledFeaturesKey) else {\n                return self.defaultEnabledFeatures\n            }\n            \n            return AirshipFeature(\n                rawValue:(fromStore & AirshipFeature.all.rawValue)\n            )\n        }\n        set {\n            self.dataStore.setValue(\n                newValue.rawValue,\n                forKey: DefaultAirshipPrivacyManager.enabledFeaturesKey\n            )\n        }\n    }\n\n    public var enabledFeatures: AirshipFeature {\n        get {\n            self.localEnabledFeatures.subtracting(self.config.remoteConfig.disabledFeatures ?? [])\n        }\n        set {\n            lock.sync {\n                self.localEnabledFeatures = newValue\n                notifyUpdate()\n            }\n        }\n    }\n\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        defaultEnabledFeatures: AirshipFeature,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared\n    ) {\n\n        self.dataStore = dataStore\n        self.config = config\n        self.defaultEnabledFeatures = defaultEnabledFeatures\n        self.notificationCenter = notificationCenter\n\n        if config.airshipConfig.resetEnabledFeatures {\n            self.dataStore.removeObject(forKey:  DefaultAirshipPrivacyManager.enabledFeaturesKey)\n        } \n\n        self.lastUpdated.value = self.enabledFeatures\n\n        self.migrateData()\n\n        self.config.addRemoteConfigListener { [weak self] _, _ in\n            self?.notifyUpdate()\n        }\n    }\n\n    func enableFeatures(_ features: AirshipFeature) {\n        self.enabledFeatures.insert(features)\n    }\n\n    func disableFeatures(_ features: AirshipFeature) {\n        self.enabledFeatures.remove(features)\n    }\n\n    func isEnabled(_ feature: AirshipFeature) -> Bool {\n        guard feature == [] else {\n            return (enabledFeatures.rawValue & feature.rawValue) == feature.rawValue\n        }\n        return enabledFeatures == []\n    }\n\n    func isAnyFeatureEnabled() -> Bool {\n        return isAnyFeatureEnabled(ignoringRemoteConfig: false)\n    }\n\n    func isAnyFeatureEnabled(ignoringRemoteConfig: Bool) -> Bool {\n        if ignoringRemoteConfig {\n            return localEnabledFeatures != []\n        } else {\n            return enabledFeatures != []\n        }\n    }\n\n    func migrateData() {\n        if dataStore.keyExists(legacyDataCollectionEnableEnableFlag) {\n            if dataStore.bool(forKey: legacyDataCollectionEnableEnableFlag) {\n                self.enabledFeatures = .all\n            } else {\n                self.enabledFeatures = []\n            }\n            dataStore.removeObject(forKey: legacyDataCollectionEnableEnableFlag)\n        }\n\n        if dataStore.keyExists(legacyPushTokenRegistrationEnableFlag) {\n            if !(dataStore.bool(forKey: legacyPushTokenRegistrationEnableFlag)) {\n                self.disableFeatures(.push)\n            }\n            dataStore.removeObject(\n                forKey: legacyPushTokenRegistrationEnableFlag\n            )\n        }\n\n        if dataStore.keyExists(legacyAnalyticsEnableFlag) {\n            if !(dataStore.bool(forKey: legacyAnalyticsEnableFlag)) {\n                self.disableFeatures(.analytics)\n            }\n            dataStore.removeObject(forKey: legacyAnalyticsEnableFlag)\n        }\n\n        if dataStore.keyExists(legacyIAAEnableFlag) {\n            if !(dataStore.bool(forKey: legacyIAAEnableFlag)) {\n                self.disableFeatures(.inAppAutomation)\n            }\n            dataStore.removeObject(forKey: legacyIAAEnableFlag)\n        }\n\n        if dataStore.keyExists(legacyChatEnableFlag) {\n            dataStore.removeObject(forKey: legacyChatEnableFlag)\n        }\n\n        if dataStore.keyExists(legacyLocationEnableFlag) {\n            dataStore.removeObject(forKey: legacyLocationEnableFlag)\n        }\n    }\n\n    private func notifyUpdate() {\n        lock.sync {\n            let enabledFeatures = self.enabledFeatures\n            guard enabledFeatures != lastUpdated.value else { return }\n            self.lastUpdated.value = enabledFeatures\n            self.notificationCenter.postOnMain(\n                name: AirshipNotifications.PrivacyManagerUpdated.name\n            )\n        }\n    }\n}\n\n/// Airship Features.\npublic struct AirshipFeature: OptionSet, Sendable, CustomStringConvertible {\n    \n    public let rawValue: UInt\n\n    /// In-App automation\n    public static let inAppAutomation: AirshipFeature = AirshipFeature(rawValue: 1 << 0)\n\n    /// Message Center\n    public static let messageCenter: AirshipFeature = AirshipFeature(rawValue: 1 << 1)\n\n    /// Push\n    public static let push: AirshipFeature = AirshipFeature(rawValue: 1 << 2)\n\n    /// Analytics\n    public static let analytics: AirshipFeature = AirshipFeature(rawValue: 1 << 4)\n\n    /// Tags, attributes, and subscription lists\n    public static let tagsAndAttributes: AirshipFeature = AirshipFeature(rawValue: 1 << 5)\n\n    /// Contacts\n    public static let contacts: AirshipFeature = AirshipFeature(rawValue: 1 << 6)\n\n    /* Do not use: UAFeaturesLocation = (1 << 7) */\n\n    /// Feature flags\n    public static let featureFlags: AirshipFeature = AirshipFeature(rawValue: 1 << 8)\n\n    /// All features\n    public static let all: AirshipFeature = [\n        inAppAutomation,\n        messageCenter,\n        push,\n        analytics,\n        tagsAndAttributes,\n        contacts,\n        featureFlags\n    ]\n\n    public init(rawValue: UInt) {\n        self.rawValue = rawValue\n    }\n\n    public var description: String {\n        var descriptions = [String]()\n        if self.contains(.inAppAutomation) {\n            descriptions.append(\"In-App Automation\")\n        }\n        if self.contains(.messageCenter) {\n            descriptions.append(\"Message Center\")\n        }\n        if self.contains(.push) {\n            descriptions.append(\"Push\")\n        }\n        if self.contains(.analytics) {\n            descriptions.append(\"Analytics\")\n        }\n        if self.contains(.tagsAndAttributes) {\n            descriptions.append(\"Tags and Attributes\")\n        }\n        if self.contains(.contacts) {\n            descriptions.append(\"Contacts\")\n        }\n        if self.contains(.featureFlags) {\n            descriptions.append(\"Feature flags\")\n        }\n\n        // add prefix indicating that these are enabled features\n        return \"Enabled features: \" + descriptions.joined(separator: \", \")\n    }\n}\n\nextension AirshipFeature: Codable {\n    static let nameMap: [String: AirshipFeature] = [\n        \"push\": .push,\n        \"contacts\": .contacts,\n        \"message_center\": .messageCenter,\n        \"analytics\": .analytics,\n        \"tags_and_attributes\": .tagsAndAttributes,\n        \"in_app_automation\": .inAppAutomation,\n        \"feature_flags\": .featureFlags,\n        \"all\": .all,\n        \"none\": []\n    ]\n\n    var names: [String] {\n        var names: [String] = []\n        if (self == .all) {\n            return AirshipFeature.nameMap.keys.filter { key in\n                key != \"none\" && key != \"all\"\n            }\n        }\n\n        if (self == []) {\n            return []\n        }\n\n        AirshipFeature.nameMap.forEach { key, value in\n            if (value != [] && value != .all) {\n                if (self.contains(value)) {\n                    names.append(key)\n                }\n            }\n        }\n\n        return names\n    }\n\n    static func parse(_ names: [Any]) throws -> AirshipFeature {\n        guard let names = names as? [String] else {\n            throw AirshipErrors.error(\"Invalid feature \\(names)\")\n        }\n\n        var features: AirshipFeature = []\n\n        try names.forEach { name in\n            guard\n                let feature = AirshipFeature.nameMap[name.lowercased()]\n            else {\n                throw AirshipErrors.error(\"Invalid feature \\(name)\")\n            }\n            features.update(with: feature)\n        }\n\n        return features\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n        try container.encode(self.names)\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n\n        if let names: [String] = try? container.decode([String].self) {\n            self = try AirshipFeature.parse(names)\n        } else {\n            throw AirshipErrors.error(\"Failed to parse features\")\n        }\n    }\n}\n\npublic extension AirshipNotifications {\n\n    /// NSNotification info when enabled feature changed on PrivacyManager.\n    final class PrivacyManagerUpdated {\n\n        /// NSNotification name.\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.privacymanager.enabledfeatures_changed\"\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipProgressView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Progress view\nstruct AirshipProgressView: View {\n\n    @State var isVisible = false\n\n    var body: some View {\n        ProgressView()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipPush.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\npublic import UserNotifications\npublic import Combine\n\n#if canImport(WatchKit)\npublic import WatchKit\n#endif\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n/// Airship Push protocol.\npublic protocol AirshipPush: AnyObject, Sendable {\n\n    /// If set, this block will be called when APNs registration succeeds or fails.\n    /// This will be called in place of the `RegistrationDelegate` delegates `apnsRegistrationSucceeded`\n    /// and `apnsRegistrationFailedWithError` methods.\n    @MainActor\n    var onAPNSRegistrationFinished: (@MainActor @Sendable (APNSRegistrationResult) -> Void)? { get set }\n\n    /// If set, this block will be called when the user notifications registration finishes.\n    /// This will be called in place of the `RegistrationDelegate` delegates `notificationRegistrationFinished` method.\n    @MainActor\n    var onNotificationRegistrationFinished: (@MainActor @Sendable (NotificationRegistrationResult) -> Void)? { get set }\n\n    /// If set, this block will be called when the notification authorization settings change.\n    /// This will be called in place of the of the `RegistrationDelegate` delegates  `notificationAuthorizedSettingsDidChange` method.\n    @MainActor\n    var onNotificationAuthorizedSettingsDidChange: (@MainActor @Sendable (AirshipAuthorizedNotificationSettings) -> Void)? { get set }\n\n    /// Checks to see if push notifications are opted in.\n    @MainActor\n    var isPushNotificationsOptedIn: Bool { get }\n\n    /// Enables/disables background remote notifications on this device through Airship.\n    /// Defaults to `true`.\n    @MainActor\n    var backgroundPushNotificationsEnabled: Bool { get set }\n\n    /// Enables/disables user notifications on this device through Airship.\n    /// Defaults to `false`. Once set to `true`, the user will be prompted for remote notifications.\n    var userPushNotificationsEnabled: Bool { get set }\n\n    /// When enabled, if the user has ephemeral notification authorization the SDK will prompt the user for\n    /// notifications.  Defaults to `false`.\n    var requestExplicitPermissionWhenEphemeral: Bool { get set }\n\n    /// The device token for this device, as a hex string.\n    @MainActor\n    var deviceToken: String? { get }\n\n    /// User Notification options this app will request from APNS.\n    ///\n    /// Defaults to alert, sound and badge.\n    var notificationOptions: UNAuthorizationOptions { get set }\n\n    #if !os(tvOS)\n    /// Custom notification categories. Airship default notification\n    /// categories will be unaffected by this field.\n    ///\n    /// Changes to this value will not take effect until the next time the app registers\n    /// with updateRegistration.\n    @MainActor\n    var customCategories: Set<UNNotificationCategory> { get set }\n\n    /// The combined set of notification categories from `customCategories` set by the app\n    /// and the Airship provided categories.\n    @MainActor\n    var combinedCategories: Set<UNNotificationCategory> { get }\n\n    #endif\n    /// Sets authorization required for the default Airship categories. Only applies\n    /// to background user notification actions.\n    @MainActor\n    var requireAuthorizationForDefaultCategories: Bool { get set }\n\n    /// Set a delegate that implements the PushNotificationDelegate protocol.\n    @MainActor\n    var pushNotificationDelegate: (any PushNotificationDelegate)? { get set }\n\n    /// Set a delegate that implements the RegistrationDelegate protocol.\n    @MainActor\n    var registrationDelegate: (any RegistrationDelegate)? { get set }\n\n    #if !os(tvOS)\n    /// Notification response that launched the application.\n    var launchNotificationResponse: UNNotificationResponse? { get }\n    #endif\n\n    /// The current authorized notification settings.\n    /// If push is disabled in privacy manager, this value could be out of date.\n    ///\n    /// Note: this value reflects all the notification settings currently enabled in the\n    /// Settings app and does not take into account which options were originally requested.\n    var authorizedNotificationSettings: AirshipAuthorizedNotificationSettings { get }\n\n    /// The current authorization status.\n    /// If push is disabled in privacy manager, this value could be out of date.\n    var authorizationStatus: UNAuthorizationStatus { get }\n\n    /// Indicates whether the user has been prompted for notifications or not.\n    /// If push is disabled in privacy manager, this value will be out of date.\n    var userPromptedForNotifications: Bool { get }\n\n    /// The default presentation options to use for foreground notifications.\n    var defaultPresentationOptions: UNNotificationPresentationOptions {\n        get set\n    }\n\n    /// Enables user notifications on this device through Airship.\n    ///\n    /// - Note: The completion handler will return the success state of system push authorization as it is defined by the\n    /// user's response to the push authorization prompt. The completion handler success state does NOT represent the\n    /// state of the userPushNotificationsEnabled flag, which will be invariably set to `true` after the completion of this call.\n    ///\n    /// - Parameter completionHandler: The completion handler with success flag representing the system authorization state.\n    func enableUserPushNotifications() async -> Bool\n\n#if !os(watchOS)\n    /// The current badge number used by the device and on the Airship server.\n    ///\n    /// - Note: This property must be accessed on the main thread and must be set asynchronously using setBadgeNumber.\n    @MainActor\n    var badgeNumber: Int { get }\n\n    /// The current badge number used by the device and on the Airship server.\n    func setBadgeNumber(_ newBadgeNumber: Int) async throws\n\n    /// Resets the badge to zero (0) on both the device and on Airships servers. This is a\n    /// convenience method for setting the `badgeNumber` property to zero.\n    func resetBadge() async throws\n\n    /// Toggle the Airship auto-badge feature. Defaults to `false` If enabled, this will update the\n    /// badge number stored by Airship every time the app is started or foregrounded.\n    var autobadgeEnabled: Bool { get set }\n#endif\n\n    /// Time Zone for quiet time. If the time zone is not set, the current\n    /// local time zone is returned.\n    var timeZone: NSTimeZone? { get set }\n\n    /// Enables/Disables quiet time\n    var quietTimeEnabled: Bool { get set }\n\n    /// Sets the quiet time start and end time.  The start and end time does not change\n    /// if the time zone changes.  To set the time zone, see 'timeZone'.\n    ///\n    /// Update the server after making changes to the quiet time with the\n    /// `updateRegistration` call. Batching these calls improves API and client performance.\n    ///\n    /// - Warning: This method does not automatically enable quiet time and does not\n    /// automatically update the server. Please refer to `quietTimeEnabled` and\n    /// `updateRegistration` for more information.\n    ///\n    /// - Parameters:\n    ///   - startHour: Quiet time start hour. Only 0-23 is valid.\n    ///   - startMinute: Quiet time start minute. Only 0-59 is valid.\n    ///   - endHour: Quiet time end hour. Only 0-23 is valid.\n    ///   - endMinute: Quiet time end minute. Only 0-59 is valid.\n    func setQuietTimeStartHour(\n        _ startHour: Int,\n        startMinute: Int,\n        endHour: Int,\n        endMinute: Int\n    )\n\n    /// Quiet time settings. Setting this value only sets the start/end time for quiet time. It still needs to be\n    /// enabled with `quietTimeEnabled`. The timzone can be set with `timeZone`.\n    var quietTime: QuietTimeSettings? { get set }\n\n    /// Notification status updates\n    @MainActor\n    var notificationStatusPublisher: AnyPublisher<AirshipNotificationStatus, Never> { get }\n    \n    /// Notification status updates\n    var notificationStatusUpdates: AsyncStream<AirshipNotificationStatus> { get async }\n\n    /// Gets the current notification status\n    var notificationStatus: AirshipNotificationStatus { get async }\n\n    /// Enables user notifications on this device through Airship.\n    ///\n    /// - Note: The result of this method does NOT represent the state of the userPushNotificationsEnabled flag,\n    /// which will be invariably set to `true` after the completion of this call.\n    ///\n    /// - Parameters:\n    ///   - fallback: The prompt permission fallback if the display notifications permission is already denied.\n    ///\n    /// - Returns: `true` if user notifications are enabled at the system level,  otherwise`false`.\n    @discardableResult\n    func enableUserPushNotifications(fallback: PromptPermissionFallback) async -> Bool\n\n    // MARK: Block-based notification callbacks\n\n    /// Callback to be called when a notification is received in the foreground.\n    /// This callback takes precedence over the delegate method if both are set.\n    @MainActor\n    var onForegroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> Void)? { get set }\n\n#if os(watchOS)\n    /// Callback to be called when a notification is received in the background.\n    /// This callback takes precedence over the delegate method if both are set.\n    @MainActor\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> WKBackgroundFetchResult)? { get set }\n#elseif os(macOS)\n    /// Callback to be called when a notification is received in the background.\n    /// This callback takes precedence over the delegate method if both are set.\n    @MainActor\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> Void)? { get set }\n#else\n    /// Callback to be called when a notification is received in the background.\n    /// This callback takes precedence over the delegate method if both are set.\n    @MainActor\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> UIBackgroundFetchResult)? { get set }\n#endif\n\n#if !os(tvOS)\n    /// Callback to be called when a notification response is received.\n    /// This callback takes precedence over the delegate method if both are set.\n    @MainActor\n    var onNotificationResponseReceived: (@MainActor @Sendable (UNNotificationResponse) async -> Void)? { get set }\n#endif\n\n    /// Callback to extend presentation options for foreground notifications.\n    /// This callback takes precedence over the delegate method if both are set.\n    @MainActor\n    var onExtendPresentationOptions: (@MainActor @Sendable (UNNotificationPresentationOptions, UNNotification) async -> UNNotificationPresentationOptions)? { get set }\n}\n\nprotocol InternalAirshipPush: Sendable {\n    @MainActor\n    var deviceToken: String? { get }\n\n    func dispatchUpdateAuthorizedNotificationTypes()\n\n    @MainActor\n    func didRegisterForRemoteNotifications(_ deviceToken: Data)\n\n    @MainActor\n    func didFailToRegisterForRemoteNotifications(_ error: any Error)\n\n    @MainActor\n    func didReceiveRemoteNotification(\n        _ notification: [AnyHashable: Any],\n        isForeground: Bool\n    ) async -> UABackgroundFetchResult\n\n    @MainActor\n    func presentationOptionsForNotification(_ notification: UNNotification) async -> UNNotificationPresentationOptions\n\n#if !os(tvOS)\n    @MainActor\n    func didReceiveNotificationResponse(_ response: UNNotificationResponse) async\n\n    @MainActor\n    var combinedCategories: Set<UNNotificationCategory> { get }\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipPushableComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(UIKit)\nimport UIKit\n#endif\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\npublic import UserNotifications\n\n/// Airship wrapper for background fetch results to provide platform-agnostic handling.\npublic enum UABackgroundFetchResult : Sendable {\n    case newData\n    case noData\n    case failed\n\n    /// Merges two fetch results.\n    /// Logic: .newData always wins. .failed wins over .noData to ensure the system\n    /// knows an error occurred even if other components had no data.\n    public func merge(_ other: UABackgroundFetchResult) -> UABackgroundFetchResult {\n        switch (self, other) {\n        case (.newData, _), (_, .newData):\n            return .newData\n        case (.failed, _), (_, .failed):\n            return .failed\n        default:\n            return .noData\n        }\n    }\n\n    /// Merges a collection of fetch results into a single result.\n    public static func merged(_ results: [UABackgroundFetchResult]) -> UABackgroundFetchResult {\n        return results.reduce(.noData) { $0.merge($1) }\n    }\n\n#if !os(watchOS) && !os(macOS)\n    var osFetchResult: UIBackgroundFetchResult {\n        return switch(self) {\n        case .newData: .newData\n        case .noData: .noData\n        case .failed: .failed\n        }\n    }\n\n    init(from osResult: UIBackgroundFetchResult) {\n        self = switch(osResult) {\n        case .newData: .newData\n        case .noData: .noData\n        case .failed: .failed\n        @unknown default: .noData\n        }\n    }\n#elseif os(watchOS)\n    var osFetchResult: WKBackgroundFetchResult {\n        return switch(self) {\n        case .newData: .newData\n        case .noData: .noData\n        case .failed: .failed\n        }\n    }\n\n    init(from osResult: WKBackgroundFetchResult) {\n        self = switch(osResult) {\n        case .newData: .newData\n        case .noData: .noData\n        case .failed: .failed\n        @unknown default: .noData\n        }\n    }\n#endif\n}\n/// Internal protocol to fan out push handling to UAComponents.\n///  - Note: For internal use only. :nodoc:\npublic protocol AirshipPushableComponent: Sendable {\n    /**\n     * Called when a remote notification is received.\n     *  - Parameters:\n     *    - notification: The notification.\n     */\n    @MainActor\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON // wrapped [AnyHashable: Any]\n    ) async -> UABackgroundFetchResult\n\n    #if !os(tvOS)\n    /**\n     * Called when a notification response is received.\n     * - Parameters:\n     *   - response: The notification response.\n     *   - completionHandler: The completion handler that must be called after processing the response.\n     */\n    @MainActor\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async\n    #endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipRequest.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Content encoding type for request body compression.\n/// - Note: For internal use only. :nodoc:\npublic enum ContentEncoding: String, Sendable, Equatable {\n    case deflate\n}\n\n/// AirshipRequest\n/// - Note: For internal use only. :nodoc:\npublic struct AirshipRequest: Sendable {\n    let url: URL?\n    let headers: [String: String]\n    let method: String?\n    let auth: AirshipRequestAuth?\n    let body: Data?\n    let contentEncoding: ContentEncoding?\n\n    public init(\n        url: URL?,\n        headers: [String: String] = [:],\n        method: String? = nil,\n        auth: AirshipRequestAuth? = nil,\n        body: Data? = nil,\n        contentEncoding: ContentEncoding? = nil\n    ) {\n        self.url = url\n        self.headers = headers\n        self.method = method\n        self.auth = auth\n        self.body = body\n        self.contentEncoding = contentEncoding\n    }\n\n    public init<T: Encodable>(\n        url: URL?,\n        headers: [String: String] = [:],\n        method: String? = nil,\n        auth: AirshipRequestAuth? = nil,\n        encodableBody: T,\n        encoder: JSONEncoder = AirshipJSON.defaultEncoder,\n        contentEncoding: ContentEncoding? = nil\n    ) throws {\n        self.url = url\n        self.headers = headers\n        self.method = method\n        self.auth = auth\n        self.body = try encoder.encode(encodableBody)\n        self.contentEncoding = contentEncoding\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipRequestSession.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CommonCrypto\npublic import Foundation\n\n\npublic protocol AirshipRequestSession: Sendable {\n\n    /// Performs an HTTP request\n    /// - Parameters:\n    ///    - request: The request\n    ///    - autoCancel: If the request should be cancelled if the task is cancelled. Defaults to false.\n    /// - Returns: An AirshipHTTPResponse.\n    func performHTTPRequest(\n        _ request: AirshipRequest,\n        autoCancel: Bool\n    ) async throws -> AirshipHTTPResponse<Void>\n\n    /// Performs an HTTP request\n    /// - Parameters:\n    ///    - request: The request\n    ///    - autoCancel: If the request should be cancelled if the task is cancelled. Defaults to false.\n    /// - Returns: An AirshipHTTPResponse.\n    func performHTTPRequest(\n        _ request: AirshipRequest\n    ) async throws -> AirshipHTTPResponse<Void>\n\n    /// Performs an HTTP request\n    /// - Parameters:\n    ///    - request: The request\n    ///    - autoCancel: If the request should be cancelled if the task is cancelled. Defaults to false.\n    ///    - responseParser: A block that parses the response.\n    /// - Returns: An AirshipHTTPResponse.\n    func performHTTPRequest<T>(\n        _ request: AirshipRequest,\n        autoCancel: Bool,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> AirshipHTTPResponse<T>\n\n    /// Performs an HTTP request\n    /// - Parameters:\n    ///    - request: The request\n    ///    - responseParser: A block that parses the response.\n    /// - Returns: An AirshipHTTPResponse.\n    func performHTTPRequest<T>(\n        _ request: AirshipRequest,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> AirshipHTTPResponse<T>\n}\n\n\n/// Airship request session.\n/// - Note: For internal use only. :nodoc:\nfinal class DefaultAirshipRequestSession: AirshipRequestSession, Sendable {\n\n    private let session: any URLRequestSessionProtocol\n    private let defaultHeaders: [String: String]\n    private let appSecret: String\n    private let appKey: String\n    private let date: any AirshipDateProtocol\n    private let nonceFactory: @Sendable () -> String\n\n    private let authTasks: AirshipAtomicValue<[AirshipRequestAuth: Task<ResolvedAuth, any Error>]> = AirshipAtomicValue([AirshipRequestAuth: Task<ResolvedAuth, any Error>]())\n\n    @MainActor\n    var channelAuthTokenProvider: (any AuthTokenProvider)?\n\n    @MainActor\n    var contactAuthTokenProvider: (any AuthTokenProvider)?\n\n    private static let sharedURLSession: any URLRequestSessionProtocol = {\n        let sessionConfig = URLSessionConfiguration.default\n        sessionConfig.urlCache = nil\n        sessionConfig.requestCachePolicy = .reloadIgnoringLocalCacheData\n        sessionConfig.tlsMinimumSupportedProtocolVersion = .TLSv12\n        return URLSession(\n            configuration: sessionConfig,\n            delegate: ChallengeResolver.shared,\n            delegateQueue: nil\n        )\n    }()\n\n    init(\n        appKey: String,\n        appSecret: String,\n        session: any URLRequestSessionProtocol = DefaultAirshipRequestSession.sharedURLSession,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        nonceFactory: @Sendable @escaping () -> String = { return UUID().uuidString }\n    ) {\n        self.appKey = appKey\n        self.appSecret = appSecret\n        self.session = session\n        self.date = date\n        self.nonceFactory = nonceFactory\n        self.defaultHeaders = [\n            \"Accept-Encoding\": \"gzip;q=1.0, compress;q=0.5\",\n            \"User-Agent\": \"(UALib \\(AirshipVersion.version); \\(appKey))\",\n            \"X-UA-App-Key\": appKey,\n        ]\n    }\n\n\n    func performHTTPRequest(\n        _ request: AirshipRequest\n    ) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.performHTTPRequest(\n            request,\n            autoCancel: false,\n            responseParser: nil\n        )\n    }\n\n    func performHTTPRequest(\n        _ request: AirshipRequest,\n        autoCancel: Bool\n    ) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.performHTTPRequest(\n            request,\n            autoCancel: autoCancel,\n            responseParser: nil\n        )\n    }\n\n    func performHTTPRequest<T>(\n        _ request: AirshipRequest,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> AirshipHTTPResponse<T> {\n        return try await self.performHTTPRequest(\n            request,\n            autoCancel: false,\n            responseParser: responseParser\n        )\n    }\n\n    func performHTTPRequest<T>(\n        _ request: AirshipRequest,\n        autoCancel: Bool = false,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> AirshipHTTPResponse<T> {\n        let result = try await self.doPerformHTTPRequest(\n            request,\n            autoCancel: autoCancel,\n            responseParser: responseParser\n        )\n\n        if (result.shouldRetry) {\n            return try await self.doPerformHTTPRequest(\n                request,\n                autoCancel: autoCancel,\n                responseParser: responseParser\n            ).response\n        } else {\n            return result.response\n        }\n    }\n\n    private func doPerformHTTPRequest<T>(\n        _ request: AirshipRequest,\n        autoCancel: Bool = false,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> (shouldRetry: Bool, response: AirshipHTTPResponse<T>) {\n        let cancellable = CancellableValueHolder<any AirshipCancellable>() { cancellable in\n            cancellable.cancel()\n        }\n\n        guard let url = request.url else {\n            throw AirshipErrors.error(\n                \"Attempted to perform request with a missing URL.\"\n            )\n        }\n\n        var urlRequest = URLRequest(url: url)\n        urlRequest.httpShouldHandleCookies = false\n        urlRequest.httpMethod = request.method ?? \"\"\n\n        var headers = request.headers.merging(self.defaultHeaders) { (current, _) in\n            current\n        }\n\n        let resolvedAuth = try await resolveAuth(requestAuth: request.auth)\n        if let authHeaders = resolvedAuth?.headers {\n            headers.merge(authHeaders) { (current, _) in current }\n        }\n\n        if let encoding = request.contentEncoding {\n            if let compressed = request.body?.compress(encoding: encoding) {\n                urlRequest.httpBody = compressed\n                headers[\"Content-Encoding\"] = encoding.rawValue\n            } else {\n                urlRequest.httpBody = request.body\n            }\n        } else {\n            urlRequest.httpBody = request.body\n        }\n\n        for (k, v) in headers { urlRequest.setValue(v, forHTTPHeaderField: k) }\n\n\n        let result: AirshipHTTPResponse<T> = try await withTaskCancellationHandler(\n            operation: {\n\n                return try await withCheckedThrowingContinuation {\n                    continuation in\n\n                    cancellable.value = self.session.dataTask(request: urlRequest) {\n                        (data, response, error) in\n                        if let error = error {\n                            continuation.resume(throwing: error)\n                            return\n                        }\n\n                        guard let response = response else {\n                            let error = AirshipErrors.error(\"Missing response\")\n                            continuation.resume(throwing: error)\n                            return\n                        }\n\n                        guard let httpResponse = response as? HTTPURLResponse\n                        else {\n                            let error = AirshipErrors.error(\n                                \"Unable to cast to HTTPURLResponse: \\(response)\"\n                            )\n                            continuation.resume(throwing: error)\n                            return\n                        }\n\n                        do {\n                            let headers = DefaultAirshipRequestSession.parseHeaders(headers: httpResponse.allHeaderFields)\n                            let result = AirshipHTTPResponse(\n                                result: try responseParser?(data, httpResponse),\n                                statusCode: httpResponse.statusCode,\n                                headers: headers\n                            )\n                            continuation.resume(with: .success(result))\n                        } catch {\n                            continuation.resume(throwing: error)\n                        }\n                    }\n                }\n            },\n            onCancel: {\n                if autoCancel {\n                    cancellable.cancel()\n                }\n            }\n        )\n\n        if result.statusCode == 401, let onExpire = resolvedAuth?.onExpire {\n            await onExpire()\n            return (true, result)\n        }\n\n        return (false, result)\n\n    }\n\n\n    @MainActor\n    private func resolveAuth(\n        requestAuth: AirshipRequestAuth?\n    ) async throws -> ResolvedAuth? {\n\n        guard let requestAuth = requestAuth else {\n            return nil\n        }\n\n        switch (requestAuth) {\n        case .basic(let username, let password):\n            let token = try DefaultAirshipRequestSession.basicAuthValue(\n                username: username,\n                password: password\n            )\n            return ResolvedAuth(\n                headers: [\n                    \"Authorization\": \"Basic \\(token)\"\n                ]\n            )\n\n        case .basicAppAuth:\n            let token = try DefaultAirshipRequestSession.basicAuthValue(\n                username: appKey,\n                password: appSecret\n            )\n            return ResolvedAuth(\n                headers: [\n                    \"Authorization\": \"Basic \\(token)\"\n                ]\n            )\n\n        case .bearer(let token):\n            return ResolvedAuth(\n                headers: [\n                    \"Authorization\": \"Bearer \\(token)\"\n                ]\n            )\n\n        case .channelAuthToken(let identifier):\n            return try await resolveTokenAuth(\n                requestAuth: requestAuth,\n                identifier: identifier,\n                provider: self.channelAuthTokenProvider\n            )\n\n        case .contactAuthToken(let identifier):\n            return try await resolveTokenAuth(\n                requestAuth: requestAuth,\n                identifier: identifier,\n                provider: self.contactAuthTokenProvider\n            )\n\n        case .generatedChannelToken(let channelID):\n            let nonce = self.nonceFactory()\n            let timestamp = AirshipDateFormatter.string(fromDate: self.date.now, format: .iso)\n            let token = try AirshipUtils.generateSignedToken(\n                secret: self.appSecret,\n                tokenParams: [\n                    self.appKey,\n                    channelID,\n                    nonce,\n                    timestamp\n                ]\n            )\n\n            return ResolvedAuth(\n                headers: [\n                    \"Authorization\": \"Bearer \\(token)\",\n                    \"X-UA-Appkey\": self.appKey,\n                    \"X-UA-Channel-ID\": channelID,\n                    \"X-UA-Nonce\": nonce,\n                    \"X-UA-Timestamp\": timestamp\n                ]\n            )\n\n        case .generatedAppToken:\n            let nonce = self.nonceFactory()\n            let timestamp = AirshipDateFormatter.string(fromDate: self.date.now, format: .iso)\n            let token = try AirshipUtils.generateSignedToken(\n                secret: self.appSecret,\n                tokenParams: [\n                    self.appKey,\n                    nonce,\n                    timestamp\n                ]\n            )\n\n            return ResolvedAuth(\n                headers: [\n                    \"Authorization\": \"Bearer \\(token)\",\n                    \"X-UA-Appkey\": self.appKey,\n                    \"X-UA-Nonce\": nonce,\n                    \"X-UA-Timestamp\": timestamp\n                ]\n            )\n        }\n    }\n\n    private class func basicAuthValue(username: String, password: String) throws -> String {\n        let credentials = \"\\(username):\\(password)\"\n\n\n        guard let encodedCredentials = credentials.data(using: .utf8) else {\n            throw AirshipErrors.error(\"Invalid basic auth for user \\(username)\")\n        }\n\n\n        return encodedCredentials.base64EncodedString(options: [])\n    }\n\n    @MainActor\n    private func resolveTokenAuth(\n        requestAuth: AirshipRequestAuth,\n        identifier: String,\n        provider: (any AuthTokenProvider)?\n    ) async throws -> ResolvedAuth {\n        guard let provider = provider else {\n            throw AirshipErrors.error(\"Missing auth provider for auth \\(requestAuth)\")\n        }\n\n        if let existingTask = self.authTasks.value[requestAuth] {\n            return try await existingTask.value\n        }\n\n        let task = Task { @MainActor in\n            defer {\n                self.authTasks.update { current in\n                    var mutable = current\n                    mutable[requestAuth] = nil\n                    return mutable\n                }\n            }\n\n            let token = try await provider.resolveAuth(\n                identifier: identifier\n            )\n            \n            return ResolvedAuth(\n                headers: [\n                    \"Authorization\": \"Bearer \\(token)\",\n                    \"X-UA-Appkey\": self.appKey,\n                    \"X-UA-Auth-Type\": \"SDK-JWT\"\n                ]\n            ) {\n                await provider.authTokenExpired(token: token)\n            }\n        }\n\n        self.authTasks.update { current in\n            var mutable = current\n            mutable[requestAuth] = task\n            return mutable\n        }\n        \n        return try await task.value\n    }\n\n    private class func parseHeaders(headers: [AnyHashable: Any]) -> [String: String] {\n        if let headers = headers as? [String : String] {\n            return headers\n        }\n\n        return Dictionary(\n            uniqueKeysWithValues: headers.compactMap { (key, value) in\n                if let key = key as? String, let value = value as? String {\n                    return (key, value)\n                }\n                return nil\n            }\n        )\n    }\n}\n\nprotocol AuthTokenProvider: Sendable {\n    func resolveAuth(identifier: String) async throws -> String\n    func authTokenExpired(token: String) async\n}\n\n/// - Note: For internal use only. :nodoc:\npublic enum AirshipRequestAuth: Sendable, Equatable, Hashable {\n    case basic(username: String, password: String)\n    case bearer(token: String)\n    case basicAppAuth\n    case channelAuthToken(identifier: String)\n    case contactAuthToken(identifier: String)\n    case generatedChannelToken(identifier: String)\n    case generatedAppToken\n}\n\n\nfileprivate struct ResolvedAuth: Sendable {\n    let headers: [String: String]\n    let onExpire: (@Sendable () async -> Void)?\n\n    init(headers: [String: String], onExpire: (@Sendable () async -> Void)? = nil) {\n        self.headers = headers\n        self.onExpire = onExpire\n    }\n}\n\nextension Data {\n    fileprivate func compress(encoding: ContentEncoding) -> Data? {\n        guard !self.isEmpty else { return nil }\n        switch encoding {\n        case .deflate:\n            do {\n                return try (self as NSData).compressed(using: .zlib) as Data\n            } catch {\n                return nil\n            }\n        }\n    }\n}\n\nprotocol URLRequestSessionProtocol: Sendable {\n    @discardableResult\n    func dataTask(\n        request: URLRequest,\n        completionHandler: @Sendable @escaping (Data?, URLResponse?, (any Error)?) -> Void\n    )\n    -> any AirshipCancellable\n}\n\nextension URLSession: URLRequestSessionProtocol {\n    @discardableResult\n    func dataTask(\n        request: URLRequest,\n        completionHandler: @Sendable @escaping (Data?, URLResponse?, (any Error)?) -> Void\n    )\n    -> any AirshipCancellable\n    {\n\n        let task = self.dataTask(\n            with: request,\n            completionHandler: completionHandler\n        )\n        task.resume()\n\n        return CancellableValueHolder(value: task) { task in\n            task.cancel()\n        }\n    }\n}\n\n/**\n * URLSession with configured optional challenge resolver\n * @note For internal use only. :nodoc:\n */\npublic extension URLSession {\n    static let airshipSecureSession: URLSession = .init(\n        configuration: .default,\n        delegate: ChallengeResolver.shared,\n        delegateQueue: nil)\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipResources.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nclass AirshipResources {\n    static let bundle: Bundle? = findBundle()\n\n    /// Assumes AirshipResources class and UrbanAirship.string resource always exist in the same bundle\n    private static func findBundle() -> Bundle? {\n        return Bundle(for: AirshipResources.self)\n    }\n\n    public static func localizedString(key: String) -> String? {\n        return AirshipLocalizationUtils.localizedString(\n            key,\n            withTable: \"UrbanAirship\",\n            moduleBundle: bundle\n        )\n    }\n}\n\n/**\n * @note For internal use only. :nodoc:\n */\nextension String {\n    public func airshipLocalizedString(fallback: String) -> String {\n        return AirshipResources.localizedString(key: self) ?? fallback\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipResponse.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// AirshipHTTPResponse when using AirshipRequestSession\n/// - Note: For internal use only. :nodoc:\npublic struct AirshipHTTPResponse<T: Sendable>: Sendable {\n    public let result: T?\n    public let statusCode: Int\n    public let headers: [String: String]\n}\n\nextension AirshipHTTPResponse {\n    public var isSuccess: Bool {\n        return self.statusCode >= 200 && self.statusCode <= 299\n    }\n\n    public var isClientError: Bool {\n        return self.statusCode >= 400 && self.statusCode <= 499\n    }\n\n    public var isServerError: Bool {\n        return self.statusCode >= 500 && self.statusCode <= 599\n    }\n}\n\n\nextension AirshipHTTPResponse: Equatable where T: Equatable {}\n\nextension AirshipHTTPResponse {\n    func map<R>(onMap: (AirshipHTTPResponse<T>) throws -> R?) throws -> AirshipHTTPResponse<R> {\n        return AirshipHTTPResponse<R>(\n            result:  try onMap(self),\n            statusCode: self.statusCode,\n            headers: self.headers\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSDKExtension.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Allowed SDK extension types.\n/// - Note: For internal use only. :nodoc:\npublic enum AirshipSDKExtension: Int {\n    /// The Cordova SDK extension.\n    case cordova = 0\n    /// The Xamarin SDK extension.\n    case xamarin = 1\n    /// The Unity SDK extension.\n    case unity = 2\n    /// The Flutter SDK extension.\n    case flutter = 3\n    /// The React Native SDK extension.\n    case reactNative = 4\n    /// The Titanium SDK extension.\n    case titanium = 5\n    /// The Capacitor SDK extension.\n    case capacitor = 6\n}\n\nextension AirshipSDKExtension {\n    var name: String {\n        switch self {\n        case .cordova:\n            return \"cordova\"\n        case .xamarin:\n            return \"xamarin\"\n        case .unity:\n            return \"unity\"\n        case .flutter:\n            return \"flutter\"\n        case .reactNative:\n            return \"react-native\"\n        case .titanium:\n            return \"titanium\"\n        case .capacitor:\n            return \"capacitor\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSDKModule.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AirshipSDKModule: NSObject {\n    var actionsManifest: (any ActionsManifest)? { get }\n    var components: [any AirshipComponent] { get }\n\n    @MainActor\n    static func load(_ args: AirshiopModuleLoaderArgs) -> (any AirshipSDKModule)?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSceneController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import Combine\n\n/// Exposes Scene controls to a custom view.\n///\n/// This class is an `ObservableObject` that can be used to control navigation flow,\n/// such as moving forward and backward, and locking the navigation. It is designed\n/// to be used with SwiftUI and must be accessed on the main actor.\n@MainActor\npublic class AirshipSceneController: ObservableObject {\n\n    /// Dismisses the current scene.\n    ///\n    /// - Parameter cancelFutureDisplays: A Boolean value that, if `true`,\n    ///   should cancel any scheduled or future displays related to this scene.\n    public func dismiss(cancelFutureDisplays: Bool = false) {\n        environment?.dismiss(cancel: cancelFutureDisplays)\n    }\n\n    /// An enumeration representing a navigation request.\n    public enum NaviagationRequest {\n        /// A request to navigate to the next scene.\n        case next\n        /// A request to navigate to the previous scene.\n        case back\n    }\n    \n    private let environment: ThomasEnvironment?\n    \n    /// Exposes pager state and allows to dispatch navigation requests\n    public let pager: PagerController\n\n    init(pagerState: PagerState?, environment: ThomasEnvironment?) {\n        self.pager = PagerController(pagerState: pagerState)\n        self.environment = environment\n    }\n\n    public convenience init() {\n        self.init(pagerState: nil, environment: nil)\n    }\n    \n    @MainActor\n    public class PagerController: ObservableObject {\n        private let pagerState: PagerState?\n        \n        init(pagerState: PagerState?) {\n            self.pagerState = pagerState\n        }\n\n        public convenience init() {\n            self.init(pagerState: nil)\n        }\n        \n        /// A Boolean value that indicates whether it is possible to navigate back.\n        ///\n        /// This property is published and read-only from outside the class. Observers\n        /// can use this to update UI elements, such as disabling a \"Back\" button.\n        public var canGoBack: Bool {\n            return pagerState?.canGoBack ?? false\n        }\n\n        /// A Boolean value that indicates whether it is possible to navigate forward.\n        ///\n        /// This property is published and read-only from outside the class. Observers\n        /// can use this to update UI elements, such as disabling a \"Next\" button.\n        public var canGoNext: Bool {\n            return pagerState?.canGoForward ?? false\n        }\n        \n        /// Attempts to navigate based on the specified request.\n        ///\n        /// - Parameter request: The navigation request, either `.next` or `.back`.\n        /// - Returns: A Boolean value indicating whether the navigation was successful.\n        public func navigate(request: NaviagationRequest) -> Bool {\n            switch(request) {\n                case .back:\n                return pagerState?.process(request: .back) != nil\n            case .next:\n                return pagerState?.process(request: .next) != nil\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSceneManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(UIKit) && !os(watchOS)\npublic import UIKit\n#endif\n\n/// - Note: for internal use only.  :nodoc:\npublic protocol AirshipSceneManagerProtocol: Sendable {\n#if !os(watchOS) && !os(macOS)\n    @MainActor\n    var lastActiveScene: UIWindowScene  { get throws }\n#endif\n}\n\n\n/**\n *  Scene manager\n *  Monitors scene connection and disconnection notifications and associated scenes to allow retrieving the latest scene.\n */\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipSceneManager: AirshipSceneManagerProtocol, Sendable {\n    public static let shared: AirshipSceneManager = AirshipSceneManager()\n\n#if !os(watchOS) && !os(macOS)\n\n    private let scenes: AirshipAtomicValue<[UIWindowScene]> = AirshipAtomicValue([UIWindowScene]())\n\n    private let notificationCenter: AirshipNotificationCenter\n\n    internal init(notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(notificationCenter: .default)) {\n        self.notificationCenter = notificationCenter\n        self.observeSceneEvents()\n    }\n\n    /**\n     * Called to get the latest connected window scene\n     *\n     * @return A window scene\n     */\n    @MainActor\n    public var lastActiveScene: UIWindowScene {\n        get throws {\n            let lastActiveMessageScene = scenes\n                .value\n                .filter { $0.activationState == .foregroundActive && $0.session.role == .windowApplication }\n                .last\n\n            guard let scene = lastActiveMessageScene else {\n                return try Self.findWindowScene()\n            }\n\n            return scene\n        }\n    }\n\n    // MARK: Notifications\n\n    private func observeSceneEvents() {\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(sceneAdded),\n            name: UIScene.willConnectNotification,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(sceneRemoved),\n            name: UIScene.didDisconnectNotification,\n            object: nil\n        )\n    }\n\n    // MARK: Helpers\n\n    @objc\n    @MainActor\n    private func sceneAdded(_ notification: Notification) {\n        guard let scene = notification.object as? UIWindowScene else {\n            AirshipLogger.debug(\"Unable to cast UIWindowScene from notification UIScene.willConnectNotification\")\n            return\n        }\n        scenes.update { current in\n            var mutableScenes = current\n            mutableScenes.append(scene)\n            return mutableScenes\n        }\n    }\n\n    @objc\n    @MainActor\n    private func sceneRemoved(_ notification: Notification) {\n        guard let scene = notification.object as? UIWindowScene else {\n            AirshipLogger.debug(\"Unable to cast UIWindowScene from notification UIScene.didDisconnectNotification\")\n            return\n        }\n        scenes.update { current in\n            var mutableScenes = current\n            mutableScenes.removeAll { $0 == scene }\n            return mutableScenes\n        }\n    }\n\n    @MainActor\n    fileprivate class func findWindowScene() throws -> UIWindowScene {\n        guard\n            let scene = UIApplication.shared.connectedScenes.first(where: {\n                $0.isKind(of: UIWindowScene.self)\n            }) as? UIWindowScene\n        else {\n            throw AirshipErrors.error(\"Unable to find a window!\")\n        }\n        return scene\n    }\n#endif\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSimpleLayoutView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n/// Simple layout class that converts airship layout into a swiftui view\n/// - Note: for internal use only.  :nodoc:\npublic struct AirshipSimpleLayoutView: View {\n    private let placement: ThomasPresentationInfo.Embedded.Placement = .init(\n        margin: nil,\n        size: .init(width: .percent(100), height: .percent(100)),\n        border: nil,\n        backgroundColor: nil\n    )\n\n    @ObservedObject\n    private var viewModel: AirshipSimpleLayoutViewModel\n    private let layout: AirshipLayout\n\n    @State\n    private var viewConstraints: ViewConstraints?\n\n    /// - Parameter viewModel: Owns the layout environment and state. Create one per layout session and reuse it so state is preserved across view updates.\n    public init(layout: AirshipLayout, viewModel: AirshipSimpleLayoutViewModel) {\n        self.layout = layout\n        self.viewModel = viewModel\n    }\n\n    public var body: some View {\n        RootView(\n            thomasEnvironment: viewModel.environment,\n            layout: layout\n        ) { orientation, windowSize in\n            AdoptLayout(\n                placement: placement,\n                viewConstraints: $viewConstraints,\n                embeddedSize: nil\n            ) {\n                if let constraints = viewConstraints {\n                    createView(\n                        constraints: constraints,\n                        placement: placement\n                    )\n                } else {\n                    Color.clear\n                }\n            }\n        }\n    }\n    \n    @MainActor\n    private func createView(\n        constraints: ViewConstraints,\n        placement: ThomasPresentationInfo.Embedded.Placement\n    ) -> some View {\n        return ViewFactory\n            .createView(layout.view, constraints: constraints)\n            .thomasBackground(\n                color: placement.backgroundColor,\n                border: placement.border\n            )\n            .margin(placement.margin)\n            .constraints(constraints)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSimpleLayoutViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import Combine\n\n/// View model that owns the Thomas layout environment and its state.\n/// Create one per layout session (e.g. per message) and pass it to ``AirshipSimpleLayoutView``.\n/// Same view model = preserved state across view updates; new view model = fresh state.\n///\n/// - Note: For internal use only.  :nodoc:\n@MainActor\npublic final class AirshipSimpleLayoutViewModel: ObservableObject {\n    let environment: ThomasEnvironment\n\n    public init(\n        delegate: any ThomasDelegate,\n        timer: (any AirshipTimerProtocol)? = nil,\n        extensions: ThomasExtensions? = nil,\n        dismissHandle: ThomasDismissHandle? = nil,\n        stateStorage: (any LayoutDataStorage)? = nil\n    ) {\n        \n        let dataStore: (any ThomasStateStorage)? = if let storage = stateStorage {\n            DefaultThomasStateStorage(store: storage)\n        } else {\n            nil\n        }\n        \n        self.environment = ThomasEnvironment(\n            delegate: delegate,\n            extensions: extensions,\n            timer: timer,\n            stateStorage: dataStore,\n            dismissHandle: dismissHandle\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipStateOverrides.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipStateOverrides: Encodable, Equatable, Sendable {\n    let appVersion: String\n    let sdkVersion: String\n    let notificationOptIn: Bool\n    let localeLangauge: String?\n    let localeCountry: String?\n\n    enum CodingKeys: String, CodingKey {\n        case appVersion = \"app_version\"\n        case sdkVersion = \"sdk_version\"\n        case notificationOptIn = \"notification_opt_in\"\n        case localeLangauge = \"locale_language\"\n        case localeCountry = \"locale_country\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSwitchToggleStyle.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n\nstruct AirshipSwitchToggleStyle: ToggleStyle {\n    let info: ThomasToggleStyleInfo.Switch\n    \n    func makeBody(configuration: Self.Configuration) -> some View {\n        Button(action: { configuration.isOn.toggle() }) {}\n            .buttonStyle(\n                AirshipSwitchButtonStyle(info: info, isOn: configuration.$isOn)\n            )\n    }\n    \n    struct AirshipSwitchButtonStyle: ButtonStyle {\n        let info: ThomasToggleStyleInfo.Switch\n        var isOn: Binding<Bool>\n        \n        func makeBody(configuration: Self.Configuration) -> some View {\n            ButtonView(configuration: configuration, info: info, isOn: isOn)\n        }\n        \n        struct ButtonView: View {\n            let configuration: ButtonStyle.Configuration\n            let info: ThomasToggleStyleInfo.Switch\n            var isOn: Binding<Bool>\n            \n            @Environment(\\.isFocused) var isFocused\n            @Environment(\\.isEnabled) var isEnabled\n            @Environment(\\.colorScheme) var colorScheme\n\n            static let trackWidth = 50.0\n            static let thumbDiameter = 30.0\n            static let thumbPadding = 1.5\n            static let pressedThumbStretch = 4.0\n            \n            static let offSet = (trackWidth - thumbDiameter) / 2\n            static let pressedOffset = offSet - (pressedThumbStretch / 2)\n            \n            @ViewBuilder\n            func createOverlay(isPressed: Bool) -> some View {\n                if isPressed {\n                    Capsule()\n                        .fill(Color.white)\n                        .shadow(radius: 1, x: 0, y: 1)\n                        .frame(width: Self.thumbDiameter + Self.pressedThumbStretch)\n                        .padding(Self.thumbPadding)\n                        .offset(x: isOn.wrappedValue ? Self.pressedOffset : -Self.pressedOffset)\n                } else {\n                    Circle()\n                        .fill(Color.white)\n                        .shadow(radius: 1, x: 0, y: 1)\n                        .padding(Self.thumbPadding)\n                        .offset(x: isOn.wrappedValue ? Self.offSet : -Self.offSet)\n                }\n            }\n            \n            var body: some View {\n                let fill = self.isOn.wrappedValue ? self.info.colors.on.toColor(colorScheme) : self.info.colors.off.toColor(colorScheme)\n   \n                Capsule()\n                    .fill(fill)\n                    .frame(width: Self.trackWidth, height: Self.thumbDiameter)\n                    .overlay(createOverlay(isPressed: configuration.isPressed))\n                    .animation(Animation.easeInOut(duration: 0.05), value: self.isOn.wrappedValue)\n                    .colorMultiply(isEnabled ? Color.white : ThomasConstants.disabledColor)\n                    .saturation(isEnabled ? 1.0 : 0.5)\n#if os(tvOS)\n                    .hoverEffect(.highlight, isEnabled: isFocused)\n#endif\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipSwizzler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport ObjectiveC\n\nfileprivate struct SwizzlerEntry {\n    let swizzledClass: AnyClass\n    let originalImplementation: IMP\n    let selectorString: String\n}\n\n@MainActor\ninternal class AirshipSwizzler {\n    @objc fileprivate protocol ForwardingCheck {\n        @objc func forwardingTarget(for aSelector: Selector!) -> Any?\n    }\n\n    private static var entryMap: [String: SwizzlerEntry] = [:]\n\n    @discardableResult\n    func swizzleInstance(\n        _ instance: any NSObjectProtocol,\n        selector: Selector,\n        protocol: Protocol? = nil,\n        implementation: IMP\n    ) -> Bool {\n        let clazz: AnyClass = classForSelector(selector, target: instance)\n        return swizzleClass(clazz, selector: selector, protocol: `protocol`, implementation: implementation)\n    }\n\n    @discardableResult\n    func swizzleClass(\n        _ clazz: AnyClass,\n        selector: Selector,\n        protocol: Protocol? = nil,\n        implementation: IMP\n    ) -> Bool {\n        let selectorString = NSStringFromSelector(selector)\n        let key = \"\\(String(describing: clazz)).\\(selectorString)\"\n\n        if Self.entryMap[key] != nil { return true }\n\n        guard let method = class_getInstanceMethod(clazz, selector) else {\n            if let proto = `protocol` {\n                let desc = protocol_getMethodDescription(proto, selector, false, true)\n                return class_addMethod(clazz, selector, implementation, desc.types)\n            }\n            return false\n        }\n\n        let typeEncoding = method_getTypeEncoding(method)\n\n        if class_addMethod(clazz, selector, implementation, typeEncoding) {\n            let original = method_getImplementation(method)\n            Self.entryMap[key] = SwizzlerEntry(swizzledClass: clazz, originalImplementation: original, selectorString: selectorString)\n        } else {\n            let existing = method_setImplementation(method, implementation)\n            if implementation != existing {\n                Self.entryMap[key] = SwizzlerEntry(swizzledClass: clazz, originalImplementation: existing, selectorString: selectorString)\n            }\n        }\n\n        return true\n    }\n\n    func originalImplementation(_ selector: Selector, forClass clazz: AnyClass) -> IMP? {\n        let key = \"\\(String(describing: clazz)).\\(NSStringFromSelector(selector))\"\n        return Self.entryMap[key]?.originalImplementation\n    }\n\n    private func classForSelector(_ selector: Selector, target: any NSObjectProtocol) -> AnyClass {\n        if class_getInstanceMethod(type(of: target), selector) != nil {\n            return type(of: target)\n        }\n\n        if\n            target.responds(to: #selector(NSObject.forwardingTarget(for:))),\n            let forwarder = target as? any ForwardingCheck,\n            let forwardingTarget = forwarder.forwardingTarget(for: selector) as? any NSObjectProtocol\n        {\n            return classForSelector(selector, target: forwardingTarget)\n        }\n\n        return type(of: target)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipTaskSleeper.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AirshipTaskSleeper: Sendable {\n    func sleep(timeInterval: TimeInterval) async throws\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic final class DefaultAirshipTaskSleeper: AirshipTaskSleeper {\n    fileprivate static let shared: DefaultAirshipTaskSleeper = DefaultAirshipTaskSleeper()\n    private static let maxDelayInterval: TimeInterval = 30\n\n    private let date: any AirshipDateProtocol\n    private let onSleep: @Sendable (TimeInterval) async throws -> Void\n\n    init(\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        onSleep: @escaping @Sendable (TimeInterval) async throws -> Void = {\n            try await Task.sleep(nanoseconds: UInt64($0 * 1_000_000_000))\n        }\n    ) {\n        self.date = date\n        self.onSleep = onSleep\n    }\n\n    public func sleep(timeInterval: TimeInterval) async throws {\n        let start: Date = date.now\n\n        /// Its unclear what clock is being used for Task.sleep(nanoseconds:) and we have had issues\n        /// with really long delays not firing at the right period of time. This works around those issues by\n        /// breaking long sleeps into chunks.\n        var remaining = timeInterval - date.now.timeIntervalSince(start)\n        while remaining > 0, !Task.isCancelled {\n            let interval = min(remaining, Self.maxDelayInterval)\n            try await onSleep(interval)\n            remaining = timeInterval - date.now.timeIntervalSince(start)\n        }\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic extension AirshipTaskSleeper where Self == DefaultAirshipTaskSleeper {\n    /// Default style\n    static var shared: Self {\n        return DefaultAirshipTaskSleeper.shared\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipTimeCriteria.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipTimeCriteria: Codable, Sendable, Equatable {\n    private let start: Int64?\n    private let end: Int64?\n\n    enum CodingKeys: String, CodingKey {\n        case start = \"start_timestamp\"\n        case end = \"end_timestamp\"\n    }\n\n    public init(start: Date? = nil, end: Date? = nil) {\n        self.start = start?.millisecondsSince1970\n        self.end = end?.millisecondsSince1970\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic extension AirshipTimeCriteria {\n    func isActive(date: Date) -> Bool {\n        let currentMS = date.millisecondsSince1970\n\n        if let startMS = self.start, currentMS < startMS {\n            return false\n        }\n\n        if let endMS = self.end, currentMS >= endMS {\n            return false\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipTimerProtocol.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic protocol AirshipTimerProtocol: Sendable {\n    @MainActor\n    var time : TimeInterval { get }\n\n    @MainActor\n    func start()\n\n    @MainActor\n    func stop()\n}\n\n/// - Note: for internal use only.  :nodoc:\n@MainActor\nfinal public class AirshipTimer: AirshipTimerProtocol {\n    private var isStarted: Bool = false\n    private var elapsedTime: TimeInterval = 0\n    private var startDate: Date? = nil\n    private let date: any AirshipDateProtocol\n\n    public init(date: any AirshipDateProtocol = AirshipDate.shared) {\n        self.date = date\n    }\n\n    public func start() {\n        guard !self.isStarted else { return }\n\n        self.startDate = date.now\n        self.isStarted = true\n    }\n\n    public func stop() {\n        guard self.isStarted else { return }\n\n        self.elapsedTime += currentSessionTime()\n        self.startDate = nil\n        self.isStarted = false\n    }\n\n    private func currentSessionTime() -> TimeInterval {\n        guard let date = self.startDate else { return 0 }\n        return self.date.now.timeIntervalSince(date)\n    }\n\n    public var time: TimeInterval {\n        return self.elapsedTime + currentSessionTime()\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipToggle.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct AirshipToggle: View {\n    private let info: ThomasViewInfo.Toggle\n    private let constraints: ViewConstraints\n\n    @Environment(\\.pageIdentifier) private var pageID\n    @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    @State private var isOn: Bool = false\n\n    init(info: ThomasViewInfo.Toggle, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .toggle,\n            thomasState: thomasState\n        )\n    }\n\n\n    var body: some View {\n        createToggle()\n            .constraints(self.constraints)\n            .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n            .accessible(\n                self.info.accessible,\n                associatedLabel: associatedLabel,\n                hideIfDescriptionIsMissing: false\n            )\n            .formElement()\n            .onAppear {\n                restoreFormState()\n                updateValue(self.isOn)\n            }\n    }\n\n    @ViewBuilder\n    private func createToggle() -> some View {\n        let binding = Binding<Bool>(\n            get: { self.isOn },\n            set: {\n                self.isOn = $0\n                self.updateValue($0)\n            }\n        )\n        \n        Toggle(isOn: binding.animation()) {}\n            .thomasToggleStyle(\n                self.info.properties.style,\n                constraints: self.constraints\n            )\n    }\n    \n    private var attributes: [ThomasFormField.Attribute]? {\n        guard\n            let name = self.info.properties.attributeName,\n            let value = self.info.properties.attributeValue\n        else {\n            return nil\n        }\n        \n        return [\n            ThomasFormField.Attribute(\n                attributeName: name,\n                attributeValue: value\n            )\n        ]\n    }\n    \n    private func checkValid(_ isOn: Bool) -> Bool {\n        return isOn || self.info.validation.isRequired != true\n    }\n\n    private func updateValue(_ isOn: Bool) {\n        let formValue: ThomasFormField.Value = .toggle(isOn)\n\n        let field: ThomasFormField = if checkValid(isOn) {\n            ThomasFormField.validField(\n                identifier: self.info.properties.identifier,\n                input: formValue,\n                result: .init(\n                    value: formValue,\n                    attributes: self.attributes\n                )\n           )\n        } else {\n            ThomasFormField.invalidField(\n                identifier: self.info.properties.identifier,\n                input: formValue\n            )\n        }\n\n        self.formDataCollector.updateField(field, pageID: pageID)\n    }\n\n    private func restoreFormState() {\n        guard\n            case .toggle(let value) = self.formState.field(\n                identifier: self.info.properties.identifier\n            )?.input\n        else {\n            self.updateValue(self.isOn)\n            return\n        }\n\n        self.isOn = value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipUnsafeSendableWrapper.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipUnsafeSendableWrapper<T>: @unchecked Sendable {\n    public var value: T\n    public init(_ value: T) {\n        self.value = value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CommonCrypto\nimport Foundation\npublic import SwiftUI\n\n#if !os(watchOS)\nimport SystemConfiguration\n#endif\n\n#if os(iOS) && !targetEnvironment(macCatalyst)\nimport CoreTelephony\n#endif\n\n\n/// The `Utils` object provides an interface for utility methods.\npublic final class AirshipUtils {\n\n    // MARK: Device Utilities\n\n    /// Get the device model name (e.g.,` iPhone3,1`).\n    ///\n    /// - Returns: The device model name.\n    @available(*, deprecated, message: \"This method is no longer supported and will be removed in a future SDK version.\")\n    public class func deviceModelName() -> String? {\n        return AirshipDevice.modelIdentifier\n    }\n\n    /// Gets the short bundle version string.\n    ///\n    /// - Returns: A short bundle version string value.\n    public class func bundleShortVersionString() -> String? {\n        return Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"]\n            as? String\n    }\n\n\n    #if !os(watchOS)\n    /// Checks if the device has network connection.\n    ///\n    /// - Returns: The true if it has connection, false otherwise.\n    public class func hasNetworkConnection() -> Bool {\n        var zeroAddress = sockaddr_in()\n        zeroAddress.sin_len = UInt8(MemoryLayout.size(ofValue: zeroAddress))\n        zeroAddress.sin_family = sa_family_t(AF_INET)\n\n        guard\n            let reachability = withUnsafePointer(\n                to: &zeroAddress,\n                {\n                    $0.withMemoryRebound(\n                        to: sockaddr.self,\n                        capacity: MemoryLayout<sockaddr>.size\n                    ) { ptr in\n                        SCNetworkReachabilityCreateWithAddress(nil, ptr)\n                    }\n                }\n            )\n        else {\n            return false\n        }\n\n        var flags = SCNetworkReachabilityFlags()\n        let isSuccess = SCNetworkReachabilityGetFlags(reachability, &flags)\n        return isSuccess && flags.contains(.reachable)\n    }\n\n    #endif\n\n    /// Compares two version strings and determines their order.\n    ///\n    /// - Parameters:\n    ///   - fromVersion: The first version.\n    ///   - toVersion: The second version.\n    ///   - maxVersionParts: Max number of version parts to compare. Use 3 to only compare major.minor.patch\n    ///\n    /// - Returns: a `ComparisonResult`.\n    public class func compareVersion(\n        _ fromVersion: String,\n        toVersion: String,\n        maxVersionParts: Int? = nil\n    ) -> ComparisonResult {\n        if let maxVersionParts, maxVersionParts <= 0 {\n            return .orderedSame\n        }\n\n        let fromParts = fromVersion.components(separatedBy: \".\").map {\n            ($0 as NSString).integerValue\n        }\n\n        let toParts = toVersion.components(separatedBy: \".\").map {\n            ($0 as NSString).integerValue\n        }\n\n        var i = 0\n        while fromParts.count > i || toParts.count > i {\n            let from: Int = fromParts.count > i ? fromParts[i] : 0\n            let to: Int = toParts.count > i ? toParts[i] : 0\n\n            if from < to {\n                return .orderedAscending\n            } else if from > to {\n                return .orderedDescending\n            }\n            i += 1\n\n            if let maxVersionParts, maxVersionParts <= i {\n                break\n            }\n        }\n\n        return .orderedSame\n    }\n\n\n    // MARK: UI Utilities\n\n    #if !os(watchOS) && !os(macOS)\n    /// Returns the main window for the app.\n    ///\n    /// This window will be positioned underneath any other windows added and removed at runtime,\n    /// by classes such a `UIAlertView` or `UIActionSheet`.\n    ///\n    /// - Returns: The main window, or `nil` if the window cannot be found.\n    @MainActor\n    public class func mainWindow() throws -> UIWindow? {\n        let scene = try AirshipSceneManager.shared.lastActiveScene\n\n        let sharedApp: UIApplication = UIApplication.shared\n        for window in scene.windows {\n            if window.isKeyWindow {\n                return window\n            }\n        }\n        return sharedApp.delegate?.window ?? nil\n    }\n\n    /// Returns the main window for the given `UIWindowScene`.\n    ///\n    /// This window will be positioned underneath any other windows added and removed at runtime,\n    /// by classes such a `UIAlertView` or `UIActionSheet`.\n    ///\n    /// - Parameter scene: The `UIWindowScene`.\n    ///\n    /// - Returns: The main window, or `nil` if the window cannot be found.\n    @MainActor\n    public class func mainWindow(scene: UIWindowScene) -> UIWindow? {\n        for w in scene.windows {\n            if !w.isHidden {\n                return w\n            }\n        }\n\n        return try? self.mainWindow()\n    }\n\n\n    #endif\n\n    // MARK: Fetch Results\n\n    #if !os(watchOS) && !os(macOS)\n    ///  Takes an array of fetch results and returns the merged result.\n    ///\n    /// - Parameter results: An `Array` of fetch results.\n    ///\n    /// - Returns: The merged fetch result.\n    public class func mergeFetchResults(\n        _ results: [UInt]\n    ) -> UIBackgroundFetchResult {\n        var mergedResult: UIBackgroundFetchResult = .noData\n        for r in results {\n            if r == UIBackgroundFetchResult.newData.rawValue {\n                return .newData\n            } else if r == UIBackgroundFetchResult.failed.rawValue {\n                mergedResult = .failed\n            }\n        }\n        return mergedResult\n    }\n    #endif\n    \n    #if os(watchOS)\n    ///  Takes an array of fetch results and returns the merged result.\n    ///\n    /// - Parameter results: An `Array` of fetch results.\n    ///\n    /// - Returns: The merged fetch result.\n    public class func mergeFetchResults(_ results: [UInt])\n        -> WKBackgroundFetchResult\n    {\n        var mergedResult: WKBackgroundFetchResult = .noData\n        for r in results {\n            if r == WKBackgroundFetchResult.newData.rawValue {\n                return .newData\n            } else if r == WKBackgroundFetchResult.failed.rawValue {\n                mergedResult = .failed\n            }\n        }\n        return mergedResult\n    }\n    #endif\n\n    // MARK: Notification Payload\n\n    /// Determine if the notification payload is a silent push (no notification elements).\n    ///\n    /// - Parameter notification The notification payload.\n    ///\n    /// - Returns: `true` the notification is a silent push, `false` otherwise.\n    public class func isSilentPush(_ notification: [AnyHashable: Any]) -> Bool {\n        guard let apsDict = notification[\"aps\"] as? [AnyHashable: Any] else {\n            return true\n        }\n\n        if apsDict[\"badge\"] != nil {\n            return false\n        }\n\n        if let soundName = apsDict[\"sound\"] as? String {\n            if !soundName.isEmpty {\n                return false\n            }\n        }\n\n        if isAlertingPush(notification) {\n            return false\n        }\n\n        return true\n    }\n\n    /// Determine if the notification payload is an alerting push.\n    ///\n    /// - Parameter notification The notification payload.\n    ///\n    /// - Returns: `true` the notification is an alerting  push, `false` otherwise.\n    public class func isAlertingPush(_ notification: [AnyHashable: Any]) -> Bool\n    {\n        guard let apsDict = notification[\"aps\"] as? [AnyHashable: Any] else {\n            return false\n        }\n\n        if let alert = apsDict[\"alert\"] as? [AnyHashable: Any] {\n            if (alert[\"body\"] as? String)?.isEmpty == false {\n                return true\n            }\n            if (alert[\"loc-key\"] as? String)?.isEmpty == false {\n                return true\n            }\n        } else if let alert = apsDict[\"alert\"] as? String {\n            if !alert.isEmpty {\n                return true\n            }\n        }\n\n        return false\n    }\n\n    // MARK: Device Tokens\n\n    /// Takes an APNS-provided device token and returns the decoded Airship device token.\n    ///\n    /// - Parameter token: An APNS-provided device token.\n    ///\n    /// - Returns: The decoded Airship device token.\n    public class func deviceTokenStringFromDeviceToken(_ token: Data) -> String\n    {\n        var tokenString = \"\"\n\n        let bytes = [UInt8](token)\n        for byte in bytes {\n            tokenString = tokenString.appendingFormat(\"%02x\", byte)\n        }\n\n        return tokenString.lowercased()\n    }\n\n    // MARK: SHA256 Utilities\n\n    /// Generates a `SHA256` digest for the input string.\n    ///\n    /// - Parameter input: `String` for which to calculate SHA.\n    /// - Returns: The `SHA256` digest as `NSData`.\n    public class func sha256Digest(input: String) -> NSData {\n        guard let dataIn = input.data(using: .utf8) as NSData? else {\n            return NSData()\n        }\n        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)\n        var digest = [UInt8](repeating: 0, count: digestLength)\n        CC_SHA256(dataIn.bytes, CC_LONG(dataIn.count), &digest)\n\n        return NSData(bytes: digest, length: digestLength)\n    }\n\n    /// Generates a `SHA256` hash for the input string.\n    ///\n    /// - Parameter input: Input string for which to calculate SHA.\n    ///\n    /// - Returns: SHA256 digest as a hex string\n    public class func sha256Hash(input: String) -> String {\n        let digestLength = Int(CC_SHA256_DIGEST_LENGTH)\n        let digest = sha256Digest(input: input)\n        var buffer = [UInt8](repeating: 0, count: digestLength)\n        digest.getBytes(&buffer, length: digestLength)\n\n        return buffer.map { String(format: \"%02x\", $0) }.joined(separator: \"\")\n    }\n\n    // MARK: UAHTTP Authenticated Request Helpers\n\n    /// Returns a basic auth header string.\n    ///\n    /// - Parameters:\n    ///   - username: The username.\n    ///   - password: The password.\n    /// - Returns: An HTTP Basic Auth header string value for the provided credentials in the form of: `Basic [Base64 Encoded \"username:password\"]`\n    public class func authHeader(username: String, password: String) -> String?\n    {\n        guard let data = \"\\(username):\\(password)\".data(using: .utf8) else {\n            return nil\n        }\n        guard let encodedData = AirshipBase64.string(from: data) else {\n            return nil\n        }\n        let authString =\n            encodedData\n            //strip carriage return and linefeed characters\n            .replacingOccurrences(of: \"\\n\", with: \"\")\n            .replacingOccurrences(of: \"\\r\", with: \"\")\n\n        return \"Basic \\(authString)\"\n    }\n\n   \n    // MARK: URL\n\n    /// Parse url for the input string.\n    ///\n    /// - Parameter value: Input string for which to create the URL.\n    ///\n    /// - Returns: returns the created URL otherwise return nil.\n    public class func parseURL(_ value: String) -> URL? {\n        if let url = URL(string: value) {\n            return url\n        }\n\n        /* Characters reserved for url  */\n        let reserved = \"!*'();:@&=+$,/?%#[]\"\n        /* Characters are not reserved for url but should not be encoded */\n        let unreserved = \":-._~/? \"\n        let allowed = NSMutableCharacterSet.alphanumeric()\n        allowed.addCharacters(in: reserved)\n        allowed.addCharacters(in: unreserved)\n        if let encoded = value.addingPercentEncoding(\n            withAllowedCharacters: allowed as CharacterSet\n        ) {\n            return URL(string: encoded)\n\n        }\n        return nil\n    }\n\n    class func generateSignedToken(secret: String, tokenParams: [String]) throws -> String {\n        let secret = NSData(data: Data(secret.utf8))\n        let message = NSData(data: Data(tokenParams.joined(separator: \":\").utf8))\n\n        let hash = NSMutableData(length: Int(CC_SHA256_DIGEST_LENGTH))\n        guard let hash else {\n            throw AirshipErrors.error(\"Failed to generate signed token\")\n        }\n\n        CCHmac(CCHmacAlgorithm(kCCHmacAlgSHA256), secret.bytes, secret.count, message.bytes, message.count, hash.mutableBytes)\n\n        return hash.base64EncodedString(options: [])\n    }\n}\n\nextension Locale {\n    func getLanguageCode() -> String {\n        return self.language.languageCode?.identifier ?? \"\"\n    }\n\n    func getRegionCode() -> String {\n        return self.region?.identifier ?? \"\"\n    }\n\n    func getVariantCode() -> String {\n        return self.variant?.identifier ?? \"\"\n    }\n}\n\ninternal extension Int {\n    func airshipLocalizedForVoiceOver() -> String {\n        let formatter = NumberFormatter()\n        formatter.numberStyle = .spellOut\n        return formatter.string(from: NSNumber(value: self)) ?? String(self)\n    }\n}\n\ninternal extension Collection {\n    subscript(safe index: Index) -> Element? {\n        indices.contains(index) ? self[index] : nil\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipVersion.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n\npublic struct AirshipVersion {\n    public static let version = \"20.6.2\"\n    public static func get() -> String {\n        return version\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipViewSizeReader.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\nimport Foundation\n\n\n/// A view that wraps the view and returns the size without causing the view to expand.\npublic struct AirshipViewSizeReader<Content> : View where Content : View {\n    @State\n    private var viewSize: CGSize?\n    private let contentBlock: (CGSize?) -> Content\n\n    /// Default constructor\n    /// - Parameters:\n    ///     - contentBlock: The content block that will have the view size if available and returns the actual content.\n    public init(@ViewBuilder contentBlock: @escaping  (CGSize?) -> Content) {\n        self.contentBlock = contentBlock\n    }\n\n    public var body: some View {\n        return contentBlock(viewSize).airshipMeasureView($viewSize)\n    }\n}\n\n\npublic extension View {\n\n    /// Adds a geometry reader to the background to fetch the size without causing the view to grow.\n    /// -  Parameter binding: The  binding to store the size.\n    @ViewBuilder\n    func airshipMeasureView(_ binding: Binding<CGSize?>) -> some View  {\n        self.background(\n            GeometryReader { geo -> Color in\n                DispatchQueue.main.async {\n                    binding.wrappedValue = geo.size\n                }\n                return Color.clear\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipViewUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\npublic import Combine\n\n/// NOTE: For internal use only. :nodoc:\npublic extension Color {\n    static var airshipTappableClear: Color { Color.white.opacity(0.001) }\n    static var airshipShadowColor: Color { Color.black.opacity(0.33) }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic extension View {\n    /// Wrapper to prevent linter warnings for deprecated onChange method\n    /// - Parameters:\n    ///   - value: The value to observe for changes.\n    ///   - initial: A Boolean value that determines whether the action should be fired initially.\n    ///   - action: The action to perform when the value changes.\n    /// NOTE: For internal use only. :nodoc:\n    @ViewBuilder\n    func airshipOnChangeOf<Value: Equatable>(_ value: Value, initial: Bool = false, _ action: @escaping (Value) -> Void) -> some View {\n        if #available(iOS 17.0, macOS 14.0, watchOS 10.0, tvOS 17.0, *) {\n            self.onChange(of: value, initial: initial, {\n                action(value)\n            })\n        } else {\n            self.onChange(of: value, perform: action)\n        }\n    }\n\n    @ViewBuilder\n    func showing(isShowing: Bool) -> some View {\n        if isShowing {\n            self.opacity(1)\n        } else {\n            self.hidden().opacity(0)\n        }\n    }\n\n    @ViewBuilder\n    func airshipAddNub(\n        isTopPlacement: Bool,\n        nub: AnyView,\n        itemSpacing: CGFloat\n    ) -> some View {\n        VStack(spacing: 0) {\n            if isTopPlacement {\n                self\n                nub.padding(.vertical, itemSpacing / 2)\n            } else {\n                nub.padding(.vertical, itemSpacing / 2)\n                self\n            }\n        }\n    }\n\n    @ViewBuilder\n    func airshipApplyTransition(\n        isTopPlacement: Bool\n    ) -> some View {\n        if isTopPlacement {\n            self.transition(\n                .asymmetric(\n                    insertion: .move(edge: .top),\n                    removal: .move(edge: .top).combined(with: .opacity)\n                )\n            )\n        } else {\n            self.transition(\n                .asymmetric(\n                    insertion: .move(edge: .bottom),\n                    removal: .move(edge: .bottom).combined(with: .opacity)\n                )\n            )\n        }\n    }\n\n    @ViewBuilder\n    func airshipFocusableCompat(\n        _ isFocusable: Bool = true\n    ) -> some View {\n        if #available(iOS 17.0, *) {\n            self.focusable(isFocusable)\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func airshipApplyTransitioningPlacement(\n        isTopPlacement: Bool\n    ) -> some View {\n        if isTopPlacement {\n            VStack {\n                self.airshipApplyTransition(isTopPlacement:isTopPlacement)\n                Spacer()\n            }\n        } else {\n            VStack {\n                Spacer()\n                self.airshipApplyTransition(isTopPlacement:isTopPlacement)\n            }\n        }\n    }\n}\n\n\n/// NOTE: For internal use only. :nodoc:\n@MainActor\npublic final class AirshipObservableTimer: ObservableObject {\n    private static let tick: TimeInterval = 0.1\n    private var elapsedTime: TimeInterval = 0\n    private let duration: TimeInterval?\n\n    private var isStarted: Bool = false\n    private var task: Task<Void, any Error>?\n    public var isPaused: Bool = false\n\n    @Published\n    public private(set) var isExpired: Bool = false\n\n    public init(duration: TimeInterval?) {\n        self.duration = duration\n    }\n\n    public func onAppear() {\n        guard !isStarted, !isExpired, let duration else {\n            return\n        }\n\n        self.isStarted = true\n\n        self.task = Task { @MainActor [weak self] in\n            while self?.isExpired == false, self?.isStarted == true {\n                try await Task.sleep(nanoseconds: UInt64(Self.tick * 1_000_000_000))\n                guard let self, self.isStarted, !Task.isCancelled else { return }\n\n                if !self.isPaused {\n                    self.elapsedTime += Self.tick\n                    if self.elapsedTime >= duration {\n                        self.isExpired = true\n                        self.task?.cancel()\n                    }\n                }\n            }\n        }\n    }\n\n    public func onDisappear() {\n        isStarted = false\n        task?.cancel()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWeakValueHolder.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic class AirshipWeakValueHolder<T: AnyObject> {\n    public weak var value: T?\n\n    public init(value: T? = nil) {\n        self.value = value\n    }\n}\n\npublic class AirshipStrongValueHolder<T: AnyObject> {\n    public var value: T?\n\n    public init(value: T? = nil) {\n        self.value = value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWebview.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\nimport SwiftUI\nimport WebKit\n\n/// Airship Webview\nstruct AirshipWebView: View {\n    \n    let info: ThomasViewInfo.WebView\n    \n    let constraints: ViewConstraints\n    \n    @State var isWebViewLoading: Bool = false\n    @EnvironmentObject var thomasEnvironment: ThomasEnvironment\n    @Environment(\\.layoutState) var layoutState\n    \n    var body: some View {\n        \n        ZStack {\n            WebViewView(\n                url: self.info.properties.url,\n                nativeBridgeExtension: self.thomasEnvironment.extensions?\n                    .nativeBridgeExtension,\n                isWebViewLoading: self.$isWebViewLoading,\n                onRunActions: { name, value, _ in\n                    return await thomasEnvironment.runAction(name, arguments: value, layoutState: layoutState)\n                },\n                onDismiss: {\n                    thomasEnvironment.dismiss(layoutState: layoutState)\n                }\n            )\n            .opacity(self.isWebViewLoading ? 0.0 : 1.0)\n            \n            if self.isWebViewLoading {\n                AirshipProgressView()\n            }\n        }\n        .constraints(constraints)\n        .thomasCommon(self.info)\n    }\n}\n\n/// Webview\nstruct WebViewView: AirshipNativeViewRepresentable {\n\n#if os(macOS)\n    typealias NSViewType = WKWebView\n#else\n    typealias UIViewType = WKWebView\n#endif\n    \n    let url: String\n    let nativeBridgeExtension: (any NativeBridgeExtensionDelegate)?\n    @Binding var isWebViewLoading: Bool\n    let onRunActions: @MainActor (String, ActionArguments, WKWebView) async -> ActionResult\n    let onDismiss: () -> Void\n    @EnvironmentObject var thomasEnvironment: ThomasEnvironment\n\n#if os(macOS)\n    func makeNSView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateNSView(_ nsView: WKWebView, context: Context) {\n        updateView(context: context)\n    }\n\n    static func dismantleNSView(_ nsView: WKWebView, coordinator: Coordinator) {\n        coordinator.teardown()\n    }\n#else\n    func makeUIView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateUIView(_ uiView: WKWebView, context: Context) {\n        updateView(context: context)\n    }\n\n    static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {\n        coordinator.teardown()\n    }\n#endif\n\n    private func updateView(context: Context) {\n        if thomasEnvironment.isDismissed {\n            context.coordinator.teardown()\n        }\n    }\n\n    func makeWebView(context: Context) -> WKWebView {\n        let webView = WKWebView()\n        webView.navigationDelegate = context.coordinator.nativeBridge\n        context.coordinator.configure(webView: webView)\n\n        if #available(iOS 16.4, *) {\n            webView.isInspectable = Airship.isFlying && Airship.config.airshipConfig.isWebViewInspectionEnabled\n        }\n\n        if let url = URL(string: self.url) {\n            updateLoading(true)\n            webView.load(URLRequest(url: url))\n        }\n\n        return webView\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self, actionRunner: BlockNativeBridgeActionRunner(onRun: onRunActions))\n    }\n\n    func updateLoading(_ isWebViewLoading: Bool) {\n        DispatchQueue.main.async {\n            self.isWebViewLoading = isWebViewLoading\n        }\n    }\n\n\n    class Coordinator: NSObject, AirshipWKNavigationDelegate,\n                       JavaScriptCommandDelegate, NativeBridgeDelegate\n    {\n        private let parent: WebViewView\n        private let challengeResolver: ChallengeResolver\n        private weak var webView: WKWebView?\n        let nativeBridge: NativeBridge\n\n        init(_ parent: WebViewView, actionRunner: any NativeBridgeActionRunner, resolver: ChallengeResolver = .shared) {\n            self.parent = parent\n            self.nativeBridge = NativeBridge(actionRunner: actionRunner)\n            self.challengeResolver = resolver\n\n            super.init()\n            AirshipLogger.trace(\"WebViewView Coordinator init\")\n            self.nativeBridge.nativeBridgeExtensionDelegate =\n            self.parent.nativeBridgeExtension\n            self.nativeBridge.forwardNavigationDelegate = self\n            self.nativeBridge.javaScriptCommandDelegate = self\n            self.nativeBridge.nativeBridgeDelegate = self\n        }\n\n        deinit {\n            AirshipLogger.trace(\"WebViewView Coordinator deinit\")\n        }\n\n        func configure(webView: WKWebView) {\n            self.webView = webView\n        }\n\n        @MainActor\n        func teardown() {\n            self.webView?.stopLoading()\n            self.webView?.navigationDelegate = nil\n            self.webView?.pauseAllMediaPlayback()\n#if !os(macOS)\n            if #unavailable(iOS 26.3) {\n                if self.webView?.superview != nil {\n                    self.webView?.removeFromSuperview()\n                }\n            }\n#endif\n            self.webView = nil\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            didFinish navigation: WKNavigation!\n        ) {\n            parent.updateLoading(false)\n        }\n\n        func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {\n            parent.updateLoading(true)\n            DispatchQueue.main.async {\n                webView.reload()\n            }\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            didFail navigation: WKNavigation!,\n            withError error: any Error\n        ) {\n            parent.updateLoading(true)\n            DispatchQueue.main.asyncAfter(deadline: .now() + 10.0) {\n                [weak webView] in\n                webView?.reload()\n            }\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            respondTo challenge: URLAuthenticationChallenge)\n        async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n            return await challengeResolver.resolve(challenge)\n        }\n\n        func performCommand(_ command: JavaScriptCommand, webView: WKWebView) -> Bool {\n            return false\n        }\n\n        nonisolated func close() {\n            DispatchQueue.main.async {\n                self.parent.onDismiss()\n            }\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWindowFactory.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(watchOS)\n\n#if canImport(AppKit) && !targetEnvironment(macCatalyst)\npublic import AppKit\n#endif\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n/// A singleton factory class to create and configure UIWindow objects.\n/// This allows apps to override behaviors like dark mode and other window-specific settings.\n@MainActor\npublic final class AirshipWindowFactory: Sendable {\n\n    /// The shared instance of `AirshipWindowFactory` for centralized window creation.\n    public static let shared = AirshipWindowFactory()\n\n    private init() {}\n\n#if os(macOS) && !targetEnvironment(macCatalyst)\n    /// A closure that allows apps to customize window creation.\n    public var makeBlock: (@MainActor @Sendable () -> NSWindow)?\n\n    /// Creates a new NSWindow.\n    public func makeWindow() -> NSWindow {\n        return makeBlock?() ?? NSWindow(\n            contentRect: .zero,\n            styleMask: [.titled, .closable, .resizable, .miniaturizable],\n            backing: .buffered,\n            defer: false\n        )\n    }\n#else\n    /// A closure that allows apps to customize window creation.\n    public var makeBlock: (@MainActor @Sendable (UIWindowScene) -> UIWindow)?\n\n    /// Creates a new UIWindow for the given window scene.\n    ///\n    /// - Parameter windowScene: The `UIWindowScene` in which the new window will be created.\n    public func makeWindow(windowScene: UIWindowScene) -> UIWindow {\n        return makeBlock?(windowScene) ?? UIWindow(windowScene: windowScene)\n    }\n#endif\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWorkManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n/// Manages work for the Airship SDK\nfinal class AirshipWorkManager: AirshipWorkManagerProtocol, Sendable {\n\n    private let rateLimiter: WorkRateLimiter = WorkRateLimiter()\n    private let conditionsMonitor: WorkConditionsMonitor = WorkConditionsMonitor()\n    private let backgroundTasks: WorkBackgroundTasks = WorkBackgroundTasks()\n    private let workers: Workers = Workers()\n    private let startTask: Task<Void, Never>\n    private let backgroundWaitTask: AirshipMainActorValue<(any AirshipCancellable)?> = AirshipMainActorValue(nil)\n    private let queue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n    private let backgroundWorkRequests: AirshipMainActorValue<[AirshipWorkRequest]> = AirshipMainActorValue([])\n\n    /// Shared instance\n    static let shared: AirshipWorkManager = AirshipWorkManager()\n\n    deinit {\n        startTask.cancel()\n    }\n\n    init() {\n        self.startTask = Task { [workers = self.workers] in\n            await workers.run()\n        }\n\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(applicationDidEnterBackground),\n            name: AppStateTracker.didEnterBackgroundNotification,\n            object: nil\n        )\n\n        NotificationCenter.default.addObserver(\n            self,\n            selector: #selector(applicationDidBecomeActive),\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n    }\n\n    @objc\n    @preconcurrency\n    @MainActor\n    private func applicationDidEnterBackground() {\n        backgroundWaitTask.value?.cancel()\n\n        guard Airship.isFlying else {\n            return\n        }\n\n        let cancellable: CancellableValueHolder<Task<Void, Never>> = CancellableValueHolder { task in\n            task.cancel()\n        }\n\n        let background = try? self.backgroundTasks.beginTask(\"AirshipWorkManager\") { [cancellable] in\n            cancellable.cancel()\n        }\n\n        let isDynamicBackgroundWaitTimeEnabled = Airship.config.airshipConfig.isDynamicBackgroundWaitTimeEnabled\n        let pending = backgroundWorkRequests.value\n\n        cancellable.value = Task { [workers, pending] in\n            await withTaskCancellationHandler {\n\n                for request in pending {\n                    await workers.dispatchWorkRequest(request)\n                }\n\n                guard isDynamicBackgroundWaitTimeEnabled else {\n                    try? await Task.sleep(nanoseconds: UInt64(5.0 * 1_000_000_000))\n                    background?.cancel()\n                    return\n                }\n\n                let sleep = await workers.calculateBackgroundWaitTime(maxTime: 15.0)\n                try? await Task.sleep(nanoseconds: UInt64(max(sleep, 5.0) * 1_000_000_000))\n                background?.cancel()\n            } onCancel: {\n                background?.cancel()\n            }\n        }\n\n        backgroundWaitTask.set(cancellable)\n    }\n\n    @objc\n    @preconcurrency\n    @MainActor\n    private func applicationDidBecomeActive() {\n        backgroundWaitTask.value?.cancel()\n    }\n\n    public func registerWorker(\n        _ workID: String,\n        workHandler: @Sendable @escaping (AirshipWorkRequest) async throws ->\n            AirshipWorkResult\n    ) {\n        let worker = Worker(\n            workID: workID,\n            conditionsMonitor: conditionsMonitor,\n            rateLimiter: rateLimiter,\n            backgroundTasks: backgroundTasks,\n            workHandler: workHandler\n        )\n\n        queue.enqueue { [workers = self.workers] in\n            await workers.addWorker(worker)\n        }\n    }\n\n    public func setRateLimit(\n        _ rateLimitID: String,\n        rate: Int,\n        timeInterval: TimeInterval\n    ) {\n        Task { [rateLimiter = self.rateLimiter] in\n            try? await rateLimiter.set(\n                rateLimitID,\n                rate: rate,\n                timeInterval: timeInterval\n            )\n        }\n    }\n\n    public func dispatchWorkRequest(_ request: AirshipWorkRequest) {\n        queue.enqueue { [workers = self.workers] in\n            await workers.dispatchWorkRequest(request)\n        }\n    }\n\n    @MainActor\n    func autoDispatchWorkRequestOnBackground(_ request: AirshipWorkRequest) {\n        backgroundWorkRequests.update { $0.append(request) }\n    }\n}\n\nprivate actor Workers {\n    private var workers: [Worker] = []\n    private let workerContinuation: AsyncStream<Worker>.Continuation\n    private let workerStream: AsyncStream<Worker>\n    private var isRunning: Bool = false\n\n    init() {\n        (self.workerStream, self.workerContinuation) = AsyncStream<Worker>.airshipMakeStreamWithContinuation()\n    }\n\n    deinit {\n        workerContinuation.finish()\n    }\n\n    func addWorker(_ worker: Worker) {\n        workers.append(worker)\n        workerContinuation.yield(worker)\n    }\n\n    func calculateBackgroundWaitTime(maxTime: TimeInterval) async -> TimeInterval {\n        let workersToProcess = self.workers\n        guard !workersToProcess.isEmpty else { return 0.0 }\n\n        let maxWaitTime = await withTaskGroup(\n            of: TimeInterval.self,\n            returning: TimeInterval.self\n        ) { group in\n            for worker in workersToProcess {\n                group.addTask {\n                    return await worker.calculateBackgroundWaitTime(maxTime: maxTime)\n                }\n            }\n\n            var currentMax: TimeInterval = 0.0\n            for await result in group {\n                currentMax = max(currentMax, result)\n            }\n            return currentMax\n        }\n\n        return maxWaitTime\n    }\n\n    func dispatchWorkRequest(_ request: AirshipWorkRequest) async {\n        for worker in workers where worker.workID == request.workID {\n            await worker.addWork(request: request)\n        }\n    }\n\n    func run() async {\n        guard !isRunning else { return }\n        isRunning = true\n        defer { isRunning = false }\n        await withTaskGroup(of: Void.self) { group in\n            for await worker in self.workerStream {\n                guard !Task.isCancelled else { break }\n                group.addTask {\n                    await worker.run()\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWorkManagerProtocol.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AirshipWorkManagerProtocol: Sendable {\n    func registerWorker(\n        _ workID: String,\n        workHandler: @Sendable @escaping (AirshipWorkRequest) async throws ->\n            AirshipWorkResult\n    )\n\n    func setRateLimit(\n        _ limitID: String,\n        rate: Int,\n        timeInterval: TimeInterval\n    )\n\n    func dispatchWorkRequest(\n        _ request: AirshipWorkRequest\n    )\n\n    @MainActor\n    func autoDispatchWorkRequestOnBackground(\n        _ request: AirshipWorkRequest\n    )\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWorkRequest.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic enum AirshipWorkRequestConflictPolicy: Sendable {\n    case append\n    case replace\n    case keepIfNotStarted\n}\n\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipWorkRequest: Equatable, Sendable, Hashable {\n    public let workID: String\n    public let extras: [String: String]?\n    public let initialDelay: TimeInterval\n    public let requiresNetwork: Bool\n    public let rateLimitIDs: Set<String>?\n    public let conflictPolicy: AirshipWorkRequestConflictPolicy\n\n    public init(\n        workID: String,\n        extras: [String: String]? = nil,\n        initialDelay: TimeInterval = 0.0,\n        requiresNetwork: Bool = true,\n        rateLimitIDs: Set<String>? = nil,\n        conflictPolicy: AirshipWorkRequestConflictPolicy = .replace\n    ) {\n        self.workID = workID\n        self.extras = extras\n        self.initialDelay = initialDelay\n        self.requiresNetwork = requiresNetwork\n        self.rateLimitIDs = rateLimitIDs\n        self.conflictPolicy = conflictPolicy\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirshipWorkResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic enum AirshipWorkResult: Sendable {\n    case success\n    case failure\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AirsihpTriggerContext.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshipTriggerContext: Codable, Sendable, Equatable {\n    let type: String\n    let goal: Double\n    let event: AirshipJSON\n\n    public init(type: String, goal: Double, event: AirshipJSON) {\n        self.type = type\n        self.goal = goal\n        self.event = event\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AnonContactData.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct AnonContactData: Codable, Sendable {\n\n    struct Channel: Codable, Sendable, Equatable, Hashable {\n        let channelType: ChannelType\n        let channelID: String\n    }\n\n    var tags: [String: [String]]\n    var attributes: [String: AirshipJSON]\n    var channels: [Channel]\n    var subscriptionLists: [String: [ChannelScope]]\n\n    var isEmpty: Bool {\n        return self.attributes.isEmpty &&\n        self.tags.isEmpty &&\n        self.channels.isEmpty &&\n        self.subscriptionLists.isEmpty\n    }\n\n    init(tags: [String : [String]], attributes: [String : AirshipJSON], channels: [Channel], subscriptionLists: [String : [ChannelScope]]) {\n        self.tags = tags\n        self.attributes = attributes\n        self.channels = channels\n        self.subscriptionLists = subscriptionLists\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.tags = try container.decode([String : [String]].self, forKey: .tags)\n        self.channels = try container.decode([Channel].self, forKey: .channels)\n        self.subscriptionLists = try container.decode([String : [ChannelScope]].self, forKey: .subscriptionLists)\n\n        do {\n            self.attributes = try container.decode([String : AirshipJSON].self, forKey: .attributes)\n        } catch {\n            let legacy = try? container.decode([String : JsonValue].self, forKey: .attributes)\n            guard let legacy = legacy else {\n                throw error\n            }\n\n            if let decoder = decoder as? JSONDecoder {\n                self.attributes = try legacy.mapValues {\n                    try AirshipJSON.from(\n                        json: $0.jsonEncodedValue,\n                        decoder: decoder\n                    )\n                }\n            } else {\n                self.attributes = try legacy.mapValues {\n                    try AirshipJSON.from(\n                        json: $0.jsonEncodedValue\n                    )\n                }\n            }\n        }\n    }\n\n    // Migration purposes\n    fileprivate struct JsonValue : Decodable {\n        let jsonEncodedValue: String?\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AppIntegration.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(WatchKit)\npublic import WatchKit\n#endif\n\n#if canImport(AppKit)\npublic import AppKit\n#endif\n\nimport Foundation\n@preconcurrency\npublic import UserNotifications\n\n\n/// Application hooks required by Airship. If `automaticSetupEnabled` is enabled\n/// (enabled by default), Airship will automatically integrate these calls into\n/// the application by swizzling methods. If `automaticSetupEnabled` is disabled,\n/// the application must call through to every method provided by this class.\npublic class AppIntegration {\n\n    /// - Note: For internal use only. :nodoc:\n    @MainActor\n    static var integrationDelegate: (any AppIntegrationDelegate)?\n\n    private class func logIgnoringCall(_ method: String = #function) {\n        AirshipLogger.impError(\n            \"Ignoring call to \\(method). Either takeOff is not called or automatic integration is enabled.\"\n        )\n    }\n\n#if os(watchOS)\n    /**\n     * Must be called by the WKExtensionDelegate's\n     * didRegisterForRemoteNotificationsWithDeviceToken:.\n     *\n     * - Parameters:\n     *   - deviceToken: The device token.\n     */\n    @MainActor\n    public class func didRegisterForRemoteNotificationsWithDeviceToken(\n        deviceToken: Data\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        delegate.didRegisterForRemoteNotifications(deviceToken: deviceToken)\n    }\n\n    /**\n     * Must be called by the WKExtensionDelegate's\n     * didFailToRegisterForRemoteNotificationsWithError:.\n     *\n     * - Parameters:\n     *   - error: The error.\n     */\n    @MainActor\n    public class func didFailToRegisterForRemoteNotificationsWithError(\n        error: any Error\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        delegate.didFailToRegisterForRemoteNotifications(error: error)\n    }\n    /**\n     * Must be called by the WKExtensionDelegate's\n     * didReceiveRemoteNotification:fetchCompletionHandler:.\n     *\n     * - Parameters:\n     *   - userInfo: The remote notification.\n     *   - completionHandler: The completion handler.\n     */\n    @MainActor\n    public class func didReceiveRemoteNotification(\n        userInfo: [AnyHashable: Any]\n    ) async -> WKBackgroundFetchResult {\n\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return .noData\n        }\n\n        let isForeground = WKApplication.shared().applicationState == .active\n        \n        return await withCheckedContinuation { continuation in\n            delegate.didReceiveRemoteNotification(\n                userInfo: userInfo,\n                isForeground: isForeground\n            ) { result in\n                let watchResult = WKBackgroundFetchResult(rawValue: result.rawValue) ?? .noData\n                continuation.resume(returning: watchResult)\n            }\n        }\n    }\n\n#elseif os(macOS)\n\n    /**\n     * Must be called by the NSApplicationDelegate's\n     * application:didRegisterForRemoteNotificationsWithDeviceToken:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - deviceToken: The device token.\n     */\n    @MainActor\n    public class func application(\n        _ application: NSApplication,\n        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        delegate.didRegisterForRemoteNotifications(deviceToken: deviceToken)\n    }\n\n    /**\n     * Must be called by the NSApplicationDelegate's\n     * application:didFailToRegisterForRemoteNotificationsWithError:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - error: The error.\n     */\n    @MainActor\n    public class func application(\n        _ application: NSApplication,\n        didFailToRegisterForRemoteNotificationsWithError error: any Error\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        delegate.didFailToRegisterForRemoteNotifications(error: error)\n    }\n\n    /**\n     * Must be called by the NSApplicationDelegate's\n     * application:didReceiveRemoteNotification:.\n     *\n     * - Parameters:\n     * - userInfo: The remote notification.\n     */\n    @MainActor\n    public class func didReceiveRemoteNotification(\n        userInfo: [AnyHashable: Any]\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        let isForeground = NSApplication.shared.isActive\n        delegate.didReceiveRemoteNotification(\n            userInfo: userInfo,\n            isForeground: isForeground\n        )\n    }\n#else\n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:performFetchWithCompletionHandler:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - completionHandler: The completion handler.\n     */\n    @MainActor\n    public class func application(\n        _ application: UIApplication,\n        performFetchWithCompletionHandler completionHandler: @escaping (\n            UIBackgroundFetchResult\n        ) -> Void\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            completionHandler(.noData)\n            return\n        }\n\n        delegate.onBackgroundAppRefresh()\n        completionHandler(.noData)\n    }\n\n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:didRegisterForRemoteNotificationsWithDeviceToken:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - deviceToken: The device token.\n     */\n    @MainActor\n    public class func application(\n        _ application: UIApplication,\n        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        delegate.didRegisterForRemoteNotifications(deviceToken: deviceToken)\n    }\n\n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:didFailToRegisterForRemoteNotificationsWithError:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - error: The error.\n     */\n    @MainActor\n    public class func application(\n        _ application: UIApplication,\n        didFailToRegisterForRemoteNotificationsWithError error: any Error\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return\n        }\n\n        delegate.didFailToRegisterForRemoteNotifications(error: error)\n    }\n\n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:didReceiveRemoteNotification:fetchCompletionHandler:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - userInfo: The remote notification.\n     *   - completionHandler: The completion handler.\n     */\n    @MainActor\n    public class func application(\n        _ application: UIApplication,\n        didReceiveRemoteNotification userInfo: [AnyHashable: Any]\n    ) async -> UIBackgroundFetchResult {\n\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return .noData\n        }\n\n        let isForeground = application.applicationState == .active\n\n        // Wrap the completion-handler-based delegate call in a continuation\n        return await withCheckedContinuation { continuation in\n            delegate.didReceiveRemoteNotification(\n                userInfo: userInfo,\n                isForeground: isForeground\n            ) { result in\n                continuation.resume(returning: result)\n            }\n        }\n    }\n#endif\n\n    /**\n     * Must be called by the UNUserNotificationDelegate's\n     * userNotificationCenter:willPresentNotification:withCompletionHandler.\n     *\n     * - Parameters:\n     *   - center: The notification center.\n     *   - notification: The notification.\n     */\n    @MainActor\n    public class func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        willPresent notification: UNNotification\n    ) async -> UNNotificationPresentationOptions {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            return []\n        }\n\n        let presentationOptions = await withCheckedContinuation { continuation in\n            delegate.presentationOptions(for: notification) { options in\n                continuation.resume(returning: options)\n            }\n        }\n\n        await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in\n            delegate.willPresentNotification(\n                notification: notification,\n                presentationOptions: presentationOptions\n            ) {\n                continuation.resume()\n            }\n        }\n\n        return presentationOptions\n    }\n\n    /**\n     * Must be called by the UNUserNotificationDelegate's\n     * userNotificationCenter:willPresentNotification:withCompletionHandler.\n     *\n     * - Parameters:\n     *   - center: The notification center.\n     *   - notification: The notification.\n     *   - completionHandler: The completion handler.\n     */\n    @MainActor\n    public class func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        willPresent notification: UNNotification,\n        withCompletionHandler completionHandler: @escaping @Sendable (UNNotificationPresentationOptions) -> Void\n    ) {\n        // This one does not depend on any lifecycle events so we can just do the async underneath\n        Task { @MainActor in\n            let options = await self.userNotificationCenter(center, willPresent: notification)\n            completionHandler(options)\n        }\n    }\n\n\n#if !os(tvOS)\n    /**\n     * Processes a user's response to a notification.\n     *\n     * - Warning: ⚠️ **Deprecated**. This asynchronous method is deprecated and will be removed in a future release.\n     * It can cause critical application lifecycle issues due to changes in how Apple's modern User Notification\n     * delegates operate.\n     *\n     * ### Lifecycle Issues Explained\n     *\n     * Apple's modern `async` notification delegate methods execute on a **background thread** by default instead of a the main\n     * thread. This creates a race condition during app launch:\n     *\n     * 1.  **Main Thread:** Proceeds with the standard launch sequence, making the app's UI active and visible.\n     * 2.  **Background Thread:** Runs this notification code. By the time it can switch back to the main\n     * thread, the app is often already active.\n     *\n     * This breaks the critical assumption that code for a \"direct open\" notification runs *before* the app is fully\n     * interactive. This can lead to incorrect direct open counts.\n     *\n     * ### Migration\n     *\n     * To fix this, you must migrate to the synchronous version of this method, which accepts and forwards a `completionHandler`.\n     * This guarantees your code runs on the main thread at the correct point in the lifecycle, before the app becomes active.\n     *\n     * - SeeAlso: `userNotificationCenter(_:didReceive:withCompletionHandler:)`\n\n     * - Parameters:\n     * - center: The notification center that delivered the notification.\n     * - response: The user's response to the notification.\n     */\n    @available(*, deprecated, message: \"Use the synchronous version with a completionHandler to avoid lifecycle issues.\")\n    @MainActor\n    public class func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        didReceive response: UNNotificationResponse\n    ) async {\n        await withCheckedContinuation { (continuation: CheckedContinuation<Void, Never>) in\n            self.userNotificationCenter(\n                center,\n                didReceive: response\n            ) {\n                continuation.resume()\n            }\n        }\n    }\n\n    /**\n     * Must be called by the UNUserNotificationDelegate's\n     * userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler.\n     *\n     * - Parameters:\n     *   - center: The notification center.\n     *   - response: The notification response.\n     *   - completionHandler: The completion handler\n     */\n    @MainActor\n    public class func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        didReceive response: UNNotificationResponse,\n        withCompletionHandler completionHandler: @escaping @Sendable () -> Void\n    ) {\n        guard let delegate = integrationDelegate else {\n            logIgnoringCall()\n            completionHandler()\n            return\n        }\n\n        delegate.didReceiveNotificationResponse(\n            response: response,\n            completionHandler: completionHandler\n        )\n    }\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AppRemoteDataProviderDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct AppRemoteDataProviderDelegate: RemoteDataProviderDelegate {\n    let source: RemoteDataSource = .app\n    let storeName: String\n\n    private let config: RuntimeConfig\n    private let apiClient: any RemoteDataAPIClientProtocol\n\n\n    init(config: RuntimeConfig, apiClient: any RemoteDataAPIClientProtocol) {\n        self.config = config\n        self.apiClient = apiClient\n        self.storeName = \"RemoteData-\\(config.appCredentials.appKey).sqlite\"\n    }\n\n    private func makeURL(locale: Locale, randomValue: Int) throws -> URL {\n        return try RemoteDataURLFactory.makeURL(\n            config: config,\n            path: \"/api/remote-data/app/\\(config.appCredentials.appKey)/ios\",\n            locale: locale,\n            randomValue: randomValue\n        )\n    }\n\n    func isRemoteDataInfoUpToDate(_  remoteDataInfo: RemoteDataInfo, locale: Locale, randomValue: Int) async -> Bool {\n        let url = try? makeURL(locale: locale, randomValue: randomValue)\n        return remoteDataInfo.url == url\n    }\n\n    func fetchRemoteData(\n        locale: Locale,\n        randomValue: Int,\n        lastRemoteDataInfo: RemoteDataInfo?\n    ) async throws -> AirshipHTTPResponse<RemoteDataResult> {\n        let url = try makeURL(locale: locale, randomValue: randomValue)\n\n        var lastModified: String? = nil\n        if (lastRemoteDataInfo?.url == url) {\n            lastModified = lastRemoteDataInfo?.lastModifiedTime\n        }\n\n        return try await self.apiClient.fetchRemoteData(\n            url: url,\n            auth: .generatedAppToken,\n            lastModified: lastModified\n        ) { newLastModified in\n            return RemoteDataInfo(\n                url: url,\n                lastModifiedTime: newLastModified,\n                source: .app\n            )\n        }\n    }\n}\n\n\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AppStateTracker.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\nimport Combine\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AppStateTrackerProtocol: Sendable {\n    /**\n     * Current application state.\n     */\n    @MainActor\n    var state: ApplicationState { get }\n\n    /**\n     * Waits for active\n     */\n    func waitForActive() async\n    \n    /**\n     * State updates\n     */\n    @MainActor\n    var stateUpdates: AsyncStream<ApplicationState> { get }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic final class AppStateTracker: AppStateTrackerProtocol, Sendable {\n\n\n    public static let didBecomeActiveNotification: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_did_become_active\"\n    )\n\n    public static let willEnterForegroundNotification: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_will_enter_foreground\"\n    )\n\n    public static let didEnterBackgroundNotification: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_did_enter_background\"\n    )\n\n    public static let willResignActiveNotification: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_will_resign_active\"\n    )\n\n    public static let willTerminateNotification: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_will_terminate\"\n    )\n\n    public static let didTransitionToBackground: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_did_transition_to_background\"\n    )\n\n    public static let didTransitionToForeground: NSNotification.Name = NSNotification.Name(\n        \"com.urbanairship.application_did_transition_to_foreground\"\n    )\n\n    @MainActor\n    public static let shared: AppStateTracker = AppStateTracker()\n\n    private let notificationCenter: NotificationCenter\n    private let adapter: any AppStateTrackerAdapter\n    private let stateValue: AirshipMainActorValue<ApplicationState>\n\n    @MainActor\n    private var _isForegrounded: Bool? = nil\n\n    @MainActor\n    public var isForegrounded: Bool {\n        ensureForegroundSet()\n        return _isForegrounded == true\n    }\n\n    @MainActor\n    public var state: ApplicationState {\n        return stateValue.value\n    }\n\n    @MainActor\n    init(\n        adapter: any AppStateTrackerAdapter = DefaultAppStateTrackerAdapter(),\n        notificationCenter: NotificationCenter = NotificationCenter.default\n    ) {\n        self.adapter = adapter\n        self.notificationCenter = notificationCenter\n        self.stateValue = AirshipMainActorValue(adapter.state)\n\n        Task { @MainActor in\n            self.ensureForegroundSet()\n        }\n        \n        self.adapter.watchAppLifeCycleEvents { event in\n            self.ensureForegroundSet()\n\n            if (self.stateValue.value != adapter.state) {\n                self.stateValue.set(adapter.state)\n            }\n\n            switch(event) {\n            case .didBecomeActive:\n                self.postNotificaition(name: AppStateTracker.didBecomeActiveNotification)\n                if self._isForegrounded == false {\n                    self._isForegrounded = true\n                    self.postNotificaition(name: AppStateTracker.didTransitionToForeground)\n                }\n                \n            case .willResignActive:\n                self.postNotificaition(name: AppStateTracker.willResignActiveNotification)\n                \n            case .willEnterForeground:\n                self.postNotificaition(name: AppStateTracker.willEnterForegroundNotification)\n                \n            case .didEnterBackground:\n                self.postNotificaition(name: AppStateTracker.didEnterBackgroundNotification)\n                if self._isForegrounded == true {\n                    self._isForegrounded = false\n                    self.postNotificaition(name: AppStateTracker.didTransitionToBackground)\n                }\n                \n            case .willTerminate:\n                self.postNotificaition(name: AppStateTracker.willTerminateNotification)\n            }\n        }\n    }\n    \n    private func postNotificaition(name: Notification.Name) {\n        notificationCenter.post(\n            name: name,\n            object: nil\n        )\n    }\n    \n    @MainActor\n    private func ensureForegroundSet() {\n        if _isForegrounded == nil {\n            _isForegrounded = self.state == .active\n        }\n    }\n\n    @MainActor\n    public func waitForActive() async {\n        var subscription: AnyCancellable?\n        await withCheckedContinuation { continuation in\n            subscription = self.notificationCenter.publisher(\n                for: AppStateTracker.didBecomeActiveNotification\n            )\n            .first()\n            .sink { _ in\n                continuation.resume()\n            }\n        }\n\n        subscription?.cancel()\n    }\n\n    @MainActor\n    public var stateUpdates: AsyncStream<ApplicationState> {\n        self.stateValue.updates\n    }\n\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AppStateTrackerAdapter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol AppStateTrackerAdapter: Sendable {\n    @MainActor\n    var state: ApplicationState { get }\n\n    @MainActor\n    func watchAppLifeCycleEvents(\n        eventHandler: @MainActor @Sendable @escaping (AppLifeCycleEvent) -> Void\n    )\n}\n\nenum AppLifeCycleEvent: Sendable {\n    case didBecomeActive\n    case willResignActive\n    case willEnterForeground\n    case didEnterBackground\n    case willTerminate\n}\n\n\n#if os(watchOS)\nimport WatchKit\n\nfinal class DefaultAppStateTrackerAdapter: AppStateTrackerAdapter {\n    var state: ApplicationState {\n        let appState = WKExtension.shared().applicationState\n        switch appState {\n        case .active:\n            return .active\n        case .inactive:\n            return .inactive\n        case .background:\n            return .background\n        @unknown default:\n            AirshipLogger.error(\"Unknown application state \\(appState)\")\n            return .background\n        }\n    }\n    \n    func watchAppLifeCycleEvents(\n        eventHandler: @MainActor @escaping (AppLifeCycleEvent) -> Void\n    ) {\n        let notificationCenter = NotificationCenter.default\n        \n        // active\n        notificationCenter.addObserver(\n            forName: WKExtension.applicationDidBecomeActiveNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.didBecomeActive)\n                }\n            }\n        )\n\n        // inactive\n        notificationCenter.addObserver(\n            forName: WKExtension.applicationWillResignActiveNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willResignActive)\n                }\n            }\n        )\n\n        // foreground\n        notificationCenter.addObserver(\n            forName: WKExtension.applicationWillEnterForegroundNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willEnterForeground)\n                }\n            }\n        )\n        \n        // background\n        notificationCenter.addObserver(\n            forName: WKExtension.applicationDidEnterBackgroundNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.didEnterBackground)\n                }\n            }\n        )\n    }\n}\n#endif\n\n#if !os(macOS) && !os(watchOS)\nimport UIKit\n\nfinal class DefaultAppStateTrackerAdapter: AppStateTrackerAdapter {\n    var state: ApplicationState {\n        let appState = UIApplication.shared.applicationState\n        switch appState {\n        case .active:\n            return .active\n        case .inactive:\n            return .inactive\n        case .background:\n            return .background\n        @unknown default:\n            AirshipLogger.error(\"Unknown application state \\(appState)\")\n            return .background\n        }\n    }\n    \n    func watchAppLifeCycleEvents(\n        eventHandler: @MainActor @Sendable @escaping (AppLifeCycleEvent) -> Void\n    ) {\n        \n        let notificationCenter = NotificationCenter.default\n        \n        // active\n        notificationCenter.addObserver(\n            forName: UIApplication.didBecomeActiveNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.didBecomeActive)\n                }\n            }\n        )\n\n        // inactive\n        notificationCenter.addObserver(\n            forName: UIApplication.willResignActiveNotification,\n            object: nil,\n            queue: nil,\n            using: {  _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willResignActive)\n                }\n            }\n        )\n\n        // foreground\n        notificationCenter.addObserver(\n            forName: UIApplication.willEnterForegroundNotification,\n            object: nil,\n            queue: nil,\n            using: {  _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willEnterForeground)\n                }\n            }\n        )\n        \n        // background\n        notificationCenter.addObserver(\n            forName: UIApplication.didEnterBackgroundNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.didEnterBackground)\n                }\n            }\n        )\n        \n        // terminate\n        notificationCenter.addObserver(\n            forName: UIApplication.willTerminateNotification,\n            object: nil,\n            queue: nil,\n            using: {  _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willTerminate)\n                }\n            }\n        )\n    }\n}\n\n#endif\n\n#if os(macOS)\nimport AppKit\n\nfinal class DefaultAppStateTrackerAdapter: AppStateTrackerAdapter {\n    var state: ApplicationState {\n        // macOS is active if it's the frontmost app and not hidden.\n        if NSApplication.shared.isActive {\n            return .active\n        } else if NSApplication.shared.isHidden {\n            return .background\n        } else {\n            return .inactive\n        }\n    }\n\n    func watchAppLifeCycleEvents(\n        eventHandler: @MainActor @Sendable @escaping (AppLifeCycleEvent) -> Void\n    ) {\n        let notificationCenter = NotificationCenter.default\n\n        // Active\n        notificationCenter.addObserver(\n            forName: NSApplication.didBecomeActiveNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.didBecomeActive)\n                }\n            }\n        )\n\n        // Resign Active (Inactive/Background transition)\n        notificationCenter.addObserver(\n            forName: NSApplication.didResignActiveNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willResignActive)\n                }\n            }\n        )\n\n        // Hide (Maps well to Enter Background)\n        notificationCenter.addObserver(\n            forName: NSApplication.didHideNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.didEnterBackground)\n                }\n            }\n        )\n\n        // Unhide (Maps well to Enter Foreground)\n        notificationCenter.addObserver(\n            forName: NSApplication.willUnhideNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willEnterForeground)\n                }\n            }\n        )\n\n        // Terminate\n        notificationCenter.addObserver(\n            forName: NSApplication.willTerminateNotification,\n            object: nil,\n            queue: nil,\n            using: { _ in\n                MainActor.assumeIsolated {\n                    eventHandler(.willTerminate)\n                }\n            }\n        )\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ApplicationState.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Platform independent representation of application state.\n/// - Note: For internal use only. :nodoc:\npublic enum ApplicationState: Int, Sendable {\n    /// The active state.\n    case active\n    /// The inactive state.\n    case inactive\n    /// The background state.\n    case background\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ArishipCustomViewManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n/// Type alias for the CustomView builder block\npublic typealias AirshipCustomViewBuilder = @MainActor @Sendable (AirshipCustomViewArguments)  -> any View\n\n\n/// Custom view arguments\npublic struct AirshipCustomViewArguments: Sendable {\n    /// The view's name.\n    public var name: String\n\n    /// Optional properties\n    public var properties: AirshipJSON?\n\n    /// Sizing info\n    public var sizeInfo: SizeInfo\n\n    public struct SizeInfo: Sendable {\n        /// If the height is `auto` or not.\n        public var isAutoHeight: Bool\n\n        /// If the width is `auto` or not.\n        public var isAutoWidth: Bool\n    }\n\n}\n\n/// Airship custom view manager for displaying an app view in a Scene based layout.\n@MainActor\npublic final class AirshipCustomViewManager: Sendable {\n    /// Shared instance\n    public static let shared = AirshipCustomViewManager()\n    \n    private var builders: [String: AirshipCustomViewBuilder] = [:]\n\n    /// Builder that is used when a view is requested that does not have a registered builder.\n    /// The default behavior is to return an empty view.\n    public var fallbackBuilder: AirshipCustomViewBuilder = { args in\n        return EmptyView()\n    }\n\n    /// Registers a custom view builder.\n    /// - Parameters:\n    ///     - name: The name of the view\n    ///     - builder: The builder block\n    public func register(name: String, @ViewBuilder builder: @escaping AirshipCustomViewBuilder) {\n        self.builders[name] = builder\n    }\n\n    /// Unregisters a custom view builder.\n    /// - Parameters:\n    ///     - name: The name of the view\n    public func unregister(name: String) {\n        self.builders.removeValue(forKey: name)\n    }\n\n    @ViewBuilder\n    internal func makeView(args: AirshipCustomViewArguments) -> some View {\n        if let block = builders[args.name] {\n            AnyView(block(args))\n        } else {\n            makeFallbackView(args: args)\n        }\n    }\n\n    private func makeFallbackView(args: AirshipCustomViewArguments) -> some View {\n        AirshipLogger.error(\"Failed to make custom view for name '\\(args.name)'\")\n        return AnyView(fallbackBuilder(args))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AssociatedIdentifiers.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Defines analytics identifiers to be associated with\n/// the device.\npublic class AssociatedIdentifiers {\n\n    /**\n     * Maximum number of associated IDs that can be set.\n     */\n    public static let maxCount: Int = 100\n\n    /**\n     * Character limit for associated IDs or keys.\n     */\n    public static let maxCharacterCount: Int = 255\n\n    /**\n     * The advertising ID.\n     */\n    public var advertisingID: String? {\n        get {\n            return self.identifiers[\"com.urbanairship.idfa\"]\n        }\n        set {\n            self.identifiers[\"com.urbanairship.idfa\"] = newValue\n        }\n    }\n\n    /**\n     * The application's vendor ID.\n     */\n    public var vendorID: String? {\n        get {\n            return self.identifiers[\"com.urbanairship.vendor\"]\n        }\n        set {\n            self.identifiers[\"com.urbanairship.vendor\"] = newValue\n        }\n    }\n\n    /**\n     * Indicates whether the user has limited ad tracking.\n     */\n    public var advertisingTrackingEnabled: Bool {\n        get {\n            return\n                self.identifiers[\"com.urbanairship.limited_ad_tracking_enabled\"]\n                == \"false\"\n        }\n        set {\n            self.identifiers[\"com.urbanairship.limited_ad_tracking_enabled\"] =\n                newValue ? \"false\" : \"true\"\n        }\n    }\n\n    /**\n     * A map of all the associated identifiers.\n     */\n    public var allIDs: [String: String] {\n        return identifiers\n    }\n\n    private var identifiers: [String: String]\n\n    public init(identifiers: [String: String]? = nil) {\n        self.identifiers = identifiers ?? [:]\n    }\n\n    /**\n     * Factory method to create an empty identifiers object.\n     * - Returns: The created associated identifiers.\n     */\n    public class func identifiers() -> AssociatedIdentifiers {\n        return AssociatedIdentifiers()\n    }\n\n    /**\n     * Factory method to create an associated identifiers instance with a dictionary\n     * of custom identifiers (containing strings only).\n     * - Returns: The created associated identifiers.\n     */\n    public class func identifiers(identifiers: [String: String]?)\n        -> AssociatedIdentifiers\n    {\n        return AssociatedIdentifiers(identifiers: identifiers)\n    }\n\n    /**\n     * Sets an identifier mapping.\n     * - Parameter identifier: The value of the identifier, or `nil` to remove the identifier.\n     * @parm key The key for the identifier\n     */\n    public func set(identifier: String?, key: String) {\n        self.identifiers[key] = identifier\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AsyncSerialQueue.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// A class that will run blocks in a FIFO order\n/// NOTE: For internal use only. :nodoc:\npublic final class AirshipAsyncSerialQueue : Sendable {\n    private let continuation: AsyncStream<@Sendable () async -> Void>.Continuation\n    private let stream: AsyncStream<@Sendable () async -> Void>\n\n    public init(priority: TaskPriority? = nil) {\n        (\n            self.stream,\n            self.continuation\n        ) = AsyncStream<@Sendable () async -> Void>.airshipMakeStreamWithContinuation()\n\n        Task.detached(priority: priority) { [stream] in\n            for await next in stream {\n                await next()\n            }\n        }\n    }\n\n    deinit {\n        self.stop()\n    }\n\n    public func enqueue(work: @Sendable @escaping () async -> Void) {\n        self.continuation.yield(work)\n    }\n\n    public func stop() {\n        self.continuation.finish()\n    }\n\n    // Waits for all the current operations to be cleared\n    public func waitForCurrentOperations() async {\n        await withCheckedContinuation{ continuation in\n            self.enqueue {\n                continuation.resume()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AsyncStream.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\npublic extension AsyncStream {\n    static func airshipMakeStreamWithContinuation(\n        _ type: Element.Type = Element.self\n    ) -> (Self, AsyncStream.Continuation) {\n        var escapee: Continuation!\n        let stream = Self(type) { continuation in\n            escapee = continuation\n        }\n        return (stream, escapee!)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Atomic.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipAtomicValue<T: Sendable>: @unchecked Sendable {\n\n    fileprivate let lock: AirshipLock = AirshipLock()\n    fileprivate var _value: T\n\n    public init(_ value: T) {\n        self._value = value\n    }\n\n    public var value: T {\n        get {\n            var result: T!\n            lock.sync {\n                result = self._value\n            }\n            return result\n        }\n\n        set {\n            lock.sync {\n                self._value = newValue\n            }\n        }\n    }\n\n    public func update(onModify: (T) -> T) {\n        lock.sync {\n            self._value = onModify(self._value)\n        }\n    }\n}\n\npublic extension AirshipAtomicValue where T: Equatable {\n\n    @discardableResult\n    func setValue(_ value: T, onChange:(() -> Void)? = nil) -> Bool {\n        return lock.sync {\n            guard self._value != value else { return false }\n            self._value = value\n            onChange?()\n            return true\n        }\n    }\n\n\n    @discardableResult\n    func compareAndSet(expected: T, value: T) -> Bool {\n        return lock.sync {\n            guard self._value == expected else { return false }\n            self._value = value\n            return true\n        }\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AttributePendingMutations.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n// Legacy attribute mutation. Used for migration to AttributeUpdates.\n@objc(UAAttributePendingMutations)\nclass AttributePendingMutations: NSObject, NSSecureCoding {\n    static let codableKey: String = \"com.urbanairship.attributes\"\n\n    public static let supportsSecureCoding: Bool = true\n    private let mutationsPayload: [[AnyHashable: Any]]\n\n    init(mutationsPayload: [[AnyHashable: Any]]) {\n        self.mutationsPayload = mutationsPayload\n        super.init()\n    }\n\n    public var attributeUpdates: [AttributeUpdate] {\n\n        return self.mutationsPayload.compactMap { update -> (AttributeUpdate?) in\n            guard let attribute = update[\"key\"] as? String,\n                let action = update[\"action\"] as? String,\n                let dateString = update[\"timestamp\"] as? String\n            else {\n                AirshipLogger.error(\"Invalid pending attribute \\(update)\")\n                return nil\n            }\n\n            guard let date = AirshipDateFormatter.date(fromISOString:  dateString) else {\n                AirshipLogger.error(\"Unexpected date \\(dateString)\")\n                return nil\n            }\n\n            if action == \"set\" {\n                guard let valueJSON = update[\"value\"],\n                      let value = try? AirshipJSON.wrap(valueJSON)\n                else {\n                    return nil\n                }\n                return AttributeUpdate(\n                    attribute: attribute,\n                    type: .set,\n                    jsonValue: value,\n                    date: date\n                )\n            } else if action == \"remove\" {\n                return AttributeUpdate(\n                    attribute: attribute,\n                    type: .remove,\n                    jsonValue: nil,\n                    date: date\n                )\n            } else {\n                AirshipLogger.error(\"Unexpected action \\(action)\")\n                return nil\n            }\n        }\n    }\n\n    func encode(with coder: NSCoder) {\n        coder.encode(\n            mutationsPayload as NSArray,\n            forKey: AttributePendingMutations.codableKey\n        )\n    }\n\n    required init?(coder: NSCoder) {\n        self.mutationsPayload =\n            coder.decodeObject(\n                of: [\n                    NSNull.self, NSNumber.self, NSArray.self, NSDictionary.self,\n                    NSString.self,\n                ],\n                forKey: AttributePendingMutations.codableKey\n            ) as? [[AnyHashable: Any]] ?? []\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AttributeUpdate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\n\nenum AttributeUpdateType: Int, Codable, Sendable, Equatable {\n    case remove\n    case set\n}\n\n/// NOTE: For internal use only. :nodoc:\nstruct AttributeUpdate: Codable, Sendable, Equatable {\n    let attribute: String\n    let type: AttributeUpdateType\n    let jsonValue: AirshipJSON?\n    let date: Date\n\n    static func remove(\n        attribute: String,\n        date: Date = Date()\n    ) -> AttributeUpdate {\n        return AttributeUpdate(\n            attribute: attribute,\n            type: .remove,\n            jsonValue: nil,\n            date: date\n        )\n    }\n\n    static func set(\n        attribute: String,\n        value: AirshipJSON,\n        date: Date = Date()\n    ) -> AttributeUpdate {\n        return AttributeUpdate(\n            attribute: attribute,\n            type: .set,\n            jsonValue: value,\n            date: date\n        )\n    }\n\n    init(attribute: String, type: AttributeUpdateType, jsonValue: AirshipJSON?, date: Date) {\n        self.attribute = attribute\n        self.type = type\n        self.jsonValue = jsonValue\n        self.date = date\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.attribute = try container.decode(String.self, forKey: .attribute)\n        self.type = try container.decode(AttributeUpdateType.self, forKey: .type)\n        self.date = try container.decode(Date.self, forKey: .date)\n\n\n        do {\n            self.jsonValue = try container.decodeIfPresent(AirshipJSON.self, forKey: .jsonValue)\n        } catch {\n            let legacy = try? container.decodeIfPresent(JsonValue.self, forKey: .jsonValue)\n            guard let legacy = legacy else {\n                throw error\n            }\n\n            if let decoder = decoder as? JSONDecoder {\n                self.jsonValue = try AirshipJSON.from(\n                    json: legacy.jsonEncodedValue,\n                    decoder: decoder\n                )\n            } else {\n                self.jsonValue = try AirshipJSON.from(\n                    json: legacy.jsonEncodedValue\n                )\n            }\n        }\n    }\n\n    // Migration purposes\n    fileprivate struct JsonValue : Decodable {\n        let jsonEncodedValue: String?\n    }\n}\n\n\nextension AttributeUpdate {\n    var operation: AttributeOperation {\n        let timestamp = AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter)\n        switch self.type {\n        case .set:\n            return AttributeOperation(\n                action: .set,\n                key: self.attribute,\n                timestamp: timestamp,\n                value: self.jsonValue\n            )\n        case .remove:\n            return AttributeOperation(\n                action: .remove,\n                key: self.attribute,\n                timestamp: timestamp,\n                value: nil\n            )\n        }\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\n// Used by ChannelBulkAPIClient and DeferredAPIClient\n\nstruct AttributeOperation: Encodable {\n    enum AttributeAction: String, Encodable {\n        case set\n        case remove\n    }\n\n    var action: AttributeAction\n    var key: String\n    var timestamp: String\n    var value: AirshipJSON?\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Attributes.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Predefined attributes.\npublic struct Attributes: Sendable {\n\n    /**\n    * Title attribute.\n    */\n    public static let title: String = \"title\"\n\n    /**\n    * First name attribute.\n    */\n    public static let firstName: String = \"first_name\"\n\n    /**\n    * Last name attribute.\n    */\n    public static let lastName: String = \"last_name\"\n\n    /**\n    * Full name attribute.\n    */\n    public static let fullName: String = \"full_name\"\n\n    /**\n    * Gender attribute.\n    */\n    public static let gender: String = \"gender\"\n\n    /**\n    * Zip code attribute.\n    */\n    public static let zipCode: String = \"zip_code\"\n\n    /**\n    * City attribute.\n    */\n    public static let city: String = \"city\"\n\n    /**\n    * Region attribute.\n    */\n    public static let region: String = \"region\"\n\n    /**\n    * Country attribute.\n    */\n    public static let country: String = \"country\"\n\n    /**\n    * Birthdate attribute.\n    */\n    public static let birthdate: String = \"birthdate\"\n\n    /**\n    * Age attribute.\n    */\n    public static let age: String = \"age\"\n\n    /**\n    * Mobile phone attribute.\n    */\n    public static let mobilePhone: String = \"mobile_phone\"\n\n    /**\n    * Home phone attribute.\n    */\n    public static let homePhone: String = \"home_phone\"\n\n    /**\n    * Work phone attribute.\n    */\n    public static let workPhone: String = \"work_phone\"\n\n    /**\n    * Loyalty tier attribute.\n    */\n    public static let loyaltyTier: String = \"loyalty_tier\"\n\n    /**\n    * Company attribute.\n    */\n    public static let company: String = \"company\"\n\n    /**\n    * Username attribute.\n    */\n    public static let username: String = \"username\"\n\n    /**\n    * Account creation attribute.\n    */\n    public static let accountCreation: String = \"account_creation\"\n\n    /**\n    * Email attribute.\n    */\n    public static let email: String = \"email\"\n\n    /**\n    * Advertising id attribute.\n    */\n    public static let advertisingId: String = \"advertising_id\"\n\n    private init() {}\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AttributesEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Attributes editor.\npublic final class AttributesEditor {\n\n    private let date: any AirshipDateProtocol\n    private var sets: [String: AirshipJSON] = [:]\n    private var removes: [String] = []\n    private let completionHandler: ([AttributeUpdate]) -> Void\n\n    private static let JSON_EXPIRY_KEY: String = \"exp\"\n\n    init(\n        date: any AirshipDateProtocol,\n        completionHandler: @escaping ([AttributeUpdate]) -> Void\n    ) {\n        self.completionHandler = completionHandler\n        self.date = date\n    }\n\n    convenience init(\n        completionHandler: @escaping ([AttributeUpdate]) -> Void\n    ) {\n        self.init(date: AirshipDate(), completionHandler: completionHandler)\n    }\n\n    /**\n     * Removes an attribute.\n     * - Parameters:\n     *   - attribute: The attribute.\n     */\n    public func remove(_ attribute: String) {\n        tryRemoveAttribute(attribute)\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - date: The value\n     *   - attribute: The attribute\n     */\n    public func set(date: Date, attribute: String) {\n        trySetAttribute(\n            attribute,\n            value: .string(\n                AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter)\n            )\n        )\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - number: The value.\n     *   - attribute: The attribute.\n     */\n    @available(*, deprecated, message: \"Use set(number:number) with Double type instead\")\n    public func set(number: NSNumber, attribute: String) {\n        trySetAttribute(attribute, value: .number(number.doubleValue))\n    }\n    \n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - number: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(number: Double, attribute: String) {\n        trySetAttribute(attribute, value: .number(number))\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - number: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(number: Int, attribute: String) {\n        trySetAttribute(attribute, value: .number(Double(number)))\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - number: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(number: UInt, attribute: String) {\n        trySetAttribute(attribute, value: .number(Double(number)))\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - string: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(string: String, attribute: String) {\n        guard string.count >= 1 && string.count <= 1024 else {\n            AirshipLogger.error(\n                \"Invalid attribute value \\(string). Must be between 1-1024 characters.\"\n            )\n            return\n        }\n\n        trySetAttribute(attribute, value: .string(string))\n    }\n\n    /// Sets a custom attribute with a JSON payload and optional expiration.\n    ///\n    /// - Parameters:\n    ///   - json: A dictionary of key-value pairs (`[String: AirshipJSON]`) representing the custom payload.\n    ///   - attribute: The name of the attribute to be set.\n    ///   - instanceID: An identifier used to differentiate instances of the attribute.\n    ///   - expiration: An optional expiration `Date`. If provided, it must be greater than 0 and less than or equal to 731 days from now.\n    ///\n    /// - Throws:\n    ///   - `AirshipErrors.error` if:\n    ///     - The expiration is invalid (more than 731 days or not in the future).\n    ///     - The input `json` is empty.\n    ///     - The JSON contains a top-level `\"exp\"` key (reserved for expiration).\n    ///     - The attribute contains `\"#\"`or is empty\n    ///     - The instanceID contains `\"#\"`or is empty\n    ///\n    public func set(\n        json: [String: AirshipJSON],\n        attribute: String,\n        instanceID: String,\n        expiration: Date? = nil\n    ) throws {\n        if let expiration, expiration.timeIntervalSinceNow > 63158400, expiration.timeIntervalSinceNow <= 0 {\n            throw AirshipErrors.error(\"The expiration is invalid (more than 731 days or not in the future).\")\n        }\n\n        guard json.isEmpty == false else {\n            throw AirshipErrors.error(\"The input `json` is empty.\")\n        }\n\n        guard json[Self.JSON_EXPIRY_KEY] == nil else {\n            throw AirshipErrors.error(\"The JSON contains a top-level `\\(Self.JSON_EXPIRY_KEY)` key (reserved for expiration).\")\n        }\n\n        let json = AirshipJSON.makeObject { builder in\n            json.forEach { key, value in\n                builder.set(json: value, key: key)\n            }\n            if let expiration {\n                builder.set(double: expiration.timeIntervalSince1970, key: Self.JSON_EXPIRY_KEY)\n            }\n        }\n\n        try setAttribute(attribute, instanceID: instanceID, value: json)\n    }\n\n    /// Removes a JSON attribute.\n    ///   - attribute: The name of the attribute to be set.\n    ///   - instanceID: An identifier used to differentiate instances of the attribute.\n    ///\n    /// - Throws:\n    ///   - `AirshipErrors.error` if:\n    ///     - The attribute contains `\"#\"`or is empty\n    ///     - The instanceID contains `\"#\"`or is empty\n    public func remove(attribute: String, instanceID: String) throws {\n        try removeAttribute(attribute, instanceID: instanceID)\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - float: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(float: Float, attribute: String) {\n        trySetAttribute(attribute, value: .number(Double(float)))\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - double: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(double: Double, attribute: String) {\n        trySetAttribute(attribute, value: .number(double))\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - int: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(int: Int, attribute: String) {\n        trySetAttribute(attribute, value: .number(Double(int)))\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - uint: The value.\n     *   - attribute: The attribute.\n     */\n    public func set(uint: UInt, attribute: String) {\n        trySetAttribute(attribute, value: .number(Double(uint)))\n    }\n    /**\n     * Applies the attribute changes.\n     */\n    public func apply() {\n        let removeOperations: [AttributeUpdate] = removes.compactMap {\n            AttributeUpdate.remove(attribute: $0, date: self.date.now)\n        }\n        let setOperations: [AttributeUpdate] = sets.compactMap {\n            AttributeUpdate.set(\n                attribute: $0.key,\n                value: $0.value,\n                date: self.date.now\n            )\n        }\n\n        self.completionHandler(removeOperations + setOperations)\n        removes.removeAll()\n        sets.removeAll()\n    }\n\n    private func setAttribute(_ attribute: String, instanceID: String? = nil, value: AirshipJSON) throws {\n        let key = try formatKey(attribute: attribute, instanceID: instanceID)\n        sets[key] = value\n        removes.removeAll(where: { $0 == key })\n    }\n\n    private func removeAttribute(_ attribute: String, instanceID: String? = nil) throws {\n        let key = try formatKey(attribute: attribute, instanceID: instanceID)\n        sets[key] = nil\n        removes.append(key)\n    }\n\n    private func trySetAttribute(_ attribute: String, instanceID: String? = nil, value: AirshipJSON) {\n        do {\n            try setAttribute(attribute, instanceID: instanceID, value: value)\n        } catch {\n            AirshipLogger.error(\"Failed to update attribute \\(attribute): \\(error)\")\n        }\n    }\n\n    private func tryRemoveAttribute(_ attribute: String, instanceID: String? = nil) {\n        do {\n            try removeAttribute(attribute, instanceID: instanceID)\n        } catch {\n            AirshipLogger.error(\"Failed to remove attribute \\(attribute): \\(error)\")\n        }\n    }\n\n    private func formatKey(attribute: String, instanceID: String? = nil) throws -> String {\n        guard\n            !attribute.isEmpty,\n            !attribute.contains(\"#\")\n        else {\n            throw AirshipErrors.error(\n                \"Invalid attribute \\(attribute). Must not be empty or contain '#'.\"\n            )\n        }\n\n        if let instanceID {\n            guard\n                !instanceID.isEmpty,\n                !instanceID.contains(\"#\")\n            else {\n                throw AirshipErrors.error(\n                    \"Invalid instanceID \\(instanceID). Must not be empty or contain '#'.\"\n                )\n            }\n            return \"\\(attribute)#\\(instanceID)\"\n        } else {\n            return attribute\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AudienceDeviceInfoProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AudienceDeviceInfoProvider: AnyObject, Sendable {\n    var isAirshipReady: Bool { get }\n    var tags: Set<String> { get }\n    var channelID: String { get async throws }\n    var locale:  Locale { get }\n    var appVersion: String? { get }\n    var sdkVersion: String { get }\n    var permissions: [AirshipPermission: AirshipPermissionStatus] { get async }\n    var isUserOptedInPushNotifications: Bool { get async }\n    var analyticsEnabled: Bool { get }\n    var installDate: Date { get }\n    var stableContactInfo: StableContactInfo { get async }\n    var isChannelCreated: Bool { get }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic final class CachingAudienceDeviceInfoProvider: AudienceDeviceInfoProvider, Sendable {\n    private let deviceInfoProvider: any AudienceDeviceInfoProvider\n\n    private let cachedTags: OneTimeValue<Set<String>>\n    private let cachedLocale: OneTimeValue<Locale>\n    private let cachedStableContactInfo: OneTimeAsyncValue<StableContactInfo>\n    private let cachedChannelID: ThrowingOneTimeAsyncValue<String>\n    private let cachedPermissions: OneTimeAsyncValue<[AirshipPermission : AirshipPermissionStatus]>\n    private let cachedIsUserOptedInPushNotifications: OneTimeAsyncValue<Bool>\n    private let cachedAnalyticsEnabled: OneTimeValue<Bool>\n    private let cachedIsChannelCreated: OneTimeValue<Bool>\n\n    public convenience init(contactID: String?) {\n        self.init(deviceInfoProvider: DefaultAudienceDeviceInfoProvider(contactID: contactID))\n    }\n\n    public init(deviceInfoProvider: any AudienceDeviceInfoProvider = DefaultAudienceDeviceInfoProvider()) {\n        self.deviceInfoProvider = deviceInfoProvider\n\n        self.cachedTags = OneTimeValue {\n            return deviceInfoProvider.tags\n        }\n\n        self.cachedLocale = OneTimeValue {\n            return deviceInfoProvider.locale\n        }\n\n        self.cachedStableContactInfo = OneTimeAsyncValue {\n            return await deviceInfoProvider.stableContactInfo\n        }\n\n        self.cachedPermissions = OneTimeAsyncValue {\n            return await deviceInfoProvider.permissions\n        }\n\n        self.cachedIsUserOptedInPushNotifications = OneTimeAsyncValue {\n            return await deviceInfoProvider.isUserOptedInPushNotifications\n        }\n\n        self.cachedAnalyticsEnabled = OneTimeValue {\n            return deviceInfoProvider.analyticsEnabled\n        }\n\n        self.cachedIsChannelCreated = OneTimeValue {\n            return deviceInfoProvider.isChannelCreated\n        }\n\n        self.cachedChannelID = ThrowingOneTimeAsyncValue {\n            return try await deviceInfoProvider.channelID\n        }\n    }\n\n    public var installDate: Date {\n        deviceInfoProvider.installDate\n    }\n\n    public var stableContactInfo: StableContactInfo {\n        get async {\n            return await cachedStableContactInfo.getValue()\n        }\n    }\n\n    public var sdkVersion: String {\n        return deviceInfoProvider.sdkVersion\n    }\n\n    public var appVersion: String? {\n        return deviceInfoProvider.appVersion\n    }\n\n    public var isAirshipReady: Bool {\n        return deviceInfoProvider.isAirshipReady\n    }\n\n    public var tags: Set<String> {\n        return cachedTags.value\n    }\n\n    public var channelID: String {\n        get async throws {\n            return try await cachedChannelID.getValue()\n        }\n    }\n\n\n    public var isChannelCreated: Bool {\n        return cachedIsChannelCreated.value\n    }\n\n\n    public var locale: Locale {\n        return cachedLocale.value\n    }\n\n    public var permissions: [AirshipPermission : AirshipPermissionStatus] {\n        get async {\n            await cachedPermissions.getValue()\n        }\n    }\n\n    public var isUserOptedInPushNotifications: Bool {\n        get async {\n            return await cachedIsUserOptedInPushNotifications.getValue()\n        }\n    }\n\n    public var analyticsEnabled: Bool {\n        return cachedAnalyticsEnabled.value\n    }\n\n}\n\n\n/// NOTE: For internal use only. :nodoc:\npublic final class DefaultAudienceDeviceInfoProvider: AudienceDeviceInfoProvider {\n    \n\n    private let contactID: String?\n    public init(contactID: String? = nil) {\n        self.contactID = contactID\n    }\n\n    public var installDate: Date {\n        Airship.shared.installDate\n    }\n\n    public var sdkVersion: String {\n        AirshipVersion.version\n    }\n\n    public var stableContactInfo: StableContactInfo {\n        get async {\n            let stableInfo = await Airship.requireComponent(\n                ofType: (any InternalAirshipContact).self\n            ).getStableContactInfo()\n\n            if let contactID {\n                if (stableInfo.contactID == contactID) {\n                    return stableInfo\n                }\n                return StableContactInfo(contactID: contactID, namedUserID: nil)\n            }\n            \n            return stableInfo\n        }\n    }\n\n    public var appVersion: String? {\n        return AirshipUtils.bundleShortVersionString()\n    }\n\n    public var isAirshipReady: Bool {\n        return Airship.isFlying\n    }\n\n    public var tags: Set<String> {\n        return Set(Airship.channel.tags)\n    }\n\n    public var isChannelCreated: Bool {\n        return Airship.channel.identifier != nil\n    }\n\n    public var channelID: String {\n        get async {\n            if let channelID = Airship.channel.identifier {\n                return channelID\n            }\n            for await update in Airship.channel.identifierUpdates {\n                return update\n            }\n            return \"\"\n        }\n    }\n\n    public var locale: Locale {\n        return Airship.localeManager.currentLocale\n    }\n\n    public var permissions: [AirshipPermission : AirshipPermissionStatus] {\n        get async {\n            var results: [AirshipPermission : AirshipPermissionStatus] = [:]\n            \n            for permission in Airship.permissionsManager.configuredPermissions {\n                results[permission] = await Airship.permissionsManager.checkPermissionStatus(permission)\n            }\n            \n            return results\n        }\n    }\n\n    public var isUserOptedInPushNotifications: Bool {\n        get async {\n            return await Airship.push.notificationStatus.isUserOptedIn\n        }\n    }\n\n    public var analyticsEnabled: Bool {\n        return Airship.privacyManager.isEnabled(.analytics)\n    }\n}\n\n\nfileprivate final class OneTimeValue<T: Equatable & Sendable>: @unchecked Sendable {\n    private let lock: AirshipLock = AirshipLock()\n    private var atomicValue: AirshipAtomicValue<T?> = AirshipAtomicValue(nil)\n    private let provider: () -> T\n\n    var cachedValue: T? {\n        get {\n            return atomicValue.value\n        }\n    }\n\n    init(provider: @escaping () -> T) {\n        self.provider = provider\n    }\n\n    var value: T {\n        get {\n            lock.sync {\n                if let cachedValue = atomicValue.value {\n                    return cachedValue\n                }\n                \n                let value = provider()\n                atomicValue.value = value\n                return value\n            }\n        }\n    }\n}\n\nfileprivate actor OneTimeAsyncValue<T: Equatable & Sendable> {\n    private let provider: @Sendable () async -> T\n    private var task: Task<T, Never>?\n\n    init(provider: @Sendable @escaping () async -> T) {\n        self.provider = provider\n    }\n\n    func getValue() async -> T {\n        if let task, !task.isCancelled {\n            return await task.value\n        }\n        \n        let newTask = Task {\n            return await provider()\n        }\n        \n        task = newTask\n        return await newTask.value\n    }\n}\n\nfileprivate actor ThrowingOneTimeAsyncValue<T: Equatable & Sendable> {\n    private var provider: @Sendable () async throws -> T\n    private var task: Task<T, any Error>?\n    private var value: T?\n\n    init(provider: @Sendable @escaping () async throws -> T) {\n        self.provider = provider\n    }\n\n    func getValue() async throws -> T {\n        if let task, let value = try? await task.value {\n            return value\n        }\n\n        let newTask = Task {\n            return try await provider()\n        }\n        task = newTask\n        return try await newTask.value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AudienceHashSelector.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AudienceHashSelector: Codable, Sendable, Equatable {\n    let hash: Hash\n    let bucket: Bucket\n    var sticky: Sticky?\n\n    init(hash: Hash, bucket: Bucket, sticky: Sticky? = nil) {\n        self.hash = hash\n        self.bucket = bucket\n        self.sticky = sticky\n    }\n    \n    enum CodingKeys: String, CodingKey {\n        case hash = \"audience_hash\"\n        case bucket = \"audience_subset\"\n        case sticky\n    }\n\n    struct Hash: Codable, Sendable, Equatable {\n        enum Identifier: String, Codable, Equatable {\n            case channel, contact\n        }\n\n        enum Algorithm: String, Codable, Equatable {\n            case farm = \"farm_hash\"\n        }\n\n        let prefix: String\n        let property: Identifier\n        let algorithm: Algorithm\n        let seed: UInt?\n        let numberOfBuckets: UInt64\n        let overrides: [String: String]?\n\n        enum CodingKeys: String, CodingKey {\n            case prefix = \"hash_prefix\"\n            case property = \"hash_identifier\"\n            case algorithm = \"hash_algorithm\"\n            case seed = \"hash_seed\"\n            case numberOfBuckets = \"num_hash_buckets\"\n            case overrides = \"hash_identifier_overrides\"\n        }\n    }\n\n    struct Bucket: Codable, Sendable, Equatable {\n        let min: UInt64\n        let max: UInt64\n\n        enum CodingKeys: String, CodingKey {\n            case min = \"min_hash_bucket\"\n            case max = \"max_hash_bucket\"\n        }\n\n        init(min: UInt64, max: UInt64) {\n            self.min = min\n            self.max = max\n        }\n    \n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.min = try container.decodeIfPresent(UInt64.self, forKey: .min) ?? 0\n            self.max = try container.decodeIfPresent(UInt64.self, forKey: .max) ?? UInt64.max\n        }\n\n        func contains(_ value: UInt64) -> Bool {\n            return value >= min && value <= max\n        }\n    }\n\n    /// Sticky has will cache the result under the `id` for the length of the `lastAccessTTL`.\n    struct Sticky: Codable, Sendable, Equatable {\n        /// The sticky ID.\n        let id: String\n        \n        /// Reporting metadata.\n        let reportingMetadata: AirshipJSON?\n\n        /// Time to cache the result.\n        var lastAccessTTL: TimeInterval\n\n        enum CodingKeys: String, CodingKey {\n            case id\n            case reportingMetadata = \"reporting_metadata\"\n            case lastAccessTTLMilliseconds = \"last_access_ttl\"\n        }\n\n        func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            try container.encode(id, forKey: .id)\n            try container.encode(reportingMetadata, forKey: .reportingMetadata)\n            try container.encode((lastAccessTTL * 1000.0), forKey: .lastAccessTTLMilliseconds)\n        }\n\n        init(id: String, reportingMetadata: AirshipJSON?, lastAccessTTL: TimeInterval) {\n            self.id = id\n            self.reportingMetadata = reportingMetadata\n            self.lastAccessTTL = lastAccessTTL\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            id = try container.decode(String.self, forKey: .id)\n            reportingMetadata = try container.decode(AirshipJSON?.self, forKey: .reportingMetadata)\n            lastAccessTTL = TimeInterval(try container.decode(Double.self, forKey: .lastAccessTTLMilliseconds)/1000.0)\n        }\n    }\n\n    func evaluate(channelID: String, contactID: String) -> Bool {\n        let param = self.hashParameter(channelID: channelID, contactID: contactID)\n        let hash = self.hashFunction(param)\n        let result: UInt64 = hash % self.hash.numberOfBuckets\n        return bucket.contains(result)\n    }\n\n    private func hashParameter(channelID: String, contactID: String) -> String {\n        var property: String!\n        switch(self.hash.property) {\n        case .channel:\n            property = channelID\n        case .contact:\n            property = contactID\n        }\n\n        let resolved: String = self.hash.overrides?[property] ?? property\n        return \"\\(self.hash.prefix)\\(resolved)\"\n    }\n\n    private var hashFunction: (String) -> UInt64 {\n        switch(self.hash.algorithm) {\n        case .farm:\n            return FarmHashFingerprint64.fingerprint\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AudienceOverridesProvider.swift",
    "content": "import Foundation\n\nprotocol AudienceOverridesProvider: Actor {\n    func setStableContactIDProvider(\n        _ provider: @escaping @Sendable () async -> String\n    )\n\n    func setPendingChannelOverridesProvider(\n        _ provider: @escaping @Sendable (String) async -> ChannelAudienceOverrides?\n    )\n\n    func setPendingContactOverridesProvider(\n        _ provider: @escaping @Sendable (String) async -> ContactAudienceOverrides?\n    )\n    \n    func contactUpdated(\n        contactID: String,\n        tags: [TagGroupUpdate]?,\n        attributes: [AttributeUpdate]?,\n        subscriptionLists: [ScopedSubscriptionListUpdate]?,\n        channels: [ContactChannelUpdate]?\n    ) async\n\n    func channelUpdated(\n        channelID: String,\n        tags: [TagGroupUpdate]?,\n        attributes: [AttributeUpdate]?,\n        subscriptionLists: [SubscriptionListUpdate]?\n    ) async\n\n    func channelOverrides(\n        channelID: String,\n        contactID: String?\n    ) async -> ChannelAudienceOverrides\n\n    func channelOverrides(\n        channelID: String\n    ) async -> ChannelAudienceOverrides\n\n    func contactOverrides(\n        contactID: String?\n    ) async -> ContactAudienceOverrides\n\n    func contactOverrides() async -> ContactAudienceOverrides\n\n    func notifyPendingChanged() async\n\n    func contactOverrideUpdates(\n        contactID: String?\n    ) async -> AsyncStream<ContactAudienceOverrides>\n}\n\nactor DefaultAudienceOverridesProvider: AudienceOverridesProvider {\n    \n    private let updates: CachedList<UpdateRecord>\n    private var pendingChannelOverridesProvider: (@Sendable (String) async -> ChannelAudienceOverrides?)? = nil\n    private var pendingContactOverridesProvider: (@Sendable (String) async -> ContactAudienceOverrides?)? = nil\n    private var stableContactIDProvider: (@Sendable () async -> String)? = nil\n    private let overridesUpdates: AirshipAsyncChannel<Bool> = AirshipAsyncChannel()\n\n    private static let maxRecordAge: TimeInterval = 600 // 10 minutes\n\n    init(date: any AirshipDateProtocol = AirshipDate.shared) {\n        self.updates = CachedList(date: date)\n    }\n\n    func setPendingChannelOverridesProvider(\n        _ provider: @escaping @Sendable (String) async -> ChannelAudienceOverrides?\n    ) {\n        self.pendingChannelOverridesProvider = provider\n    }\n\n    func setPendingContactOverridesProvider(\n        _ provider: @escaping @Sendable (String) async -> ContactAudienceOverrides?\n    ) {\n        self.pendingContactOverridesProvider = provider\n    }\n\n    func setStableContactIDProvider(\n        _ provider: @escaping @Sendable () async -> String\n    )  {\n        self.stableContactIDProvider = provider\n    }\n\n    func pendingOverrides(channelID: String) async -> ChannelAudienceOverrides? {\n        return await self.pendingChannelOverridesProvider?(channelID)\n    }\n\n    func pendingOverrides(contactID: String) async -> ContactAudienceOverrides? {\n        return await self.pendingContactOverridesProvider?(contactID)\n    }\n\n    func contactUpdated(\n        contactID: String,\n        tags: [TagGroupUpdate]?,\n        attributes: [AttributeUpdate]?,\n        subscriptionLists: [ScopedSubscriptionListUpdate]?,\n        channels: [ContactChannelUpdate]?\n    ) async {\n        self.updates.append(\n            UpdateRecord(\n                recordType: .contact(contactID),\n                tags: tags,\n                attributes: attributes,\n                subscriptionLists: nil,\n                scopedSubscriptionLists: subscriptionLists,\n                channels: channels\n            ),\n            expiresIn: DefaultAudienceOverridesProvider.maxRecordAge\n        )\n\n        await self.notifyPendingChanged()\n    }\n\n    func channelUpdated(\n        channelID: String,\n        tags: [TagGroupUpdate]?,\n        attributes: [AttributeUpdate]?,\n        subscriptionLists: [SubscriptionListUpdate]?\n    ) async {\n        self.updates.append(\n            UpdateRecord(\n                recordType: .channel(channelID),\n                tags: tags,\n                attributes: attributes,\n                subscriptionLists: subscriptionLists,\n                scopedSubscriptionLists: nil,\n                channels: nil\n            ),\n            expiresIn: DefaultAudienceOverridesProvider.maxRecordAge\n        )\n\n        await self.notifyPendingChanged()\n    }\n\n    func convertAppScopes(scoped: [ScopedSubscriptionListUpdate]) -> [SubscriptionListUpdate] {\n        return scoped.compactMap { update in\n            if (update.scope == .app) {\n                return SubscriptionListUpdate(listId: update.listId, type: update.type)\n            } else {\n                return nil\n            }\n        }\n    }\n\n    func channelOverrides(\n        channelID: String\n    ) async -> ChannelAudienceOverrides {\n        return await channelOverrides(\n            channelID: channelID,\n            contactID: nil\n        )\n    }\n\n    func channelOverrides(\n        channelID: String,\n        contactID: String?\n    ) async -> ChannelAudienceOverrides {\n        let contactID = await resolveContactID(contactID: contactID)\n        let pendingChannel = await self.pendingOverrides(channelID: channelID)\n        var pendingContact: ContactAudienceOverrides?\n        if let contactID = contactID {\n            pendingContact = await self.pendingOverrides(contactID: contactID)\n        }\n\n        var tags: [TagGroupUpdate]  = []\n        var attributes: [AttributeUpdate] = []\n        var subscriptionLists: [SubscriptionListUpdate] = []\n\n\n        /// Apply updates first\n        self.updates.values.forEach { update  in\n            switch (update.recordType) {\n            case .contact(let identifier):\n                if let contactID = contactID, contactID == identifier {\n                    if let updateTags = update.tags {\n                        tags += updateTags\n                    }\n\n                    if let updateAttributes = update.attributes {\n                        attributes += updateAttributes\n                    }\n\n                    if let updateSubscriptionLists = update.subscriptionLists {\n                        subscriptionLists += updateSubscriptionLists\n                    }\n\n                    if let updateScopedSubscriptionLists = update.scopedSubscriptionLists {\n                        subscriptionLists += convertAppScopes(scoped: updateScopedSubscriptionLists)\n                    }\n                }\n            case .channel(let identifier):\n                if channelID == identifier {\n                    if let updateTags = update.tags {\n                        tags += updateTags\n                    }\n\n                    if let updateAttributes = update.attributes {\n                        attributes += updateAttributes\n                    }\n\n                    if let updateSubscriptionLists = update.subscriptionLists {\n                        subscriptionLists += updateSubscriptionLists\n                    }\n                }\n            }\n        }\n\n        // Pending channel\n        if let pendingChannel = pendingChannel {\n            tags += pendingChannel.tags\n            attributes += pendingChannel.attributes\n            subscriptionLists += pendingChannel.subscriptionLists\n        }\n\n        // Pending contact\n        if let pendingContact = pendingContact {\n            tags += pendingContact.tags\n            attributes += pendingContact.attributes\n            subscriptionLists += convertAppScopes(scoped: pendingContact.subscriptionLists)\n        }\n\n        return ChannelAudienceOverrides(\n            tags: tags,\n            attributes: attributes,\n            subscriptionLists: subscriptionLists\n        )\n    }\n\n    func contactOverrides() async -> ContactAudienceOverrides {\n        return await contactOverrides(contactID: nil)\n    }\n\n    func contactOverrides(\n        contactID: String?\n    ) async -> ContactAudienceOverrides {\n        let contactID = await resolveContactID(contactID: contactID)\n        guard let contactID = contactID else {\n            return ContactAudienceOverrides()\n        }\n\n        let pendingContactOverrides = await self.pendingOverrides(contactID: contactID)\n        var tags: [TagGroupUpdate]  = []\n        var attributes: [AttributeUpdate] = []\n        var scopedSubscriptionLists: [ScopedSubscriptionListUpdate] = []\n        var channels: [ContactChannelUpdate] = []\n\n        // Contact updates\n        self.updates.values.forEach { update in\n            if case let .contact(identifier) = update.recordType, identifier == contactID {\n                if let updateTags = update.tags {\n                    tags += updateTags\n                }\n\n                if let updateAttributes = update.attributes {\n                    attributes += updateAttributes\n                }\n\n                if let updateScopedSubscriptionLists = update.scopedSubscriptionLists {\n                    scopedSubscriptionLists += updateScopedSubscriptionLists\n                }\n\n                if let updateChannel = update.channels {\n                    channels += updateChannel\n                }\n            }\n        }\n\n        // Pending contact\n        if let pendingContactOverrides = pendingContactOverrides {\n            tags += pendingContactOverrides.tags\n            attributes += pendingContactOverrides.attributes\n            scopedSubscriptionLists += pendingContactOverrides.subscriptionLists\n            channels += pendingContactOverrides.channels\n        }\n\n        return ContactAudienceOverrides(\n            tags: tags,\n            attributes: attributes,\n            subscriptionLists: scopedSubscriptionLists,\n            channels: channels\n        )\n    }\n\n    func contactOverrideUpdates(\n        contactID: String?\n    ) async -> AsyncStream<ContactAudienceOverrides> {\n        let updates = await self.overridesUpdates.makeStream(\n            bufferPolicy: .bufferingNewest(1)\n        )\n\n        let initial: ContactAudienceOverrides = await self.contactOverrides(contactID: contactID)\n\n        return AsyncStream { [weak self] continuation in\n            continuation.yield(initial)\n\n            let task = Task { [weak self] in\n                for await _ in updates {\n                    let overrides = await self?.contactOverrides(contactID: contactID)\n                    guard !Task.isCancelled, let overrides else {\n                        return\n                    }\n                    continuation.yield(overrides)\n                }\n            }\n\n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n\n\n    func notifyPendingChanged() async {\n        await self.overridesUpdates.send(true)\n    }\n\n    private func resolveContactID(contactID: String?) async -> String? {\n        guard let contactID = contactID else {\n            return await self.stableContactIDProvider?()\n        }\n        return contactID\n    }\n\n    fileprivate struct UpdateRecord {\n        enum RecordType {\n            case channel(String)\n            case contact(String)\n        }\n\n        let recordType: RecordType\n        let tags: [TagGroupUpdate]?\n        let attributes: [AttributeUpdate]?\n        let subscriptionLists: [SubscriptionListUpdate]?\n        let scopedSubscriptionLists: [ScopedSubscriptionListUpdate]?\n        let channels: [ContactChannelUpdate]?\n    }\n\n    fileprivate struct ContactRecord: Sendable {\n        let contactID: String\n        let tags: [TagGroupUpdate]\n        let attributes: [AttributeUpdate]\n        let subscriptionLists: [ScopedSubscriptionListUpdate]\n        let channels: [ContactChannelUpdate]\n    }\n}\n\n\nstruct ContactAudienceOverrides: Sendable {\n    let tags: [TagGroupUpdate]\n    let attributes: [AttributeUpdate]\n    let subscriptionLists: [ScopedSubscriptionListUpdate]\n    let channels: [ContactChannelUpdate]\n\n    init(tags: [TagGroupUpdate] = [], attributes: [AttributeUpdate] = [], subscriptionLists: [ScopedSubscriptionListUpdate] = [], channels: [ContactChannelUpdate] = []) {\n        self.tags = tags\n        self.attributes = attributes\n        self.subscriptionLists = subscriptionLists\n        self.channels = channels\n    }\n}\n\nstruct ChannelAudienceOverrides: Sendable, Equatable {\n    let tags: [TagGroupUpdate]\n    let attributes: [AttributeUpdate]\n    let subscriptionLists: [SubscriptionListUpdate]\n\n    init(tags: [TagGroupUpdate] = [], attributes: [AttributeUpdate] = [], subscriptionLists: [SubscriptionListUpdate] = []) {\n        self.tags = tags\n        self.attributes = attributes\n        self.subscriptionLists = subscriptionLists\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AudienceUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nfinal class AudienceUtils {\n\n    class func collapse(\n        _ updates: [ScopedSubscriptionListUpdate]\n    ) -> [ScopedSubscriptionListUpdate] {\n        var handled = Set<String>()\n        var collapsed: [ScopedSubscriptionListUpdate] = []\n\n        updates.reversed()\n            .forEach { update in\n                let key = \"\\(update.scope.rawValue):\\(update.listId)\"\n                if !handled.contains(key) {\n                    collapsed.append(update)\n                    handled.insert(key)\n                }\n            }\n\n        return collapsed.reversed()\n    }\n\n    class func collapse(\n        _ updates: [SubscriptionListUpdate]\n    ) -> [SubscriptionListUpdate] {\n        var handled = Set<String>()\n        var collapsed: [SubscriptionListUpdate] = []\n\n        updates.reversed()\n            .forEach { update in\n                if !handled.contains(update.listId) {\n                    collapsed.append(update)\n                    handled.insert(update.listId)\n                }\n            }\n\n        return collapsed.reversed()\n    }\n\n    class func collapse(\n        _ updates: [TagGroupUpdate]\n    ) -> [TagGroupUpdate] {\n        var adds: [String: [String]] = [:]\n        var removes: [String: [String]] = [:]\n        var sets: [String: [String]] = [:]\n\n        updates.forEach { update in\n            switch update.type {\n            case .add:\n                if sets[update.group] != nil {\n                    update.tags.forEach { sets[update.group]?.append($0) }\n                } else {\n                    removes[update.group]?\n                        .removeAll(where: { update.tags.contains($0) })\n                    if adds[update.group] == nil {\n                        adds[update.group] = []\n                    }\n                    update.tags.forEach { adds[update.group]?.append($0) }\n                }\n            case .remove:\n                if sets[update.group] != nil {\n                    sets[update.group]?\n                        .removeAll(where: { update.tags.contains($0) })\n                } else {\n                    adds[update.group]?\n                        .removeAll(where: { update.tags.contains($0) })\n                    if removes[update.group] == nil {\n                        removes[update.group] = []\n                    }\n                    update.tags.forEach { removes[update.group]?.append($0) }\n                }\n            case .set:\n                removes[update.group] = nil\n                adds[update.group] = nil\n                sets[update.group] = update.tags\n            }\n        }\n\n        let setUpdates = sets.map {\n            TagGroupUpdate(group: $0.key, tags: Array($0.value), type: .set)\n        }\n        let addUpdates = adds.compactMap {\n            $0.value.isEmpty\n                ? nil\n                : TagGroupUpdate(\n                    group: $0.key,\n                    tags: Array($0.value),\n                    type: .add\n                )\n        }\n        let removeUpdates = removes.compactMap {\n            $0.value.isEmpty\n                ? nil\n                : TagGroupUpdate(\n                    group: $0.key,\n                    tags: Array($0.value),\n                    type: .remove\n                )\n        }\n\n        return setUpdates + addUpdates + removeUpdates\n    }\n\n    class func collapse(\n        _ updates: [AttributeUpdate]\n    ) -> [AttributeUpdate] {\n        var found: [String] = []\n        let latest: [AttributeUpdate] = updates.reversed()\n            .compactMap { update in\n                guard !found.contains(update.attribute) else {\n                    return nil\n                }\n                found.append(update.attribute)\n                return update\n            }\n        return latest.reversed()\n    }\n\n    class func applyTagUpdates(\n        _ tagGroups: [String: [String]]?,\n        updates: [TagGroupUpdate]?\n    ) -> [String: [String]] {\n        var updated = tagGroups ?? [:]\n\n        updates?\n            .forEach { update in\n                switch update.type {\n                case .add:\n                    if updated[update.group] == nil {\n                        updated[update.group] = []\n                    }\n                    updated[update.group]?.append(contentsOf: update.tags)\n                case .remove:\n                    updated[update.group]?\n                        .removeAll(where: { update.tags.contains($0) })\n                case .set:\n                    updated[update.group] = update.tags\n                }\n            }\n\n        return updated.compactMapValues({ $0.isEmpty ? nil : $0 })\n    }\n\n    class func normalizeTags(\n        _ tags: [String]\n    ) -> [String] {\n        var normalized: [String] = []\n\n        for tag in tags {\n            let trimmed = tag.trimmingCharacters(in: .whitespacesAndNewlines)\n            if trimmed.isEmpty || trimmed.count > 128 {\n                AirshipLogger.error(\n                    \"Tag \\(trimmed) must be between 1-128 characters. Ignoring\"\n                )\n                continue\n            }\n\n            if !normalized.contains(trimmed) {\n                normalized.append(trimmed)\n            }\n        }\n\n        return normalized\n    }\n\n    class func normalizeTagGroup(_ group: String) -> String {\n        return group.trimmingCharacters(in: .whitespacesAndNewlines)\n    }\n\n    class func applyAttributeUpdates(\n        _ attributes: [String: AirshipJSON]?,\n        updates: [AttributeUpdate]?\n    ) -> [String: AirshipJSON] {\n        var updated = attributes ?? [:]\n\n        updates?\n            .forEach { update in\n                switch update.type {\n                case .set:\n                    updated[update.attribute] = update.jsonValue\n                case .remove:\n                    updated[update.attribute] = nil\n                }\n            }\n\n        return updated\n    }\n\n\n    class func applySubscriptionListsUpdates(\n        _ subscriptionLists: [String: [ChannelScope]]?,\n        updates: [ScopedSubscriptionListUpdate]?\n    ) -> [String: [ChannelScope]] {\n        var updated = subscriptionLists ?? [:]\n        updates?\n            .forEach { update in\n                var scopes = updated[update.listId] ?? []\n                switch update.type {\n                case .subscribe:\n                    if !scopes.contains(update.scope) {\n                        scopes.append(update.scope)\n                        updated[update.listId] = scopes\n                    }\n                case .unsubscribe:\n                    scopes.removeAll(where: { $0 == update.scope })\n                    updated[update.listId] = scopes.isEmpty ? nil : scopes\n                }\n            }\n\n        return updated\n    }\n\n    class func normalize(_ updates: [LiveActivityUpdate])\n        -> [LiveActivityUpdate]\n    {\n        var timeStamps: Set<Int64> = Set()\n        var normalized: [LiveActivityUpdate] = []\n\n        updates.forEach { update in\n            var timeStamp = update.actionTimeMS\n            while timeStamps.contains(timeStamp) {\n                timeStamp = timeStamp + 1\n            }\n\n            if (timeStamp != update.actionTimeMS) {\n                var mutable = update\n                mutable.actionTimeMS = timeStamp\n                normalized.append(mutable)\n            } else {\n                normalized.append(update)\n            }\n\n            timeStamps.insert(timeStamp)\n        }\n\n        return normalized\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AuthToken.swift",
    "content": "import Foundation\n\nstruct AuthToken {\n    let identifier: String\n    let token: String\n    let expiration: Date\n\n    init(identifier: String, token: String, expiration: Date) {\n        self.identifier = identifier\n        self.token = token\n        self.expiration = expiration\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/AutoIntegration.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n@MainActor\nfinal class AutoIntegration {\n    public static let shared = AutoIntegration()\n\n    private let swizzler: AirshipSwizzler = AirshipSwizzler()\n    private let dummyNotificationDelegate = UAAutoIntegrationDummyDelegate()\n    weak var delegate: (any AppIntegrationDelegate)?\n\n\n\n    public func integrate(with delegate: any AppIntegrationDelegate) {\n        self.delegate = delegate\n\n#if os(watchOS)\n        performWatchIntegration(delegate: delegate)\n#elseif os(macOS)\n        performMacIntegration(delegate: delegate)\n#else\n        performMobileIntegration(delegate: delegate)\n#endif\n        // Notification Center swizzling (Platform independent)\n        swizzleNotificationCenter(delegate: delegate)\n    }\n\n    private func swizzleNotificationCenter(delegate: any AppIntegrationDelegate) {\n        self.swizzler.swizzleNotificationCenterDelegateSetter(\n            delegate: delegate,\n            dummyDelegate: self.dummyNotificationDelegate\n        )\n\n        if let current = UNUserNotificationCenter.current().delegate {\n            self.swizzler.swizzleNotificationCenterDelegate(current, delegate: delegate)\n        } else {\n            UNUserNotificationCenter.current().delegate = dummyNotificationDelegate\n        }\n    }\n\n    // MARK: - Platform Specific Integration Logic\n\n#if os(watchOS)\n    private func performWatchIntegration(delegate: any AppIntegrationDelegate) {\n        // Access via WKApplication (Modern/SwiftUI friendly)\n        guard let appDelegate = WKApplication.shared().delegate else {\n            AirshipLogger.info(\"Watch app delegate not set, deferring until didFinishLaunching.\")\n            NotificationCenter.default.addObserver(\n                forName: WKApplication.didFinishLaunchingNotification,\n                object: nil,\n                queue: .main\n            ) { [weak self] _ in\n                MainActor.assumeIsolated {\n                    self?.performWatchIntegration(delegate: delegate)\n                }\n            }\n            return\n        }\n\n        AirshipLogger.debug(\"Integrating Airship Auto-Integration (watchOS).\")\n        self.swizzler.swizzleWatchDidRegister(appDelegate, delegate: delegate)\n        self.swizzler.swizzleWatchDidFailToRegister(appDelegate, delegate: delegate)\n        self.swizzler.swizzleWatchDidReceiveRemoteNotification(appDelegate, delegate: delegate)\n    }\n\n#elseif os(macOS)\n\n    private func performMacIntegration(delegate: any AppIntegrationDelegate) {\n        guard let appDelegate = NSApplication.shared.delegate else {\n            AirshipLogger.info(\"macOS app delegate not set, deferring until didFinishLaunching.\")\n            NotificationCenter.default.addObserver(\n                forName: NSApplication.didFinishLaunchingNotification,\n                object: nil,\n                queue: .main\n            ) { [weak self] _ in\n                MainActor.assumeIsolated {\n                    self?.performMacIntegration(delegate: delegate)\n                }\n            }\n            return\n        }\n\n        AirshipLogger.debug(\"Integrating Airship Auto-Integration (macOS).\")\n        self.swizzler.swizzleMacDidRegister(appDelegate, delegate: delegate)\n        self.swizzler.swizzleMacDidFailToRegister(appDelegate, delegate: delegate)\n        self.swizzler.swizzleMacDidReceiveRemoteNotification(appDelegate, delegate: delegate)\n    }\n\n#else\n\n    private func performMobileIntegration(delegate: any AppIntegrationDelegate) {\n        guard let appDelegate = UIApplication.shared.delegate else {\n            AirshipLogger.info(\"App delegate not set, deferring until didFinishLaunching.\")\n            NotificationCenter.default.addObserver(\n                forName: UIApplication.didFinishLaunchingNotification,\n                object: nil,\n                queue: .main\n            ) { [weak self] _ in\n                MainActor.assumeIsolated {\n                    self?.performMobileIntegration(delegate: delegate)\n                }\n            }\n            return\n        }\n\n        AirshipLogger.debug(\"Integrating Airship Auto-Integration (iOS/tvOS/visionOS).\")\n        self.swizzler.swizzleDidRegister(appDelegate, delegate: delegate)\n        self.swizzler.swizzleDidFailToRegister(appDelegate, delegate: delegate)\n        self.swizzler.swizzleDidReceiveRemoteNotification(appDelegate, delegate: delegate)\n\n#if !os(visionOS)\n        self.swizzler.swizzleBackgroundFetch(appDelegate, delegate: delegate)\n#endif\n    }\n\n#endif\n}\n\n// MARK: - Default delegate\n\nfileprivate class UAAutoIntegrationDummyDelegate: NSObject, UNUserNotificationCenterDelegate {\n    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {\n        completionHandler([])\n    }\n\n#if !os(tvOS)\n    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {\n        completionHandler()\n    }\n#endif\n}\n\n// MARK: - Notification Center (common)\n\nfileprivate extension AirshipSwizzler {\n    private typealias NotificationCenterDelegateSetterBlock = @convention(block) (UNUserNotificationCenter, (any UNUserNotificationCenterDelegate)?) -> Void\n    private typealias WillPresentNotificationBlock = @convention(block) (NSObject, UNUserNotificationCenter, UNNotification, @Sendable @escaping (UNNotificationPresentationOptions) -> Void) -> Void\n\n#if !os(tvOS)\n    private typealias DidReceiveNotificationResponseBlock = @convention(block) (NSObject, UNUserNotificationCenter, UNNotificationResponse, @Sendable @escaping () -> Void) -> Void\n#endif\n\n    func swizzleNotificationCenterDelegateSetter(delegate: any AppIntegrationDelegate, dummyDelegate: UAAutoIntegrationDummyDelegate) {\n        let setter = #selector(setter: UNUserNotificationCenter.delegate)\n        let block: NotificationCenterDelegateSetterBlock = { [weak self] (center, newDelegate) in\n            guard let self else { return }\n            if let original = self.originalImplementation(setter, forClass: UNUserNotificationCenter.self) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (UNUserNotificationCenter, Selector, (any UNUserNotificationCenterDelegate)?) -> Void).self)\n                fn(center, setter, newDelegate)\n            }\n\n            if let newDelegate {\n                self.swizzleNotificationCenterDelegate(newDelegate, delegate: delegate)\n            } else {\n                UNUserNotificationCenter.current().delegate = dummyDelegate\n            }\n        }\n        self.swizzleClass(UNUserNotificationCenter.self, selector: setter, implementation: imp_implementationWithBlock(block))\n    }\n\n    func swizzleNotificationCenterDelegate(_ delegate: any UNUserNotificationCenterDelegate, delegate integrationDelegate: any AppIntegrationDelegate) {\n        swizzleNotificationCenterWillPresent(delegate, delegate: integrationDelegate)\n\n#if !os(tvOS)\n        swizzleNotificationCenterDidReceive(delegate, delegate: integrationDelegate)\n#endif\n    }\n\n    private func swizzleNotificationCenterWillPresent(_ delegate: any UNUserNotificationCenterDelegate, delegate integrationDelegate: any AppIntegrationDelegate) {\n        let willPresentSelector = #selector((any UNUserNotificationCenterDelegate).userNotificationCenter(_:willPresent:withCompletionHandler:))\n        let willPresentBlock: WillPresentNotificationBlock = { [weak self] (receiver, center, notification, handler) in\n            guard receiver === UNUserNotificationCenter.current().delegate else {\n                handler([]); return\n            }\n\n            let group = DispatchGroup()\n            let result: AirshipAtomicValue<UNNotificationPresentationOptions> = AirshipAtomicValue([])\n\n            if\n                let strongSelf = self,\n                let original = strongSelf.originalImplementation(willPresentSelector, forClass: type(of: receiver))\n            {\n                group.enter()\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, UNUserNotificationCenter, UNNotification, @escaping (UNNotificationPresentationOptions) -> Void) -> Void).self)\n                let safeCompletion = strongSelf.ensureOnce(selector: willPresentSelector) { options in\n                    result.update { $0.union(options) }\n                    group.leave()\n                }\n                fn(receiver, willPresentSelector, center, notification, safeCompletion)\n            }\n\n            group.enter()\n            integrationDelegate.presentationOptions(for: notification) { options in\n                result.update { $0.union(options) }\n                group.leave()\n            }\n\n            group.notify(queue: .main) {\n                integrationDelegate.willPresentNotification(notification: notification, presentationOptions: result.value) {\n                    handler(result.value)\n                }\n            }\n        }\n        self.swizzleInstance(\n            delegate,\n            selector: willPresentSelector,\n            protocol: (any UNUserNotificationCenterDelegate).self,\n            implementation: imp_implementationWithBlock(willPresentBlock)\n        )\n    }\n\n#if !os(tvOS)\n    private func swizzleNotificationCenterDidReceive(_ delegate: any UNUserNotificationCenterDelegate, delegate integrationDelegate: any AppIntegrationDelegate) {\n        let responseSelector = #selector((any UNUserNotificationCenterDelegate).userNotificationCenter(_:didReceive:withCompletionHandler:))\n        let responseBlock: DidReceiveNotificationResponseBlock = { [weak self] (receiver, center, response, handler) in\n            guard receiver === UNUserNotificationCenter.current().delegate else {\n                handler(); return\n            }\n\n            let group = DispatchGroup()\n            if\n                let strongSelf = self,\n                let original = strongSelf.originalImplementation(responseSelector, forClass: type(of: receiver))\n            {\n                group.enter()\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, UNUserNotificationCenter, UNNotificationResponse, @escaping () -> Void) -> Void).self)\n                let safeCompletion = strongSelf.ensureOnce(selector: responseSelector) {\n                    group.leave()\n                }\n                fn(receiver, responseSelector, center, response, safeCompletion)\n            }\n\n            group.enter()\n            integrationDelegate.didReceiveNotificationResponse(response: response) { group.leave() }\n\n            group.notify(queue: .main) { handler() }\n        }\n\n        self.swizzleInstance(\n            delegate,\n            selector: responseSelector,\n            protocol: (any UNUserNotificationCenterDelegate).self,\n            implementation: imp_implementationWithBlock(responseBlock)\n        )\n    }\n#endif\n}\n\n#if os(watchOS)\n\n// MARK: - App Delegate watchOS\nextension AirshipSwizzler {\n    private typealias WatchDidRegisterForRemoteNotificationsBlock = @convention(block) (NSObject, Data) -> Void\n    private typealias WatchDidReceiveRemoteNotificationBlock = @convention(block) (NSObject, [AnyHashable: Any], @escaping (WKBackgroundFetchResult) -> Void) -> Void\n    private typealias WatchDidFailToRegisterBlock = @convention(block) (NSObject, any Error) -> Void\n\n    func swizzleWatchDidRegister(_ delegate: any NSObjectProtocol, delegate integrationDelegate: any AppIntegrationDelegate) {\n        let regSelector = #selector((any WKExtensionDelegate).didRegisterForRemoteNotifications(withDeviceToken:))\n        let regBlock: WatchDidRegisterForRemoteNotificationsBlock = { [weak self] (receiver, token) in\n            integrationDelegate.didRegisterForRemoteNotifications(deviceToken: token)\n            if let strongSelf = self, let original = strongSelf.originalImplementation(regSelector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, Data) -> Void).self)\n                fn(receiver, regSelector, token)\n            }\n        }\n        self.swizzleInstance(\n            delegate,\n            selector: regSelector,\n            protocol: (any WKExtensionDelegate).self,\n            implementation: imp_implementationWithBlock(regBlock)\n        )\n    }\n\n    func swizzleWatchDidReceiveRemoteNotification(_ delegate: any NSObjectProtocol, delegate integrationDelegate: any AppIntegrationDelegate) {\n        let selector = #selector((any WKExtensionDelegate).didReceiveRemoteNotification(_:fetchCompletionHandler:))\n\n        let block: WatchDidReceiveRemoteNotificationBlock = { [weak self] (receiver, userInfo, handler) in\n            let resultValue: AirshipAtomicValue<WKBackgroundFetchResult> = AirshipAtomicValue(.noData)\n            let group = DispatchGroup()\n\n            let updateResult: @Sendable (WKBackgroundFetchResult) -> Void = { next in\n                resultValue.update { current in\n                    // Logic: .newData wins, otherwise .failed wins over .noData\n                    return (current == .newData || next == .newData) ? .newData : (next == .failed ? .failed : current)\n                }\n            }\n\n            if let strongSelf = self, let original = strongSelf.originalImplementation(selector, forClass: type(of: receiver)) {\n                group.enter()\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, [AnyHashable: Any], @escaping (WKBackgroundFetchResult) -> Void) -> Void).self)\n                let safeCompletion = strongSelf.ensureOnce(selector: selector) { res in\n                    updateResult(res)\n                    group.leave()\n                }\n                fn(receiver, selector, userInfo, safeCompletion)\n            }\n\n            group.enter()\n\n            // watchOS doesn't have applicationState == .active in the same way,\n            // but you can check if the app is in the foreground via WKApplication\n            let isForeground = WKApplication.shared().applicationState == .active\n            integrationDelegate.didReceiveRemoteNotification(userInfo: userInfo, isForeground: isForeground) { res in\n                updateResult(res)\n                group.leave()\n            }\n\n            group.notify(queue: .main) {\n                handler(resultValue.value)\n            }\n        }\n\n        self.swizzleInstance(\n            delegate,\n            selector: selector,\n            protocol: (any WKExtensionDelegate).self,\n            implementation: imp_implementationWithBlock(block)\n        )\n    }\n\n    func swizzleWatchDidFailToRegister(_ delegate: any NSObjectProtocol, delegate integrationDelegate: any AppIntegrationDelegate) {\n        let selector = #selector((any WKExtensionDelegate).didFailToRegisterForRemoteNotificationsWithError(_:))\n\n        let block: WatchDidFailToRegisterBlock = { [weak self] (receiver, error) in\n            integrationDelegate.didFailToRegisterForRemoteNotifications(error: error)\n\n            if let strongSelf = self,\n               let original = strongSelf.originalImplementation(selector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, any Error) -> Void).self)\n                fn(receiver, selector, error)\n            }\n        }\n\n        self.swizzleInstance(\n            delegate,\n            selector: selector,\n            protocol: (any WKExtensionDelegate).self,\n            implementation: imp_implementationWithBlock(block)\n        )\n    }\n}\n\n\n\n#elseif os(macOS)\n\n// MARK: - App Delegate macOS\nextension AirshipSwizzler {\n    private typealias MacDidRegisterBlock = @convention(block) (NSObject, NSApplication, Data) -> Void\n    private typealias MacDidFailBlock = @convention(block) (NSObject, NSApplication, any Error) -> Void\n    private typealias MacDidReceiveRemoteNotificationBlock = @convention(block) (NSObject, NSApplication, [AnyHashable: Any]) -> Void\n\n    func swizzleMacDidRegister(_ appDelegate: any NSApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let selector = #selector((any NSApplicationDelegate).application(_:didRegisterForRemoteNotificationsWithDeviceToken:))\n        let block: MacDidRegisterBlock = { [weak self] (receiver, app, token) in\n            delegate.didRegisterForRemoteNotifications(deviceToken: token)\n            if let strongSelf = self, let original = strongSelf.originalImplementation(selector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, NSApplication, Data) -> Void).self)\n                fn(receiver, selector, app, token)\n            }\n        }\n        self.swizzleInstance(appDelegate, selector: selector, protocol: (any NSApplicationDelegate).self, implementation: imp_implementationWithBlock(block))\n    }\n\n    func swizzleMacDidFailToRegister(_ appDelegate: any NSApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let selector = #selector((any NSApplicationDelegate).application(_:didFailToRegisterForRemoteNotificationsWithError:))\n        let block: MacDidFailBlock = { [weak self] (receiver, app, error) in\n            delegate.didFailToRegisterForRemoteNotifications(error: error)\n            if let strongSelf = self, let original = strongSelf.originalImplementation(selector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, NSApplication, any Error) -> Void).self)\n                fn(receiver, selector, app, error)\n            }\n        }\n        self.swizzleInstance(appDelegate, selector: selector, protocol: (any NSApplicationDelegate).self, implementation: imp_implementationWithBlock(block))\n    }\n\n    func swizzleMacDidReceiveRemoteNotification(_ appDelegate: any NSApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let selector = #selector((any NSApplicationDelegate).application(_:didReceiveRemoteNotification:))\n        let block: MacDidReceiveRemoteNotificationBlock = { [weak self] (receiver, app, userInfo) in\n            let isForeground = NSApplication.shared.isActive\n            delegate.didReceiveRemoteNotification(userInfo: userInfo, isForeground: isForeground)\n            if let strongSelf = self,\n               let original = strongSelf.originalImplementation(selector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, NSApplication, [AnyHashable: Any]) -> Void).self)\n                fn(receiver, selector, app, userInfo)\n            }\n        }\n\n        self.swizzleInstance(\n            appDelegate,\n            selector: selector,\n            protocol: (any NSApplicationDelegate).self,\n            implementation: imp_implementationWithBlock(block)\n        )\n    }\n}\n\n#else\n\n// MARK: - App Delegate tvOS, ipadOS, iOS\nfileprivate extension AirshipSwizzler {\n    private typealias DidRegisterForRemoteNotificationsBlock = @convention(block) (NSObject, UIApplication, Data) -> Void\n    private typealias DidFailToRegisterForRemoteNotificationsBlock = @convention(block) (NSObject, UIApplication, any Error) -> Void\n    private typealias DidReceiveRemoteNotificationFetchBlock = @convention(block) (NSObject, UIApplication, [AnyHashable: Any], @Sendable @escaping (UIBackgroundFetchResult) -> Void) -> Void\n    private typealias BackgroundFetchBlock = @convention(block) (NSObject, UIApplication, @Sendable @escaping (UIBackgroundFetchResult) -> Void) -> Void\n\n    func swizzleDidRegister(_ appDelegate: any UIApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let regSelector = #selector((any UIApplicationDelegate).application(_:didRegisterForRemoteNotificationsWithDeviceToken:))\n        let regBlock: DidRegisterForRemoteNotificationsBlock = { [weak self] (receiver, app, token) in\n            delegate.didRegisterForRemoteNotifications(deviceToken: token)\n            if let strongSelf = self, let original = strongSelf.originalImplementation(regSelector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, UIApplication, Data) -> Void).self)\n                fn(receiver, regSelector, app, token)\n            }\n        }\n\n        self.swizzleInstance(\n            appDelegate,\n            selector: regSelector,\n            protocol: (any UIApplicationDelegate).self,\n            implementation: imp_implementationWithBlock(regBlock)\n        )\n    }\n\n    func swizzleDidFailToRegister(_ appDelegate: any UIApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let failSelector = #selector((any UIApplicationDelegate).application(_:didFailToRegisterForRemoteNotificationsWithError:))\n        let failBlock: DidFailToRegisterForRemoteNotificationsBlock = { [weak self] (receiver, app, error) in\n            delegate.didFailToRegisterForRemoteNotifications(error: error)\n            if let strongSelf = self, let original = strongSelf.originalImplementation(failSelector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, UIApplication, any Error) -> Void).self)\n                fn(receiver, failSelector, app, error)\n            }\n        }\n\n        self.swizzleInstance(\n            appDelegate,\n            selector: failSelector,\n            protocol: (any UIApplicationDelegate).self,\n            implementation: imp_implementationWithBlock(failBlock)\n        )\n    }\n\n    func swizzleDidReceiveRemoteNotification(_ appDelegate: any UIApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let fetchSelector = #selector((any UIApplicationDelegate).application(_:didReceiveRemoteNotification:fetchCompletionHandler:))\n\n        let fetchBlock: DidReceiveRemoteNotificationFetchBlock = { [weak self] (receiver, app, userInfo, handler) in\n            let resultValue: AirshipAtomicValue<UIBackgroundFetchResult> = AirshipAtomicValue(.noData)\n            let group = DispatchGroup()\n\n            let updateResult: @Sendable (UIBackgroundFetchResult) -> Void = { next in\n                resultValue.update { current in\n                    return (current == .newData || next == .newData) ? .newData : (next == .failed ? .failed : current)\n                }\n            }\n\n            if\n                let strongSelf = self,\n                let original = strongSelf.originalImplementation(fetchSelector, forClass: type(of: receiver))\n            {\n                group.enter()\n                typealias RawFetchFunc = @convention(c) (NSObject, Selector, UIApplication, [AnyHashable: Any], @escaping (UIBackgroundFetchResult) -> Void) -> Void\n                let fn = unsafeBitCast(original, to: RawFetchFunc.self)\n\n                let safeCompletion = strongSelf.ensureOnce(selector: fetchSelector) { res in\n                    updateResult(res)\n                    group.leave()\n                }\n                fn(receiver, fetchSelector, app, userInfo, safeCompletion)\n            }\n\n            group.enter()\n            delegate.didReceiveRemoteNotification(userInfo: userInfo, isForeground: app.applicationState == .active) { res in\n                updateResult(res)\n                group.leave()\n            }\n\n            group.notify(queue: .main) { handler(resultValue.value) }\n        }\n\n        self.swizzleInstance(\n            appDelegate,\n            selector: fetchSelector,\n            protocol: (any UIApplicationDelegate).self,\n            implementation: imp_implementationWithBlock(fetchBlock)\n        )\n    }\n\n#if !os(visionOS)\n    func swizzleBackgroundFetch(_ appDelegate: any UIApplicationDelegate, delegate: any AppIntegrationDelegate) {\n        let backgroundSelector = #selector((any UIApplicationDelegate).application(_:performFetchWithCompletionHandler:))\n        let backgroundBlock: BackgroundFetchBlock = { [weak self] (receiver, app, handler) in\n            delegate.onBackgroundAppRefresh()\n            if let strongSelf = self, let original = strongSelf.originalImplementation(backgroundSelector, forClass: type(of: receiver)) {\n                let fn = unsafeBitCast(original, to: (@convention(c) (NSObject, Selector, UIApplication, @escaping (UIBackgroundFetchResult) -> Void) -> Void).self)\n                let safeCompletion = strongSelf.ensureOnce(selector: backgroundSelector, completion: handler)\n                fn(receiver, backgroundSelector, app, safeCompletion)\n            } else {\n                handler(.noData)\n            }\n        }\n\n        self.swizzleInstance(\n            appDelegate,\n            selector: backgroundSelector,\n            protocol: (any UIApplicationDelegate).self,\n            implementation: imp_implementationWithBlock(backgroundBlock)\n        )\n    }\n#endif\n}\n#endif\n\n\nfileprivate extension AirshipSwizzler {\n    func ensureOnce<T>(selector: Selector, completion: @escaping (T) -> Void) -> (T) -> Void {\n        let called = AirshipAtomicValue(false)\n        return { value in\n            if called.compareAndSet(expected: false, value: true) {\n                completion(value)\n            } else {\n                AirshipLogger.error(\n                    \"\"\"\n                    Completion handler for \\(selector) was called multiple times. \n                    Airship has ignored the extra calls to prevent a crash, but you should \n                    check your delegate implementation to ensure the handler is called exactly once.\n                    \"\"\"\n                )\n            }\n        }\n    }\n\n    func ensureOnce(selector: Selector, completion: @escaping () -> Void) -> () -> Void {\n        let called = AirshipAtomicValue(false)\n        return {\n            if called.compareAndSet(expected: false, value: true) {\n                completion()\n            } else {\n                AirshipLogger.error(\n                    \"\"\"\n                    Completion handler for \\(selector) was called multiple times. \n                    Airship has ignored the extra calls to prevent a crash, but you should \n                    check your delegate implementation to ensure the handler is called exactly once.\n                    \"\"\"\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BackgroundColorViewModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n\nstruct BackgroundViewModifier: ViewModifier {\n    @Environment(\\.colorScheme) var colorScheme\n    @EnvironmentObject var state: ThomasState\n\n    var backgroundColor: ThomasColor?\n    var backgroundColorOverrides: [ThomasPropertyOverride<ThomasColor>]?\n    var border: ThomasBorder?\n    var borderOverrides: [ThomasPropertyOverride<ThomasBorder>]?\n    var shadow: ThomasShadow?\n\n    func body(content: Content) -> some View {\n        let resolvedBorder = ThomasPropertyOverride<ThomasBorder>.resolveOptional(\n            state: state,\n            overrides: borderOverrides,\n            defaultValue: border\n        )\n        let resolvedBackgroundColor = ThomasPropertyOverride<ThomasColor>.resolveOptional(\n            state: state,\n            overrides: backgroundColorOverrides,\n            defaultValue: backgroundColor\n        )\n\n        let innerCornerRadii = CustomCornerRadii(innerRadiiFor: resolvedBorder)\n\n        return content\n            .clipContent(border: resolvedBorder)\n            .applyPadding(padding: resolvedBorder?.strokeWidth)\n            .applyBackground(\n                color: resolvedBackgroundColor,\n                border: resolvedBorder,\n                colorScheme: colorScheme\n            )\n            .applyShadow(\n                shadow,\n                border: resolvedBorder,\n                colorScheme: colorScheme\n            )\n            .contentShape(\n                CustomRoundedRectangle(\n                    cornerRadii: innerCornerRadii,\n                    style: .continuous\n                )\n            )\n    }\n}\n\nfileprivate extension View {\n    @ViewBuilder\n    func applyPadding(padding: Double?) -> some View {\n        if let padding {\n            self.padding(padding)\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func applyShadow(_ shadow: ThomasShadow?, border: ThomasBorder?, colorScheme: ColorScheme) -> some View {\n        if let boxShadow = shadow?.resolvedBoxShadow {\n            if let borderShape = border?.borderShape {\n                self.background(\n                    ZStack {\n                        borderShape\n                            .fill(boxShadow.color.toColor(colorScheme))\n                            .padding(.all, -boxShadow.radius/2.0)\n                            .blur(radius: boxShadow.blurRadius, opaque: false)\n                            .offset(\n                                x: boxShadow.offsetX ?? 0,\n                                y: boxShadow.offsetY ?? 0\n                            )\n                        borderShape.blendMode(.destinationOut)\n                    }\n                        .compositingGroup()\n                        .allowsHitTesting(false)\n                )\n            } else {\n                self.background(\n                    ZStack {\n                        Rectangle()\n                            .fill(boxShadow.color.toColor(colorScheme))\n                            .padding(.all, -boxShadow.radius/2.0)\n                            .blur(radius: boxShadow.blurRadius, opaque: false)\n                            .offset(\n                                x: boxShadow.offsetX ?? 0,\n                                y: boxShadow.offsetY ?? 0\n                            )\n                        Rectangle().blendMode(.destinationOut)\n                    }\n                        .compositingGroup()\n                        .allowsHitTesting(false)\n                 )\n            }\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func applyBackground(\n        color: ThomasColor?,\n        border: ThomasBorder?,\n        colorScheme: ColorScheme\n    ) -> some View {\n        // Defaults to black to match Android & Web\n        let strokeColor: Color = border?.strokeColor?.toColor(colorScheme) ?? .black\n        let backgroundColor: Color = color?.toColor(colorScheme) ?? .clear\n\n        if let strokeWidth = border?.strokeWidth, strokeWidth > 0 {\n            if let borderShape = border?.borderShape {\n                self.background(\n                    borderShape\n                        .strokeBorder(strokeColor, lineWidth: strokeWidth)\n                        .background(borderShape.inset(by: strokeWidth).fill(backgroundColor))\n                )\n                .clipShape(borderShape)\n            } else {\n                self.background(\n                    Rectangle()\n                        .strokeBorder(strokeColor, lineWidth: strokeWidth)\n                        .background(Rectangle().inset(by: strokeWidth).fill(backgroundColor))\n                )\n            }\n        } else if let borderShape = border?.borderShape {\n            self.background(backgroundColor)\n                .clipShape(borderShape)\n        } else {\n            self.background(backgroundColor)\n        }\n    }\n\n    @ViewBuilder\n    func clipContent(\n        border: ThomasBorder?\n    ) -> some View {\n        if let cornerRadius = border?.maxRadius,\n           let width = border?.strokeWidth,\n           cornerRadius > width\n        {\n            let cornerRadii = CustomCornerRadii(innerRadiiFor: border)\n            ZStack {\n                let shape = CustomRoundedRectangle(\n                    cornerRadii: cornerRadii,\n                    style: .continuous)\n                self.clipShape(shape)\n            }\n        } else {\n            self\n        }\n    }\n}\n\nfileprivate extension ThomasShadow {\n    var resolvedBoxShadow: ThomasShadow.BoxShadow? {\n        selectors?.first {\n            $0.platform == nil || $0.platform == .ios\n        }?.shadow.boxShadow\n    }\n}\n\nfileprivate extension ThomasBorder {\n    var borderShape: CustomRoundedRectangle? {\n        let cornerRadii = CustomCornerRadii(outerRadiiFor: self)\n\n        return CustomRoundedRectangle(\n            cornerRadii: cornerRadii,\n            style: .continuous\n        )\n    }\n}\n\nextension View {\n    @ViewBuilder\n    func thomasBackground(\n        color: ThomasColor? = nil,\n        colorOverrides: [ThomasPropertyOverride<ThomasColor>]? = nil,\n        border: ThomasBorder? = nil,\n        borderOverrides: [ThomasPropertyOverride<ThomasBorder>]? = nil,\n        shadow: ThomasShadow? = nil\n    ) -> some View {\n        if border != nil ||\n            shadow != nil ||\n            color != nil ||\n            (borderOverrides?.isEmpty == false) ||\n            (colorOverrides?.isEmpty == false) {\n            self.modifier(\n                BackgroundViewModifier(\n                    backgroundColor: color,\n                    backgroundColorOverrides: colorOverrides,\n                    border: border,\n                    borderOverrides: borderOverrides,\n                    shadow: shadow\n                )\n            )\n        } else {\n            self\n        }\n    }\n}\n\nstruct CustomCornerRadii: Equatable, Animatable {\n    var topLeading: CGFloat\n    var topTrailing: CGFloat\n    var bottomLeading: CGFloat\n    var bottomTrailing: CGFloat\n\n    /// Initializes CustomCornerRadii representing the INNER radii\n    /// calculated from a ThomasBorder, subtracting the stroke width.\n    init(innerRadiiFor border: ThomasBorder?) {\n        // Use guard let for safe unwrapping\n        guard let border = border else {\n            // If border is nil, initialize with all zeros\n            self.init(topLeading: 0, topTrailing: 0, bottomLeading: 0, bottomTrailing: 0)\n            return\n        }\n\n        // Convert properties to CGFloat for calculations\n        let strokeWidth = CGFloat(border.strokeWidth ?? 0.0)\n\n        if let corners = border.cornerRadius {\n            // Per-corner radii defined, calculate inner values using max(0, ...)\n            self.init( // Call the memberwise initializer\n                topLeading: max(0, CGFloat(corners.topLeft ?? 0.0) - strokeWidth),\n                topTrailing: max(0, CGFloat(corners.topRight ?? 0.0) - strokeWidth),\n                bottomLeading: max(0, CGFloat(corners.bottomLeft ?? 0.0) - strokeWidth),\n                bottomTrailing: max(0, CGFloat(corners.bottomRight ?? 0.0) - strokeWidth)\n            )\n        } else {\n            // Fallback to single radius\n            let radius = CGFloat(border.radius ?? 0.0)\n            let innerRadius = max(0, radius - strokeWidth) // Ensure non-negative\n            // Call the memberwise initializer with the single inner radius\n            self.init(\n                topLeading: innerRadius,\n                topTrailing: innerRadius,\n                bottomLeading: innerRadius,\n                bottomTrailing: innerRadius\n            )\n        }\n    }\n\n    init(outerRadiiFor border: ThomasBorder?) {\n        guard let border = border else {\n            self.init(topLeading: 0, topTrailing: 0, bottomLeading: 0, bottomTrailing: 0)\n            return\n        }\n\n        if let corners = border.cornerRadius {\n            self.init(\n                topLeading: CGFloat(corners.topLeft ?? 0.0),\n                topTrailing: CGFloat(corners.topRight ?? 0.0),\n                bottomLeading: CGFloat(corners.bottomLeft ?? 0.0),\n                bottomTrailing: CGFloat(corners.bottomRight ?? 0.0)\n            )\n        } else {\n            let radius = CGFloat(border.radius ?? 0.0)\n            self.init(\n                topLeading: radius,\n                topTrailing: radius,\n                bottomLeading: radius,\n                bottomTrailing: radius\n            )\n        }\n    }\n\n    init(\n        topLeading: CGFloat = 0,\n        topTrailing: CGFloat = 0,\n        bottomLeading: CGFloat = 0,\n        bottomTrailing: CGFloat = 0\n    ) {\n        self.topLeading = topLeading\n        self.topTrailing = topTrailing\n        self.bottomLeading = bottomLeading\n        self.bottomTrailing = bottomTrailing\n    }\n\n    var animatableData: AnimatablePair<\n        AnimatablePair<CGFloat, CGFloat>,\n        AnimatablePair<CGFloat, CGFloat>\n    > {\n        get {\n            AnimatablePair(\n                AnimatablePair(topLeading, bottomLeading),\n                AnimatablePair(bottomTrailing, topTrailing)\n            )\n        }\n        set {\n            topLeading = newValue.first.first\n            bottomLeading = newValue.first.second\n            bottomTrailing = newValue.second.first\n            topTrailing = newValue.second.second\n        }\n    }\n}\n\nstruct CustomRoundedRectangle: InsettableShape {\n    let cornerRadii: CustomCornerRadii\n    let style: RoundedCornerStyle\n    var insetAmount: CGFloat = 0\n\n    func path(in rect: CGRect) -> Path {\n        let insetRect = rect.insetBy(dx: insetAmount, dy: insetAmount)\n        return UnevenRoundedRectangle(\n            topLeadingRadius: cornerRadii.topLeading,\n            bottomLeadingRadius: cornerRadii.bottomLeading,\n            bottomTrailingRadius: cornerRadii.bottomTrailing,\n            topTrailingRadius: cornerRadii.topTrailing,\n            style: .continuous\n        ).path(in: insetRect)\n    }\n\n    func inset(by amount: CGFloat) -> CustomRoundedRectangle {\n        let adjustedRadii = CustomCornerRadii(\n            topLeading: max(0, cornerRadii.topLeading - amount),\n            topTrailing: max(0, cornerRadii.topTrailing - amount),\n            bottomLeading: max(0, cornerRadii.bottomLeading - amount),\n            bottomTrailing: max(0, cornerRadii.bottomTrailing - amount)\n        )\n\n        return CustomRoundedRectangle(\n            cornerRadii: adjustedRadii,\n            style: self.style,\n            insetAmount: self.insetAmount + amount\n        )\n    }\n}\n\nextension ThomasBorder {\n    var maxRadius: Double? {\n        if let cornerRadius = self.cornerRadius {\n            return [\n                cornerRadius.bottomLeft ?? 0,\n                cornerRadius.bottomRight ?? 0,\n                cornerRadius.topLeft ?? 0,\n                cornerRadius.topRight ?? 0,\n            ].max()\n        } else {\n            return self.radius\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Badger.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\nprotocol BadgerProtocol: AnyObject, Sendable {\n    func setBadgeNumber(_ newBadgeNumber: Int) async throws\n\n    @MainActor\n    var badgeNumber: Int { get }\n}\n\nfinal class Badger: Sendable, BadgerProtocol {\n    public static let shared: Badger = Badger()\n\n    func setBadgeNumber(_ newBadgeNumber: Int) async throws {\n#if os(watchOS)\n        // watchOS does not support app icon badges\n        return\n#else\n        AirshipLogger.debug(\"Updating badge \\(newBadgeNumber)\")\n        try await UNUserNotificationCenter.current().setBadgeCount(newBadgeNumber)\n#endif\n    }\n\n    @MainActor\n    var badgeNumber: Int {\n#if os(watchOS)\n        return 0\n#elseif os(macOS)\n        return Int(NSApplication.shared.dockTile.badgeLabel ?? \"\") ?? 0\n#else\n        // Covers iOS, tvOS, and visionOS\n        return UIApplication.shared.applicationIconBadgeNumber\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BannerView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\nstruct BannerView: View {\n    @Environment(\\.layoutState) private var layoutState\n    @Environment(\\.windowSize) private var windowSize\n    @Environment(\\.orientation) private var orientation\n    @Environment(\\.colorScheme) private var colorScheme\n\n    static let animationInOutDuration = 0.2\n\n    private let viewControllerOptions: ThomasViewControllerOptions\n    private let presentation: ThomasPresentationInfo.Banner\n    private let layout: AirshipLayout\n\n    @ObservedObject\n    private var thomasEnvironment: ThomasEnvironment\n\n    @ObservedObject\n    private var bannerConstraints: ThomasBannerConstraints\n\n    @StateObject\n    private var timer: AirshipObservableTimer\n\n    /// The dismiss action callback\n    private let onDismiss: () -> Void\n\n    @State private var isShowing: Bool = false\n    @State private var swipeOffset: CGFloat = 0\n    @State private var isButtonTapsDisabled: Bool = false\n    @State private var contentSize: CGSize? = nil\n\n    init(\n        viewControllerOptions: ThomasViewControllerOptions,\n        presentation: ThomasPresentationInfo.Banner,\n        layout: AirshipLayout,\n        thomasEnvironment: ThomasEnvironment,\n        bannerConstraints: ThomasBannerConstraints,\n        onDismiss: @escaping () -> Void\n    ) {\n        self.viewControllerOptions = viewControllerOptions\n        self.presentation = presentation\n        self.layout = layout\n        self.thomasEnvironment = thomasEnvironment\n        self.bannerConstraints = bannerConstraints\n        self._timer = StateObject(\n            wrappedValue: AirshipObservableTimer(\n                duration: TimeInterval(presentation.duration ?? Int(INT_MAX))\n            )\n        )\n        self.onDismiss = onDismiss\n    }\n\n    var body: some View {\n        ZStack {\n            GeometryReader { metrics in\n                RootView(\n                    thomasEnvironment: thomasEnvironment,\n                    layout: layout\n                ) { orientation, windowSize in\n                    let placement = resolvePlacement(\n                        orientation: orientation,\n                        windowSize: windowSize\n                    )\n\n                    let banner = createBanner(\n                        placement: placement,\n                        metrics: metrics\n                    )\n                    Group {\n                        if isShowing {\n                            banner\n                        } else {\n                            banner.opacity(0)\n                        }\n                    }\n                    .airshipApplyTransition(\n                        isTopPlacement: placement.position == .top\n                    )\n                }\n                .airshipOnChangeOf(thomasEnvironment.isDismissed) { _ in\n                    setShowing(state: false) {\n                        self.swipeOffset = 0\n                        onDismiss()\n                    }\n                    timer.onDisappear()\n                }\n                .onAppear {\n                    timer.onAppear()\n                    if contentSize != nil {\n                        setShowing(state: true)\n                    }\n                }\n                .airshipOnChangeOf(contentSize) { size in\n                    if size != nil && !isShowing {\n                        setShowing(state: true)\n                    }\n                }\n                .airshipOnChangeOf(swipeOffset) { value in\n                    self.isButtonTapsDisabled = value != 0\n                    self.timer.isPaused = value != 0\n                }\n                .onReceive(timer.$isExpired) { expired in\n                    if expired {\n                        self.thomasEnvironment.dismiss()\n                    }\n                }\n                // Invalidate cached content size on orientation change\n                .airshipOnChangeOf(orientation) { _ in\n                    self.contentSize = nil\n                }\n            }\n            .id(orientation)\n            .ignoresSafeArea(ignoreKeyboardSafeArea ? [.keyboard] : [])\n        }\n    }\n\n    private var ignoreKeyboardSafeArea: Bool {\n        presentation.ios?.keyboardAvoidance == .overTheTop\n    }\n\n    @ViewBuilder\n    private func nub(placement: ThomasPresentationInfo.Banner.Placement) -> some View {\n        if let nubInfo = placement.nubInfo {\n            Capsule()\n                .frame(\n                    width: nubInfo.size.width.calculateSize(nil) ?? 36,\n                    height: nubInfo.size.height.calculateSize(nil) ?? 4\n                )\n                .foregroundColor(nubInfo.color.toColor(colorScheme))\n                .margin(nubInfo.margin)\n        } else {\n            Capsule()\n                .frame(width: 36, height: 4)\n                .foregroundColor(Color.red.opacity(0.42))\n        }\n    }\n\n    private func createBanner(\n        placement: ThomasPresentationInfo.Banner.Placement,\n        metrics: GeometryProxy\n    ) -> some View {\n        let alignment = Alignment(\n            horizontal: .center,\n            vertical: placement.position == .top ? .top : .bottom\n        )\n\n        let constraints = ViewConstraints(\n            size: self.bannerConstraints.windowSize,\n            safeAreaInsets: placement.ignoreSafeArea != true ? EdgeInsets() : metrics.safeAreaInsets\n        )\n\n        let contentConstraints = constraints.contentConstraints(\n            placement.size,\n            contentSize: self.contentSize,\n            margin: placement.margin\n        )\n\n        /**\n         * Banners rely on the viewController to reduce the parent view to avoid blocking the underlying view from recieving taps outside of the\n         * banner. When we adjust the view controller size, it also adjusts the GeometryReader metrics making them inaccurate. We still use the metrics to get safe area insets,\n         * but when calculating the size we need to use the window size in the shared bannerConstraints. Placement margins are also handled by the\n         * viewController to avoid margins being touchable dead areas.\n         */\n        return VStack {\n            ViewFactory.createView(\n                layout.view,\n                constraints: contentConstraints\n            )\n            .airshipAddNub(\n                isTopPlacement: placement.position == .top,\n                nub: AnyView(nub(placement: placement)),\n                itemSpacing: 16\n            )\n            .thomasBackground(\n                color: placement.backgroundColor,\n                border: placement.border\n            )\n            .offset(x: 0, y: swipeOffset)\n#if !os(tvOS)\n            .simultaneousGesture(swipeGesture(placement: placement))\n#endif\n            .background(\n                GeometryReader(content: { contentMetrics -> Color in\n                    let size = contentMetrics.size\n                    DispatchQueue.main.async {\n                        self.bannerConstraints.updateContentSize(\n                            size,\n                            constraints: contentConstraints,\n                            placement: placement\n                        )\n                        if self.contentSize != size {\n                            // Update cached size if constraints match\n                            self.contentSize = size\n                        }\n                    }\n                    return Color.airshipTappableClear\n                })\n            )\n           \n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment)\n        .edgesIgnoringSafeArea(.all)\n        .accessibilityElement(children: .contain)\n        .accessibilityAction(.escape) {\n            onDismiss()\n        }\n    }\n\n    private func resolvePlacement(\n        orientation: ThomasOrientation,\n        windowSize: ThomasWindowSize\n    ) -> ThomasPresentationInfo.Banner.Placement {\n\n        var placement = presentation.defaultPlacement\n        for placementSelector in presentation.placementSelectors ?? [] {\n            if let requiredSize = placementSelector.windowSize,\n               requiredSize != windowSize {\n                continue\n            }\n\n            if let requiredOrientation = placementSelector.orientation,\n               requiredOrientation != orientation {\n                continue\n            }\n\n            // its a match!\n            placement = placementSelector.placement\n        }\n\n        viewControllerOptions.bannerPlacement = placement\n        return placement\n    }\n\n    private func setShowing(state: Bool, completion: (() -> Void)? = nil) {\n        withAnimation(.easeInOut(duration: BannerView.animationInOutDuration)) {\n            self.isShowing = state\n        }\n\n        DispatchQueue.main.asyncAfter(\n            deadline: .now() + BannerView.animationInOutDuration\n        ) {\n            completion?()\n        }\n    }\n\n#if !os(tvOS)\n    private func swipeGesture(placement: ThomasPresentationInfo.Banner.Placement) -> some Gesture {\n        let minSwipeDistance: CGFloat = if let height = self.contentSize?.height, height > 0 {\n            min(100.0, height * 0.5)\n        } else {\n            100.0\n        }\n\n        return DragGesture(minimumDistance: 10)\n            .onChanged { gesture in\n                withAnimation(.interpolatingSpring(stiffness: 300, damping: 20)) {\n                    let offset = gesture.translation.height\n                    let upwardSwipeTopPlacement = (placement.position == .top && offset < 0)\n                    let downwardSwipeBottomPlacement = (placement.position == .bottom && offset > 0)\n\n                    if upwardSwipeTopPlacement || downwardSwipeBottomPlacement {\n                        self.swipeOffset = offset\n                    }\n                }\n            }\n            .onEnded { gesture in\n                withAnimation(.interpolatingSpring(stiffness: 300, damping: 20)) {\n                    let offset = gesture.translation.height\n                    swipeOffset = offset\n\n                    let upwardSwipeTopPlacement = (placement.position == .top && offset < -minSwipeDistance)\n                    let downwardSwipeBottomPlacement = (placement.position == .bottom && offset > minSwipeDistance)\n\n                    if upwardSwipeTopPlacement || downwardSwipeBottomPlacement {\n                        thomasEnvironment.dismiss()\n                    } else {\n                        // Return to origin\n                        swipeOffset = 0\n                    }\n                }\n            }\n    }\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BaseCachingRemoteDataProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\nprotocol CachingRemoteDataProviderResult: Sendable, Equatable {\n    var isSuccess: Bool { get }\n    \n    static func error(_ error: CachingRemoteDataError) -> any CachingRemoteDataProviderResult\n}\n\nfinal actor BaseCachingRemoteDataProvider<Output: CachingRemoteDataProviderResult, Overrides: Sendable> {\n    private let remoteFetcher: @Sendable (String) async throws -> AirshipHTTPResponse<Output>\n    private let cacheTtl: TimeInterval\n    private let overridesProvider: @Sendable (String) async -> AsyncStream<Overrides>\n    private let overridesApplier: @Sendable (Output, Overrides) async -> Output\n    private let isEnabled: @Sendable () -> Bool\n    private let taskSleeper: any AirshipTaskSleeper\n    private var resolvers: [String: Resolver] = [:]\n    private let date: any AirshipDateProtocol\n    \n    init(\n        remoteFetcher: @Sendable @escaping (String) async throws -> AirshipHTTPResponse<Output>,\n        overridesProvider: @Sendable @escaping (String) async -> AsyncStream<Overrides>,\n        overridesApplier: @Sendable @escaping (Output, Overrides) async -> Output,\n        isEnabled: @Sendable @escaping () -> Bool,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared,\n        cacheTtl: TimeInterval = 600\n    ) {\n        self.remoteFetcher = remoteFetcher\n        self.overridesProvider = overridesProvider\n        self.overridesApplier = overridesApplier\n        self.taskSleeper = taskSleeper\n        self.cacheTtl = cacheTtl\n        self.isEnabled = isEnabled\n        self.date = date\n    }\n    \n    private func getResolver(identifier: String, lastKnownIdentifier: String?) -> Resolver {\n        // The resolver for the lastKnownIdentifier can always be dropped, but we\n        // can't assume the identifier (channel or contact id) is the current stable contact ID or channel ID since\n        // its an async stream and we might not be on the last element.\n        \n        if let lastKnownIdentifier, lastKnownIdentifier != identifier {\n            resolvers[lastKnownIdentifier] = nil\n        }\n\n        if let resolver = resolvers[identifier] {\n            return resolver\n        }\n\n        let resolver = Resolver(\n            identifier: identifier,\n            overridesProvider: overridesProvider,\n            remoteFetcher: remoteFetcher,\n            cacheTtl: cacheTtl,\n            taskSleeper: taskSleeper,\n            overridesApplier: overridesApplier,\n            isEnabled: isEnabled,\n            date: self.date\n        )\n\n        resolvers[identifier] = resolver\n\n        return resolver\n    }\n    \n    func refresh() async {\n        for resolver in resolvers.values {\n            await resolver.expireCache()\n        }\n    }\n    \n    /// Returns the latest channel result stream from the latest stable identifier\n    nonisolated func updates(identifierUpdates: AsyncStream<String>) -> AsyncStream<Output> {\n        return AsyncStream { continuation in\n            let fetchTask = Task { [weak self] in\n                var resolverTask: Task<Void, Never>?\n                var lastKnownIdentifier: String? = nil\n                for await identifier in identifierUpdates {\n                    resolverTask?.cancel()\n                    guard !Task.isCancelled else { return }\n\n                    guard\n                        let resolver = await self?.getResolver(\n                            identifier: identifier,\n                            lastKnownIdentifier: lastKnownIdentifier\n                        )\n                    else {\n                        return\n                    }\n\n                    resolverTask = Task {\n                        for await update in await resolver.updates() {\n                            guard !Task.isCancelled else { return }\n                            continuation.yield(update)\n                        }\n                    }\n\n                    lastKnownIdentifier  = identifier\n                }\n            }\n\n            continuation.onTermination = { _ in\n                fetchTask.cancel()\n            }\n        }\n    }\n    \n    /// Manages the contact update API calls including backoff and override application\n    fileprivate actor Resolver {\n        private let identifier: String\n        private let overridesProvider: @Sendable (String) async -> AsyncStream<Overrides>\n        private let remoteFetcher: @Sendable (String) async throws -> AirshipHTTPResponse<Output>\n        private let cachedValue: CachedValue<Output>\n        private let fetchQueue: AirshipSerialQueue = AirshipSerialQueue()\n        private let cacheTtl: TimeInterval\n        private let taskSleeper: any AirshipTaskSleeper\n        private let overridesApplier: (Output, Overrides) async -> Output\n        private let isEnabled: () -> Bool\n\n        private let initialBackoff: TimeInterval = 8.0\n        private let maxBackoff: TimeInterval = 64.0\n        private var lastResults: [String: Output] = [:]\n        \n        private var waitTask: Task<Void, Never>? = nil\n\n        func expireCache() {\n            cachedValue.expire()\n            waitTask?.cancel()\n        }\n\n        init(\n            identifier: String,\n            overridesProvider: @Sendable @escaping (String) async -> AsyncStream<Overrides>,\n            remoteFetcher: @Sendable @escaping (String) async throws -> AirshipHTTPResponse<Output>,\n            cacheTtl: TimeInterval,\n            taskSleeper: any AirshipTaskSleeper,\n            overridesApplier: @escaping (Output, Overrides) async -> Output,\n            isEnabled: @escaping () -> Bool,\n            date: any AirshipDateProtocol\n        ) {\n            self.identifier = identifier\n            self.overridesProvider = overridesProvider\n            self.remoteFetcher = remoteFetcher\n            self.cacheTtl = cacheTtl\n            self.taskSleeper = taskSleeper\n            self.overridesApplier = overridesApplier\n            self.isEnabled = isEnabled\n            self.cachedValue = CachedValue(date: date)\n        }\n\n        func updates() -> AsyncStream<Output> {\n            let id = UUID().uuidString\n\n            return AsyncStream { continuation in\n                let refreshTask = Task {\n                    var backoff = self.initialBackoff\n\n                    repeat {\n                        let fetched = await self.fetch()\n                        let workingResult = if fetched.isSuccess {\n                            fetched\n                        } else if let lastResult = lastResults[id], lastResult.isSuccess {\n                            lastResult\n                        } else {\n                            fetched\n                        }\n\n                        guard !Task.isCancelled else { return }\n\n                        let overrideUpdates = await self.overridesProvider(identifier)\n\n                        let updateTask = Task {\n                            for await overrides in overrideUpdates {\n                                guard !Task.isCancelled else {\n                                    return\n                                }\n                                \n                                let result = await overridesApplier(workingResult, overrides)\n\n                                if (lastResults[id] != result) {\n                                    continuation.yield(result)\n                                    lastResults[id] = result\n                                }\n                            }\n                        }\n                        \n                        let timeToWait: TimeInterval\n                        \n                        if (fetched.isSuccess) {\n                            timeToWait = cachedValue.timeRemaining\n                            backoff = self.initialBackoff\n                        } else {\n                            timeToWait = backoff\n                            \n                            if backoff < self.maxBackoff {\n                                backoff = backoff * 2\n                            }\n                        }\n                        \n                        waitTask = Task {\n                            try? await self.taskSleeper.sleep(timeInterval: timeToWait)\n                        }\n                        \n                        await waitTask?.value\n\n                        updateTask.cancel()\n                    } while (!Task.isCancelled)\n                }\n\n                continuation.onTermination = { _ in\n                    refreshTask.cancel()\n                }\n            }\n        }\n\n        private func fetch() async -> Output {\n            guard isEnabled() else {\n                return Output.error(.disabled) as! Output\n            }\n\n            return await self.fetchQueue.runSafe { [cachedValue, remoteFetcher, identifier, cacheTtl] in\n                \n                if let cached = cachedValue.value {\n                    return cached\n                }\n\n                do {\n                    let response = try await remoteFetcher(identifier)\n\n                    guard response.isSuccess, let outputData = response.result else {\n                        throw AirshipErrors.error(\"Failed to fetch associated channels list\")\n                    }\n                    \n                    cachedValue.set(value: outputData, expiresIn: cacheTtl)\n\n                    return outputData\n                    \n                } catch {\n                    AirshipLogger.warn(\n                        \"Received error when fetching contact channels \\(error))\"\n                    )\n\n                    return Output.error(.failedToFetch) as! Output\n                }\n            }\n        }\n    }\n}\n\nenum CachingRemoteDataError: Error, Equatable, Sendable, Hashable {\n    case disabled\n    case failedToFetch\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BasementImport.swift",
    "content": "#if canImport(AirshipBasement)\n// In SDK 21, look into public import AirshipBasement as an alternative\n@_exported import AirshipBasement\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BasicToggleLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct BasicToggleLayout: View {\n\n    @Environment(\\.pageIdentifier) private var pageID\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n    @State private var isOn: Bool = false\n\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .basicToggleLayout,\n            thomasState: thomasState\n        )\n    }\n\n    private let info: ThomasViewInfo.BasicToggleLayout\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.BasicToggleLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        ToggleLayout(\n            isOn: $isOn,\n            onToggleOn: self.info.properties.onToggleOn,\n            onToggleOff: self.info.properties.onToggleOff\n        ) {\n            ViewFactory.createView(\n                self.info.properties.view,\n                constraints: constraints\n            )\n        }\n        .constraints(self.constraints)\n        .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n        .accessible(\n            self.info.accessible,\n            associatedLabel: self.associatedLabel,\n            hideIfDescriptionIsMissing: false\n        )\n        .formElement()\n        .airshipOnChangeOf(self.isOn, initial: true) { value in\n            updateFormState(value)\n        }\n        .onAppear {\n            restoreFormState()\n        }\n    }\n\n    private var attributes: [ThomasFormField.Attribute]? {\n        guard\n            let name = self.info.properties.attributeName,\n            let value = self.info.properties.attributeValue\n        else {\n            return nil\n        }\n\n        return [\n            ThomasFormField.Attribute(\n                attributeName: name,\n                attributeValue: value\n            )\n        ]\n    }\n\n    private func checkValid(_ isOn: Bool) -> Bool {\n        return isOn || self.info.validation.isRequired != true\n    }\n\n    private func updateFormState(_ isOn: Bool) {\n        let formValue: ThomasFormField.Value = .toggle(isOn)\n\n        let field: ThomasFormField = if checkValid(isOn) {\n            ThomasFormField.validField(\n                identifier: self.info.properties.identifier,\n                input: formValue,\n                result: .init(\n                    value: formValue,\n                    attributes: self.attributes\n                )\n           )\n        } else {\n            ThomasFormField.invalidField(\n                identifier: self.info.properties.identifier,\n                input: formValue\n            )\n        }\n\n        self.formDataCollector.updateField(field, pageID: pageID)\n    }\n\n    private func restoreFormState() {\n        guard\n            case .toggle(let value) = self.formState.field(\n                identifier: self.info.properties.identifier\n            )?.input\n        else {\n            self.updateFormState(self.isOn)\n            return\n        }\n\n        self.isOn = value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BlockAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Action that runs a block.\npublic final class BlockAction: AirshipAction {\n\n    private let block: @Sendable (ActionArguments) async throws -> AirshipJSON?\n    private let predicate: (@Sendable(ActionArguments) async -> Bool)?\n    \n    /**\n     * Block action constructor.\n     *  - Parameters:\n     *    - predicate: Optional predicate.\n     *    - block: The action block.\n     */\n    public init(\n        predicate:(@Sendable(ActionArguments) async -> Bool)? = nil,\n        block: @escaping @Sendable (ActionArguments) async throws -> AirshipJSON?) {\n            self.predicate = predicate\n            self.block = block\n        }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        return (await self.predicate?(arguments)) ?? true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        return try await self.block(arguments)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/BundleExtensions.swift",
    "content": "public import Foundation\n\npublic extension Bundle {\n\n    /// Returns the bundle for the AirshipModule.\n    /// NOTE: For internal use only. :nodoc:\n    static func airshipFindModule(\n        moduleName: String,\n        sourceBundle: Bundle\n    ) -> Bundle {\n        AirshipLogger.trace(\"Searching for module \\(moduleName) Bundle\")\n        let candidates = [\n            \"Airship_\\(moduleName)\", // SPM/Tuist\n            \"\\(moduleName)Resources\",  // Cocoapods\n            \"\\(moduleName)_\\(moduleName)\" // Fallback for SPM/Tuist\n        ]\n\n        for searchContainer in [sourceBundle, Bundle.main] {\n            for candidate in candidates {\n                guard\n                    let path = searchContainer.path(forResource: candidate, ofType: \"bundle\"),\n                    let bundle = Bundle(path: path)\n                else\n                {\n                    continue\n                }\n\n                AirshipLogger.trace(\"Found Bundle for \\(moduleName) in \\(searchContainer) with path \\(candidate)\")\n                return bundle\n            }\n        }\n\n        // Fallback to source bundle (XCFrameworks)\n        AirshipLogger.trace(\"Using source Bundle for \\(moduleName)\")\n        return sourceBundle\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ButtonLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Button layout view.\nstruct ButtonLayout : View {\n    @Environment(\\.isVoiceOverRunning) private var isVoiceOverRunning\n    @Environment(\\.layoutState) private var layoutState\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var pagerState: PagerState\n    @EnvironmentObject private var videoState: VideoState\n    @EnvironmentObject private var thomasState: ThomasState\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n\n    @State private var actionTask: Task<Void, Never>?\n\n    private let info: ThomasViewInfo.ButtonLayout\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.ButtonLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var isButtonForAccessibility: Bool {\n        guard let role = info.properties.accessibilityRole else {\n            // Default to button\n            return true\n        }\n\n        return switch(role) {\n        case .container:\n            false\n        case .button:\n            true\n        }\n    }\n\n    var body: some View {\n        if isVoiceOverRunning, !isButtonForAccessibility {\n            // Container mode\n            if let contentDescription = info.accessible.resolveContentDescription {\n                // Container WITH content description: Add accessibility action\n                ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                    .thomasCommon(self.info, scope: [.background, .visibility])\n                    .accessibilityElement(children: .contain)\n                    .accessibilityLabel(contentDescription)\n                    .accessibilityAction(named: contentDescription) {\n                        let previousTask = actionTask\n                        actionTask = Task { @MainActor in\n                            await previousTask?.value\n                            await performButtonAction()\n                        }\n                    }\n                    .accessibilityHidden(info.accessible.accessibilityHidden ?? false)\n            } else {\n                // Container WITHOUT content description: Transparent parent\n                ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                    .thomasCommon(self.info, scope: [.background, .visibility])\n                    .accessibilityHidden(info.accessible.accessibilityHidden ?? false)\n            }\n        } else {\n            AirshipButton(\n                identifier: self.info.properties.identifier,\n                reportingMetadata: self.info.properties.reportingMetadata,\n                description: self.info.accessible.resolveContentDescription,\n                clickBehaviors: self.info.properties.clickBehaviors,\n                eventHandlers: self.info.commonProperties.eventHandlers,\n                actions: self.info.properties.actions,\n                tapEffect: self.info.properties.tapEffect\n            ) {\n                ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                    .thomasCommon(self.info, scope: [.background])\n                    .background(Color.airshipTappableClear)\n            }\n            .thomasCommon(self.info, scope: [.enableBehaviors, .visibility])\n            .environment(\n                \\.layoutState,\n                 layoutState.override(\n                    buttonState: ButtonState(identifier: self.info.properties.identifier)\n                 )\n            )\n            .accessibilityHidden(info.accessible.accessibilityHidden ?? false)\n        }\n    }\n\n    @MainActor\n    private func performButtonAction() async {\n        // Form validation\n        if info.properties.clickBehaviors?.contains(.formSubmit) == true ||\n           info.properties.clickBehaviors?.contains(.formValidate) == true {\n            guard await formState.validate() else { return }\n        }\n\n        // Tap event handlers\n        let taps = info.commonProperties.eventHandlers?.filter { $0.type == .tap }\n        if let taps, !taps.isEmpty {\n            taps.forEach { tap in\n                thomasState.processStateActions(tap.stateActions)\n            }\n            await Task.yield()\n        }\n\n        // Button reporting\n        thomasEnvironment.buttonTapped(\n            buttonIdentifier: info.properties.identifier,\n            reportingMetadata: info.properties.reportingMetadata,\n            layoutState: layoutState\n        )\n\n        // Click behaviors\n        await handleBehaviors(info.properties.clickBehaviors ?? [])\n\n        // Actions\n        handleActions(info.properties.actions)\n    }\n\n    private func handleBehaviors(_ behaviors: [ThomasButtonClickBehavior]) async {\n        for behavior in behaviors {\n            switch(behavior) {\n            case .dismiss:\n                thomasEnvironment.dismiss(\n                    buttonIdentifier: info.properties.identifier,\n                    buttonDescription: info.accessible.resolveContentDescription ?? info.properties.identifier,\n                    cancel: false,\n                    layoutState: layoutState\n                )\n\n            case .cancel:\n                thomasEnvironment.dismiss(\n                    buttonIdentifier: info.properties.identifier,\n                    buttonDescription: info.accessible.resolveContentDescription ?? info.properties.identifier,\n                    cancel: true,\n                    layoutState: layoutState\n                )\n\n            case .pagerNext:\n                pagerState.process(request: .next)\n\n            case .pagerPrevious:\n                pagerState.process(request: .back)\n\n            case .pagerNextOrDismiss:\n                if pagerState.isLastPage {\n                    thomasEnvironment.dismiss(\n                        buttonIdentifier: info.properties.identifier,\n                        buttonDescription: info.accessible.resolveContentDescription ?? info.properties.identifier,\n                        cancel: false,\n                        layoutState: layoutState\n                    )\n                } else {\n                    pagerState.process(request: .next)\n                }\n\n            case .pagerNextOrFirst:\n                if pagerState.isLastPage {\n                    pagerState.process(request: .first)\n                } else {\n                    pagerState.process(request: .next)\n                }\n\n            case .pagerPause:\n                pagerState.pause()\n\n            case .pagerResume:\n                pagerState.resume()\n\n            case .pagerPauseToggle:\n                pagerState.togglePause()\n\n            case .formValidate:\n                break\n\n            case .formSubmit:\n                do {\n                    try await formState.submit(layoutState: layoutState)\n                } catch {\n                    AirshipLogger.error(\"Failed to submit \\(error)\")\n                }\n\n            case .videoPlay:\n                videoState.play()\n\n            case .videoPause:\n                videoState.pause()\n\n            case .videoTogglePlay:\n                videoState.togglePlay()\n\n            case .videoMute:\n                videoState.mute()\n\n            case .videoUnmute:\n                videoState.unmute()\n\n            case .videoToggleMute:\n                videoState.toggleMute()\n            }\n        }\n    }\n\n    private func handleActions(_ actionPayload: ThomasActionsPayload?) {\n        if let actionPayload {\n            thomasEnvironment.runActions(actionPayload, layoutState: layoutState)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ButtonState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@MainActor\nclass ButtonState: ObservableObject {\n    let identifier: String\n    init(identifier: String) {\n        self.identifier = identifier\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CachedList.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// A class that manages a list of cached values, where each value has its own expiry.\nfinal class CachedList<Value> where Value: Any {\n    private let date: any AirshipDateProtocol\n    private let lock: AirshipLock = AirshipLock()\n    private var cachedValues: [(Value, Date)] = []\n\n    var values: [Value] {\n        var result: [Value]!\n\n        lock.sync {\n            self.trim()\n            result = self.cachedValues.map { $0.0 }\n        }\n\n        return result\n    }\n\n    func append(_ value: Value, expiresIn: TimeInterval) {\n        let expiration = self.date.now.advanced(by: expiresIn)\n        lock.sync {\n            self.cachedValues.append((value, expiration))\n        }\n    }\n\n    private func trim() {\n        self.cachedValues.removeAll(where: {\n            return self.date.now >= $0.1\n        })\n    }\n\n    init(date: any AirshipDateProtocol = AirshipDate.shared) {\n        self.date = date\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CachedValue.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nfinal class CachedValue<Value>: @unchecked Sendable where Value: Any {\n    private let date: any AirshipDateProtocol\n    private let lock: AirshipLock = AirshipLock()\n    private var expiration: Date?\n\n    private var _value: Value?\n    var value: Value? {\n        get {\n            var cachedValue: Value?\n            lock.sync {\n                guard let expiration = expiration,\n                    self.date.now < expiration\n                else {\n                    self.expiration = nil\n                    self._value = nil\n                    return\n                }\n\n                cachedValue = _value\n            }\n\n            return cachedValue\n        }\n    }\n\n    var timeRemaining: TimeInterval {\n        var timeRemaining: TimeInterval = 0\n        lock.sync {\n            if let expiration = self.expiration {\n                timeRemaining = max(0, expiration.timeIntervalSince(self.date.now))\n            }\n\n        }\n        return timeRemaining\n    }\n\n    func set(value: Value, expiresIn: TimeInterval) {\n        lock.sync {\n            self.expiration = self.date.now.advanced(by: expiresIn)\n            self._value = value\n        }\n    }\n\n    func set(value: Value, expiration: Date) {\n        lock.sync {\n            self.expiration = expiration\n            self._value = value\n        }\n    }\n\n    func expireIf(predicate: (Value) -> Bool) {\n        lock.sync {\n            if let value = self._value, predicate(value) {\n                self._value = nil\n            }\n        }\n    }\n\n    func expire() {\n        lock.sync {\n            self._value = nil\n        }\n    }\n\n    init(date: any AirshipDateProtocol = AirshipDate.shared) {\n        self.date = date\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CachingSMSValidatorAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nactor CachingSMSValidatorAPIClient: SMSValidatorAPIClientProtocol {\n    private struct CacheEntry: Sendable, Equatable{\n        let msisdn: String\n        let sender: String?\n        let prefix: String?\n        let result: AirshipHTTPResponse<SMSValidatorAPIClientResult>\n    }\n\n    private let client: any SMSValidatorAPIClientProtocol\n    private var cache: [CacheEntry] = []\n    private let maxCachedEntries: UInt\n\n    init(\n        client: any SMSValidatorAPIClientProtocol,\n        maxCachedEntries: UInt = 10\n    ) {\n        self.client = client\n        self.maxCachedEntries = maxCachedEntries\n    }\n\n    func validateSMS(msisdn: String, sender: String) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        let result = if let cached = cachedResult(msisdn: msisdn, sender: sender) {\n            cached\n        } else {\n            try await client.validateSMS(msisdn: msisdn, sender: sender)\n        }\n\n        cacheResult(result, msisdn: msisdn, prefix: nil, sender: sender)\n\n        return result\n    }\n\n    func validateSMS(msisdn: String, prefix: String) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        let result = if let cached = cachedResult(msisdn: msisdn, prefix: prefix) {\n            cached\n        } else {\n            try await client.validateSMS(msisdn: msisdn, prefix: prefix)\n        }\n\n        cacheResult(result, msisdn: msisdn, prefix: prefix, sender: nil)\n        return result\n    }\n\n    private func cachedResult(msisdn: String, sender: String) -> AirshipHTTPResponse<SMSValidatorAPIClientResult>? {\n        return cache.first { entry in\n            entry.msisdn == msisdn && entry.sender == sender\n        }?.result\n    }\n\n    private func cachedResult(msisdn: String, prefix: String) -> AirshipHTTPResponse<SMSValidatorAPIClientResult>? {\n        return cache.first { entry in\n            entry.msisdn == msisdn && entry.prefix == prefix\n        }?.result\n    }\n\n    private func cacheResult(\n        _ result: AirshipHTTPResponse<SMSValidatorAPIClientResult>,\n        msisdn: String,\n        prefix: String?,\n        sender: String?\n    ) {\n        guard result.isSuccess else { return }\n\n        let entry = CacheEntry(\n            msisdn: msisdn,\n            sender: sender,\n            prefix: prefix,\n            result: result\n        )\n\n        cache.removeAll { $0 == entry }\n\n        cache.append(\n            entry\n        )\n\n        if cache.count > self.maxCachedEntries {\n            cache.remove(at: 0)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CancellableValueHolder.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Utility class that holds a value in a thread safe way. Once cancelled, setting a value\n/// on the holder will cause it to immediately be cancelled with the block and the value to be\n/// cleared.\n/// - Note: for internal use only.  :nodoc:\npublic final class CancellableValueHolder<T: Sendable>: AirshipCancellable, @unchecked Sendable {\n    private let lock: AirshipLock = AirshipLock()\n    private let onCancel: (T) -> Void\n    private var isCancelled: Bool = false\n    private var _value: T?\n    \n    public var value: T? {\n        get {\n            var value: T? = nil\n            lock.sync {\n                value = _value\n            }\n            return value\n        }\n        set {\n            lock.sync {\n                if isCancelled {\n                    if let value = newValue {\n                        onCancel(value)\n                    }\n                } else {\n                    _value = newValue\n                }\n            }\n        }\n    }\n    \n    \n    public init(value: T, onCancel: @escaping @Sendable (T) -> Void) {\n        self._value = value\n        self.onCancel = onCancel\n    }\n    \n    public init(onCancel: @escaping @Sendable (T) -> Void) {\n        self.onCancel = onCancel\n    }\n    \n    public func cancel() {\n        lock.sync {\n            guard isCancelled == false else { return }\n            isCancelled = true\n            if let value = value {\n                onCancel(value)\n                _value = nil\n            }\n        }\n    }\n    \n    public static func cancellableHolder() -> CancellableValueHolder<any AirshipCancellable> {\n        return CancellableValueHolder<any AirshipCancellable>() { cancellable in\n            cancellable.cancel()\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChallengeResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/**\n * Authentication challenge resolver class\n * @note For internal use only. :nodoc:\n */\npublic final class ChallengeResolver: NSObject, Sendable  {\n    \n    public static let shared: ChallengeResolver = ChallengeResolver()\n    \n    @MainActor\n    var resolver: ChallengeResolveClosure?\n    \n    private override init() {}\n    \n    public func resolve(_ challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n        guard\n            let resolver = await self.resolver,\n            challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,\n            challenge.protectionSpace.serverTrust != nil\n        else {\n            return (.performDefaultHandling, nil)\n        }\n        \n        return resolver(challenge)\n    }\n}\n\nextension ChallengeResolver: URLSessionTaskDelegate {\n    \n    public func urlSession(_ session: URLSession,\n                    didReceive challenge: URLAuthenticationChallenge)\n    async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n        \n        return await self.resolve(challenge)\n    }\n    \n    public func urlSession(_ session: URLSession,\n                             task: URLSessionTask,\n                             didReceive challenge: URLAuthenticationChallenge)\n    async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n        return await self.resolve(challenge)\n    }\n    \n}\n\n\npublic typealias ChallengeResolveClosure = @Sendable (URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?)\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: For internal use only. :nodoc:\nfinal class ChannelAPIClient: ChannelAPIClientProtocol, Sendable {\n    private let channelPath: String = \"/api/channels/\"\n\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(\n        config: RuntimeConfig,\n        session: any AirshipRequestSession\n    ) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    private func makeURL(path: String) throws -> URL {\n        guard let deviceAPIURL = self.config.deviceAPIURL else {\n            throw AirshipErrors.error(\"Initial config not resolved.\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(path)\"\n\n        guard let url = URL(string: \"\\(deviceAPIURL)\\(path)\") else {\n            throw AirshipErrors.error(\"Invalid ChannelAPIClient URL: \\(String(describing: urlString))\")\n        }\n\n        return url\n    }\n\n    func makeChannelLocation(channelID: String) throws -> URL {\n        return try makeURL(path: \"\\(self.channelPath)\\(channelID)\")\n    }\n\n\n    func createChannel(\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipHTTPResponse<ChannelAPIResponse> {\n        let url = try makeURL(path: self.channelPath)\n        let data = try JSONEncoder().encode(payload)\n\n        AirshipLogger.debug(\n            \"Creating channel with: \\(payload)\"\n        )\n\n        let request = AirshipRequest(\n            url: url,\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: data\n        )\n\n        return try await session.performHTTPRequest(\n            request\n        ) { data, response in\n            \n            AirshipLogger.debug(\"Channel creation finished with response: \\(response)\")\n\n            let status = response.statusCode\n            guard status == 200 || status == 201 else {\n                return nil\n            }\n\n            guard let data = data else {\n                throw AirshipErrors.parseError(\"Missing body\")\n            }\n\n            let json = try JSONSerialization.jsonObject(\n                with: data,\n                options: .allowFragments\n            ) as? [AnyHashable: Any]\n\n            guard let channelID = json?[\"channel_id\"] as? String else {\n                throw AirshipErrors.parseError(\"Missing channel_id\")\n            }\n\n            return ChannelAPIResponse(\n                channelID: channelID,\n                location: try self.makeChannelLocation(channelID: channelID)\n            )\n        }\n    }\n\n    func updateChannel(\n        _ channelID: String,\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipHTTPResponse<ChannelAPIResponse> {\n\n        let url = try makeChannelLocation(channelID: channelID)\n        let data = try JSONEncoder().encode(payload)\n\n        AirshipLogger.debug(\n            \"Updating channel \\(channelID) with: \\(payload)\"\n        )\n\n        let request = AirshipRequest(\n            url: url,\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"PUT\",\n            auth: .channelAuthToken(identifier: channelID),\n            body: data\n        )\n\n        return try await session.performHTTPRequest(request) { data, response in\n            \n            AirshipLogger.debug(\"Update channel finished with response: \\(response)\")\n            \n            return ChannelAPIResponse(\n                channelID: channelID,\n                location: url\n            )\n        }\n    }\n    \n    var isURLConfigured: Bool {\n        return self.config.deviceAPIURL?.isEmpty == false\n    }\n\n}\n\n/// - Note: For internal use only. :nodoc:\nprotocol ChannelAPIClientProtocol: Sendable {\n    func makeChannelLocation(channelID: String) throws -> URL\n\n    func createChannel(\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipHTTPResponse<ChannelAPIResponse>\n\n    func updateChannel(\n        _ channelID: String,\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipHTTPResponse<ChannelAPIResponse>\n\n    var isURLConfigured: Bool { get }\n}\n\nstruct ChannelAPIResponse {\n    let channelID: String\n    let location: URL\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelAudienceManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@preconcurrency import Combine\nimport Foundation\n\nprotocol ChannelAudienceManagerProtocol: AnyObject, Sendable {\n    var channelID: String? { get set }\n\n    var enabled: Bool { get set }\n\n    var subscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never> { get }\n    \n    func addLiveActivityUpdate(_ update: LiveActivityUpdate)\n\n    func editSubscriptionLists() -> SubscriptionListEditor\n\n    func editTagGroups(allowDeviceGroup: Bool) -> TagGroupsEditor\n\n    func editAttributes() -> AttributesEditor\n\n    func fetchSubscriptionLists() async throws -> [String]\n\n    func clearSubscriptionListCache()\n\n    var liveActivityUpdates: AsyncStream<[LiveActivityUpdate]> { get }\n}\n\n/// NOTE: For internal use only. :nodoc:\nfinal class ChannelAudienceManager: ChannelAudienceManagerProtocol {\n    static let updateTaskID: String = \"ChannelAudienceManager.update\"\n    static let updatesKey: String = \"UAChannel.audienceUpdates\"\n\n    static let legacyPendingTagGroupsKey: String =\n        \"com.urbanairship.tag_groups.pending_channel_tag_groups_mutations\"\n    static let legacyPendingAttributesKey: String =\n        \"com.urbanairship.channel_attributes.registrar_persistent_queue_key\"\n\n    static let maxCacheTime: TimeInterval = 600  // 10 minutes\n\n    private let dataStore: PreferenceDataStore\n    private let privacyManager: any AirshipPrivacyManager\n    private let workManager: any AirshipWorkManagerProtocol\n    private let subscriptionListProvider: any ChannelSubscriptionListProviderProtocol\n    private let updateClient: any ChannelBulkUpdateAPIClientProtocol\n    private let audienceOverridesProvider: any AudienceOverridesProvider\n\n    private let date: any AirshipDateProtocol\n    private let updateLock: AirshipLock = AirshipLock()\n\n    private let cachedSubscriptionLists: CachedValue<[String]>\n\n    private let subscriptionListEditsSubject: PassthroughSubject<\n        SubscriptionListEdit, Never\n    > = PassthroughSubject<SubscriptionListEdit, Never>()\n\n    var subscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never> {\n        self.subscriptionListEditsSubject.eraseToAnyPublisher()\n    }\n\n    private let _channelID: AirshipAtomicValue<String?> = AirshipAtomicValue(nil)\n    var channelID: String? {\n        get {\n            _channelID.value\n        }\n        set {\n            if (_channelID.setValue(newValue)) {\n                self.enqueueTask()\n            }\n        }\n    }\n\n    private let _enabled: AirshipAtomicValue<Bool> = AirshipAtomicValue(false)\n    var enabled: Bool {\n        get {\n            _enabled.value\n        }\n        set {\n            if (_enabled.setValue(newValue)) {\n                self.enqueueTask()\n            }\n        }\n    }\n\n    let liveActivityUpdates: AsyncStream<[LiveActivityUpdate]>\n    private let liveActivityUpdatesContinuation: AsyncStream<[LiveActivityUpdate]>.Continuation\n\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        workManager: any AirshipWorkManagerProtocol,\n        subscriptionListProvider: any ChannelSubscriptionListProviderProtocol,\n        updateClient: any ChannelBulkUpdateAPIClientProtocol,\n        privacyManager: any AirshipPrivacyManager,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        audienceOverridesProvider: any AudienceOverridesProvider\n    ) {\n        self.dataStore = dataStore\n        self.workManager = workManager\n        self.privacyManager = privacyManager\n        self.subscriptionListProvider = subscriptionListProvider\n        self.updateClient = updateClient\n        self.date = date\n        self.cachedSubscriptionLists = CachedValue(date: date)\n        self.audienceOverridesProvider = audienceOverridesProvider\n        (self.liveActivityUpdates, self.liveActivityUpdatesContinuation) = AsyncStream<[LiveActivityUpdate]>.airshipMakeStreamWithContinuation()\n\n        self.workManager.registerWorker(\n            ChannelAudienceManager.updateTaskID\n        ) { [weak self] _ in\n            return try await self?.handleUpdateTask() ?? .success\n        }\n\n        self.workManager.autoDispatchWorkRequestOnBackground(\n            AirshipWorkRequest(workID: ChannelAudienceManager.updateTaskID)\n        )\n\n        self.migrateMutations()\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(checkPrivacyManager),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(enqueueTask),\n            name: RuntimeConfig.configUpdatedEvent,\n            object: nil\n        )\n\n        self.checkPrivacyManager()\n\n        Task {\n            await self.audienceOverridesProvider.setPendingChannelOverridesProvider { channelID in\n                return self.pendingOverrides(channelID: channelID)\n            }\n        }\n    }\n\n    @MainActor\n    convenience init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        privacyManager: any AirshipPrivacyManager,\n        audienceOverridesProvider: any AudienceOverridesProvider\n    ) {\n        self.init(\n            dataStore: dataStore,\n            workManager: AirshipWorkManager.shared,\n            subscriptionListProvider: ChannelSubscriptionListProvider(\n                audienceOverrides: audienceOverridesProvider,\n                apiClient: SubscriptionListAPIClient(config: config)),\n            updateClient: ChannelBulkUpdateAPIClient(config: config),\n            privacyManager: privacyManager,\n            audienceOverridesProvider: audienceOverridesProvider\n        )\n    }\n\n    func editSubscriptionLists() -> SubscriptionListEditor {\n        return SubscriptionListEditor { updates in\n            guard !updates.isEmpty else {\n                return\n            }\n\n            guard self.privacyManager.isEnabled(.tagsAndAttributes) else {\n                AirshipLogger.warn(\n                    \"Tags and attributes are disabled. Enable to apply subscription list edits.\"\n                )\n                return\n            }\n\n            self.addUpdate(\n                AudienceUpdate(subscriptionListUpdates: updates)\n            )\n\n            Task { @MainActor in\n                updates.forEach {\n                    switch $0.type {\n                    case .subscribe:\n                        self.subscriptionListEditsSubject.send(\n                            .subscribe($0.listId)\n                        )\n                    case .unsubscribe:\n                        self.subscriptionListEditsSubject.send(\n                            .unsubscribe($0.listId)\n                        )\n                    }\n                }\n            }\n\n            self.enqueueTask()\n        }\n    }\n\n    func editTagGroups(allowDeviceGroup: Bool) -> TagGroupsEditor {\n        return TagGroupsEditor(allowDeviceTagGroup: allowDeviceGroup) {\n            updates in\n            guard !updates.isEmpty else {\n                return\n            }\n\n            guard self.privacyManager.isEnabled(.tagsAndAttributes) else {\n                AirshipLogger.warn(\n                    \"Tags and attributes are disabled. Enable to apply tag group edits.\"\n                )\n                return\n            }\n\n            self.addUpdate(\n                AudienceUpdate(tagGroupUpdates: updates)\n            )\n            self.enqueueTask()\n        }\n    }\n\n    func editAttributes() -> AttributesEditor {\n        return AttributesEditor { updates in\n            guard !updates.isEmpty else {\n                return\n            }\n\n            guard self.privacyManager.isEnabled(.tagsAndAttributes) else {\n                AirshipLogger.warn(\n                    \"Tags and attributes are disabled. Enable to apply attribute edits.\"\n                )\n                return\n            }\n\n            self.addUpdate(\n                AudienceUpdate(attributeUpdates: updates)\n            )\n            self.enqueueTask()\n        }\n    }\n\n    func fetchSubscriptionLists() async throws -> [String] {\n        guard let channelID = self.channelID else {\n            throw AirshipErrors.error(\"Channel not created yet\")\n        }\n\n        return try await subscriptionListProvider.fetch(channelID: channelID)\n    }\n\n    func pendingOverrides(channelID: String) -> ChannelAudienceOverrides {\n        guard self.channelID == channelID else {\n            return ChannelAudienceOverrides()\n        }\n\n        var tags: [TagGroupUpdate] = []\n        var attributes: [AttributeUpdate] = []\n        var subscriptionLists: [SubscriptionListUpdate] = []\n\n        self.updateLock.sync {\n            self.getUpdates().forEach { update in\n                attributes += update.attributeUpdates\n                tags += update.tagGroupUpdates\n                subscriptionLists += update.subscriptionListUpdates\n            }\n        }\n\n        return ChannelAudienceOverrides(\n            tags: tags,\n            attributes: attributes,\n            subscriptionLists: subscriptionLists\n        )\n    }\n\n    @objc\n    private func checkPrivacyManager() {\n        if !self.privacyManager.isEnabled(.tagsAndAttributes) {\n            updateLock.sync {\n                self.dataStore.removeObject(\n                    forKey: ChannelAudienceManager.updatesKey\n                )\n            }\n        }\n    }\n\n    @objc\n    private func enqueueTask() {\n        if self.enabled && self.channelID != nil {\n            self.workManager.dispatchWorkRequest(\n                AirshipWorkRequest(\n                    workID: ChannelAudienceManager.updateTaskID,\n                    requiresNetwork: true\n                )\n            )\n        }\n    }\n\n    private func handleUpdateTask() async throws -> AirshipWorkResult {\n        guard self.enabled,\n              let channelID = self.channelID,\n              let update = self.prepareNextUpdate()\n        else {\n            return .success\n        }\n\n        let response = try await self.updateClient.update(\n            update,\n            channelID: channelID\n        )\n\n        AirshipLogger.debug(\n            \"Update finished with response: \\(response)\"\n        )\n\n        guard response.isSuccess else {\n            return response.isServerError ? .failure : .success\n        }\n\n        if (!update.liveActivityUpdates.isEmpty) {\n            self.liveActivityUpdatesContinuation.yield(update.liveActivityUpdates)\n        }\n\n        await self.audienceOverridesProvider.channelUpdated(\n            channelID: channelID,\n            tags: update.tagGroupUpdates,\n            attributes: update.attributeUpdates,\n            subscriptionLists: update.subscriptionListUpdates\n        )\n\n        self.popFirstUpdate()\n        self.enqueueTask()\n\n        return .success\n    }\n\n    private func addUpdate(_ update: AudienceUpdate) {\n        guard !update.isEmpty else { return }\n        self.updateLock.sync {\n            var updates = getUpdates()\n            updates.append(update)\n            self.storeUpdates(updates)\n        }\n    }\n\n    func addLiveActivityUpdate(_ update: LiveActivityUpdate) {\n        AirshipLogger.debug(\"Live activity update: \\(update)\")\n        self.addUpdate(\n            AudienceUpdate(liveActivityUpdates: [update])\n        )\n        self.enqueueTask()\n    }\n\n    private func getUpdates() -> [AudienceUpdate] {\n        var result: [AudienceUpdate]?\n        updateLock.sync {\n            if let data = self.dataStore.data(\n                forKey: ChannelAudienceManager.updatesKey\n            ) {\n                result = try? JSONDecoder().decode(\n                    [AudienceUpdate].self,\n                    from: data\n                )\n            }\n        }\n        return result ?? []\n    }\n\n    private func storeUpdates(_ operations: [AudienceUpdate]) {\n        updateLock.sync {\n            if let data = try? JSONEncoder().encode(operations) {\n                self.dataStore.setObject(\n                    data,\n                    forKey: ChannelAudienceManager.updatesKey\n                )\n            }\n        }\n    }\n\n    private func popFirstUpdate() {\n        updateLock.sync {\n            var updates = getUpdates()\n            if !updates.isEmpty {\n                updates.removeFirst()\n                storeUpdates(updates)\n            }\n        }\n    }\n\n    private func prepareNextUpdate() -> AudienceUpdate? {\n        var nextUpdate: AudienceUpdate? = nil\n        updateLock.sync {\n            let updates = self.getUpdates()\n            if let collapsed = AudienceUpdate.collapse(updates) {\n                self.storeUpdates([collapsed])\n                nextUpdate = collapsed\n            } else {\n                self.storeUpdates([])\n            }\n        }\n\n        if !self.privacyManager.isEnabled(.tagsAndAttributes) {\n            nextUpdate?.attributeUpdates = []\n            nextUpdate?.tagGroupUpdates = []\n            nextUpdate?.attributeUpdates = []\n        }\n\n        guard nextUpdate?.isEmpty == false else {\n            return nil\n        }\n\n        return nextUpdate\n    }\n\n    func migrateMutations() {\n        defer {\n            self.dataStore.removeObject(\n                forKey: ChannelAudienceManager.legacyPendingTagGroupsKey\n            )\n            self.dataStore.removeObject(\n                forKey: ChannelAudienceManager.legacyPendingAttributesKey\n            )\n        }\n\n        if self.privacyManager.isEnabled(.tagsAndAttributes) {\n            var pendingTagUpdates: [TagGroupUpdate]?\n            var pendingAttributeUpdates: [AttributeUpdate]?\n\n            if let pendingTagGroupsData = self.dataStore.data(\n                forKey: ChannelAudienceManager.legacyPendingTagGroupsKey\n            ) {\n\n                let classes = [NSArray.self, TagGroupsMutation.self]\n                let pendingTagGroups = try? NSKeyedUnarchiver.unarchivedObject(\n                    ofClasses: classes,\n                    from: pendingTagGroupsData\n                )\n\n                if let pendingTagGroups = pendingTagGroups\n                    as? [TagGroupsMutation]\n                {\n                    pendingTagUpdates =\n                        pendingTagGroups.map { $0.tagGroupUpdates }\n                        .reduce([], +)\n                }\n            }\n\n            if let pendingAttributesData = self.dataStore.data(\n                forKey: ChannelAudienceManager.legacyPendingAttributesKey\n            ) {\n\n                let classes = [NSArray.self, AttributePendingMutations.self]\n                let pendingAttributes = try? NSKeyedUnarchiver.unarchivedObject(\n                    ofClasses: classes,\n                    from: pendingAttributesData\n                )\n\n                if let pendingAttributes = pendingAttributes\n                    as? [AttributePendingMutations]\n                {\n                    pendingAttributeUpdates =\n                        pendingAttributes.map {\n                            $0.attributeUpdates\n                        }\n                        .reduce([], +)\n                }\n            }\n\n            let update = AudienceUpdate(\n                tagGroupUpdates: pendingTagUpdates ?? [],\n                attributeUpdates: pendingAttributeUpdates ?? []\n            )\n            addUpdate(update)\n        }\n    }\n\n    func clearSubscriptionListCache() {\n        self.cachedSubscriptionLists.expire()\n    }\n}\n\ninternal struct AudienceUpdate: Codable {\n    var subscriptionListUpdates: [SubscriptionListUpdate]\n    var tagGroupUpdates: [TagGroupUpdate]\n    var attributeUpdates: [AttributeUpdate]\n    var liveActivityUpdates: [LiveActivityUpdate]\n\n    init(\n        subscriptionListUpdates: [SubscriptionListUpdate] = [],\n        tagGroupUpdates: [TagGroupUpdate] = [],\n        attributeUpdates: [AttributeUpdate] = [],\n        liveActivityUpdates: [LiveActivityUpdate] = []\n    ) {\n        self.subscriptionListUpdates = subscriptionListUpdates\n        self.tagGroupUpdates = tagGroupUpdates\n        self.attributeUpdates = attributeUpdates\n        self.liveActivityUpdates = liveActivityUpdates\n    }\n\n    var isEmpty: Bool {\n        return subscriptionListUpdates.isEmpty && tagGroupUpdates.isEmpty\n            && attributeUpdates.isEmpty && liveActivityUpdates.isEmpty\n    }\n\n    static func collapse(_ updates: [AudienceUpdate]) -> AudienceUpdate? {\n        var subscriptionListUpdates: [SubscriptionListUpdate] = []\n        var tagGroupUpdates: [TagGroupUpdate] = []\n        var attributeUpdates: [AttributeUpdate] = []\n        var liveActivityUpdates: [LiveActivityUpdate] = []\n\n        updates.forEach { update in\n            subscriptionListUpdates.append(\n                contentsOf: update.subscriptionListUpdates\n            )\n            tagGroupUpdates.append(contentsOf: update.tagGroupUpdates)\n            attributeUpdates.append(contentsOf: update.attributeUpdates)\n            liveActivityUpdates.append(contentsOf: update.liveActivityUpdates)\n        }\n\n        let collapsed = AudienceUpdate(\n            subscriptionListUpdates: AudienceUtils.collapse(\n                subscriptionListUpdates\n            ),\n            tagGroupUpdates: AudienceUtils.collapse(tagGroupUpdates),\n            attributeUpdates: AudienceUtils.collapse(attributeUpdates),\n            liveActivityUpdates: AudienceUtils.normalize(liveActivityUpdates)\n        )\n\n        return collapsed.isEmpty ? nil : collapsed\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelAuthTokenAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nfinal class ChannelAuthTokenAPIClient: ChannelAuthTokenAPIClientProtocol, Sendable {\n    private let tokenPath: String = \"/api/auth/device\"\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(\n        config: RuntimeConfig,\n        session: any AirshipRequestSession\n    ) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    private func makeURL(path: String) throws -> URL {\n        guard let deviceAPIURL = self.config.deviceAPIURL else {\n            throw AirshipErrors.error(\"Initial config not resolved.\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(path)\"\n\n        guard let url = URL(string: \"\\(deviceAPIURL)\\(path)\") else {\n            throw AirshipErrors.error(\"Invalid ChannelAPIClient URL: \\(String(describing: urlString))\")\n        }\n\n        return url\n    }\n\n\n    ///\n    /// Retrieves the token associated with the provided channel ID.\n    /// - Parameters:\n    ///   - channelID: The channel ID.\n    /// - Returns: AuthToken if succeed otherwise it throws an error\n    func fetchToken(\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<ChannelAuthTokenResponse> {\n        let url = try makeURL(path: self.tokenPath)\n        let request = AirshipRequest(\n            url: url,\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            ],\n            method: \"GET\",\n            auth: .generatedChannelToken(identifier: channelID)\n        )\n\n        return try await session.performHTTPRequest(request) { data, response in\n            \n            AirshipLogger.debug(\"Channel auth token request finished with response: \\(response)\")\n\n            guard response.statusCode == 200 else {\n                return nil\n            }\n\n            return try AirshipJSONUtils.decode(data: data)\n        }\n    }\n}\n\nstruct ChannelAuthTokenResponse: Decodable, Sendable {\n    let token: String\n    let expiresInMillseconds: UInt\n\n    enum CodingKeys: String, CodingKey {\n        case token = \"token\"\n        case expiresInMillseconds = \"expires_in\"\n    }\n}\n\n/// - Note: For internal use only. :nodoc:\nprotocol ChannelAuthTokenAPIClientProtocol: Sendable {\n    func fetchToken(\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<ChannelAuthTokenResponse>\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelAuthTokenProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nfinal class ChannelAuthTokenProvider: AuthTokenProvider {\n    private let cachedAuthToken: CachedValue<AuthToken>\n\n    private let channel: any AirshipChannel\n    private let apiClient: any ChannelAuthTokenAPIClientProtocol\n    private let date: any AirshipDateProtocol\n\n    init(\n        channel: any AirshipChannel,\n        apiClient: any ChannelAuthTokenAPIClientProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.channel = channel\n        self.apiClient = apiClient\n        self.cachedAuthToken = CachedValue(date: date)\n        self.date = date\n    }\n\n    convenience init(\n        channel: any AirshipChannel,\n        runtimeConfig: RuntimeConfig\n    ) {\n        self.init(channel: channel, apiClient: ChannelAuthTokenAPIClient(config: runtimeConfig))\n    }\n\n    func resolveAuth(identifier: String) async throws -> String {\n        guard self.channel.identifier == identifier else {\n            throw AirshipErrors.error(\"Unable to generate auth for stale channel \\(identifier)\")\n        }\n\n        if let token = self.cachedAuthToken.value,\n           token.identifier == identifier,\n           self.cachedAuthToken.timeRemaining >= 30\n        {\n            return token.token\n        }\n\n        let response = try await self.apiClient.fetchToken(channelID: identifier)\n\n        guard response.isSuccess, let result = response.result else {\n            throw AirshipErrors.error(\"Failed to fetch auth token for channel \\(identifier)\")\n        }\n\n        let token = AuthToken(\n            identifier: identifier,\n            token: result.token,\n            expiration: self.date.now.advanced(by: \n                Double(result.expiresInMillseconds/1000)\n            )\n        )\n\n        self.cachedAuthToken.set(value: token, expiration: token.expiration)\n        return result.token\n    }\n\n    func authTokenExpired(token: String) async {\n        self.cachedAuthToken.expireIf { auth in\n            return auth.token == token\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelBulkUpdateAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nprotocol ChannelBulkUpdateAPIClientProtocol: Sendable {\n    func update(\n        _ update: AudienceUpdate,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void>\n}\n\n/// NOTE: For internal use only. :nodoc:\nfinal class ChannelBulkUpdateAPIClient: ChannelBulkUpdateAPIClientProtocol {\n    private static let path: String = \"/api/channels/sdk/batch/\"\n\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    func update(\n        _ update: AudienceUpdate,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void> {\n        let url = try makeURL(channelID: channelID)\n        let payload = update.clientPayload\n\n        let encoder = JSONEncoder()\n        let data = try encoder.encode(payload)\n\n        AirshipLogger.debug(\n            \"Updating channel with url \\(url.absoluteString) payload \\(String(data: data, encoding: .utf8) ?? \"\")\"\n        )\n\n        let request = AirshipRequest(\n            url: url,\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"PUT\",\n            auth: .channelAuthToken(identifier: channelID),\n            body: try encoder.encode(payload)\n        )\n        let result = try await session.performHTTPRequest(request)\n        AirshipLogger.debug(\n            \"Updating channel finished with result \\(result)\"\n        )\n        return result\n    }\n\n    func makeURL(channelID: String) throws -> URL {\n        guard let deviceUrl = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"URL config not downloaded.\")\n        }\n\n        var urlComps = URLComponents(\n            string: \"\\(deviceUrl)\\(ChannelBulkUpdateAPIClient.path)\\(channelID)\"\n        )\n        urlComps?.queryItems = [URLQueryItem(name: \"platform\", value: \"ios\")]\n\n        guard let url = urlComps?.url else {\n            throw AirshipErrors.error(\"Invalid url from \\(String(describing: urlComps)).\")\n        }\n\n        return url\n    }\n}\n\nextension AudienceUpdate {\n    fileprivate var clientPayload: ClientPayload {\n        var subscriptionLists: [SubscriptionListOperation]?\n        if (!self.subscriptionListUpdates.isEmpty) {\n            subscriptionLists = self.subscriptionListUpdates.map { $0.operation }\n        }\n\n        var attributes: [AttributeOperation]?\n        if (!self.attributeUpdates.isEmpty) {\n            attributes = self.attributeUpdates.map { $0.operation }\n        }\n\n        var tags: TagGroupOverrides?\n        if (!self.tagGroupUpdates.isEmpty) {\n            tags = TagGroupOverrides.from(updates: self.tagGroupUpdates)\n        }\n\n        var liveActivities: [LiveActivityUpdate]?\n        if (!self.liveActivityUpdates.isEmpty) {\n            liveActivities = self.liveActivityUpdates\n        }\n\n\n        return ClientPayload(\n            tags: tags,\n            subscriptionLists: subscriptionLists,\n            attributes: attributes,\n            liveActivities: liveActivities\n        )\n    }\n}\n\nprivate struct ClientPayload: Encodable {\n    var tags: TagGroupOverrides?\n    var subscriptionLists: [SubscriptionListOperation]?\n    var attributes: [AttributeOperation]?\n    var liveActivities: [LiveActivityUpdate]?\n\n    enum CodingKeys: String, CodingKey {\n        case tags = \"tags\"\n        case subscriptionLists = \"subscription_lists\"\n        case attributes = \"attributes\"\n        case liveActivities = \"live_activities\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelCapture.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n#if !os(watchOS) \n\n@available(tvOS, unavailable)\n@MainActor\npublic protocol AirshipChannelCapture: Sendable {\n    \n    /**\n     * Flag indicating whether channel capture is enabled. Clear to disable. Set to enable.\n     * Note: Does not persist through app launches.\n     */\n    var enabled: Bool { get set }\n}\n\n/// Channel Capture copies the channelId to the device clipboard after a specific number of\n/// knocks (app foregrounds) within a specific timeframe. Channel Capture can be enabled\n/// or disabled in Airship Config.\n@available(tvOS, unavailable)\nfinal public class DefaultAirshipChannelCapture: AirshipChannelCapture {\n    private static let knocksToTriggerChannelCapture: Int = 6\n    private static let knocksMaxTimeSeconds: TimeInterval = 30\n    private static let pasteboardExpirationSeconds: TimeInterval = 60\n\n    private let config: RuntimeConfig\n    private let channel: any AirshipChannel\n    private let notificationCenter: NotificationCenter\n    private let date: any AirshipDateProtocol\n    private let pasteboard: any AirshipPasteboardProtocol\n\n    @MainActor\n    private var knockTimes: [Date] = []\n\n    @MainActor\n    public var enabled: Bool {\n        didSet {\n            AirshipLogger.trace(\"Channel capture enabled: \\(enabled)\")\n        }\n    }\n\n    init(\n        config: RuntimeConfig,\n        channel: any AirshipChannel,\n        notificationCenter: NotificationCenter = NotificationCenter.default,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        pasteboard: (any AirshipPasteboardProtocol) = DefaultAirshipPasteboard()\n    ) {\n        self.config = config\n        self.channel = channel\n        self.notificationCenter = notificationCenter\n        self.date = date\n        self.pasteboard = pasteboard\n        self.enabled = config.airshipConfig.isChannelCaptureEnabled\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationDidTransitionToForeground),\n            name: AppStateTracker.didTransitionToForeground,\n            object: nil\n        )\n    }\n\n    @objc\n    @MainActor\n    private func applicationDidTransitionToForeground() {\n        guard enabled else {\n            AirshipLogger.trace(\"Channel Capture disabled, ignoring foreground.\")\n            return\n        }\n\n        if knockTimes.count >= DefaultAirshipChannelCapture.knocksToTriggerChannelCapture {\n            knockTimes.remove(at: 0)\n        }\n\n        AirshipLogger.trace(\"Channel Capture capturing foreground at time \\(date.now)\")\n        knockTimes.append(date.now)\n\n        if knockTimes.count < DefaultAirshipChannelCapture.knocksToTriggerChannelCapture {\n            return\n        }\n\n        let firstKnock = knockTimes[0]\n        let lastKnock = knockTimes[DefaultAirshipChannelCapture.knocksToTriggerChannelCapture - 1]\n\n        if lastKnock.timeIntervalSince(firstKnock) > DefaultAirshipChannelCapture.knocksMaxTimeSeconds {\n            return\n        }\n\n        knockTimes.removeAll()\n\n        let identifier = \"ua:\\(channel.identifier ?? \"\")\"\n        AirshipLogger.debug(\n            \"Channel Capture setting channel ID:\\(identifier) to pasteboard.\"\n        )\n        AirshipLogger.debug(\"Channel Capture setting channel ID:\\(identifier) to pasteboard.\")\n\n        self.pasteboard.copy(\n            value: identifier,\n            expiry: DefaultAirshipChannelCapture.pasteboardExpirationSeconds\n        )\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelRegistrar.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nprotocol ChannelRegistrarProtocol: AnyObject, Sendable {\n    var channelID: String? { get }\n    var registrationUpdates: AirshipAsyncChannel<ChannelRegistrationUpdate> { get }\n\n    @MainActor\n    var payloadCreateBlock: (@Sendable () async -> ChannelRegistrationPayload?)? { get set }\n\n    func register(forcefully: Bool)\n}\n\nenum ChannelRegistrationUpdate: Equatable, Sendable {\n    case created(channelID: String, isExisting: Bool)\n    case updated(channelID: String)\n}\n\n/// The ChannelRegistrar class is responsible for device registrations.\n/// - Note: For internal use only. :nodoc:\nfinal class ChannelRegistrar: ChannelRegistrarProtocol, Sendable {\n    static let workID: String = \"UAChannelRegistrar.registration\"\n    private static let payloadCadence: TimeInterval = 24 * 60 * 60\n\n    fileprivate static let forcefullyKey: String = \"forcefully\"\n    private static let channelIDKey: String = \"UAChannelID\"\n    private static let lastRegistrationInfo: String = \"ChannelRegistrar.lastRegistrationInfo\"\n\n\n    private let dataStore: PreferenceDataStore\n    private let channelAPIClient: any ChannelAPIClientProtocol\n    private let date: any AirshipDateProtocol\n    private let workManager: any AirshipWorkManagerProtocol\n    private let appStateTracker: any AppStateTrackerProtocol\n\n    @MainActor\n    private var checkAppRestoreTask: Task<Void, Never>?\n    \n    private let channelCreateMethod: (@Sendable () async throws -> ChannelGenerationMethod)?\n\n    @MainActor\n    var payloadCreateBlock: (@Sendable () async -> ChannelRegistrationPayload?)?\n\n    private var lastRegistrationInfo: LastRegistrationInfo? {\n        get {\n            do {\n                return try self.dataStore.codable(\n                    forKey: ChannelRegistrar.lastRegistrationInfo\n                )\n            } catch {\n                AirshipLogger.error(\"Unable to load last registration info \\(error)\")\n                return nil\n            }\n        }\n        set {\n            do {\n                try self.dataStore.setCodable(\n                    newValue,\n                    forKey: ChannelRegistrar.lastRegistrationInfo\n                )\n            } catch {\n                AirshipLogger.error(\"Unable to store last registration info \\(error)\")\n            }\n        }\n    }\n    /**\n     * The channel ID for this device.\n     */\n    var channelID: String? {\n        get {\n            self.dataStore.string(forKey: ChannelRegistrar.channelIDKey)\n        }\n        set {\n            self.dataStore.setObject(\n                newValue,\n                forKey: ChannelRegistrar.channelIDKey\n            )\n        }\n    }\n\n    let registrationUpdates: AirshipAsyncChannel<ChannelRegistrationUpdate> = .init()\n\n    private let privacyManager: any AirshipPrivacyManager\n\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        channelAPIClient: any ChannelAPIClientProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        workManager: any AirshipWorkManagerProtocol = AirshipWorkManager.shared,\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        channelCreateMethod: AirshipChannelCreateOptionClosure? = nil,\n        privacyManager: any AirshipPrivacyManager\n    ) {\n        self.dataStore = dataStore\n        self.channelAPIClient = channelAPIClient\n        self.date = date\n        self.workManager = workManager\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n        self.channelCreateMethod = channelCreateMethod\n\n        self.privacyManager = privacyManager\n\n        if self.channelID != nil {\n            checkAppRestoreTask = Task { [weak self] in\n                if await self?.dataStore.isAppRestore == true {\n                    self?.clearChannelData()\n                }\n            }\n        }\n\n        self.workManager.registerWorker(\n            ChannelRegistrar.workID\n        ) { [weak self] request in\n           return try await self?.handleRegistrationWorkRequest(request) ?? .success\n        }\n    }\n\n    @MainActor\n    convenience init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        privacyManager: any AirshipPrivacyManager\n    ) {\n        self.init(\n            dataStore: dataStore,\n            channelAPIClient: ChannelAPIClient(config: config),\n            channelCreateMethod: config.airshipConfig.restoreChannelID,\n            privacyManager: privacyManager\n        )\n    }\n\n    /**\n     * Register the device with Airship.\n     *\n     * - Note: This method will execute asynchronously on the main thread.\n     *\n     * - Parameter forcefully: YES to force the registration.\n     */\n    func register(forcefully: Bool) {\n        guard self.channelAPIClient.isURLConfigured else {\n            return\n        }\n                \n        self.workManager.dispatchWorkRequest(\n            AirshipWorkRequest(\n                workID: ChannelRegistrar.workID,\n                extras: [\n                    ChannelRegistrar.forcefullyKey: String(forcefully)\n                ],\n                requiresNetwork: true,\n                conflictPolicy: forcefully ? .replace : .keepIfNotStarted\n            )\n        )\n    }\n\n    private func handleRegistrationWorkRequest(\n        _ workRequest: AirshipWorkRequest\n    ) async throws -> AirshipWorkResult {\n\n        _ = await self.checkAppRestoreTask?.value\n\n        let payload = try await self.makePayload()\n\n        guard let channelID = self.channelID else {\n            return try await self.createChannel(\n                payload: payload\n            )\n        }\n\n        let forcefully = workRequest.extras?[\n            ChannelRegistrar.forcefullyKey\n        ]?.lowercased() == \"true\"\n\n        let updatePayload = try await makeNextUpdatePayload(\n            channelID: channelID,\n            forcefully: forcefully,\n            payload: payload,\n            lastRegistrationInfo: self.lastRegistrationInfo\n        )\n\n        guard let updatePayload = updatePayload else {\n            AirshipLogger.debug(\n                \"Ignoring registration request, registration is up to date.\"\n            )\n            return .success\n        }\n\n        return try await self.updateChannel(\n            channelID,\n            payload: payload,\n            minimizedPayload: updatePayload\n        )\n    }\n\n    private func updateChannel(\n        _ channelID: String,\n        payload: ChannelRegistrationPayload,\n        minimizedPayload: ChannelRegistrationPayload\n    ) async throws -> AirshipWorkResult {\n        let response = try await self.channelAPIClient.updateChannel(\n            channelID,\n            payload: minimizedPayload\n        )\n\n        AirshipLogger.debug(\"Channel update request finished with response: \\(response)\")\n        let fullPayloadUploadDate = payload == minimizedPayload ? self.date.now : self.lastRegistrationInfo?.lastFullPayloadSent \n\n        if response.isSuccess, let result = response.result {\n            await self.registrationSuccess(\n                channelID: channelID,\n                registrationInfo: LastRegistrationInfo(\n                    date: self.date.now,\n                    lastFullPayloadSent: fullPayloadUploadDate,\n                    payload: payload,\n                    location: result.location\n                )\n            )\n\n            await registrationUpdates.send(.updated(channelID: channelID))\n\n            return .success\n        } else if response.statusCode == 409 {\n            AirshipLogger.trace(\"Channel conflict, recreating\")\n            self.clearChannelData()\n            self.register(forcefully: true)\n            return .success\n        } else {\n            if response.isServerError || response.statusCode == 429 {\n                return .failure\n            } else {\n                return .success\n            }\n        }\n    }\n\n    private func createChannel(\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipWorkResult {\n        \n        let method = try await channelCreateMethod?() ?? .automatic\n        \n        guard \n            case .restore(let channelID) = method,\n            method.isValid,\n            let result = try await tryRestoreChannel(channelID, payload: payload)\n        else {\n            return try await regularCreateChannel(payload: payload)\n        }\n        \n        return result\n    }\n    \n    private func tryRestoreChannel(\n        _ channelId: String,\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipWorkResult? {\n        \n        let response: AirshipHTTPResponse<ChannelAPIResponse> = .init(\n            result: .init(\n                channelID: channelId,\n                location: try channelAPIClient.makeChannelLocation(channelID: channelId)),\n            statusCode: 200,\n            headers: [:]\n        )\n        \n        guard \n            await onNewChannelID(payload: payload, response: response) == .success,\n            let nextPayload = try await self.makeNextUpdatePayload(\n                channelID: channelId,\n                forcefully: true,\n                payload: payload,\n                lastRegistrationInfo: lastRegistrationInfo)\n        else {\n            return nil\n        }\n        \n        return try await updateChannel(channelId, payload: payload, minimizedPayload: nextPayload)\n    }\n    \n    private func regularCreateChannel(\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipWorkResult {\n        \n        let response = try await self.channelAPIClient.createChannel(\n            payload: payload\n        )\n\n        AirshipLogger.debug(\"Channel create request finished with response: \\(response)\")\n\n        return await onNewChannelID(payload: payload, response: response)\n    }\n    \n    private func onNewChannelID(\n        payload: ChannelRegistrationPayload,\n        response: AirshipHTTPResponse<ChannelAPIResponse>\n    ) async -> AirshipWorkResult {\n        \n        guard response.isSuccess, let result = response.result else {\n            if response.isServerError || response.statusCode == 429 {\n                return .failure\n            } else {\n                return .success\n            }\n        }\n        \n        self.channelID = result.channelID\n\n        await registrationUpdates.send(\n            .created(\n                channelID: result.channelID,\n                isExisting: response.statusCode == 200\n            )\n        )\n\n        await self.registrationSuccess(\n            channelID: result.channelID,\n            registrationInfo: LastRegistrationInfo(\n                date: self.date.now,\n                lastFullPayloadSent: self.date.now,\n                payload: payload,\n                location: result.location\n            )\n        )\n        \n        return .success\n    }\n\n    private func clearChannelData() {\n        self.channelID = nil\n        self.lastRegistrationInfo = nil\n    }\n\n    private func registrationSuccess(\n        channelID: String,\n        registrationInfo: LastRegistrationInfo\n    ) async {\n        self.lastRegistrationInfo = registrationInfo\n        let nextUploadPayload = try? await self.makeNextUpdatePayload(\n            channelID: channelID,\n            forcefully: false,\n            payload: await makePayload(),\n            lastRegistrationInfo: registrationInfo\n        )\n\n        if (nextUploadPayload != nil) {\n            self.register(forcefully: false)\n        }\n    }\n\n    @MainActor\n    private func makePayload() async throws -> ChannelRegistrationPayload {\n        guard let payloadCreateBlock, let payload = await payloadCreateBlock() else {\n            throw AirshipErrors.error(\"Failed to make a payload\")\n        }\n        return payload\n    }\n\n    private func makeNextUpdatePayload(\n        channelID: String,\n        forcefully: Bool,\n        payload: ChannelRegistrationPayload,\n        lastRegistrationInfo: LastRegistrationInfo?\n    ) async throws -> ChannelRegistrationPayload? {\n        let currentLocation = try self.channelAPIClient.makeChannelLocation(\n            channelID: channelID\n        )\n\n        // If no channel registrations are enabled - skip the cadence check\n        guard privacyManager.isAnyFeatureEnabled() else {\n            return if lastRegistrationInfo?.location != currentLocation || lastRegistrationInfo?.payload != payload {\n                payload.minimizePayload(previous: lastRegistrationInfo?.payload)\n            } else {\n                nil\n            }\n        }\n\n        guard let lastRegistrationInfo = lastRegistrationInfo,\n              currentLocation == lastRegistrationInfo.location,\n              let lastFullPayloadSent = lastRegistrationInfo.lastFullPayloadSent,\n              self.date.now.timeIntervalSince(lastFullPayloadSent) <= Self.payloadCadence\n        else {\n            return payload\n        }\n\n        let timeSinceLastUpdate = self.date.now.timeIntervalSince(\n            lastRegistrationInfo.date\n        )\n\n        let isActive = await self.appStateTracker.state == .active\n        let shouldUpdateForActive = isActive && timeSinceLastUpdate >= Self.payloadCadence\n\n        guard forcefully || shouldUpdateForActive || payload != lastRegistrationInfo.payload else {\n            return nil\n        }\n\n        return payload.minimizePayload(previous: lastRegistrationInfo.payload)\n    }\n\n    fileprivate struct LastRegistrationInfo: Codable {\n        let date: Date\n        let lastFullPayloadSent: Date?\n        let payload: ChannelRegistrationPayload\n        let location: URL\n    }\n}\n\nfileprivate extension ChannelGenerationMethod {\n    var isValid: Bool {\n        switch self {\n        case .automatic: return true\n        case .restore(let id): return UUID(uuidString: id) != nil\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelRegistrationPayload.swift",
    "content": "import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ChannelRegistrationPayload: Codable, Equatable, Sendable {\n\n    public var channel: ChannelInfo\n\n    public var identityHints: IdentityHints?\n\n    enum CodingKeys: String, CodingKey {\n        case channel = \"channel\"\n        case identityHints = \"identity_hints\"\n    }\n\n    public init() {\n        self.channel = ChannelInfo()\n    }\n\n    public func minimizePayload(\n        previous: ChannelRegistrationPayload?\n    ) -> ChannelRegistrationPayload {\n        guard let previous = previous else {\n            return self\n        }\n\n        var minPayload = self\n        minPayload.channel = self.channel.minimize(\n            previous: previous.channel\n        )\n        minPayload.identityHints = nil\n        return minPayload\n    }\n\n    /// NOTE: For internal use only. :nodoc:\n    public struct ChannelInfo: Codable, Equatable, Sendable {\n\n        var deviceType: String = \"ios\"\n\n        /// This flag indicates that the user is able to receive push notifications.\n        public var isOptedIn: Bool = false\n\n        /// This flag indicates that the user is able to receive background notifications.\n        public var isBackgroundEnabled: Bool = false\n\n        /// The address to push notifications to.  This should be the device token.\n        public var pushAddress: String?\n\n        /// The flag indicates tags in this request should be handled.\n        public var setTags: Bool = false\n\n        /// The tags for this device.\n        public var tags: [String]?\n\n        /// Tag changes.\n        public var tagChanges: TagChanges?\n\n        /// The locale language for this device.\n        public var language: String?\n\n        /// The locale country for this device.\n        public var country: String?\n\n        /// The time zone for this device.\n        public var timeZone: String?\n\n        /// The flag indicating if the user is active.\n        public var isActive: Bool = false\n\n        /// The app version.\n        public var appVersion: String?\n\n        /// The sdk version.\n        public var sdkVersion: String?\n\n        /// The device model.\n        public var deviceModel: String?\n\n        /// The device OS.\n        public var deviceOS: String?\n\n        public var contactID: String?\n        public var iOSChannelSettings: iOSChannelSettings?\n        public var permissions: [String: String]?\n\n        enum CodingKeys: String, CodingKey {\n            case deviceType = \"device_type\"\n            case isOptedIn = \"opt_in\"\n            case isBackgroundEnabled = \"background\"\n            case pushAddress = \"push_address\"\n            case setTags = \"set_tags\"\n            case tags = \"tags\"\n            case tagChanges = \"tag_changes\"\n            case language = \"locale_language\"\n            case country = \"locale_country\"\n            case timeZone = \"timezone\"\n            case appVersion = \"app_version\"\n            case sdkVersion = \"sdk_version\"\n            case deviceModel = \"device_model\"\n            case deviceOS = \"device_os\"\n            case contactID = \"contact_id\"\n            case iOSChannelSettings = \"ios\"\n            case isActive = \"is_activity\"\n            case permissions = \"permissions\"\n        }\n\n        fileprivate func minimize(previous: ChannelInfo?) -> ChannelInfo {\n            guard let previous = previous else { return self }\n            var channel = self\n\n            if channel.setTags && previous.setTags {\n                if channel.tags == previous.tags {\n                    channel.tags = nil\n                    channel.setTags = false\n                } else {\n                    let channelTags = channel.tags ?? []\n                    let previousTags = previous.tags ?? []\n                    let adds = channelTags.filter { !previousTags.contains($0) }\n                    let removes = previousTags.filter { !channelTags.contains($0) }\n                    channel.tagChanges = TagChanges(adds: adds, removes: removes)\n                }\n            }\n\n            if channel.contactID == previous.contactID {\n                if channel.language == previous.language {\n                    channel.language = nil\n                }\n                if channel.country == previous.country {\n                    channel.country = nil\n                }\n                if channel.timeZone == previous.timeZone {\n                    channel.timeZone = nil\n                }\n                if channel.appVersion == previous.appVersion {\n                    channel.appVersion = nil\n                }\n                if channel.sdkVersion == previous.sdkVersion {\n                    channel.sdkVersion = nil\n                }\n                if channel.deviceModel == previous.deviceModel {\n                    channel.deviceModel = nil\n                }\n                if channel.deviceOS == previous.deviceOS {\n                    channel.deviceOS = nil\n                }\n            }\n            \n            if previous.permissions == channel.permissions {\n                channel.permissions = nil\n            }\n\n            channel.iOSChannelSettings = channel.iOSChannelSettings?.minimize(\n                previous: previous.iOSChannelSettings\n            )\n\n            return channel\n        }\n    }\n\n    /// NOTE: For internal use only. :nodoc:\n    public struct TagChanges: Codable, Equatable, Sendable {\n        let adds: [String]?\n        let removes: [String]?\n\n        enum CodingKeys: String, CodingKey {\n            case adds = \"add\"\n            case removes = \"remove\"\n        }\n\n        init?(adds: [String], removes: [String]) {\n            guard !adds.isEmpty || removes.isEmpty else {\n                return nil\n            }\n\n            self.adds = adds.isEmpty ? nil : adds\n            self.removes = removes.isEmpty ? nil : removes\n        }\n    }\n\n    /// NOTE: For internal use only. :nodoc:\n    public struct iOSChannelSettings: Codable, Equatable, Sendable {\n        /// Quiet time settings for this device.\n        public var quietTime: QuietTime?\n\n        /// Quiet time time zone.\n        public var quietTimeTimeZone: String?\n\n        /// The badge for this device.\n        public var badge: Int?\n        public var isScheduledSummary: Bool?\n        public var isTimeSensitive: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case quietTime = \"quiettime\"\n            case quietTimeTimeZone = \"tz\"\n            case badge = \"badge\"\n            case isScheduledSummary = \"scheduled_summary\"\n            case isTimeSensitive = \"time_sensitive\"\n        }\n\n        func minimize(previous: iOSChannelSettings?) -> iOSChannelSettings {\n            guard let previous = previous else { return self }\n\n            var minimized = self\n\n            if minimized.isScheduledSummary == previous.isScheduledSummary {\n                minimized.isScheduledSummary = nil\n            }\n\n            if minimized.isTimeSensitive == previous.isTimeSensitive {\n                minimized.isTimeSensitive = nil\n            }\n\n            return minimized\n        }\n    }\n\n    /// NOTE: For internal use only. :nodoc:\n    public struct IdentityHints: Codable, Equatable, Sendable {\n        /// The user ID.\n        public var userID: String?\n\n        public init(userID: String? = nil) {\n            self.userID = userID\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case userID = \"user_id\"\n        }\n    }\n\n    /// NOTE: For internal use only. :nodoc:\n    public struct QuietTime: Codable, Equatable, Sendable {\n        public var start: String\n        public var end: String\n\n        enum CodingKeys: String, CodingKey {\n            case start = \"start\"\n            case end = \"end\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelScope.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Channel scope.\npublic enum ChannelScope: String, Codable, Sendable, Equatable {\n    /**\n     * App channels - amazon, android, iOS\n     */\n    case app\n\n    /**\n     * Web channels\n     */\n    case web\n\n    /**\n     * Email channels\n     */\n    case email\n\n    /**\n     * SMS channels\n     */\n    case sms\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelSubscriptionListProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n/**\n * Subscription list provider protocol for receiving contact updates.\n * @note For internal use only. :nodoc:\n */\nprotocol ChannelSubscriptionListProviderProtocol: Sendable {\n    func fetch(channelID: String) async throws -> [String]\n}\n\nfinal class ChannelSubscriptionListProvider: ChannelSubscriptionListProviderProtocol {\n\n    private let actor: BaseCachingRemoteDataProvider<ChannelSubscriptionListResult, ChannelAudienceOverrides>\n    private let overridesApplier: OverridesApplier = OverridesApplier()\n    \n    init(\n        audienceOverrides: any AudienceOverridesProvider,\n        apiClient: any SubscriptionListAPIClientProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared,\n        maxChannelListCacheAgeSeconds: TimeInterval = 600\n    ) {\n        \n        self.actor = BaseCachingRemoteDataProvider(\n            remoteFetcher: { channelID in\n                return try await apiClient\n                    .get(channelID: channelID)\n                    .map(onMap: { response in\n                        guard let result = response.result else {\n                            return nil\n                        }\n                        \n                        return .success(result)\n                    })\n            },\n            overridesProvider: { channelID in\n                return AsyncStream { continuation in\n                    Task {\n                        let override = await audienceOverrides.channelOverrides(channelID: channelID)\n                        continuation.yield(override)\n                        continuation.finish()\n                    }\n                }\n            },\n            overridesApplier: { [overridesApplier] result, overrides in\n                guard\n                    case .success(let list) = result\n                else {\n                    return result\n                }\n                \n                return .success(overridesApplier.applySubscriptionListUpdates(list, updates: overrides.subscriptionLists))\n            },\n            isEnabled: { true },\n            date: date,\n            taskSleeper: taskSleeper,\n            cacheTtl: maxChannelListCacheAgeSeconds\n        )\n    }\n    \n    \n    func fetch(channelID: String) async throws -> [String] {\n        var stream = actor.updates(identifierUpdates: AsyncStream { continuation in\n            continuation.yield(channelID)\n            continuation.finish()\n        })\n            .makeAsyncIterator()\n        \n        guard let result = await stream.next() else {\n            throw AirshipErrors.error(\"Failed to get subscription list\")\n        }\n        \n        switch result {\n        case .fail(let error): throw error\n        case .success(let list): return list\n        }\n    }\n}\n\nenum ChannelSubscriptionListResult: Equatable, Sendable, Hashable, CachingRemoteDataProviderResult {\n    static func error(_ error: CachingRemoteDataError) -> any CachingRemoteDataProviderResult {\n        return ChannelSubscriptionListResult.fail(error)\n    }\n    \n    case success([String])\n    case fail(CachingRemoteDataError)\n\n    public var subscriptionList: [String] {\n        get throws {\n            switch(self) {\n            case .fail(let error): throw error\n            case .success(let list): return list\n            }\n        }\n    }\n\n    public var isSuccess: Bool {\n        switch(self) {\n        case .fail(_): return false\n        case .success(_): return true\n        }\n    }\n}\n\nprivate struct OverridesApplier {\n    \n    func applySubscriptionListUpdates(\n        _ ids: [String],\n        updates: [SubscriptionListUpdate]\n    ) -> [String] {\n        guard !updates.isEmpty else {\n            return ids\n        }\n\n        var result = ids\n        updates.forEach { update in\n            switch update.type {\n            case .subscribe:\n                if !result.contains(update.listId) {\n                    result.append(update.listId)\n                }\n            case .unsubscribe:\n                result.removeAll(where: { $0 == update.listId })\n            }\n        }\n\n        return result\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ChannelType.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Channel type\npublic enum ChannelType: String, Codable, Sendable, Equatable {\n\n    /**\n     * Email channel\n     */\n    case email\n\n    /**\n     * SMS channel\n     */\n    case sms\n\n    /**\n     * Open channel\n     */\n    case open\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Checkbox.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct Checkbox: View {\n    private let info: ThomasViewInfo.Checkbox\n    private let constraints: ViewConstraints\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var checkboxState: CheckboxState\n    @EnvironmentObject private var thomasState: ThomasState\n\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    init(info: ThomasViewInfo.Checkbox, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .checkbox,\n            thomasState: thomasState\n        )\n    }\n\n    private var isOnBinding: Binding<Bool> {\n        self.checkboxState.makeBinding(\n            identifier: nil,\n            reportingValue: info.properties.reportingValue\n        )\n    }\n\n    private var isEnabled: Bool {\n        let isSelected = self.checkboxState.isSelected(\n            reportingValue: info.properties.reportingValue\n        )\n\n        return isSelected || !self.checkboxState.isMaxSelectionReached\n    }\n    \n\n    var body: some View {\n        Toggle(isOn: self.isOnBinding.animation()) {}\n            .thomasToggleStyle(\n                self.info.properties.style,\n                constraints: self.constraints\n            )\n            .constraints(constraints)\n            .thomasCommon(self.info)\n            .accessible(\n                self.info.accessible,\n                associatedLabel: associatedLabel,\n                hideIfDescriptionIsMissing: false\n            )\n            .formElement()\n            .disabled(!self.isEnabled)\n            .accessibilityRemoveTraits(.isSelected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CheckboxController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct CheckboxController: View {\n    private let info: ThomasViewInfo.CheckboxController\n    private let constraints: ViewConstraints\n\n    @EnvironmentObject private var environment: ThomasEnvironment\n\n    init(info: ThomasViewInfo.CheckboxController, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: constraints,\n            environment: environment\n        )\n        .id(info.properties.identifier)\n        .accessibilityElement(children: .contain)\n    }\n\n    @MainActor\n    struct Content: View {\n        private let info: ThomasViewInfo.CheckboxController\n        private let constraints: ViewConstraints\n\n        @Environment(\\.pageIdentifier) private var pageID\n        @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n        @EnvironmentObject private var formState: ThomasFormState\n        @EnvironmentObject private var thomasState: ThomasState\n        @ObservedObject private var checkboxState: CheckboxState\n        @EnvironmentObject private var validatableHelper: ValidatableHelper\n        @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n        private var associatedLabel: String? {\n            associatedLabelResolver?.labelFor(\n                identifier: info.properties.identifier,\n                viewType: .checkboxController,\n                thomasState: thomasState\n            )\n        }\n\n        init(\n            info: ThomasViewInfo.CheckboxController,\n            constraints: ViewConstraints,\n            environment: ThomasEnvironment\n        ) {\n            self.info = info\n            self.constraints = constraints\n\n            // Use the environment to create or retrieve the state in case the view\n            // stack changes and we lose our state.\n            let checkboxState = environment.retrieveState(identifier: info.properties.identifier) {\n                CheckboxState(\n                    minSelection: info.properties.minSelection,\n                    maxSelection: info.properties.maxSelection\n                )\n            }\n\n            self._checkboxState = ObservedObject(wrappedValue: checkboxState)\n        }\n\n        var body: some View {\n            ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                .constraints(constraints)\n                .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n                .accessible(\n                    self.info.accessible,\n                    associatedLabel: associatedLabel,\n                    hideIfDescriptionIsMissing: false\n                )\n                .formElement()\n                .environmentObject(checkboxState)\n                .airshipOnChangeOf(self.checkboxState.selected) { incoming in\n                    updateFormState(selected: incoming)\n                }\n                .onAppear {\n                    updateFormState(selected: self.checkboxState.selected)\n                    if self.formState.validationMode == .onDemand {\n                        validatableHelper.subscribe(\n                            forIdentifier: info.properties.identifier,\n                            formState: formState,\n                            initialValue: checkboxState.selected,\n                            valueUpdates: checkboxState.$selected,\n                            validatables: info.validation\n                        ) { [weak thomasState, weak checkboxState] actions in\n                            guard let thomasState, let checkboxState else { return }\n                            thomasState.processStateActions(\n                                actions,\n                                formFieldValue: .multipleCheckbox(\n                                    Set(checkboxState.selected.map { $0.reportingValue })\n                                )\n                            )\n                        }\n                    }\n                }\n        }\n\n        private func checkValid(_ value: Set<AirshipJSON>) -> Bool {\n            let min = info.properties.minSelection ?? 0\n            let max = info.properties.maxSelection ?? Int.max\n\n            guard value.count >= min, value.count <= max else {\n                return false\n            }\n\n            guard !value.isEmpty else {\n                return info.validation.isRequired != true\n            }\n\n            return true\n        }\n\n        private func updateFormState(selected: Set<CheckboxState.Selected>) {\n            let value: Set<AirshipJSON> = Set(selected.map { $0.reportingValue })\n            let formValue: ThomasFormField.Value = .multipleCheckbox(value)\n            let field: ThomasFormField = if checkValid(value) {\n                ThomasFormField.validField(\n                    identifier: self.info.properties.identifier,\n                    input: formValue,\n                    result: .init(\n                        value: formValue\n                    )\n                )\n            } else {\n                ThomasFormField.invalidField(\n                    identifier: self.info.properties.identifier,\n                    input: formValue\n                )\n            }\n\n            self.formDataCollector.updateField(field, pageID: pageID)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CheckboxState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n@MainActor\nclass CheckboxState: ObservableObject {\n\n    let minSelection: Int\n    let maxSelection: Int\n\n    @Published\n    var selected: Set<Selected> = Set()\n\n    init(minSelection: Int?, maxSelection: Int?) {\n        self.minSelection = minSelection ?? 0\n        self.maxSelection = maxSelection ?? Int.max\n    }\n\n    var isMaxSelectionReached: Bool {\n        selected.count >= maxSelection\n    }\n\n    func isSelected(identifier: String) -> Bool {\n        return selected.contains { item in\n            item.identifier == identifier\n        }\n    }\n\n    func isSelected(reportingValue: AirshipJSON) -> Bool {\n        return selected.contains { item in\n            item.reportingValue == reportingValue\n        }\n    }\n\n    struct Selected: ThomasSerializable, Hashable {\n        var identifier: String?\n        var reportingValue: AirshipJSON\n    }\n}\n\nextension CheckboxState {\n    func makeBinding(\n        identifier: String?,\n        reportingValue: AirshipJSON\n    ) -> Binding<Bool> {\n        return Binding<Bool>(\n            get: {\n                if let identifier {\n                    self.isSelected(\n                        identifier: identifier\n                    )\n                } else {\n                    self.isSelected(\n                        reportingValue: reportingValue\n                    )\n                }\n            },\n            set: {\n                let selected = Selected(\n                    identifier: identifier,\n                    reportingValue: reportingValue\n                )\n                if $0 {\n                    self.selected.insert(selected)\n                } else {\n                    self.selected.remove(selected)\n                }\n            }\n        )\n    }\n}\n\n// MARK: - ThomasStateProvider\nextension CheckboxState: ThomasStateProvider {\n    typealias SnapshotType = Set<Selected>\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return $selected\n            .removeDuplicates()\n            .map(\\.self)\n            .eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> SnapshotType {\n        return selected\n    }\n    \n    func restorePersistentState(_ state: SnapshotType) {\n        self.selected = state\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CheckboxToggleLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct CheckboxToggleLayout: View {\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var checkboxState: CheckboxState\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .checkboxToggleLayout,\n            thomasState: thomasState\n        )\n    }\n\n    private let info: ThomasViewInfo.CheckboxToggleLayout\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.CheckboxToggleLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var isOnBinding: Binding<Bool> {\n        self.checkboxState.makeBinding(\n            identifier: info.properties.identifier,\n            reportingValue: info.properties.reportingValue\n        )\n    }\n\n    private var isEnabled: Bool {\n        let isSelected = self.checkboxState.isSelected(\n            identifier: info.properties.identifier\n        )\n\n        return isSelected || !self.checkboxState.isMaxSelectionReached\n    }\n\n    var body: some View {\n        ToggleLayout(\n            isOn: self.isOnBinding,\n            onToggleOn: self.info.properties.onToggleOn,\n            onToggleOff: self.info.properties.onToggleOff\n        ) {\n            ViewFactory.createView(\n                self.info.properties.view,\n                constraints: constraints\n            )\n        }\n        .constraints(self.constraints)\n        .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n        .accessible(\n            self.info.accessible,\n            associatedLabel: associatedLabel,\n            hideIfDescriptionIsMissing: false\n        )\n        .formElement()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CircularRegion.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// A  circular region defines a radius, and latitude and longitude from its center.\npublic class CircularRegion {\n\n    let radius: Double\n    let latitude: Double\n    let longitude: Double\n\n    /**\n     * Default constructor.\n     *\n     * - Parameter radius: The radius of the circular region in meters.\n     * - Parameter latitude: The latitude of the circular region's center point in degrees.\n     * - Parameter longitude: The longitude of the circular region's center point in degrees.\n     *\n     * - Returns: Circular region object or `nil` if error occurs\n     */\n    public init?(radius: Double, latitude: Double, longitude: Double) {\n        guard CircularRegion.isValid(radius: radius) else {\n            return nil\n        }\n\n        guard EventUtils.isValid(latitude: latitude) else {\n            return nil\n        }\n\n        guard EventUtils.isValid(longitude: longitude) else {\n            return nil\n        }\n\n        self.radius = radius\n        self.latitude = latitude\n        self.longitude = longitude\n    }\n\n    /**\n     * Factory method for creating a circular region.\n     *\n     * - Parameter radius: The radius of the circular region in meters.\n     * - Parameter latitude: The latitude of the circular region's center point in degrees.\n     * - Parameter longitude: The longitude of the circular region's center point in degrees.\n     *\n     * - Returns: Circular region object or `nil` if error occurs\n     */\n    public class func circularRegion(\n        radius: Double,\n        latitude: Double,\n        longitude: Double\n    ) -> CircularRegion? {\n        return CircularRegion(\n            radius: radius,\n            latitude: latitude,\n            longitude: longitude\n        )\n    }\n\n    class func isValid(radius: Double) -> Bool {\n        guard radius >= 0.1 && radius <= 100000 else {\n            AirshipLogger.error(\n                \"Invalid radius \\(radius). Must be between .1 and 100000\"\n            )\n            return false\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CloudSite.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents the possible sites.\npublic enum CloudSite: String, Sendable, Decodable {\n    /// Represents the US cloud site. This is the default value.\n    /// Projects available at go.airship.com must use this value.\n    case us\n    /// Represents the EU cloud site.\n    /// Projects available at go.airship.eu must use this value.\n    case eu\n    \n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        do {\n            let stringValue = try container.decode(String.self)\n            switch(stringValue.lowercased()) {\n            case \"us\":\n                self = .us\n            case \"eu\":\n                self = .eu\n            default:\n                self = .us\n            }\n        } catch {\n            guard let intValue = try? container.decode(Int.self) else {\n                throw error\n            }\n            \n            switch(intValue) {\n                \n            case 0:\n                self = .us\n            case 1:\n                self = .eu\n            default:\n                throw error\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CompoundDeviceAudienceSelector.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Compound audience selector\npublic indirect enum CompoundDeviceAudienceSelector: Sendable, Codable, Equatable {\n    /// Atomic selector. Defines an actual audience selector.\n    case atomic(DeviceAudienceSelector)\n\n    /// NOT selector. Negates the results.\n    case not(CompoundDeviceAudienceSelector)\n\n    /// AND selector. All selectors have to evaluate true to match. If empty, evaluates to true.\n    case and([CompoundDeviceAudienceSelector])\n\n    /// OR selector. At least once selector has to evaluate true to match. If empty, evaluates to false.\n    case or([CompoundDeviceAudienceSelector])\n\n    enum CodingKeys: String, CodingKey {\n        case type\n        case audience\n        case selector\n        case selectors\n    }\n    \n    private enum SelectorType: String, RawRepresentable, Codable {\n        case atomic\n        case not\n        case and\n        case or\n    }\n    \n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(SelectorType.self, forKey: .type)\n        switch type {\n        case .atomic:\n            self = .atomic(try container.decode(DeviceAudienceSelector.self, forKey: .audience))\n        case .not:\n            self = .not(try container.decode(CompoundDeviceAudienceSelector.self, forKey: .selector))\n        case .and:\n            self = .and(try container.decode([CompoundDeviceAudienceSelector].self, forKey: .selectors))\n        case .or:\n            self = .or(try container.decode([CompoundDeviceAudienceSelector].self, forKey: .selectors))\n        }\n    }\n    \n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        \n        switch self {\n        case .atomic(let content):\n            try container.encode(SelectorType.atomic, forKey: .type)\n            try container.encode(content, forKey: .audience)\n        case .not(let content):\n            try container.encode(SelectorType.not, forKey: .type)\n            try container.encode(content, forKey: .selector)\n        case .and(let content):\n            try container.encode(SelectorType.and, forKey: .type)\n            try container.encode(content, forKey: .selectors)\n        case .or(let content):\n            try container.encode(SelectorType.or, forKey: .type)\n            try container.encode(content, forKey: .selectors)\n        }\n    }\n}\n\npublic extension CompoundDeviceAudienceSelector {\n    /// Combines old and new selector into a CompoundDeviceAudienceSelector\n    /// - Parameters:\n    ///     - compoundSelector: An optional `CompoundDeviceAudienceSelector`.\n    ///     - deviceSelector: An optional `DeviceAudienceSelector`.\n    /// - Returns: A `CompoundDeviceAudienceSelector` if either provided selector\n    ///  is non nill, otherwise nil.\n    static func combine(\n        compoundSelector: CompoundDeviceAudienceSelector?,\n        deviceSelector: DeviceAudienceSelector?\n    ) -> CompoundDeviceAudienceSelector? {\n        if let compoundSelector, let deviceSelector {\n            return .and([.atomic(deviceSelector), compoundSelector])\n        } else if let compoundSelector {\n            return compoundSelector\n        } else if let deviceSelector {\n            return .atomic(deviceSelector)\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nprotocol ContactsAPIClientProtocol: Sendable {\n    func resolve(\n        channelID: String,\n        contactID: String?,\n        possiblyOrphanedContactID: String?\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult>\n\n    func identify(\n        channelID: String,\n        namedUserID: String,\n        contactID: String?,\n        possiblyOrphanedContactID: String?\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult>\n\n    func reset(\n        channelID: String,\n        possiblyOrphanedContactID: String?\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult>\n\n    func update(\n        contactID: String,\n        tagGroupUpdates: [TagGroupUpdate]?,\n        attributeUpdates: [AttributeUpdate]?,\n        subscriptionListUpdates: [ScopedSubscriptionListUpdate]?\n    ) async throws ->  AirshipHTTPResponse<Void>\n\n    func associateChannel(\n        contactID: String,\n        channelID: String,\n        channelType: ChannelType\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult>\n\n    func registerEmail(\n        contactID: String,\n        address: String,\n        options: EmailRegistrationOptions,\n        locale: Locale\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult>\n\n    func registerSMS(\n        contactID: String,\n        msisdn: String,\n        options: SMSRegistrationOptions,\n        locale: Locale\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult>\n\n    func registerOpen(\n        contactID: String,\n        address: String,\n        options: OpenRegistrationOptions,\n        locale: Locale\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult>\n\n    func disassociateChannel(\n        contactID: String,\n        disassociateOptions: DisassociateOptions\n    ) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult>\n\n    func resend(\n        resendOptions: ResendOptions\n    ) async throws ->  AirshipHTTPResponse<Bool>\n}\n\n/// NOTE: For internal use only. :nodoc:\nfinal class ContactAPIClient: ContactsAPIClientProtocol {\n\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    private var decoder: JSONDecoder {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in\n            let container = try decoder.singleValueContainer()\n            let dateStr = try container.decode(String.self)\n\n            guard let date = AirshipDateFormatter.date(fromISOString: dateStr) else {\n                throw AirshipErrors.error(\"Invalid date \\(dateStr)\")\n            }\n            return date\n        })\n        return decoder\n    }\n\n    private var encoder: JSONEncoder {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .custom({ date, encoder in\n            var container = encoder.singleValueContainer()\n            try container.encode(\n                AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter)\n            )\n        })\n        return encoder\n    }\n\n    init(config: RuntimeConfig, session: (any AirshipRequestSession)? = nil) {\n        self.config = config\n        self.session = session ?? config.requestSession\n    }\n\n    func resolve(\n        channelID: String,\n        contactID: String?,\n        possiblyOrphanedContactID: String?\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult> {\n        return try await self.performIdentify(\n            channelID: channelID,\n            identifyRequest: .resolve(contactID: contactID, possiblyOrphanedContactID: possiblyOrphanedContactID)\n        )\n    }\n\n    func reset(\n        channelID: String,\n        possiblyOrphanedContactID: String?\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult> {\n        return try await self.performIdentify(\n            channelID: channelID,\n            identifyRequest: .reset(possiblyOrphanedContactID: possiblyOrphanedContactID)\n        )\n    }\n\n    func identify(\n        channelID: String,\n        namedUserID: String,\n        contactID: String?,\n        possiblyOrphanedContactID: String?\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult> {\n        return try await self.performIdentify(\n            channelID: channelID,\n            identifyRequest: .identify(namedUserID: namedUserID, contactID: contactID, possiblyOrphanedContactID: possiblyOrphanedContactID)\n        )\n    }\n\n    func update(\n        contactID: String,\n        tagGroupUpdates: [TagGroupUpdate]?,\n        attributeUpdates: [AttributeUpdate]?,\n        subscriptionListUpdates: [ScopedSubscriptionListUpdate]?\n    ) async throws ->  AirshipHTTPResponse<Void> {\n        let requestBody = ContactUpdateRequestBody(\n            attributes: try attributeUpdates?.toRequestBody(),\n            tags: tagGroupUpdates?.toRequestBody(),\n            subscriptionLists: subscriptionListUpdates?.toRequestBody(),\n            associate: nil\n        )\n\n        return try await performUpdate(contactID: contactID, requestBody: requestBody)\n    }\n\n    func associateChannel(\n        contactID: String,\n        channelID: String,\n        channelType: ChannelType\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult> {\n        let requestBody = ContactUpdateRequestBody(\n            attributes: nil,\n            tags: nil,\n            subscriptionLists: nil,\n            associate: [\n                ContactUpdateRequestBody.AssociateChannelOperation(\n                    deviceType: channelType,\n                    channelID: channelID\n                )\n            ]\n        )\n\n        return try await performUpdate(\n            contactID: contactID,\n            requestBody: requestBody\n        ).map { response in\n            if (response.isSuccess) {\n                return ContactAssociateChannelResult(channelType: channelType, channelID: channelID)\n            } else {\n                return nil\n            }\n        }\n    }\n\n    func registerEmail(\n        contactID: String,\n        address: String,\n        options: EmailRegistrationOptions,\n        locale: Locale\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await performChannelRegistration(\n            contactID: contactID,\n            requestBody: EmailChannelRegistrationBody(\n                address: address,\n                options: options,\n                locale: locale,\n                timezone: TimeZone.current.identifier\n            ),\n            channelType: .email\n        )\n    }\n\n    func registerSMS(\n        contactID: String,\n        msisdn: String,\n        options: SMSRegistrationOptions,\n        locale: Locale\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await performChannelRegistration(\n            contactID: contactID,\n            requestBody: SMSRegistrationBody(\n                msisdn: msisdn,\n                options: options,\n                locale: locale,\n                timezone: TimeZone.current.identifier\n            ),\n            channelType: .sms\n        )\n    }\n\n    func registerOpen(\n        contactID: String,\n        address: String,\n        options: OpenRegistrationOptions,\n        locale: Locale\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await performChannelRegistration(\n            contactID: contactID,\n            requestBody: OpenChannelRegistrationBody(\n                address: address,\n                options: options,\n                locale: locale,\n                timezone: TimeZone.current.identifier\n            ),\n            channelType: .open\n        )\n    }\n\n\n    func disassociateChannel(\n        contactID: String,\n        disassociateOptions: DisassociateOptions\n    ) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult> {\n\n        return try await performDisassociate(\n            contactID: contactID,\n            requestBody: disassociateOptions\n        )\n    }\n\n    func resend(resendOptions: ResendOptions) async throws -> AirshipHTTPResponse<Bool> {\n        return try await performResend(resendOptions: resendOptions)\n    }\n\n    private func makeURL(path: String) throws -> URL {\n        guard let deviceAPIURL = self.config.deviceAPIURL else {\n            throw AirshipErrors.error(\"Initial config not resolved.\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(path)\"\n\n        guard let url = URL(string: \"\\(deviceAPIURL)\\(path)\") else {\n            throw AirshipErrors.error(\"Invalid ContactAPIClient URL: \\(String(describing: urlString))\")\n        }\n\n        return url\n    }\n\n    private func makeChannelCreateURL(channelType: ChannelType) throws -> URL {\n        switch(channelType) {\n        case .email:\n            return try self.makeURL(path: \"/api/channels/restricted/email\")\n        case .open:\n            return try self.makeURL(path: \"/api/channels/restricted/open\")\n        case .sms:\n            return try self.makeURL(path: \"/api/channels/restricted/sms\")\n        }\n    }\n\n    private func performChannelRegistration<T: Encodable>(\n        contactID: String,\n        requestBody: T,\n        channelType: ChannelType\n    ) async throws ->  AirshipHTTPResponse<ContactAssociateChannelResult> {\n        let request = AirshipRequest(\n            url: try self.makeChannelCreateURL(channelType: channelType),\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: try self.encoder.encode(requestBody)\n        )\n\n        let decoder = self.decoder\n        let createResponse: AirshipHTTPResponse<ChannelCreateResult> = try await self.session.performHTTPRequest(\n            request\n        ) { (data, response) in\n\n            AirshipLogger.debug(\"Channel \\(channelType) created with response: \\(response)\")\n\n            guard let data = data, response.statusCode == 200 || response.statusCode == 201 else {\n                return nil\n            }\n\n            return try decoder.decode(ChannelCreateResult.self, from: data)\n        }\n\n        guard createResponse.isSuccess, let channelID = createResponse.result?.channelID else {\n            return try createResponse.map { _ in return nil }\n        }\n\n        return try await associateChannel(\n            contactID: contactID,\n            channelID: channelID,\n            channelType: channelType\n        )\n    }\n\n    private func performIdentify(\n        channelID: String,\n        identifyRequest: ContactIdentifyRequestBody\n    ) async throws ->  AirshipHTTPResponse<ContactIdentifyResult> {\n        AirshipLogger.debug(\"Identifying contact for channel ID \\(channelID) request \\(identifyRequest)\")\n\n        let request = AirshipRequest(\n            url: try makeURL(path: \"/api/contacts/identify/v2\"),\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\",\n            ],\n            method: \"POST\",\n            auth: .generatedChannelToken(identifier: channelID),\n            body: try self.encoder.encode(identifyRequest)\n        )\n\n        let decoder = self.decoder\n        return try await session.performHTTPRequest(request) { (data, response) in\n\n            AirshipLogger.debug(\"Contact identify request finished with response: \\(response)\")\n\n            guard response.statusCode == 200, let data = data else {\n                return nil\n            }\n\n            return try decoder.decode(ContactIdentifyResult.self, from: data)\n        }\n    }\n\n    private func performDisassociate(\n        contactID: String,\n        requestBody: DisassociateOptions\n    ) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult> {\n        AirshipLogger.debug(\"Disassociating with \\(requestBody)\")\n\n        let encodedRequestBody = try self.encoder.encode(requestBody)\n        let requestBodyString = String(data: encodedRequestBody, encoding: .utf8)\n        AirshipLogger.debug(\"Encoded request body: \\(requestBodyString ?? \"Unable to convert data to string\")\")\n\n        let request =  AirshipRequest(\n            url: try self.makeURL(path: \"/api/contacts/disassociate/\\(contactID)\"),\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .basicAppAuth,\n            body: encodedRequestBody\n        )\n\n        let decoder = self.decoder\n        return try await session.performHTTPRequest(request) { (data, response) in\n            AirshipLogger.debug(\"Update finished with response: \\(response)\")\n\n            guard response.statusCode == 200, let data = data else {\n                return nil\n            }\n\n            return try decoder.decode(ContactDisassociateChannelResult.self, from: data)\n        }\n    }\n\n    private func performResend(\n        resendOptions: ResendOptions\n    ) async throws -> AirshipHTTPResponse<Bool> {\n        let requestBodyData: Data?\n\n        switch resendOptions {\n        case .channel(let channel):\n            requestBodyData = try self.encoder.encode(channel)\n        case .email(let email):\n            requestBodyData = try self.encoder.encode(email)\n        case .sms(let sms):\n            requestBodyData = try self.encoder.encode(sms)\n        }\n\n        guard let requestBodyData = requestBodyData else {\n            throw AirshipErrors.error(\"Unable to encode resend operation data.\")\n        }\n\n        AirshipLogger.debug(\"Re-sending double opt-in message\")\n\n        let request =  AirshipRequest(\n            url: try self.makeURL(path: \"/api/channels/resend\"),\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: requestBodyData\n        )\n\n        return try await session.performHTTPRequest(request) { (data, response) in\n\n            AirshipLogger.debug(\"Update finished with response: \\(response)\")\n\n            return nil\n        }\n    }\n\n    private func performUpdate(\n        contactID: String,\n        requestBody: ContactUpdateRequestBody\n    ) async throws -> AirshipHTTPResponse<Void> {\n        AirshipLogger.debug(\"Updating contact \\(contactID) with \\(requestBody)\")\n\n        let request =  AirshipRequest(\n            url: try self.makeURL(path: \"/api/contacts/\\(contactID)\"),\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\",\n                \"X-UA-Appkey\": self.config.appCredentials.appKey\n            ],\n            method: \"POST\",\n            auth: .contactAuthToken(identifier: contactID),\n            body: try self.encoder.encode(requestBody)\n        )\n\n        return try await session.performHTTPRequest(request) { (data, response) in\n\n            AirshipLogger.debug(\"Update finished with response: \\(response)\")\n\n            return nil\n        }\n    }\n}\n\nstruct ContactIdentifyResult: Decodable, Equatable {\n    let contact: ContactInfo\n    let token: String\n    let tokenExpiresInMilliseconds: UInt\n\n    enum CodingKeys: String, CodingKey {\n        case tokenExpiresInMilliseconds = \"token_expires_in\"\n        case token = \"token\"\n        case contact = \"contact\"\n    }\n\n    struct ContactInfo: Decodable, Equatable {\n        let channelAssociatedDate: Date\n        let contactID: String\n        let isAnonymous: Bool\n\n        enum CodingKeys: String, CodingKey {\n            case channelAssociatedDate = \"channel_association_timestamp\"\n            case contactID = \"contact_id\"\n            case isAnonymous = \"is_anonymous\"\n        }\n    }\n}\n\nstruct ContactAssociateChannelResult: Decodable, Equatable {\n   public let channelType: ChannelType\n   public let channelID: String\n}\n\nstruct ContactDisassociateChannelResult: Decodable, Equatable {\n   public let channelID: String\n\n    enum CodingKeys: String, CodingKey {\n        case channelID = \"channel_id\"\n    }\n}\n\nenum DisassociateOptions: Sendable, Equatable, Codable, Hashable {\n    case channel(Channel)\n    case email(Email)\n    case sms(SMS)\n\n    init(channelID: String, channelType: ChannelType, optOut: Bool) {\n        self = .channel(Channel(channelID: channelID, optOut: optOut, channelType: channelType))\n    }\n\n    init(emailAddress: String, optOut: Bool) {\n        self = .email(Email(address: emailAddress, optOut: optOut))\n    }\n\n    init(msisdn: String, senderID: String, optOut: Bool) {\n        self = .sms(SMS(msisdn: msisdn, senderID: senderID, optOut: optOut))\n    }\n\n    struct Channel: Sendable, Equatable, Codable, Hashable {\n        let channelID: String\n        let optOut: Bool\n        let channelType: ChannelType\n\n        enum CodingKeys: String, CodingKey {\n            case channelID = \"channel_id\"\n            case optOut = \"opt_out\"\n            case channelType = \"channel_type\"\n        }\n    }\n\n    struct Email: Sendable, Equatable, Codable, Hashable {\n        let channelType: String = \"email\"\n        let address: String\n        let optOut: Bool\n\n        enum CodingKeys: String, CodingKey {\n            case address = \"email_address\"\n            case optOut = \"opt_out\"\n            case channelType = \"channel_type\"\n        }\n    }\n\n    struct SMS: Sendable, Equatable, Codable, Hashable {\n        let channelType: String = \"sms\"\n        let msisdn: String\n        let senderID: String\n        let optOut: Bool\n\n        enum CodingKeys: String, CodingKey {\n            case msisdn = \"msisdn\"\n            case senderID = \"sender\"\n            case optOut = \"opt_out\"\n            case channelType = \"channel_type\"\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case channel\n        case email\n        case sms\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n        switch self {\n        case .channel(let channel):\n            try container.encode(channel)\n        case .email(let email):\n            try container.encode(email)\n        case .sms(let sms):\n            try container.encode(sms)\n        }\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        if let channel = try? container.decode(Channel.self) {\n            self = .channel(channel)\n        } else if let email = try? container.decode(Email.self) {\n            self = .email(email)\n        } else if let sms = try? container.decode(SMS.self) {\n            self = .sms(sms)\n        } else {\n            throw DecodingError.dataCorruptedError(in: container, debugDescription: \"Invalid data for DisassociateOptions\")\n        }\n    }\n}\n\nenum ResendOptions: Sendable, Equatable, Codable, Hashable {\n    case channel(Channel)\n    case email(Email)\n    case sms(SMS)\n\n    init(channelID: String, channelType: ChannelType) {\n        self = .channel(Channel(channelType: channelType, channelID: channelID))\n    }\n\n    init(emailAddress: String) {\n        self = .email(Email(address: emailAddress))\n    }\n\n    init(msisdn: String, senderID: String) {\n        self = .sms(SMS(msisdn: msisdn, senderID: senderID))\n    }\n\n    struct Channel: Sendable, Equatable, Codable, Hashable {\n        let channelType: ChannelType\n        let channelID: String\n\n        enum CodingKeys: String, CodingKey {\n            case channelType = \"channel_type\"\n            case channelID = \"channel_id\"\n        }\n    }\n\n    struct Email: Sendable, Equatable, Codable, Hashable {\n        let channelType: String = \"email\"\n        let address: String\n\n        enum CodingKeys: String, CodingKey {\n            case address = \"email_address\"\n            case channelType = \"channel_type\"\n        }\n    }\n\n    struct SMS: Sendable, Equatable, Codable, Hashable {\n        let channelType: String = \"sms\"\n        let msisdn: String\n        let senderID: String\n\n        enum CodingKeys: String, CodingKey {\n            case channelType = \"channel_type\"\n            case msisdn = \"msisdn\"\n            case senderID = \"sender\"\n        }\n    }\n}\n\nfileprivate struct ContactUpdateRequestBody: Encodable {\n    let attributes: [AttributeOperation]?\n    let tags: TagUpdates?\n    let subscriptionLists: [SubscriptionListOperation]?\n    let associate: [AssociateChannelOperation]?\n\n    enum CodingKeys: String, CodingKey {\n        case attributes = \"attributes\"\n        case tags = \"tags\"\n        case subscriptionLists = \"subscription_lists\"\n        case associate = \"associate\"\n    }\n\n    enum AttributeOperationAction: String, Encodable {\n        case set\n        case remove\n    }\n\n    struct AttributeOperation: Encodable {\n        let action: AttributeOperationAction\n        let key: String\n        let value: AirshipJSON?\n        let timestamp: Date\n    }\n\n    struct TagUpdates: Encodable {\n        let adds: [String: [String]]?\n        let removes: [String: [String]]?\n        let sets: [String: [String]]?\n\n        enum CodingKeys: String, CodingKey {\n            case adds = \"add\"\n            case removes = \"remove\"\n            case sets = \"set\"\n        }\n    }\n\n    enum SubscriptionListOperationAction: String, Encodable {\n        case subscribe\n        case unsubscribe\n    }\n\n    struct SubscriptionListOperation: Encodable {\n        let action: SubscriptionListOperationAction\n        let scope: ChannelScope\n        let timestamp: Date\n        let listID: String\n\n        enum CodingKeys: String, CodingKey {\n            case action\n            case scope\n            case timestamp\n            case listID = \"list_id\"\n        }\n    }\n\n    struct AssociateChannelOperation: Encodable {\n        let deviceType: ChannelType\n        let channelID: String\n\n        enum CodingKeys: String, CodingKey {\n            case deviceType = \"device_type\"\n            case channelID = \"channel_id\"\n        }\n    }\n}\n\n\nfileprivate struct ContactIdentifyRequestBody: Encodable {\n    private let deviceInfo: DeviceInfo = DeviceInfo()\n    private let action: RequestAction\n\n    internal init(action: RequestAction) {\n        self.action = action\n    }\n\n    static func identify(namedUserID: String, contactID: String?, possiblyOrphanedContactID: String?) -> ContactIdentifyRequestBody {\n        return ContactIdentifyRequestBody(\n            action: RequestAction(type: \"identify\", namedUserID: namedUserID, contactID: contactID, possiblyOrphanedContactID: possiblyOrphanedContactID)\n        )\n    }\n\n    static func reset(possiblyOrphanedContactID: String?) -> ContactIdentifyRequestBody {\n        return ContactIdentifyRequestBody(\n            action: RequestAction(type: \"reset\", namedUserID: nil, contactID: nil, possiblyOrphanedContactID: possiblyOrphanedContactID)\n        )\n    }\n\n    static func resolve(contactID: String?, possiblyOrphanedContactID: String?) -> ContactIdentifyRequestBody {\n        return ContactIdentifyRequestBody(\n            action: RequestAction(type: \"resolve\", namedUserID: nil, contactID: contactID, possiblyOrphanedContactID: possiblyOrphanedContactID)\n        )\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case deviceInfo = \"device_info\"\n        case action = \"action\"\n    }\n\n    internal struct DeviceInfo: Codable {\n        let deviceType: String = \"ios\"\n\n        enum CodingKeys: String, CodingKey {\n            case deviceType = \"device_type\"\n        }\n    }\n\n    internal struct RequestAction: Codable {\n        let type: String\n        let namedUserID: String?\n        let contactID: String?\n        let possiblyOrphanedContactID: String?\n\n        enum CodingKeys: String, CodingKey {\n            case type = \"type\"\n            case namedUserID = \"named_user_id\"\n            case contactID = \"contact_id\"\n            case possiblyOrphanedContactID = \"possibly_orphaned_contact_id\"\n        }\n    }\n}\n\nfileprivate struct OpenChannelRegistrationBody: Encodable {\n    let channel: ChannelPayload\n\n    init(\n        address: String,\n        options: OpenRegistrationOptions,\n        locale: Locale,\n        timezone: String\n    ) {\n\n        self.channel = ChannelPayload(\n            address: address,\n            timezone: timezone,\n            localeCountry: locale.getRegionCode(),\n            localeLanguage: locale.getLanguageCode(),\n            openInfo: OpenPayload(\n                platformName: options.platformName,\n                identifiers: options.identifiers\n            )\n        )\n    }\n\n    internal struct ChannelPayload: Encodable {\n        let type: String = \"open\"\n        let optIn: Bool = true\n        let address: String\n        let timezone: String\n        let localeCountry: String?\n        let localeLanguage: String?\n        let openInfo: OpenPayload\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case optIn = \"opt_in\"\n            case address\n            case timezone\n            case localeCountry = \"locale_country\"\n            case localeLanguage  = \"locale_language\"\n            case openInfo = \"open\"\n        }\n    }\n\n    internal struct OpenPayload: Encodable {\n        let platformName: String\n        let identifiers: [String: String]?\n\n        enum CodingKeys: String, CodingKey {\n            case platformName = \"open_platform_name\"\n            case identifiers\n        }\n    }\n}\n\nfileprivate struct EmailChannelUpdateBody: Encodable {\n    let channel: ChannelPartialPayload\n\n    let optInMode: String = \"double\"\n\n    enum CodingKeys: String, CodingKey {\n        case channel\n        case optInMode = \"opt_in_mode\"\n    }\n\n    internal struct ChannelPartialPayload: Encodable {\n        let type: String\n\n        enum CodingKeys: String, CodingKey {\n            case type\n        }\n    }\n\n}\n\nfileprivate struct EmailChannelRegistrationBody: Encodable {\n    let channel: ChannelPayload\n    let properties: AirshipJSON?\n    let optInMode: OptInMode\n\n    init(\n        address: String,\n        options: EmailRegistrationOptions,\n        locale: Locale,\n        timezone: String\n    ) {\n        self.channel = ChannelPayload(\n            address: address,\n            timezone: timezone,\n            localeCountry: locale.getRegionCode(),\n            localeLanguage: locale.getLanguageCode(),\n            commercialOptedIn: options.commercialOptedIn,\n            transactionalOptedIn: options.transactionalOptedIn\n        )\n\n        self.optInMode = options.doubleOptIn ? .double : .classic\n        self.properties = options.properties\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case channel\n        case properties\n        case optInMode = \"opt_in_mode\"\n    }\n\n    internal enum OptInMode: String, Encodable {\n        case classic\n        case double\n    }\n\n    internal struct ChannelPayload: Encodable {\n        let type: String = \"email\"\n        let address: String\n        let timezone: String\n        let localeCountry: String?\n        let localeLanguage: String?\n        let commercialOptedIn: Date?\n        let transactionalOptedIn: Date?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case address\n            case timezone\n            case localeCountry = \"locale_country\"\n            case localeLanguage  = \"locale_language\"\n            case commercialOptedIn = \"commercial_opted_in\"\n            case transactionalOptedIn = \"transactional_opted_in\"\n        }\n    }\n\n    internal struct OpenPayload: Encodable {\n        let platformName: String\n        let identifiers: [String: String]?\n\n        enum CodingKeys: String, CodingKey {\n            case platformName = \"open_platform_name\"\n            case identifiers\n        }\n    }\n}\n\nfileprivate struct SMSRegistrationBody: Encodable {\n    let msisdn: String\n    let sender: String\n    let timezone: String\n    let localeCountry: String?\n    let localeLanguage: String?\n\n    init(\n        msisdn: String,\n        options: SMSRegistrationOptions,\n        locale: Locale,\n        timezone: String\n    ) {\n        self.msisdn = msisdn\n        self.sender = options.senderID\n        self.timezone = timezone\n        self.localeCountry = locale.getRegionCode()\n        self.localeLanguage = locale.getLanguageCode()\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case msisdn\n        case sender\n        case timezone\n        case localeCountry = \"locale_country\"\n        case localeLanguage  = \"locale_language\"\n    }\n}\n\n\nfileprivate struct ChannelCreateResult: Decodable {\n    let channelID: String\n\n    enum CodingKeys: String, CodingKey {\n        case channelID = \"channel_id\"\n    }\n}\n\n\nfileprivate extension Array where Element == TagGroupUpdate {\n    func toRequestBody() -> ContactUpdateRequestBody.TagUpdates? {\n        var adds: [String: [String]] = [:]\n        var removes: [String: [String]] = [:]\n        var sets: [String: [String]] = [:]\n\n\n        self.forEach { update in\n            switch update.type {\n            case .add:\n                adds[update.group] = update.tags\n            case .remove:\n                removes[update.group] = update.tags\n            case .set:\n                sets[update.group] = update.tags\n            }\n        }\n\n        guard !adds.isEmpty || !removes.isEmpty || !sets.isEmpty else {\n            return nil\n        }\n\n        return ContactUpdateRequestBody.TagUpdates(\n            adds: adds.isEmpty ? nil : adds,\n            removes: removes.isEmpty ? nil : removes,\n            sets: sets.isEmpty ? nil : sets\n        )\n    }\n}\n\n\nfileprivate extension Array where Element == ScopedSubscriptionListUpdate {\n    func toRequestBody() -> [ContactUpdateRequestBody.SubscriptionListOperation]? {\n        let mapped = self.map { update in\n            switch(update.type) {\n            case .subscribe:\n                return ContactUpdateRequestBody.SubscriptionListOperation(\n                    action: .subscribe,\n                    scope: update.scope,\n                    timestamp: update.date,\n                    listID: update.listId\n                )\n            case .unsubscribe:\n                return ContactUpdateRequestBody.SubscriptionListOperation(\n                    action: .unsubscribe,\n                    scope: update.scope,\n                    timestamp: update.date,\n                    listID: update.listId\n                )\n            }\n        }\n\n        guard !mapped.isEmpty else {\n            return nil\n        }\n\n        return mapped\n    }\n}\n\nfileprivate extension Array where Element == AttributeUpdate {\n    func toRequestBody() throws -> [ContactUpdateRequestBody.AttributeOperation]? {\n        let mapped = self.map { update in\n            switch(update.type) {\n            case .set:\n                return ContactUpdateRequestBody.AttributeOperation(\n                    action: .set,\n                    key: update.attribute,\n                    value: update.jsonValue,\n                    timestamp: update.date\n                )\n            case .remove:\n                return ContactUpdateRequestBody.AttributeOperation(\n                    action: .remove,\n                    key: update.attribute,\n                    value: nil,\n                    timestamp: update.date\n                )\n            }\n        }\n\n        guard !mapped.isEmpty else {\n            return nil\n        }\n\n        return mapped\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactChannel.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n\n/// Representation of a channel and its registration state after being associated to a contact\npublic enum ContactChannel: Sendable, Equatable, Codable, Hashable {\n    case sms(Sms)\n    case email(Email)\n\n    /// Channel type\n    public var channelType: ChannelType {\n        switch (self) {\n        case .email(_): return .email\n        case .sms(_): return .sms\n        }\n    }\n    \n    /// Masked address\n    public var maskedAddress: String {\n        switch (self) {\n        case .email(let email):\n            switch(email) {\n            case .pending(let pending): return pending.address.maskEmail\n            case .registered(let registered): return registered.maskedAddress\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .pending(let pending): return pending.address.maskPhoneNumber\n            case .registered(let registered): return registered.maskedAddress\n            }\n        }\n    }\n\n    /// Checks if its registered or not.\n    public var isRegistered: Bool {\n        switch (self) {\n        case .email(let email):\n            switch(email) {\n            case .pending(_): return false\n            case .registered(_): return false\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .pending(_): return false\n            case .registered(_): return false\n            }\n        }\n    }\n\n    /// SMS channel info\n    public enum Sms: Sendable, Equatable, Codable, Hashable {\n        /// Registered channel\n        case registered(Registered)\n\n        /// Pending registration\n        case pending(Pending)\n\n        /// Registered info\n        public struct Registered: Sendable, Equatable, Codable, Hashable {\n            /// Channel ID\n            public let channelID: String\n\n            /// Masked MSISDN address.\n            public let maskedAddress: String\n\n            /// Opt-in status\n            public let isOptIn: Bool\n\n            /// Identifier from which the SMS opt-in message is received\n            public let senderID: String\n        }\n\n        /// Pending info\n        public struct Pending: Sendable, Equatable, Codable, Hashable {\n            /// The MSISDN.\n            public let address: String\n\n            /// Registration options.\n            public let registrationOptions: SMSRegistrationOptions\n        }\n    }\n\n    /// Email channel info\n    public enum Email: Sendable, Equatable, Codable, Hashable {\n        /// Registered channel\n        case registered(Registered)\n\n        /// Pending registration\n        case pending(Pending)\n\n        /// Registered info\n        public struct Registered:  Sendable, Equatable, Codable, Hashable {\n            /// Channel ID\n            public let channelID: String\n\n            /// Masked email address\n            public let maskedAddress: String\n\n            /// Transactional opted-in value\n            public let transactionalOptedIn: Date?\n\n            /// Transactional opted-out value\n            public let transactionalOptedOut: Date?\n\n            /// Commercial opted-in value - used to determine the email opted-in state\n            public let commercialOptedIn: Date?\n\n            /// Commercial opted-out value\n            public let commercialOptedOut: Date?\n\n            init(\n                channelID: String,\n                maskedAddress: String,\n                transactionalOptedIn: Date? = nil,\n                transactionalOptedOut: Date? = nil,\n                commercialOptedIn: Date? = nil,\n                commercialOptedOut: Date? = nil\n            ) {\n                self.channelID = channelID\n                self.maskedAddress = maskedAddress\n                self.transactionalOptedIn = transactionalOptedIn\n                self.transactionalOptedOut = transactionalOptedOut\n                self.commercialOptedIn = commercialOptedIn\n                self.commercialOptedOut = commercialOptedOut\n            }\n        }\n\n        /// Pending info\n        public struct Pending: Sendable, Equatable, Codable, Hashable {\n            /// The email address.\n            public let address: String\n\n            /// Registration options.\n            public let registrationOptions: EmailRegistrationOptions\n        }\n    }\n}\n\n/**\n * An associative or dissociative update operation\n */\npublic enum ContactChannelUpdate: Sendable, Equatable, Hashable {\n    case disassociated(ContactChannel, channelID: String? = nil)\n    case associated(ContactChannel, channelID: String? = nil)\n    case associatedAnonChannel(channelType: ChannelType, channelID: String)\n}\n\n\nprivate extension String {\n    var maskEmail: String {\n        if !self.isEmpty {\n            let firstLetter = String(self.prefix(1))\n            if let atIndex = self.firstIndex(of: \"@\") {\n                let suffix = self.suffix(self.count - self.distance(from: self.startIndex, to: atIndex) - 1)\n                return \"\\(firstLetter)*******\\(suffix)\"\n            }\n        }\n\n        return self\n    }\n\n    var maskPhoneNumber: String {\n        if !self.isEmpty && self.count > 4 {\n            return (\"*******\" + self.suffix(4))\n        }\n\n        return self\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactChannelsAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n// NOTE: For internal use only. :nodoc:\npublic protocol ContactChannelsAPIClientProtocol: Sendable {\n    func fetchAssociatedChannelsList(\n        contactID: String\n    ) async throws -> AirshipHTTPResponse<[ContactChannel]>\n}\n\n// NOTE: For internal use only. :nodoc:\nfinal class ContactChannelsAPIClient: ContactChannelsAPIClientProtocol {\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n    \n    private var decoder: JSONDecoder {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in\n            let container = try decoder.singleValueContainer()\n            let dateStr = try container.decode(String.self)\n\n            guard let date = AirshipDateFormatter.date(fromISOString: dateStr) else {\n                throw AirshipErrors.error(\"Invalid date \\(dateStr)\")\n            }\n            return date\n        })\n        return decoder\n    }\n    \n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n    \n    convenience init(config: RuntimeConfig) {\n        self.init(config: config, session: config.requestSession)\n    }\n\n    func fetchAssociatedChannelsList(\n        contactID: String\n    ) async throws -> AirshipHTTPResponse<[ContactChannel]> {\n        AirshipLogger.debug(\"Retrieving associated channels list\")\n\n        let request = AirshipRequest(\n            url: try self.makeURL(path: \"/api/contacts/associated_types/\\(contactID)\"),\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"GET\",\n            auth: .contactAuthToken(identifier: contactID)\n        )\n\n        return try await self.session.performHTTPRequest(\n            request\n        ) { (data, response) in\n\n            AirshipLogger.debug(\"Fetching associated channels list finished with response: \\(response)\")\n\n            guard response.statusCode == 200, let data = data else {\n                return nil\n            }\n\n\n            let result = try self.decoder.decode(\n                ContactChannelsResponseBody.self,\n                from: data\n            )\n\n            return result.channels.compactMap { channel in\n                channel.contactChannel\n            }\n        }\n    }\n\n    private func makeURL(path: String) throws -> URL {\n        guard let deviceAPIURL = self.config.deviceAPIURL else {\n            throw AirshipErrors.error(\"Initial config not resolved.\")\n        }\n        \n        let urlString = \"\\(deviceAPIURL)\\(path)\"\n        \n        guard let url = URL(string: \"\\(deviceAPIURL)\\(path)\") else {\n            throw AirshipErrors.error(\"Invalid ContactAPIClient URL: \\(String(describing: urlString))\")\n        }\n        \n        return url\n    }\n}\n\nfileprivate struct ContactChannelsResponseBody: Decodable, Sendable {\n    let channels: [Channel]\n\n    enum CodingKeys: String, CodingKey {\n        case channels = \"channels\"\n    }\n    \n    enum Channel: Decodable, Sendable {\n        case sms(SMSChannel)\n        case email(EmailChannel)\n        case unknown\n\n        enum DeviceType: String, Decodable, Sendable {\n            case email\n            case sms\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case deviceType = \"type\"\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let deviceType = try? container.decode(DeviceType.self, forKey: .deviceType)\n            let singleValueContainer = try decoder.singleValueContainer()\n            guard let deviceType else {\n                self = .unknown\n                return\n            }\n\n            switch (deviceType) {\n            case .email:\n                self = .email(\n                    try singleValueContainer.decode(EmailChannel.self)\n                )\n            case .sms:\n                self = .sms(\n                    try singleValueContainer.decode(SMSChannel.self)\n                )\n            }\n        }\n    }\n\n    struct SMSChannel: Decodable, Sendable {\n        var channelID: String\n        var sender: String\n        var isOptIn: Bool\n        var deIdentifiedAddress: String\n\n\n        enum CodingKeys: String, CodingKey {\n            case channelID = \"channel_id\"\n            case isOptIn = \"opt_in\"\n            case sender = \"sender\"\n            case deIdentifiedAddress = \"msisdn\"\n        }\n    }\n\n    struct EmailChannel: Decodable, Sendable {\n        var channelID: String\n        var deIdentifiedAddress: String\n        var commericalOptedIn: Date?\n        var commericalOptedOut: Date?\n\n        var transactionalOptedIn: Date?\n        var transactionalOptedOut: Date?\n\n\n        enum CodingKeys: String, CodingKey {\n            case channelID = \"channel_id\"\n            case deIdentifiedAddress = \"email_address\"\n            case commericalOptedIn = \"commercial_opted_in\"\n            case commericalOptedOut = \"commercial_opted_out\"\n            case transactionalOptedIn = \"transactional_opted_in\"\n            case transactionalOptedOut = \"transactional_opted_out\"\n        }\n    }\n}\n\nfileprivate extension ContactChannelsResponseBody.Channel {\n    var contactChannel: ContactChannel? {\n        switch(self) {\n        case .email(let email):\n            return .email(\n                .registered(\n                    ContactChannel.Email.Registered(\n                        channelID: email.channelID,\n                        maskedAddress: email.deIdentifiedAddress,\n                        transactionalOptedIn: email.transactionalOptedIn,\n                        transactionalOptedOut: email.transactionalOptedOut,\n                        commercialOptedIn: email.commericalOptedIn,\n                        commercialOptedOut: email.commericalOptedOut\n                    )\n                )\n            )\n        case .sms(let sms):\n            return .sms(\n                .registered(\n                    ContactChannel.Sms.Registered(\n                        channelID: sms.channelID,\n                        maskedAddress: sms.deIdentifiedAddress,\n                        isOptIn: sms.isOptIn,\n                        senderID: sms.sender\n                    )\n                )\n            )\n        case .unknown:\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactChannelsProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n/**\n * Contact channels provider protocol for receiving contact updates.\n * @note For internal use only. :nodoc:\n */\nprotocol ContactChannelsProviderProtocol: Sendable {\n    func contactChannels(stableContactIDUpdates: AsyncStream<String>) -> AsyncStream<ContactChannelsResult>\n    func refresh() async\n    func refreshAsync()\n}\n\nfinal class ContactChannelsProvider: ContactChannelsProviderProtocol {\n    private let actor: BaseCachingRemoteDataProvider<ContactChannelsResult, ContactAudienceOverrides>\n    private let overridesApplier: OverridesApplier = OverridesApplier()\n    \n    init(\n        audienceOverrides: any AudienceOverridesProvider,\n        apiClient: any ContactChannelsAPIClientProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared,\n        maxChannelListCacheAgeSeconds: TimeInterval = 600,\n        privacyManager: any AirshipPrivacyManager\n    ) {\n        self.actor = BaseCachingRemoteDataProvider(\n            remoteFetcher: { contactID in\n                return try await apiClient\n                    .fetchAssociatedChannelsList(contactID: contactID)\n                    .map { response in\n                        guard let result = response.result else {\n                            return nil\n                        }\n                        return .success(result)\n                    }\n            }, \n            overridesProvider: { identifier in\n                return await audienceOverrides.contactOverrideUpdates(contactID: identifier)\n            },\n            overridesApplier: { [overridesApplier] result, overrides in\n                return await overridesApplier.applyUpdates(result: result, overrides: overrides)\n            },\n            isEnabled: { privacyManager.isEnabled(.contacts) },\n            date: date,\n            taskSleeper: taskSleeper,\n            cacheTtl: maxChannelListCacheAgeSeconds\n        )\n    }\n    \n    /// Returns the latest contact channel result stream from the latest stable contact ID\n    func contactChannels(stableContactIDUpdates: AsyncStream<String>) -> AsyncStream<ContactChannelsResult> {\n        return actor.updates(identifierUpdates: stableContactIDUpdates)\n    }\n    \n    func refresh() async {\n        await actor.refresh()\n    }\n\n    func refreshAsync() {\n        Task {\n            await refresh()\n        }\n    }\n}\n\npublic enum ContactChannelErrors: Error, Equatable, Sendable, Hashable {\n    case contactsDisabled\n    case failedToFetchContacts\n}\n\nfileprivate extension CachingRemoteDataError {\n    func toChannelError() -> ContactChannelErrors {\n        switch (self) {\n        case .disabled: return .contactsDisabled\n        case .failedToFetch: return .failedToFetchContacts\n        }\n    }\n}\n\npublic enum ContactChannelsResult: Equatable, Sendable, Hashable, CachingRemoteDataProviderResult {\n    static func error(_ error: CachingRemoteDataError) -> any CachingRemoteDataProviderResult {\n        return ContactChannelsResult.error(error.toChannelError())\n    }\n    \n    case success([ContactChannel])\n    case error(ContactChannelErrors)\n\n    public var channels: [ContactChannel] {\n        get throws {\n            switch(self) {\n            case .error(let error): throw error\n            case .success(let channels): return channels\n            }\n        }\n    }\n\n    public var isSuccess: Bool {\n        switch(self) {\n        case .error(_): return false\n        case .success(_): return true\n        }\n    }\n}\n\n\nfileprivate actor OverridesApplier {\n    private var addressToChannelIDMap: [String: String] = [:]\n\n    func applyUpdates(result: ContactChannelsResult, overrides: ContactAudienceOverrides) -> ContactChannelsResult {\n        guard\n            case .success(let channels) = result,\n            !overrides.channels.isEmpty\n        else {\n            return result\n        }\n\n        var mutated = channels\n\n        overrides.channels.forEach { update in\n            switch(update) {\n            case .associated(let channel, let channelID):\n                if let address = channel.canonicalAddress, let channelID {\n                    self.addressToChannelIDMap[address] = channelID\n                }\n            case .disassociated(let channel, let channelID):\n                if let address = channel.canonicalAddress, let channelID {\n                    self.addressToChannelIDMap[address] = channelID\n                }\n            case .associatedAnonChannel(_, _):\n                // no-op\n                break\n            }\n        }\n\n        for update in overrides.channels {\n            switch(update) {\n            case .associated(let channel, _):\n                let found = mutated.contains(\n                    where: {\n                        isMatch(\n                            channel: $0,\n                            update: update\n                        )\n                    }\n                )\n\n                if (!found) {\n                    mutated.append(channel)\n                }\n\n\n            case .disassociated(_, _):\n                mutated.removeAll {\n                    isMatch(\n                        channel: $0,\n                        update: update\n                    )\n                }\n            case .associatedAnonChannel(_, _):\n                // no-op\n                break\n            }\n        }\n\n        return .success(mutated)\n    }\n\n    private func isMatch(\n        channel: ContactChannel,\n        update: ContactChannelUpdate\n    ) -> Bool {\n        let canonicalAddress = channel.canonicalAddress\n        let resolvedChannelID = resolveChannelID(\n            channelID: channel.channelID,\n            canonicalAddress: canonicalAddress\n        )\n\n        let updateCanonicalAddress = update.canonicalAddress\n        let updateChannelID = resolveChannelID(\n            channelID: update.channelID,\n            canonicalAddress: updateCanonicalAddress\n        )\n\n        if let resolvedChannelID, resolvedChannelID == updateChannelID {\n            return true\n        }\n\n        if let canonicalAddress, canonicalAddress == updateCanonicalAddress {\n            return true\n        }\n\n        return false\n    }\n\n    private func resolveChannelID(\n        channelID: String?,\n        canonicalAddress: String?\n    ) -> String? {\n        if let channelID {\n            return channelID\n        }\n\n        if let canonicalAddress {\n            return addressToChannelIDMap[canonicalAddress]\n        }\n\n        return nil\n    }\n}\n\n\nextension ContactChannelUpdate {\n    var canonicalAddress: String? {\n        switch (self) {\n        case .associated(let channel, _): return channel.canonicalAddress\n        case .disassociated(let channel, _): return channel.canonicalAddress\n        case .associatedAnonChannel(_, _): return nil\n        }\n    }\n\n    var channelID: String? {\n        switch (self) {\n        case .associated(let channel, let channelID): return channelID ?? channel.channelID\n        case .disassociated(let channel, let channelID): return channelID ?? channel.channelID\n        case .associatedAnonChannel(_, let channelID): return channelID\n        }\n    }\n}\n\nextension ContactChannel {\n    var channelID: String? {\n        switch (self) {\n        case .email(let email):\n            switch(email) {\n            case .pending(_): return nil\n            case .registered(let info): return info.channelID\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .pending(_): return nil\n            case .registered(let info): return info.channelID\n            }\n        }\n    }\n\n    var canonicalAddress: String? {\n        switch (self) {\n        case .email(let email):\n            switch(email) {\n            case .pending(let info): return info.address\n            case .registered(_): return nil\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .pending(let info): return \"(\\(info.address):\\(info.registrationOptions.senderID)\"\n            case .registered(_): return nil\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactConflictEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Contact data.\npublic struct ContactConflictEvent: Sendable, Equatable {\n\n    /**\n     * The named user ID if the conflict was caused by an identify operation with an existing named user through the SDK.\n     */\n    public let conflictingNamedUserID: String?\n\n    /**\n     * Subscription lists.\n     */\n    public let subscriptionLists: [String: [ChannelScope]]\n\n    /**\n     * Tag groups.\n     */\n    public let tags: [String: [String]]\n\n    /**\n     * Attributes.\n     */\n    public let attributes: [String: AirshipJSON]\n\n    /**\n     * Associated channels.\n     */\n    public let associatedChannels: [ChannelInfo]\n\n    /**\n     * Default constructor.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - attributes: The attributes.\n     *   - subscriptionLists: The subscription lists.\n     *   - channels: The associated channels.\n     *   - conflictingNamedUserID: The conflicting named user ID.\n     */\n   init(\n        tags: [String: [String]],\n        attributes: [String: AirshipJSON],\n        associatedChannels: [ChannelInfo],\n        subscriptionLists: [String: [ChannelScope]],\n        conflictingNamedUserID: String?\n    ) {\n\n        self.tags = tags\n        self.associatedChannels = associatedChannels\n        self.subscriptionLists = subscriptionLists\n        self.conflictingNamedUserID = conflictingNamedUserID\n        self.attributes = attributes\n    }\n\n\n    public struct ChannelInfo: Sendable, Equatable {\n\n        /**\n         * Channel type\n         */\n        public let channelType: ChannelType\n\n        /**\n         * channel ID\n         */\n        public let channelID: String\n\n    }\n\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactManager.swift",
    "content": "import Foundation\n\nactor ContactManager: ContactManagerProtocol {\n    private static let operationsKey: String = \"Contact.operationEntries\"\n    private static let legacyOperationsKey: String = \"Contact.operations\" // operations without the date\n    private static let contactInfoKey: String = \"Contact.contactInfo\"\n    private static let anonContactDataKey: String = \"Contact.anonContactData\"\n\n    static let identityRateLimitID: String = \"Contact.identityRateLimitID\"\n    static let updateRateLimitID: String = \"Contact.updateRateLimitID\"\n    static let updateTaskID: String = \"Contact.update\"\n\n    private let cachedAuthToken: CachedValue<AuthToken> = CachedValue()\n    private var dataStore: PreferenceDataStore\n    private let identifySerialQueue: AirshipSerialQueue = AirshipSerialQueue()\n    private let channel: any AirshipChannel\n    private let apiClient: any ContactsAPIClientProtocol\n    private let workManager: any AirshipWorkManagerProtocol\n    private let date: any AirshipDateProtocol\n    private let internalIdentifyRateLimit: TimeInterval\n\n    private let localeManager: any AirshipLocaleManager\n    private var onAudienceUpdatedCallback: ((ContactAudienceUpdate) async -> Void)?\n    private var lastContactIDUpdate: ContactIDInfo?\n    private var lastNamedUserUpdate: String?\n\n    let contactUpdates: AsyncStream<ContactUpdate>\n    private let contactUpdatesContinuation: AsyncStream<ContactUpdate>.Continuation\n\n    private var isEnabled: Bool = false\n\n    private var lastIdentifyOperationDate: Date = Date.distantPast\n\n    private var lastSuccessfulIdentifyDate: Date = Date.distantPast\n\n\n    private var operationEntries: [ContactOperationEntry] {\n        get {\n            if (dataStore.keyExists(ContactManager.operationsKey)) {\n                return dataStore.safeCodable(forKey: ContactManager.operationsKey) ?? []\n            } else if (dataStore.keyExists(ContactManager.legacyOperationsKey)) {\n                let operations: [ContactOperation] = dataStore.safeCodable(forKey: ContactManager.operationsKey) ?? []\n                let now = date.now\n                let entries = operations.map { operation in\n                    return ContactOperationEntry(date: now, operation: operation, identifier: UUID().uuidString)\n                }\n\n                dataStore.setSafeCodable(entries, forKey: ContactManager.operationsKey)\n                dataStore.removeObject(forKey: ContactManager.legacyOperationsKey)\n                return entries\n            }\n            return []\n        }\n        set {\n            dataStore.setSafeCodable(newValue, forKey: ContactManager.operationsKey)\n        }\n    }\n\n    private var anonData: AnonContactData? {\n        get {\n            return dataStore.safeCodable(forKey: ContactManager.anonContactDataKey)\n        }\n        set {\n            dataStore.setSafeCodable(newValue, forKey: ContactManager.anonContactDataKey)\n        }\n    }\n\n    private var lastContactInfo: InternalContactInfo?  {\n        get {\n            return dataStore.safeCodable(forKey: ContactManager.contactInfoKey)\n        }\n        set {\n            dataStore.setSafeCodable(newValue, forKey: ContactManager.contactInfoKey)\n        }\n    }\n\n    private var possiblyOrphanedContactID: String? {\n        guard let lastContactInfo = self.lastContactInfo,\n              lastContactInfo.isAnonymous,\n              (anonData?.channels.isEmpty ?? true)\n        else {\n            return nil\n        }\n\n        return lastContactInfo.contactID\n    }\n\n    init(\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel,\n        localeManager: any AirshipLocaleManager,\n        apiClient: any ContactsAPIClientProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        workManager: any AirshipWorkManagerProtocol = AirshipWorkManager.shared,\n        internalIdentifyRateLimit: TimeInterval = 5.0\n    ) {\n        self.dataStore = dataStore\n        self.apiClient = apiClient\n        self.channel = channel\n        self.date = date\n        self.workManager = workManager\n        self.localeManager = localeManager\n        self.internalIdentifyRateLimit = internalIdentifyRateLimit\n\n        (\n            self.contactUpdates,\n            self.contactUpdatesContinuation\n        ) = AsyncStream<ContactUpdate>.airshipMakeStreamWithContinuation()\n\n        self.workManager.registerWorker(\n            ContactManager.updateTaskID\n        ) { [weak self] _ in\n            if (try await self?.performNextOperation() != false) {\n                return .success\n            }\n            return .failure\n        }\n\n        workManager.setRateLimit(\n            ContactManager.identityRateLimitID,\n            rate: 1,\n            timeInterval: 5.0\n        )\n\n        workManager.setRateLimit(\n            ContactManager.updateRateLimitID,\n            rate: 1,\n            timeInterval: 0.5\n        )\n\n        Task { [weak channel] in\n            await self.yieldContactUpdates()\n\n            let startingChannelID = channel?.identifier\n            let updates = channel?.identifierUpdates.filter { $0 != startingChannelID }\n\n            if let updates {\n                for await _ in updates {\n                    try Task.checkCancellation()\n                    await enqueueTask()\n                }\n            }\n        }\n    }\n\n    func onAudienceUpdated(onAudienceUpdatedCallback: (@Sendable (ContactAudienceUpdate) async -> Void)?) {\n        self.onAudienceUpdatedCallback = onAudienceUpdatedCallback\n    }\n\n    func resetIfNeeded() {\n        guard\n            self.operationEntries.isEmpty == false || lastContactInfo?.isAnonymous == false || self.hasAnonData() || self.lastContactInfo == nil\n        else {\n            return\n        }\n\n        addOperation(.reset)\n    }\n\n    func addOperation(_ operation: ContactOperation) {\n        self.operationEntries.append(\n            ContactOperationEntry(date: self.date.now, operation: operation, identifier: UUID().uuidString)\n        )\n\n        self.yieldContactUpdates()\n        self.enqueueTask()\n    }\n\n    func generateDefaultContactIDIfNotSet() -> Void {\n        guard self.lastContactInfo == nil else {\n            return\n        }\n\n        self.lastContactInfo = InternalContactInfo(\n            contactID: UUID().uuidString.lowercased(),\n            isAnonymous: true,\n            namedUserID: nil,\n            channelAssociatedDate: self.date.now,\n            resolveDate: self.date.now\n        )\n\n        self.yieldContactUpdates()\n    }\n\n    func currentNamedUserID() -> String? {\n        let entries = self.operationEntries.reversed().first { entry in\n            entry.operation.type == .identify || entry.operation.type == .reset\n        }\n\n        if let entry = entries {\n            switch(entry.operation) {\n            case .reset:\n                return nil\n            case .identify(let identifier):\n                return identifier\n            default: break\n            }\n        }\n\n        return self.lastContactInfo?.namedUserID\n    }\n\n    func resolveAuth(identifier: String) async throws -> String {\n        if lastContactInfo?.contactID == identifier, let token = tokenIfValid() {\n            return token\n        }\n\n        _ = try await performOperation(.resolve)\n        self.yieldContactUpdates()\n\n        guard lastContactInfo?.contactID == identifier else {\n            throw AirshipErrors.error(\"Mismatch contact ID\")\n        }\n\n        if let token = tokenIfValid() {\n            return token\n        }\n\n        throw AirshipErrors.error(\"Failed to refresh token\")\n    }\n\n    @inline(never)\n    func authTokenExpired(token: String) async {\n        self.cachedAuthToken.expireIf { auth in\n            return auth.token == token\n        }\n    }\n\n    func setEnabled(enabled: Bool) {\n        guard self.isEnabled != enabled else  { return }\n        self.isEnabled = enabled\n\n        if enabled {\n            enqueueTask()\n        }\n    }\n\n    @inline(never)\n    func currentContactIDInfo() -> ContactIDInfo? {\n        guard let lastContactInfo = self.lastContactInfo else {\n            return nil\n        }\n\n        return ContactIDInfo(\n            contactID: lastContactInfo.contactID,\n            isStable: self.isContactIDStable(),\n            namedUserID: lastContactInfo.namedUserID,\n            resolveDate: lastContactInfo.resolveDate ?? .distantPast\n        )\n    }\n\n    // Worker -> one at a time\n    @inline(never)\n    private func performNextOperation() async throws -> Bool {\n        guard self.isEnabled else {\n            AirshipLogger.trace(\"Contact manager is not enabled, unable to perform operation\")\n            return true\n        }\n\n        guard !self.operationEntries.isEmpty else {\n            AirshipLogger.trace(\"Operations are empty\")\n            return true\n        }\n\n        defer {\n            self.yieldContactUpdates()\n        }\n\n        // Make sure we have a valid token so we know we are operating on\n        // the correct contact ID to hopefully avoid any error logs if the\n        // contact ID changes in the middle of an update\n        if tokenIfValid() == nil  {\n            let resolveResult = try await performOperation(.resolve)\n            self.yieldContactUpdates()\n            if (!resolveResult) {\n                return false\n            }\n        }\n\n        self.clearSkippableOperations()\n        yieldContactUpdates()\n\n        guard let operationGroup = prepareNextOperationGroup() else {\n            AirshipLogger.trace(\"Next operation group is nil\")\n            return true\n        }\n\n        let result = try await performOperation(operationGroup.mergedOperation)\n        if (result) {\n            let identifiers = operationGroup.operations.map { $0.identifier }\n\n            self.operationEntries.removeAll { entry in\n                identifiers.contains(entry.identifier)\n            }\n\n            if (!self.operationEntries.isEmpty) {\n                self.enqueueTask()\n            }\n        }\n\n        return result\n    }\n\n    private func tokenIfValid() -> String? {\n        if let token = self.cachedAuthToken.value,\n           token.identifier == self.lastContactInfo?.contactID,\n           self.cachedAuthToken.timeRemaining >= 30\n        {\n            return token.token\n        }\n\n        return nil\n    }\n\n    private func enqueueTask() {\n        guard self.isEnabled else {\n            AirshipLogger.trace(\"Contact manager is not enabled, unable to enqueue task\")\n            return\n        }\n\n        guard self.channel.identifier != nil else {\n            AirshipLogger.trace(\"Channel not created, unable to enqueue task\")\n            return\n        }\n\n        var rateLimitIDs = [ContactManager.updateRateLimitID]\n\n        let next = self.operationEntries.first { !self.isSkippable(operation: $0.operation) }?.operation\n        if (next?.type == .reset || next?.type == .identify || tokenIfValid() == nil) {\n            rateLimitIDs += [ContactManager.identityRateLimitID]\n        }\n\n        self.workManager.dispatchWorkRequest(\n            AirshipWorkRequest(\n                workID: ContactManager.updateTaskID,\n                requiresNetwork: true,\n                rateLimitIDs: Set(rateLimitIDs)\n            )\n        )\n    }\n\n    private func clearSkippableOperations() {\n        var operations = self.operationEntries\n        while let next = operations.first {\n            if (isSkippable(operation: next.operation)) {\n                operations.removeFirst()\n            } else {\n                break\n            }\n        }\n        self.operationEntries = operations\n    }\n\n    private func performOperation(_ operation: ContactOperation) async throws -> Bool {\n        AirshipLogger.trace(\"Performing operation \\(operation.type)\")\n        guard !self.isSkippable(operation: operation) else {\n            AirshipLogger.trace(\"Operation skippable, finished operation \\(operation.type)\")\n            return true\n        }\n\n        switch operation {\n        case .update(let tagUpdates, let attributeUpdates, let subListUpdates):\n            return try await performUpdateOperation(\n                tagGroupUpdates: tagUpdates,\n                attributeUpdates: attributeUpdates,\n                subscriptionListsUpdates: subListUpdates\n            )\n\n        case .identify(let identifier):\n            return try await doIdentify {\n                return try await self.performIdentifyOperation(\n                    identifier: identifier\n                )\n            }\n\n        case .reset:\n            return try await doIdentify {\n                return try await self.performResetOperation()\n            }\n\n        case .resolve:\n            return try await doIdentify {\n                return try await self.performResolveOperation()\n            }\n\n        case .verify(_, _):\n            return try await doIdentify {\n                return try await self.performResolveOperation()\n            }\n\n        case .registerEmail(let address, let options):\n            return try await performRegisterEmailOperation(\n                address: address,\n                options: options\n            )\n\n        case .registerSMS(let msisdn, let options):\n            return try await performRegisterSMSOperation(\n                msisdn: msisdn,\n                options: options\n            )\n\n        case .registerOpen(let address, let options):\n            return try await performRegisterOpenChannelOperation(\n                address: address,\n                options: options\n            )\n\n        case .associateChannel(let channelID, let type):\n            return try await performAssociateChannelOperation(\n                channelID: channelID,\n                type: type\n            )\n\n        case .disassociateChannel(let channel):\n            return try await performDisassociateChannel(\n                channel: channel\n            )\n        case .resend(channel: let channel):\n            return try await performResend(channel: channel)\n        }\n    }\n\n    private func performResolveOperation() async throws -> Bool {\n        let response = try await self.apiClient.resolve(\n            channelID: try requireChannelID(),\n            contactID: self.lastContactInfo?.contactID,\n            possiblyOrphanedContactID: possiblyOrphanedContactID\n        )\n\n        if let result = response.result, response.isSuccess {\n            await updateContactInfo(result: result, operationType: .resolve)\n        }\n\n        return response.isOperationComplete\n    }\n\n    private func performResetOperation() async throws -> Bool {\n        let response = try await self.apiClient.reset(\n            channelID: try requireChannelID(),\n            possiblyOrphanedContactID: possiblyOrphanedContactID\n        )\n\n        if let result = response.result, response.isSuccess {\n            await updateContactInfo(result: result, operationType: .reset)\n        }\n\n        return response.isOperationComplete\n    }\n\n    private func performIdentifyOperation(identifier: String) async throws -> Bool {\n        let response = try await self.apiClient.identify(\n            channelID: try requireChannelID(),\n            namedUserID: identifier,\n            contactID: self.lastContactInfo?.contactID,\n            possiblyOrphanedContactID: possiblyOrphanedContactID\n        )\n\n        if let result = response.result, response.isSuccess {\n            await updateContactInfo(\n                result: result,\n                namedUserID: identifier,\n                operationType: .identify\n            )\n        }\n\n        return response.isOperationComplete\n    }\n\n    private func performRegisterEmailOperation(\n        address: String,\n        options: EmailRegistrationOptions\n    ) async throws -> Bool {\n        let contactID = try requireContactID()\n\n        let response = try await self.apiClient.registerEmail(\n            contactID: contactID,\n            address: address,\n            options: options,\n            locale: self.localeManager.currentLocale\n        )\n\n        if response.isSuccess, let result = response.result {\n            await self.contactUpdated(\n                contactID: contactID,\n                channelUpdates: [\n                    .associated(\n                        .email(\n                            .pending(\n                                ContactChannel.Email.Pending(\n                                    address: address,\n                                    registrationOptions: options\n                                )\n                            )\n                        ),\n                        channelID: result.channelID\n                    )\n                ]\n            )\n        }\n\n        return response.isOperationComplete\n    }\n\n    private func performRegisterSMSOperation(\n        msisdn: String,\n        options: SMSRegistrationOptions\n    ) async throws -> Bool {\n        let contactID = try requireContactID()\n\n        let response = try await self.apiClient.registerSMS(\n            contactID: contactID,\n            msisdn: msisdn,\n            options: options,\n            locale: self.localeManager.currentLocale\n        )\n\n        if response.isSuccess, let result = response.result {\n            await self.contactUpdated(\n                contactID: contactID,\n                channelUpdates: [\n                    .associated(\n                        .sms(\n                            .pending(\n                                ContactChannel.Sms.Pending(\n                                    address: msisdn,\n                                    registrationOptions: options\n                                )\n                            )\n                        ),\n                        channelID: result.channelID\n                    )\n                ]\n            )\n        }\n        return response.isOperationComplete\n    }\n\n    private func performRegisterOpenChannelOperation(address: String, options: OpenRegistrationOptions) async throws -> Bool {\n        let contactID = try requireContactID()\n\n        let response = try await self.apiClient.registerOpen(\n            contactID: contactID,\n            address: address,\n            options: options,\n            locale: self.localeManager.currentLocale\n        )\n\n        if response.isSuccess, let result = response.result {\n            await self.contactUpdated(\n                contactID: contactID,\n                channelUpdates: [\n                    /// TODO: Backend does not support open channels for ContactChannel yet\n                    .associatedAnonChannel(\n                        channelType: .open,\n                        channelID: result.channelID\n                    )\n                ]\n            )\n        }\n\n        return response.isOperationComplete\n    }\n\n    private func performAssociateChannelOperation(\n        channelID: String,\n        type: ChannelType\n    ) async throws -> Bool {\n        let contactID = try requireContactID()\n        let response = try await self.apiClient.associateChannel(\n            contactID: contactID,\n            channelID: channelID,\n            channelType: type\n        )\n\n        if response.isSuccess {\n            await self.contactUpdated(\n                contactID: contactID,\n                channelUpdates: [\n                    .associatedAnonChannel(channelType: type, channelID: channelID)\n                ]\n            )\n        }\n        return response.isOperationComplete\n    }\n\n    private func performDisassociateChannel(\n        channel: ContactChannel\n    ) async throws -> Bool {\n        let contactID = try requireContactID()\n\n\n        let options: DisassociateOptions = switch(channel) {\n        case .email(let email):\n            switch(email) {\n            case .registered(let info):\n                DisassociateOptions(\n                    channelID: info.channelID,\n                    channelType: .email,\n                    optOut: true\n                )\n            case .pending(let info):\n                DisassociateOptions(\n                    emailAddress: info.address,\n                    optOut: false\n                )\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .registered(let info):\n                DisassociateOptions(\n                    channelID: info.channelID,\n                    channelType: .sms,\n                    optOut: true\n                )\n            case .pending(let info):\n                DisassociateOptions(\n                    msisdn: info.address,\n                    senderID: info.registrationOptions.senderID,\n                    optOut: false\n                )\n            }\n        }\n\n        let response = try await self.apiClient.disassociateChannel(\n            contactID: contactID,\n            disassociateOptions: options\n        )\n\n        if response.isSuccess, let result = response.result {\n            await self.contactUpdated(\n                contactID: contactID,\n                channelUpdates: [.disassociated(channel, channelID: result.channelID)]\n            )\n        }\n\n        return response.isOperationComplete\n    }\n\n    private func performResend(\n        channel: ContactChannel\n    ) async throws -> Bool {\n        let resendOptions:ResendOptions = switch(channel) {\n        case .email(let email):\n            switch(email) {\n            case .registered(let info):\n                ResendOptions(channelID: info.channelID, channelType: .email)\n            case .pending(let info):\n                ResendOptions(emailAddress: info.address)\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .registered(let info):\n                ResendOptions(channelID: info.channelID, channelType: channel.channelType)\n            case .pending(let info):\n                ResendOptions(msisdn: info.address, senderID: info.registrationOptions.senderID)\n            }\n        }\n\n        let response = try await self.apiClient.resend(\n            resendOptions: resendOptions\n        )\n\n        return response.isOperationComplete\n    }\n\n    private func performUpdateOperation(\n        tagGroupUpdates: [TagGroupUpdate]?,\n        attributeUpdates: [AttributeUpdate]?,\n        subscriptionListsUpdates: [ScopedSubscriptionListUpdate]?\n    ) async throws -> Bool {\n        guard let contactInfo = self.lastContactInfo else {\n            AirshipLogger.error(\"Failed to update contact, missing contact ID.\")\n            return false\n        }\n\n        let response = try await self.apiClient.update(\n            contactID: contactInfo.contactID,\n            tagGroupUpdates: tagGroupUpdates,\n            attributeUpdates: attributeUpdates,\n            subscriptionListUpdates: subscriptionListsUpdates\n        )\n\n        if response.isSuccess {\n            await self.contactUpdated(\n                contactID: contactInfo.contactID,\n                tagGroupUpdates: tagGroupUpdates,\n                attributeUpdates: attributeUpdates,\n                subscriptionListsUpdates: subscriptionListsUpdates\n            )\n        }\n\n        return response.isOperationComplete\n    }\n\n    func pendingAudienceOverrides(contactID: String) -> ContactAudienceOverrides {\n        // If we have a contact ID but its stale, return an empty overrides\n        guard contactID == self.lastContactInfo?.contactID else {\n            return ContactAudienceOverrides()\n        }\n\n        var tags: [TagGroupUpdate] = []\n        var attributes: [AttributeUpdate] = []\n        var subscriptionLists: [ScopedSubscriptionListUpdate] = []\n        let operations = operationEntries.map { $0.operation }\n        var channels: [ContactChannelUpdate] = []\n\n        var lastOperationNamedUser: String? = nil\n\n        for operation in operations {\n            // A reset will generate a new contact ID\n            if case .reset = operation {\n               break\n            }\n\n            if case let .identify(namedUserID) = operation {\n                // If we come across an identify operation that does not match the current contact info,\n                // then any further operations are for a different contact\n                if self.lastContactInfo?.isAnonymous == false, namedUserID != self.lastContactInfo?.namedUserID {\n                    break\n                }\n\n                // If we have a lastOperationNamedUser and it does not match the operation\n                if lastOperationNamedUser != nil, lastOperationNamedUser != namedUserID {\n                    break\n                }\n\n                lastOperationNamedUser = namedUserID\n                continue\n            }\n\n            if case let .update(tagUpdates, attributesUpdates, subscriptionListsUpdates) = operation {\n                if let tagUpdates = tagUpdates {\n                    tags += tagUpdates\n                }\n                if let attributesUpdates = attributesUpdates {\n                    attributes += attributesUpdates\n                }\n                if let subscriptionListsUpdates = subscriptionListsUpdates {\n                    subscriptionLists += subscriptionListsUpdates\n                }\n\n                continue\n            }\n\n            if case .registerSMS(let address, let options) = operation {\n                channels.append(\n                    .associated(\n                        .sms(\n                            .pending(\n                                ContactChannel.Sms.Pending(\n                                    address: address,\n                                    registrationOptions: options\n                                )\n                            )\n                        )\n                    )\n                )\n                continue\n            }\n\n            if case .registerEmail(let address, let options) = operation {\n                channels.append(\n                    .associated(\n                        .email(\n                            .pending(\n                                ContactChannel.Email.Pending(\n                                    address: address,\n                                    registrationOptions: options\n                                )\n                            )\n                        )\n                    )\n                )\n                continue\n            }\n\n            if case .disassociateChannel(let channel) = operation {\n                channels.append(\n                    .disassociated(channel)\n                )\n                continue\n            }\n\n            if case .associateChannel(let channelID, let channelType) = operation {\n                channels.append(\n                    .associatedAnonChannel(\n                        channelType: channelType,\n                        channelID: channelID\n                    )\n                )\n                continue\n            }\n        }\n\n        return ContactAudienceOverrides(\n            tags: tags,\n            attributes: attributes,\n            subscriptionLists: subscriptionLists,\n            channels: channels\n        )\n    }\n\n    private func hasAnonData() -> Bool {\n        guard self.lastContactInfo?.isAnonymous == true,\n              let anonData = self.anonData else {\n            return false\n        }\n        return !anonData.isEmpty\n    }\n\n    private func requireContactID() throws -> String {\n        guard let contactID = lastContactInfo?.contactID else {\n            throw AirshipErrors.error(\"Missing contact ID\")\n        }\n        return contactID\n    }\n\n    private func requireChannelID() throws -> String {\n        guard let channelID = self.channel.identifier else {\n            throw AirshipErrors.error(\"Missing channel ID\")\n        }\n        return channelID\n    }\n\n    private func prepareNextOperationGroup() -> ContactOperationGroup? {\n        var operations = self.operationEntries\n\n        guard let next = operations.first else {\n            return nil\n        }\n\n        operations.removeFirst()\n\n\n        switch (next.operation) {\n        case .update(let nextTags, let nextAttributes, let nextSubList):\n            // Group updates into a single update\n\n            var group = [next]\n            var mergedTags = nextTags ?? []\n            var mergedAttributes = nextAttributes ?? []\n            var mergedSubLists = nextSubList ?? []\n\n            for nextNext in operations {\n                if case let .update(otherTags, otherAttributes, otherSubLists) = nextNext.operation {\n                    mergedTags += (otherTags ?? [])\n                    mergedAttributes += (otherAttributes ?? [])\n                    mergedSubLists += (otherSubLists ?? [])\n                    group.append(nextNext)\n                } else {\n                    break\n                }\n            }\n\n            let mergedUpdate: ContactOperation = .update(\n                tagUpdates: AudienceUtils.collapse(mergedTags),\n                attributeUpdates: AudienceUtils.collapse(mergedAttributes),\n                subscriptionListsUpdates: AudienceUtils.collapse(mergedSubLists)\n            )\n\n            return ContactOperationGroup(\n                operations: group,\n                mergedOperation: mergedUpdate\n            )\n        case .identify(_): fallthrough\n        case .reset:\n            // A series of resets and identifies can be skipped and only the last reset or identify\n            // can be performed if we do not have any anon data.\n\n            guard !hasAnonData() else {\n                return ContactOperationGroup(\n                    operations: [next],\n                    mergedOperation: next.operation\n                )\n            }\n\n            var group = [next]\n            var last = next\n            for nextNext in operations {\n                if (nextNext.operation.type == .identify || nextNext.operation.type == .reset) {\n                    group.append(nextNext)\n                    last = nextNext\n                } else {\n                    break\n                }\n            }\n\n            return ContactOperationGroup(\n                operations: group,\n                mergedOperation: last.operation\n            )\n\n        default:\n            return ContactOperationGroup(\n                operations: [next],\n                mergedOperation: next.operation\n            )\n        }\n    }\n\n    private func isContactIDStable() -> Bool {\n        guard let lastContactInfo = lastContactInfo else {\n            return false\n        }\n\n        return !self.operationEntries.contains { entry in\n            switch entry.operation {\n            case .reset: return true\n            case .verify(_, let required):\n                return required == true\n            case .identify(let identifier):\n                if lastContactInfo.namedUserID != identifier {\n                    return true\n                } else {\n                    return false\n                }\n            default: return false\n            }\n        }\n    }\n\n    private func isSkippable(operation: ContactOperation) -> Bool {\n        switch operation {\n        case .update(let tagUpdates, let attributeUpdates, let subListUpdates):\n            // Skip if its an empty update\n            return tagUpdates?.isEmpty ?? true &&\n            attributeUpdates?.isEmpty ?? true &&\n            subListUpdates?.isEmpty ?? true\n\n        case .identify(let identifier):\n            // Skip if we are already this user and have a valid token\n            return self.lastContactInfo?.namedUserID == identifier && tokenIfValid() != nil\n\n        case .reset:\n            // Skip if we are already anonymous, have no data, and a valid token.\n            return lastContactInfo?.isAnonymous == true &&\n            self.anonData?.isEmpty != false &&\n            tokenIfValid() != nil\n\n        case .resolve:\n            // Skip if we have a valid token for the current contact info\n            return tokenIfValid() != nil\n\n        case .verify(let date, _):\n            // Skip if we have a valid token and the resolveDate is newer than the verify date\n            let resolveDate = self.lastContactInfo?.resolveDate ?? .distantPast\n            return tokenIfValid() != nil && date <= resolveDate\n\n        default: return false\n        }\n    }\n\n    private func updateContactInfo(\n        result: ContactIdentifyResult,\n        namedUserID: String? = nil,\n        operationType: ContactOperation.OperationType,\n        resolveDate: Date? = nil\n    ) async {\n        let expiration = self.date.now.advanced(by: \n            Double(result.tokenExpiresInMilliseconds)/1000.0\n        )\n\n        // Update token\n        self.cachedAuthToken.set(\n            value: AuthToken(\n                identifier: result.contact.contactID,\n                token: result.token,\n                expiration: expiration\n            ),\n            expiration: expiration\n        )\n\n\n        // Doing a lowercased check so if backend normalizes the contact ID we wont lose data.\n        let isNewContactID = lastContactInfo?.contactID.lowercased() != result.contact.contactID.lowercased()\n\n        var resolvedNamedUser = namedUserID\n        if !isNewContactID, resolvedNamedUser == nil {\n            resolvedNamedUser = lastContactInfo?.namedUserID\n        }\n\n        let newContactInfo = InternalContactInfo(\n            contactID: result.contact.contactID,\n            isAnonymous: result.contact.isAnonymous,\n            namedUserID: resolvedNamedUser,\n            channelAssociatedDate: result.contact.channelAssociatedDate,\n            resolveDate: self.date.now\n        )\n\n        // Conflict events\n        if isNewContactID,\n           self.lastContactInfo?.isAnonymous == true,\n           let anonData = self.anonData,\n           !anonData.isEmpty\n        {\n            self.contactUpdatesContinuation.yield(\n                .conflict(\n                    ContactConflictEvent(\n                        tags: anonData.tags,\n                        attributes: anonData.attributes,\n                        associatedChannels: anonData.channels.map { .init(channelType: $0.channelType, channelID: $0.channelID) },\n                        subscriptionLists: anonData.subscriptionLists,\n                        conflictingNamedUserID: namedUserID\n                    )\n                )\n            )\n            self.anonData = nil\n        }\n\n        // Reset anon data\n        if !newContactInfo.isAnonymous {\n            self.anonData = nil\n        }\n\n        // If we have a resolve that returns a new contactID then it means\n        // it was changed server side. Clear any pending operations that are\n        // older than the resolve date.\n        if self.lastContactInfo != nil, isNewContactID, operationType == .resolve {\n            self.operationEntries = self.operationEntries.filter { entry in\n                if (result.contact.channelAssociatedDate < entry.date) {\n                    return true\n                } else {\n                    AirshipLogger.trace(\"Dropping operation \\(entry.operation.type) due to channel association date\")\n                    return false\n                }\n            }\n        }\n\n        self.lastContactInfo = newContactInfo\n    }\n\n    private func updateLastIdentifyOperationDate() {\n        self.lastIdentifyOperationDate = self.date.now\n    }\n\n    private func yieldContactUpdates() {\n        let currentContactIDInfo = self.currentContactIDInfo()\n        if let currentContactIDInfo = currentContactIDInfo, currentContactIDInfo != self.lastContactIDUpdate {\n            self.contactUpdatesContinuation.yield(.contactIDUpdate(currentContactIDInfo))\n            self.lastContactIDUpdate = currentContactIDInfo\n        }\n\n        let currentNamedUserID = self.currentNamedUserID()\n        if currentNamedUserID != self.lastNamedUserUpdate {\n            self.contactUpdatesContinuation.yield(.namedUserUpdate(currentNamedUserID))\n            self.lastNamedUserUpdate = currentNamedUserID\n        }\n    }\n\n    private func doIdentify<T: Sendable>(work: @escaping @Sendable () async throws -> T) async throws -> T {\n        try await self.identifySerialQueue.run {\n            // We handle identify rate limit internally since both work and auth token might cause identify to be called\n            let remainingTime = self.internalIdentifyRateLimit - self.date.now.timeIntervalSince(\n                await self.lastIdentifyOperationDate\n            )\n\n            if (remainingTime > 0) {\n                try await Task.sleep(\n                    nanoseconds: UInt64(remainingTime * 1_000_000_000)\n                )\n            }\n\n            let result: T = try await work()\n            await self.updateLastIdentifyOperationDate()\n            return result\n        }\n    }\n\n    private func contactUpdated(\n        contactID: String,\n        tagGroupUpdates: [TagGroupUpdate]? = nil,\n        attributeUpdates: [AttributeUpdate]? = nil,\n        subscriptionListsUpdates: [ScopedSubscriptionListUpdate]? = nil,\n        channelUpdates: [ContactChannelUpdate]? = nil\n    ) async {\n\n        guard let contactInfo = self.lastContactInfo, contactInfo.contactID == contactID else {\n            return\n        }\n\n        if tagGroupUpdates?.isEmpty == false || attributeUpdates?.isEmpty == false || subscriptionListsUpdates?.isEmpty == false || channelUpdates?.isEmpty == false {\n           await self.onAudienceUpdatedCallback?(\n                ContactAudienceUpdate(\n                    contactID: contactID,\n                    tags: tagGroupUpdates,\n                    attributes: attributeUpdates,\n                    subscriptionLists: subscriptionListsUpdates,\n                    contactChannels: channelUpdates\n                )\n            )\n        }\n\n        if contactInfo.isAnonymous {\n            let data = self.anonData\n            var tags: [String: [String]] = data?.tags ?? [:]\n            var attributes: [String: AirshipJSON] = data?.attributes ?? [:]\n            var channels: Set<AnonContactData.Channel> = Set(data?.channels ?? [])\n            var subscriptionLists: [String: [ChannelScope]] = data?.subscriptionLists ?? [:]\n\n            tags = AudienceUtils.applyTagUpdates(\n                data?.tags,\n                updates: tagGroupUpdates\n            )\n\n            attributes = AudienceUtils.applyAttributeUpdates(\n                data?.attributes,\n                updates: attributeUpdates\n            )\n\n            subscriptionLists = AudienceUtils.applySubscriptionListsUpdates(\n                data?.subscriptionLists,\n                updates: subscriptionListsUpdates\n            )\n\n            channelUpdates?.forEach { channelUpdate in\n                switch(channelUpdate) {\n                case .disassociated(let contactChannel, let channelID):\n                    if let channelID = channelID ?? contactChannel.channelID {\n                        channels.remove(.init(channelType: contactChannel.channelType, channelID: channelID))\n                    }\n                case .associated(let contactChannel, let channelID):\n                    if let channelID = channelID ?? contactChannel.channelID {\n                        channels.insert(.init(channelType: contactChannel.channelType, channelID: channelID))\n                    }\n                case .associatedAnonChannel(let channelType, let channelID):\n                    channels.insert(.init(channelType: channelType, channelID: channelID))\n                }\n            }\n\n\n            self.anonData = AnonContactData(\n                tags: tags,\n                attributes: attributes,\n                channels: Array(channels),\n                subscriptionLists: subscriptionLists\n            )\n        }\n    }\n}\n\n\nfileprivate struct InternalContactInfo: Codable, Equatable {\n    let contactID: String\n    let isAnonymous: Bool\n    let namedUserID: String?\n    let channelAssociatedDate: Date?\n    let resolveDate: Date?\n}\n\nfileprivate struct ContactOperationGroup {\n    let operations: [ContactOperationEntry]\n    let mergedOperation: ContactOperation\n}\n\nfileprivate struct ContactOperationEntry: Codable, Sendable {\n    let date: Date\n    let operation: ContactOperation\n    let identifier: String\n}\n\nfileprivate extension AirshipHTTPResponse {\n    var isOperationComplete: Bool {\n        // Consider the operation complete if we have a success\n        // response or a client error. The client error is to avoid\n        // blocking the queue on either an invalid operation or\n        // if the app is not configured to properly.\n        return self.isSuccess || self.isClientError\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactManagerProtocol.swift",
    "content": "import Foundation\n\nprotocol ContactManagerProtocol: Actor, AuthTokenProvider {\n\n    var contactUpdates: AsyncStream<ContactUpdate> { get }\n    \n    func onAudienceUpdated(onAudienceUpdatedCallback: (@Sendable (ContactAudienceUpdate) async -> Void)?)\n\n    func addOperation(_ operation: ContactOperation)\n\n    func generateDefaultContactIDIfNotSet() -> Void\n\n    func currentNamedUserID() -> String?\n\n    func setEnabled(enabled: Bool)\n\n    func currentContactIDInfo() -> ContactIDInfo?\n\n    func resetIfNeeded()\n\n    func pendingAudienceOverrides(contactID: String) -> ContactAudienceOverrides\n}\n\nstruct ContactAudienceUpdate: Equatable, Sendable {\n    let contactID: String\n    let tags: [TagGroupUpdate]?\n    let attributes: [AttributeUpdate]?\n    let subscriptionLists: [ScopedSubscriptionListUpdate]?\n    let contactChannels: [ContactChannelUpdate]?\n\n    init(contactID: String, tags: [TagGroupUpdate]? = nil, attributes: [AttributeUpdate]? = nil, subscriptionLists: [ScopedSubscriptionListUpdate]? = nil, contactChannels: [ContactChannelUpdate]? = nil) {\n        self.contactID = contactID\n        self.tags = tags\n        self.attributes = attributes\n        self.subscriptionLists = subscriptionLists\n        self.contactChannels = contactChannels\n    }\n}\n\nstruct ContactIDInfo: Equatable, Sendable {\n    let contactID: String\n    let namedUserID: String?\n    let isStable: Bool\n    let resolveDate: Date\n\n    init(contactID: String, isStable: Bool, namedUserID: String?, resolveDate: Date = Date.distantPast) {\n        self.contactID = contactID\n        self.isStable = isStable\n        self.resolveDate = resolveDate\n        self.namedUserID = namedUserID\n    }\n}\n\nenum ContactUpdate: Equatable, Sendable {\n    case contactIDUpdate(ContactIDInfo)\n    case namedUserUpdate(String?)\n    case conflict(ContactConflictEvent)\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic struct StableContactInfo: Sendable, Equatable {\n    public let contactID: String\n    public let namedUserID: String?\n    \n    public init(contactID: String, namedUserID: String? = nil) {\n        self.contactID = contactID\n        self.namedUserID = namedUserID\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactOperation.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n\n\n/// NOTE: For internal use only. :nodoc:\nenum ContactOperation: Codable, Equatable, Sendable {\n    var type: OperationType {\n        switch (self) {\n        case .update(_, _, _): return .update\n        case .identify(_): return .identify\n        case .resolve: return .resolve\n        case .verify(_, _): return .verify\n        case .reset: return .reset\n        case .registerEmail(_, _): return .registerEmail\n        case .registerSMS(_, _): return .registerSMS\n        case .registerOpen(_, _): return .registerOpen\n        case .associateChannel(_, _): return .associateChannel\n        case .disassociateChannel(_): return .disassociateChannel\n        case .resend(_): return .resend\n        }\n    }\n\n    enum OperationType: String, Codable {\n        case update\n        case identify\n        case resolve\n        case reset\n        case verify\n        case registerEmail\n        case registerSMS\n        case registerOpen\n        case associateChannel\n        case disassociateChannel\n        case resend\n    }\n\n    case update(\n        tagUpdates: [TagGroupUpdate]? = nil,\n        attributeUpdates: [AttributeUpdate]? = nil,\n        subscriptionListsUpdates: [ScopedSubscriptionListUpdate]? = nil\n    )\n    case identify(String)\n    case resolve\n    case reset\n    case verify(Date, required: Bool? = nil)\n    case registerEmail(\n        address: String,\n        options: EmailRegistrationOptions\n    )\n\n    case registerSMS(\n        msisdn: String,\n        options: SMSRegistrationOptions\n    )\n\n    case registerOpen(\n        address: String,\n        options: OpenRegistrationOptions\n    )\n\n    case associateChannel(\n        channelID: String,\n        channelType: ChannelType\n    )\n\n    case disassociateChannel(\n        channel: ContactChannel\n    )\n\n    case resend(\n        channel: ContactChannel\n    )\n\n    enum CodingKeys: String, CodingKey {\n        case payload\n        case type\n    }\n\n    enum PayloadCodingKeys: String, CodingKey {\n        case tagUpdates\n        case attrubuteUpdates\n        case attributeUpdates\n        case subscriptionListsUpdates\n        case address\n        case options\n        case msisdn\n        case identifier\n        case channelID\n        case channelType\n        case date\n        case required\n        case channelOptions\n        case dissociateChannelInfo\n        case resendInfo\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n\n        switch self {\n        case .update(let tagUpdates, let attributeUpdates, let subscriptionListsUpdates):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encodeIfPresent(tagUpdates, forKey: .tagUpdates)\n            try payloadContainer.encodeIfPresent(attributeUpdates, forKey: .attributeUpdates)\n            try payloadContainer.encodeIfPresent(subscriptionListsUpdates, forKey: .subscriptionListsUpdates)\n            try container.encode(OperationType.update, forKey: .type)\n\n        case .identify(let identifier):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(identifier, forKey: .identifier)\n            try container.encode(OperationType.identify, forKey: .type)\n\n        case .resolve:\n            try container.encodeNil(forKey: .payload)\n            try container.encode(OperationType.resolve, forKey: .type)\n\n        case .reset:\n            try container.encodeNil(forKey: .payload)\n            try container.encode(OperationType.reset, forKey: .type)\n\n        case .verify(let date, let required):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(date, forKey: .date)\n            try payloadContainer.encodeIfPresent(required, forKey: .required)\n            try container.encode(OperationType.verify, forKey: .type)\n\n        case .registerEmail(let address, let options):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(address, forKey: .address)\n            try payloadContainer.encode(options, forKey: .options)\n            try container.encode(OperationType.registerEmail, forKey: .type)\n\n        case .registerSMS(let msisdn, let options):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(msisdn, forKey: .msisdn)\n            try payloadContainer.encode(options, forKey: .options)\n            try container.encode(OperationType.registerSMS, forKey: .type)\n\n        case .registerOpen(let address, let options):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(address, forKey: .address)\n            try payloadContainer.encode(options, forKey: .options)\n            try container.encode(OperationType.registerOpen, forKey: .type)\n\n        case .associateChannel(let channelID, let channelType):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(channelID, forKey: .channelID)\n            try payloadContainer.encode(channelType, forKey: .channelType)\n            try container.encode(OperationType.associateChannel, forKey: .type)\n\n        case .disassociateChannel(let info):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(info, forKey: .dissociateChannelInfo)\n            try container.encode(OperationType.disassociateChannel, forKey: .type)\n\n        case .resend(let info):\n            var payloadContainer = container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            try payloadContainer.encode(info, forKey: .resendInfo)\n            try container.encode(OperationType.resend, forKey: .type)\n        }\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(OperationType.self, forKey: .type)\n\n        switch type {\n        case .update:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .update(\n                tagUpdates: try payloadContainer.decodeIfPresent(\n                    [TagGroupUpdate].self,\n                    forKey: .tagUpdates\n                ),\n                attributeUpdates: try payloadContainer.decodeIfPresent(\n                    [AttributeUpdate].self,\n                    forKey: .attributeUpdates\n                ) ?? payloadContainer.decodeIfPresent(\n                    [AttributeUpdate].self,\n                    forKey: .attrubuteUpdates\n                ),\n                subscriptionListsUpdates: try payloadContainer.decodeIfPresent(\n                    [ScopedSubscriptionListUpdate].self,\n                    forKey: .subscriptionListsUpdates\n                )\n            )\n        case .identify:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .identify(\n                try payloadContainer.decode(\n                    String.self,\n                    forKey: .identifier\n                )\n            )\n\n        case .registerEmail:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .registerEmail(\n                address: try payloadContainer.decode(\n                    String.self,\n                    forKey: .address\n                ),\n                options: try payloadContainer.decode(\n                    EmailRegistrationOptions.self,\n                    forKey: .options\n                )\n            )\n            \n        case .registerSMS:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .registerSMS(\n                msisdn: try payloadContainer.decode(\n                    String.self,\n                    forKey: .msisdn\n                ),\n                options: try payloadContainer.decode(\n                    SMSRegistrationOptions.self,\n                    forKey: .options\n                )\n            )\n            \n        case .registerOpen:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .registerOpen(\n                address: try payloadContainer.decode(\n                    String.self,\n                    forKey: .address\n                ),\n                options: try payloadContainer.decode(\n                    OpenRegistrationOptions.self,\n                    forKey: .options\n                )\n            )\n\n        case .associateChannel:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .associateChannel(\n                channelID: try payloadContainer.decode(\n                    String.self,\n                    forKey: .channelID\n                ),\n                channelType: try payloadContainer.decode(\n                    ChannelType.self,\n                    forKey: .channelType\n                )\n            )\n          \n        case .disassociateChannel:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .disassociateChannel(\n                channel: try payloadContainer.decode(\n                    ContactChannel.self,\n                    forKey: .dissociateChannelInfo\n                )\n            )\n\n        case .resend:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .resend (\n                channel: try payloadContainer.decode(\n                    ContactChannel.self,\n                    forKey: .resendInfo\n                )\n            )\n\n        case .resolve:\n            self = .resolve\n\n        case .reset:\n            self = .reset\n\n        case .verify:\n            let payloadContainer = try container.nestedContainer(keyedBy: PayloadCodingKeys.self, forKey: .payload)\n            self = .verify(\n                try payloadContainer.decode(\n                    Date.self,\n                    forKey: .date\n                ),\n                required: try payloadContainer.decodeIfPresent(\n                    Bool.self,\n                    forKey: .required\n                )\n            )\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactRemoteDataProviderDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ContactRemoteDataProviderDelegate: RemoteDataProviderDelegate {\n    let source: RemoteDataSource = .contact\n    let storeName: String\n\n    private let config: RuntimeConfig\n    private let apiClient: any RemoteDataAPIClientProtocol\n    private let contact: any InternalAirshipContact\n\n    init(config: RuntimeConfig, apiClient: any RemoteDataAPIClientProtocol, contact: any InternalAirshipContact) {\n        self.storeName = \"RemoteData-Contact-\\(config.appCredentials.appKey).sqlite\"\n        self.config = config\n        self.apiClient = apiClient\n        self.contact = contact\n    }\n\n    private func makeURL(contactID: String, locale: Locale, randomValue: Int) throws -> URL {\n        return try RemoteDataURLFactory.makeURL(\n            config: config,\n            path: \"/api/remote-data-contact/ios/\\(contactID)\",\n            locale: locale,\n            randomValue: randomValue\n        )\n    }\n\n    func isRemoteDataInfoUpToDate(\n        _ remoteDataInfo: RemoteDataInfo,\n        locale: Locale,\n        randomValue: Int\n    ) async -> Bool {\n        let contactInfo = await contact.contactIDInfo\n        guard let contactInfo = contactInfo, contactInfo.isStable else { return false }\n\n        let url = try? self.makeURL(contactID: contactInfo.contactID, locale: locale, randomValue: randomValue)\n        return remoteDataInfo.url == url && remoteDataInfo.contactID == contactInfo.contactID\n    }\n\n    func fetchRemoteData(\n        locale: Locale,\n        randomValue: Int,\n        lastRemoteDataInfo: RemoteDataInfo?\n    ) async throws -> AirshipHTTPResponse<RemoteDataResult> {\n        let stableContactID = await contact.getStableContactID()\n        let url = try self.makeURL(contactID: stableContactID, locale: locale, randomValue: randomValue)\n\n        var lastModified: String? = nil\n        if (lastRemoteDataInfo?.url == url) {\n            lastModified = lastRemoteDataInfo?.lastModifiedTime\n        }\n\n        return try await self.apiClient.fetchRemoteData(\n            url: url,\n            auth: .contactAuthToken(identifier: stableContactID),\n            lastModified: lastModified\n        ) { newLastModified in\n            return RemoteDataInfo(\n                url: url,\n                lastModifiedTime: newLastModified,\n                source: .contact,\n                contactID: stableContactID\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ContactSubscriptionListClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nprotocol ContactSubscriptionListAPIClientProtocol: Sendable {\n    func fetchSubscriptionLists(\n        contactID: String\n    ) async throws ->  AirshipHTTPResponse<[String: [ChannelScope]]>\n}\n\n/// NOTE: For internal use only. :nodoc:\nfinal class ContactSubscriptionListAPIClient: ContactSubscriptionListAPIClientProtocol {\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(config: config, session: config.requestSession)\n    }\n\n    func fetchSubscriptionLists(\n        contactID: String\n    ) async throws -> AirshipHTTPResponse<[String: [ChannelScope]]> {\n        AirshipLogger.debug(\"Retrieving subscription lists associated with a contact\")\n\n        let request = AirshipRequest(\n            url: try self.makeURL(path: \"/api/subscription_lists/contacts/\\(contactID)\"),\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\",\n                \"X-UA-Appkey\": self.config.appCredentials.appKey\n            ],\n            method: \"GET\",\n            auth: .contactAuthToken(identifier: contactID)\n        )\n\n        return try await session.performHTTPRequest(request) { data, response in\n            \n            AirshipLogger.debug(\"Fetch subscription lists finished with response: \\(response)\")\n            \n            guard response.statusCode == 200, let data = data else {\n                return nil\n            }\n\n            let parsedBody = try JSONDecoder().decode(\n                SubscriptionResponseBody.self,\n                from: data\n            )\n\n            return parsedBody.toScopedSubscriptionLists()\n        }\n    }\n\n    private func makeURL(path: String) throws -> URL {\n        guard let deviceAPIURL = self.config.deviceAPIURL else {\n            throw AirshipErrors.error(\"Initial config not resolved.\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(path)\"\n\n        guard let url = URL(string: \"\\(deviceAPIURL)\\(path)\") else {\n            throw AirshipErrors.error(\"Invalid ContactAPIClient URL: \\(String(describing: urlString))\")\n        }\n\n        return url\n    }\n}\n\nstruct SubscriptionResponseBody: Decodable {\n    let subscriptionLists: [Entry]\n\n    enum CodingKeys: String, CodingKey {\n        case subscriptionLists = \"subscription_lists\"\n    }\n\n    struct Entry: Decodable, Equatable {\n        let lists: [String]\n        let scope: ChannelScope\n\n        enum CodingKeys: String, CodingKey {\n            case lists = \"list_ids\"\n            case scope = \"scope\"\n        }\n    }\n\n    func toScopedSubscriptionLists() -> [String: [ChannelScope]] {\n        var parsed: [String: [ChannelScope]] = [:]\n        self.subscriptionLists.forEach { entry in\n            let scope = entry.scope\n            entry.lists.forEach { listID in\n                var scopes = parsed[listID] ?? []\n                if !scopes.contains(scope) {\n                    scopes.append(scope)\n                    parsed[listID] = scopes\n                }\n            }\n        }\n        return parsed\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Container.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Container view.\n\nstruct Container: View {\n    /// Container model.\n    private let info: ThomasViewInfo.Container\n\n    /// View constraints.\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.Container, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        NewContainer(info: self.info, constraints: self.constraints)\n    }\n}\n\nfileprivate struct NewContainer: View {\n    @Environment(\\.layoutDirection) private var layoutDirection\n\n    /// Container model.\n    private let info: ThomasViewInfo.Container\n\n    /// View constraints.\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.Container, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        ContainerLayout(\n            constraints: self.constraints,\n            layoutDirection: layoutDirection\n        ) {\n            ForEach(0..<info.properties.items.count, id: \\.self) { idx in\n                childItem(idx, item: info.properties.items[idx])\n            }\n        }\n        .accessibilityElement(children: .contain)\n        .airshipGeometryGroupCompat()\n        .constraints(constraints)\n        .clipped()\n        .thomasCommon(self.info)\n    }\n\n    @ViewBuilder\n    @MainActor\n    private func childItem(_ index: Int, item: ThomasViewInfo.Container.Item) -> some View {\n        let consumeSafeAreaInsets = item.ignoreSafeArea != true\n\n        let borderPadding = self.info.commonProperties.border?.strokeWidth ?? 0\n        let childConstraints = self.constraints.childConstraints(\n            item.size,\n            margin: item.margin,\n            padding: borderPadding,\n            safeAreaInsetsMode: consumeSafeAreaInsets ? .consumeMargin : .ignore\n        )\n\n        ViewFactory.createView(\n            item.view,\n            constraints: childConstraints\n        )\n        .margin(item.margin)\n        .airshipApplyIf(consumeSafeAreaInsets) {\n            $0.padding(self.constraints.safeAreaInsets)\n        }\n        .frame(\n            alignment: item.position.alignment\n        )\n        .layoutValue(key: ContainerLayout.ContainerItemPositionKey.self, value: item.position)\n    }\n}\n\nfileprivate struct ContainerLayout: Layout {\n    struct ContainerItemPositionKey: LayoutValueKey {\n        static let defaultValue = ThomasPosition(horizontal: .center, vertical: .center)\n    }\n    \n    struct Cache {\n        var childSizes: [CGSize]\n    }\n\n    let constraints: ViewConstraints\n    let layoutDirection: LayoutDirection\n\n    func makeCache(subviews: Subviews) -> Cache {\n        Cache(\n            childSizes: Array(repeating: .zero, count: subviews.count)\n        )\n    }\n\n    func sizeThatFits(\n        proposal: ProposedViewSize,\n        subviews: Subviews,\n        cache: inout Cache\n    ) -> CGSize {\n        var maxWidth: CGFloat = (constraints.width == nil) ? 0 : proposal.width ?? 0\n        var maxHeight: CGFloat = (constraints.height == nil) ? 0 : proposal.height ?? 0\n\n        for (index, subview) in subviews.enumerated() {\n            let size = subview.dimensions(in: proposal)\n\n            let childSize = CGSize(\n                width: size.width.safeValue ?? 0,\n                height: size.height.safeValue ?? 0\n            )\n            cache.childSizes[index] = childSize\n\n            maxWidth = max(maxWidth, childSize.width)\n            maxHeight = max(maxHeight, childSize.height)\n        }\n\n        return CGSize(width: maxWidth, height: maxHeight)\n    }\n\n    func placeSubviews(\n        in bounds: CGRect,\n        proposal: ProposedViewSize,\n        subviews: Subviews,\n        cache: inout Cache\n    ) {\n        for (subviewIndex, subview) in subviews.enumerated() {\n            let position = subview[ContainerItemPositionKey.self]\n            let childSize = cache.childSizes[subviewIndex]\n\n            let x: CGFloat = switch position.horizontal {\n            case .start:\n                layoutDirection == .leftToRight ? bounds.minX : bounds.maxX - childSize.width\n            case .end:\n                layoutDirection == .leftToRight ? bounds.maxX - childSize.width : bounds.minX\n            case .center:\n                bounds.midX - (childSize.width / 2)\n            }\n\n            let y: CGFloat = switch position.vertical {\n            case .top:\n                bounds.minY\n            case .bottom:\n                bounds.maxY - childSize.height\n            case .center:\n                bounds.midY - (childSize.height / 2)\n            }\n\n            subview.place(\n                at: CGPoint(\n                    x: x.safeValue ?? bounds.minX,\n                    y: y.safeValue ?? bounds.minY\n                ),\n                proposal: ProposedViewSize(\n                    width: childSize.width,\n                    height: childSize.height\n                )\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CustomEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// CustomEvent captures information regarding a custom event for\n/// Analytics.\npublic struct CustomEvent: Sendable {\n\n    /// Max properties size in bytes\n    public static let maxPropertiesSize: Int = 65536\n\n    static let eventNameKey: String = \"event_name\"\n    static let eventValueKey: String = \"event_value\"\n    static let eventPropertiesKey: String = \"properties\"\n    static let eventTransactionIDKey: String = \"transaction_id\"\n    static let eventInteractionIDKey: String = \"interaction_id\"\n    static let eventInteractionTypeKey: String = \"interaction_type\"\n    static let eventInAppKey: String = \"in_app\"\n    static let eventConversionMetadataKey: String = \"conversion_metadata\"\n    static let eventConversionSendIDKey: String = \"conversion_send_id\"\n    static let eventTemplateTypeKey: String = \"template_type\"\n    static let eventType: String  = \"enhanced_custom_event\"\n    static let interactionMCRAP: String = \"ua_mcrap\"\n\n    /// Internal conversion send ID\n    var conversionSendID: String?\n\n    /// Internal conversion push metadata\n    var conversionPushMetadata: String?\n\n    /// Template type\n    var templateType: String?\n\n    /// The in-app message context for custom event attribution\n    var inApp: AirshipJSON?\n\n    /// Default encoder. Uses `iso8601` date encoding strategy.\n    public static func defaultEncoder() -> JSONEncoder {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        return encoder\n    }\n\n    /// The event's value. The value must be between -2^31 and\n    /// 2^31 - 1 or it will invalidate the event.\n    public var eventValue: Decimal\n\n    /// The event's name. The name's length must not exceed 255 characters or it will  or it will\n    /// invalidate the event.\n    public var eventName: String\n\n    /// The event's transaction ID. The ID's length must not exceed 255 characters or it will\n    /// invalidate the event.\n    public var transactionID: String?\n\n    /// The event's interaction type. The type's length must not exceed 255 characters or it will\n    /// invalidate the event.\n    public var interactionType: String?\n\n    /// The event's interaction ID. The ID's length must not exceed 255 characters or it will\n    /// invalidate the event.\n    public var interactionID: String?\n\n    /// The event's properties.\n    public private(set) var properties: [String: AirshipJSON] = [:]\n\n    /// Sets a property string value.\n    /// - Parameters:\n    ///     - string: The string value to set.\n    ///     - forKey: The properties key\n    public mutating func setProperty(\n        string: String,\n        forKey key: String\n    ) {\n        properties[key] = .string(string)\n    }\n\n    /// Removes a property.\n    /// - Parameters:\n    ///     - forKey: The properties key\n    public mutating func removeProperty(\n        forKey key: String\n    ) {\n        properties[key] = nil\n    }\n\n    /// Sets a property double value.\n    /// - Parameters:\n    ///     - double: The double value to set.\n    ///     - forKey: The properties key\n    public mutating func setProperty(\n        double: Double,\n        forKey key: String\n    ) {\n        properties[key] = .number(double)\n    }\n\n    /// Sets a property bool value.\n    /// - Parameters:\n    ///     - bool: The bool value to set.\n    ///     - forKey: The properties key\n    public mutating func setProperty(\n        bool: Bool,\n        forKey key: String\n    ) {\n        properties[key] = .bool(bool)\n    }\n\n    /// Sets a property value.\n    /// - Parameters:\n    ///     - value: The value to set.\n    ///     - forKey: The properties key\n    ///     - encoder: JSONEncoder to use.'\n    public mutating func setProperty(\n        value: Any,\n        forKey key: String,\n        encoder: @autoclosure () -> JSONEncoder = Self.defaultEncoder()\n    ) throws {\n        properties[key] = try AirshipJSON.wrap(value, encoder: encoder())\n    }\n\n    /// Sets a property value.\n    /// - Parameters:\n    ///     - value: The values to set. The value must result in a JSON object or an error will be thrown.\n    ///     - encoder: JSONEncoder to use.\n    public mutating func setProperties(\n        _ object: Any,\n        encoder: @autoclosure () -> JSONEncoder = Self.defaultEncoder()\n    ) throws {\n        let json = try AirshipJSON.wrap(object, encoder: encoder())\n        guard json.isObject, let properties = json.object else {\n            throw AirshipErrors.error(\"Properties must be an object\")\n        }\n\n        self.properties = properties\n    }\n\n    /// Default constructor.\n    /// - Parameter name: The name of the event. The event's name must not exceed\n    /// 255 characters or it will invalidate the event.\n    /// - Parameter value: The event value. The value must be between -2^31 and\n    /// 2^31 - 1 or it will invalidate the event. Defaults to 1.\n    public init(name: String, value: Double = 1.0) {\n        self.eventName = name\n        if value.isFinite {\n            self.eventValue = Decimal(value)\n        } else {\n            self.eventValue = Decimal(1.0)\n        }\n    }\n\n    /// Default constructor.\n    /// - Parameter name: The name of the event. The event's name must not exceed\n    /// 255 characters or it will invalidate the event.\n    /// - Parameter value: The event value. The value must be between -2^31 and\n    /// 2^31 - 1 or it will invalidate the event. Defaults to 1.\n    public init(name: String, decimalValue: Decimal) {\n        self.eventName = name\n        self.eventValue = decimalValue\n    }\n}\n\nextension CustomEvent {\n    /// Validates the event.\n    /// - Returns: `true` if the event is valid, otherwise `false`.\n    public func isValid() -> Bool {\n        let areFieldsValid = validateFields()\n        let isValueValid = validateValue()\n        let areProperitiesValid = validateProperties()\n        return areFieldsValid && isValueValid && areProperitiesValid\n    }\n\n    mutating func setInteractionFromMessageCenterMessage(_ messageID: String) {\n        self.interactionID = messageID\n        self.interactionType = CustomEvent.interactionMCRAP\n    }\n\n    /**\n     * - Note: For internal use only. :nodoc:\n     */\n    func eventBody(sendID: String?, metadata: String?, formatValue: Bool) -> AirshipJSON {\n        return AirshipJSON.makeObject { object in\n            object.set(string: eventName, key: CustomEvent.eventNameKey)\n            object.set(string: conversionSendID ?? sendID, key: CustomEvent.eventConversionSendIDKey)\n            object.set(string: conversionPushMetadata ?? metadata, key: CustomEvent.eventConversionMetadataKey)\n            object.set(string: interactionID, key: CustomEvent.eventInteractionIDKey)\n            object.set(string: interactionType, key: CustomEvent.eventInteractionTypeKey)\n            object.set(string: transactionID, key: CustomEvent.eventTransactionIDKey)\n            object.set(string: templateType, key: CustomEvent.eventTemplateTypeKey)\n            object.set(json: .object(properties), key: CustomEvent.eventPropertiesKey)\n            object.set(json: inApp, key: CustomEvent.eventInAppKey)\n\n            if formatValue {\n                let number = (self.eventValue as NSDecimalNumber).multiplying(byPowerOf10: 6)\n                object.set(double: number.doubleValue.rounded(.down), key: CustomEvent.eventValueKey)\n            } else {\n                object.set(double: (self.eventValue as NSDecimalNumber).doubleValue, key: CustomEvent.eventValueKey)\n            }\n        }\n    }\n\n    /// Adds the event to analytics.\n    public func track() {\n        Airship.analytics.recordCustomEvent(self)\n    }\n\n    private func validateValue() -> Bool {\n        if !eventValue.isFinite {\n            AirshipLogger.error(\"Event value \\(eventValue) is not finite.\")\n            return false\n        }\n\n        if eventValue > Decimal(Int32.max) {\n            AirshipLogger.error(\n                \"Event value \\(eventValue) is larger than 2^31-1.\"\n            )\n            return false\n        }\n\n        if eventValue < Decimal(Int32.min) {\n            AirshipLogger.error(\n                \"Event value \\(eventValue) is smaller than -2^31.\"\n            )\n            return false\n        }\n\n        return true\n    }\n\n    private func validateProperties() -> Bool {\n        do {\n            let encodedProperties = try AirshipJSON.object(properties).toData()\n            if encodedProperties.count > CustomEvent.maxPropertiesSize {\n                AirshipLogger.error(\n                    \"Event properties (%lu bytes) are larger than the maximum size of \\(CustomEvent.maxPropertiesSize) bytes.\"\n                )\n                return false\n            }\n        } catch {\n            AirshipLogger.error(\"Event properties serialization error \\(error)\")\n            return false\n        }\n        return true\n    }\n\n    private func validateFields() -> Bool {\n        let fields: [(name: String, value: String?, required: Bool)] =  [\n            (name: \"eventName\", value: self.eventName, required: true),\n            (name: \"interactionType\", value: self.interactionType, required: false),\n            (name: \"interactionID\", value: self.interactionID, required: false),\n            (name: \"transactionID\", value: self.transactionID, required: false),\n            (name: \"templateType\", value: self.templateType, required: false),\n            (name: \"transactionID\", value: self.templateType, required: false)\n        ]\n\n        let mapped = fields.map { field in\n            if field.required, (field.value?.count ?? 0) == 0 {\n                AirshipLogger.error(\"Missing required field \\(field.name)\")\n                return false\n            }\n\n            if let value = field.value {\n                if value.count > 255 {\n                    AirshipLogger.error(\n                        \"Field \\(field.name) must be between 0 and 255 characters.\"\n                    )\n                    return false\n                }\n            }\n\n            return true\n        }\n\n        return !mapped.contains(false)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/CustomView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\n\n/**\n * Internal only\n * :nodoc:\n */\nstruct CustomView: View {\n    let info: ThomasViewInfo.CustomView\n    let constraints: ViewConstraints\n\n    @EnvironmentObject\n    var thomasEnvironment: ThomasEnvironment\n\n    @EnvironmentObject\n    var pagerState: PagerState\n\n    @Environment(\\.layoutState)\n    var layoutState\n\n    var body: some View {\n        let args = AirshipCustomViewArguments(\n            name: self.info.properties.name,\n            properties: self.info.properties.properties,\n            sizeInfo: AirshipCustomViewArguments.SizeInfo(\n                isAutoHeight: constraints.height == nil,\n                isAutoWidth: constraints.width == nil\n            )\n        )\n        \n        AirshipCustomViewManager.shared.makeView(args: args)\n            .constraints(constraints)\n            .clipped() /// Clip to view frame to ensure we don't overflow when the view has an intrinsic size it's trying to enforce\n            .thomasCommon(self.info)\n            .environmentObject(\n                AirshipSceneController(pagerState: pagerState, environment: thomasEnvironment)\n            )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeepLinkAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Opens a deep link URL.\n///\n/// Expected argument values: A valid URL String.\n///\n/// Valid situations: All but `backgroundPush` and `backgroundInteractiveButton`\npublic final class DeepLinkAction: AirshipAction {\n\n    /// Default names - \"deep_link_action\", \"^d\"\n    public static let defaultNames: [String] = [\"deep_link_action\", \"^d\"]\n\n    /// Default predicate - Rejects `Airship.foregroundPush`\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.situation != .foregroundPush\n    }\n\n    private let urlOpener: any URLOpenerProtocol\n\n    init(urlOpener: any URLOpenerProtocol) {\n        self.urlOpener = urlOpener\n    }\n\n    public convenience init() {\n        self.init(urlOpener: DefaultURLOpener())\n    }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .backgroundPush:\n            return false\n        case .backgroundInteractiveButton:\n            return false\n        default:\n            return true\n        }\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let url = try parseURL(arguments.value)\n        let result = await Airship.processDeepLink(url)\n\n        if !result {\n            try await self.openURL(url)\n        }\n\n        return nil\n    }\n\n    @MainActor\n    private func openURL(_ url: URL) async throws {\n        guard Airship.urlAllowList.isAllowed(url, scope: .openURL) else {\n            throw AirshipErrors.error(\"URL \\(url) not allowed\")\n        }\n\n        guard await urlOpener.openURL(url) else {\n            throw AirshipErrors.error(\"Unable to open URL \\(url).\")\n        }\n    }\n\n    private func parseURL(_ value: AirshipJSON) throws -> URL {\n        if let value = value.unWrap() as? String {\n            if let url = AirshipUtils.parseURL(value) {\n                return url\n            }\n        }\n\n        throw AirshipErrors.error(\"Invalid URL: \\(value)\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeepLinkDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Protocol to be implemented by deep link handlers.\npublic protocol DeepLinkDelegate: AnyObject, Sendable {\n\n    /// Called when a deep link has been triggered from Airship. If implemented, the delegate is responsible for processing the provided url.\n    /// - Parameters:\n    ///     - deepLink: The deep link.\n    @MainActor\n    func receivedDeepLink(_ deepLink: URL) async\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DefaultAirshipAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n\n/// The Analytics object provides an interface to the Airship Analytics API.\nfinal class DefaultAirshipAnalytics: AirshipAnalytics, @unchecked Sendable {\n    private static let associatedIdentifiers: String = \"UAAssociatedIdentifiers\"\n\n    static let missingSendID: String = \"MISSING_SEND_ID\"\n    static let pushMetadata: String = \"com.urbanairship.metadata\"\n    static let pushSendID: String = \"_\"\n\n    private let config: RuntimeConfig\n    private let dataStore: PreferenceDataStore\n    private let channel: any AirshipChannel\n    private let privacyManager: any AirshipPrivacyManager\n    private let notificationCenter: AirshipNotificationCenter\n    private let date: any AirshipDateProtocol\n    private let eventManager: any EventManagerProtocol\n    private let localeManager: any AirshipLocaleManager\n    private let permissionsManager: any AirshipPermissionsManager\n    private let sessionTracker: any SessionTrackerProtocol\n    private let serialQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n\n    private let sdkExtensions: AirshipAtomicValue<[String]> = AirshipAtomicValue([])\n\n    // Screen tracking state\n    private let screenState: AirshipMainActorValue<ScreenState> = AirshipMainActorValue(ScreenState())\n    private let restoreScreenOnForeground: AirshipMainActorValue<String?> = AirshipMainActorValue(nil)\n\n    private let regions: AirshipMainActorValue<Set<String>> = AirshipMainActorValue(Set())\n\n\n    private var isAirshipReady: Bool = false\n\n    /// The conversion send ID. :nodoc:\n   public var conversionSendID: String? {\n       return self.sessionTracker.sessionState.conversionSendID\n    }\n\n   /// The conversion push metadata. :nodoc:\n   public var conversionPushMetadata: String? {\n       return self.sessionTracker.sessionState.conversionMetadata\n   }\n\n    /// The current session ID.\n    public var sessionID: String {\n        return self.sessionTracker.sessionState.sessionID\n    }\n\n    private let eventSubject: PassthroughSubject<AirshipEventData, Never> = PassthroughSubject<AirshipEventData, Never>()\n\n    /// Airship event publisher\n    public var eventPublisher: AnyPublisher<AirshipEventData, Never> {\n        eventSubject.eraseToAnyPublisher()\n    }\n\n    public let eventFeed: AirshipAnalyticsFeed\n\n    private var isAnalyticsEnabled: Bool {\n        return self.privacyManager.isEnabled(.analytics) &&\n        self.config.airshipConfig.isAnalyticsEnabled\n    }\n\n    @MainActor\n    convenience init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel,\n        localeManager: any AirshipLocaleManager,\n        privacyManager: any AirshipPrivacyManager,\n        permissionsManager: any AirshipPermissionsManager\n    ) {\n        self.init(\n            config: config,\n            dataStore: dataStore,\n            channel: channel,\n            localeManager: localeManager,\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager,\n            eventManager: EventManager(\n                config: config,\n                dataStore: dataStore,\n                channel: channel\n            )\n        )\n    }\n\n    @MainActor\n    @inline(never)\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        localeManager: any AirshipLocaleManager,\n        privacyManager: any AirshipPrivacyManager,\n        permissionsManager: any AirshipPermissionsManager,\n        eventManager: any EventManagerProtocol,\n        sessionTracker: (any SessionTrackerProtocol)? = nil,\n        sessionEventFactory: any SessionEventFactoryProtocol = SessionEventFactory()\n    ) {\n        self.config = config\n        self.dataStore = dataStore\n        self.channel = channel\n        self.notificationCenter = notificationCenter\n        self.date = date\n        self.localeManager = localeManager\n        self.privacyManager = privacyManager\n        self.permissionsManager = permissionsManager\n        self.eventManager = eventManager\n        self.sessionTracker = sessionTracker ?? SessionTracker()\n        self.eventFeed = AirshipAnalyticsFeed(\n            privacyManager: privacyManager,\n            isAnalyticsEnabled: config.airshipConfig.isAnalyticsEnabled\n        )\n\n        self.eventManager.addHeaderProvider {\n            await self.makeHeaders()\n        }\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationWillEnterForeground),\n            name: AppStateTracker.willEnterForegroundNotification,\n            object: nil\n        )\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationDidEnterBackground),\n            name: AppStateTracker.didEnterBackgroundNotification,\n            object: nil\n        )\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationWillTerminate),\n            name: AppStateTracker.willTerminateNotification,\n            object: nil\n        )\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(updateEnablement),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(updateEnablement),\n            name: AirshipNotifications.ChannelCreated.name,\n            object: nil\n        )\n\n        Task { @MainActor [weak self, tracker = self.sessionTracker] in\n            for await event in tracker.events {\n                guard let self else {\n                    return\n                }\n                self.recordEvent(\n                    sessionEventFactory.make(event: event),\n                    date: event.date,\n                    sessionID: event.sessionState.sessionID\n                )\n            }\n        }\n    }\n\n    @objc\n    @MainActor\n    private func applicationWillEnterForeground() {\n        // Start tracking previous screen before backgrounding began\n        if let previousScreen = self.restoreScreenOnForeground.value,\n           self.screenState.value.current == nil\n        {\n            trackScreen(previousScreen)\n        }\n        self.restoreScreenOnForeground.set(nil)\n    }\n\n    @objc\n    @MainActor\n    private func applicationDidEnterBackground() {\n        self.restoreScreenOnForeground.set(self.screenState.value.current)\n        self.trackScreen(nil)\n    }\n\n    @objc\n    @MainActor\n    private func applicationWillTerminate() {\n        self.trackScreen(nil)\n    }\n\n\n    // MARK: -\n    // MARK: Analytics Headers\n\n    /// :nodoc:\n    @MainActor\n    public func addHeaderProvider(\n        _ headerProvider: @Sendable @escaping () async -> [String: String]\n    ) {\n        self.eventManager.addHeaderProvider(headerProvider)\n    }\n\n    private func makeHeaders() async -> [String: String] {\n        var headers: [String: String] = [:]\n\n        headers[\"X-UA-Device-Family\"] = await AirshipDevice.deviceFamily\n        headers[\"X-UA-OS-Version\"] = await AirshipDevice.osVersion\n        headers[\"X-UA-Device-Model\"] = AirshipDevice.modelIdentifier\n\n        // App info\n        if let infoDictionary = Bundle.main.infoDictionary {\n            headers[\"X-UA-Package-Name\"] =\n                infoDictionary[kCFBundleIdentifierKey as String] as? String\n        }\n\n        headers[\"X-UA-Package-Version\"] = AirshipUtils.bundleShortVersionString() ?? \"\"\n\n        // Time zone\n        let currentLocale = self.localeManager.currentLocale\n        headers[\"X-UA-Timezone\"] = NSTimeZone.default.identifier\n        headers[\"X-UA-Locale-Language\"] = currentLocale.getLanguageCode()\n        headers[\"X-UA-Locale-Country\"] = currentLocale.getRegionCode()\n        headers[\"X-UA-Locale-Variant\"] = currentLocale.getVariantCode()\n\n        // Airship identifiers\n        headers[\"X-UA-Channel-ID\"] = self.channel.identifier\n        headers[\"X-UA-App-Key\"] = self.config.appCredentials.appKey\n\n        // SDK Version\n        headers[\"X-UA-Lib-Version\"] = AirshipVersion.version\n\n        // SDK Extensions\n        let extensions = self.sdkExtensions.value\n        if extensions.count > 0 {\n            headers[\"X-UA-Frameworks\"] = extensions.joined(\n                separator: \", \"\n            )\n        }\n\n        // Permissions\n        for permission in self.permissionsManager.configuredPermissions {\n            let status = await self.permissionsManager.checkPermissionStatus(permission)\n            headers[\"X-UA-Permission-\\(permission.rawValue)\"] = status.rawValue\n        }\n\n        return headers\n    }\n\n    public func recordCustomEvent(_ event: CustomEvent) {\n        guard self.isAnalyticsEnabled else {\n            AirshipLogger.info(\n                \"Analytics disabled, ignoring custom event \\(event)\"\n            )\n            return\n        }\n\n        guard event.isValid() else {\n            AirshipLogger.info(\n                \"Custom event is invalid, ignoring custom event \\(event)\"\n            )\n            return\n        }\n\n        recordEvent(\n            AirshipEvent(\n                eventType: .customEvent,\n                eventData: event.eventBody(\n                    sendID: self.conversionSendID,\n                    metadata: self.conversionPushMetadata,\n                    formatValue: true\n                )\n            ),\n            feedEvent: .analytics(\n                eventType: .customEvent,\n                body: event.eventBody(\n                    sendID: self.conversionSendID,\n                    metadata: self.conversionPushMetadata,\n                    formatValue: false\n                ),\n                value: (event.eventValue as NSDecimalNumber).doubleValue\n            ),\n            date: self.date.now,\n            sessionID: self.sessionTracker.sessionState.sessionID\n        )\n    }\n\n    public func recordRegionEvent(_ event: RegionEvent) {\n        let shouldInsert: Bool = event.boundaryEvent == .enter\n        let regionID = event.regionID\n\n        Task { @MainActor in\n            self.regions.update { regions in\n                if (shouldInsert) {\n                    regions.insert(regionID)\n                } else {\n                    regions.remove(regionID)\n                }\n            }\n        }\n\n        guard self.isAnalyticsEnabled else {\n            AirshipLogger.info(\n                \"Analytics disabled, ignoring region event \\(event)\"\n            )\n            return\n        }\n\n        /// Upload\n        do {\n            let eventType: EventType = switch(event.boundaryEvent) {\n            case .enter: .regionEnter\n            case .exit: .regionExit\n            }\n            recordEvent(\n                AirshipEvent(\n                    eventType: eventType,\n                    eventData: try event.eventBody(stringifyFields: true)\n                )\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to generate event body \\(error)\")\n        }\n    }\n\n    public func trackInstallAttribution(\n        appPurchaseDate: Date?,\n        iAdImpressionDate: Date?\n    ) {\n        recordEvent(\n            AirshipEvents.installAttirbutionEvent(\n                appPurchaseDate: appPurchaseDate,\n                iAdImpressionDate: iAdImpressionDate\n            )\n        )\n    }\n\n    public func recordEvent(_ event: AirshipEvent) {\n        self.recordEvent(\n            event,\n            date: self.date.now,\n            sessionID: self.sessionTracker.sessionState.sessionID\n        )\n    }\n\n    private func recordEvent(\n        _ event: AirshipEvent,\n        date: Date,\n        sessionID: String\n    ) {\n        self.recordEvent(\n            event,\n            feedEvent: AirshipAnalyticsFeed.Event.analytics(\n                eventType: event.eventType,\n                body: event.eventData,\n                value: nil\n            ),\n            date: date,\n            sessionID: sessionID\n        )\n    }\n\n    \n\n    private func recordEvent(\n        _ event: AirshipEvent,\n        feedEvent: AirshipAnalyticsFeed.Event,\n        date: Date,\n        sessionID: String\n    ) {\n        self.serialQueue.enqueue {\n            guard self.isAnalyticsEnabled else {\n                AirshipLogger.trace(\n                    \"Analytics disabled, ignoring event: \\(event.eventType)\"\n                )\n                return\n            }\n\n            await self.eventFeed.notifyEvent(feedEvent)\n\n            let eventData = AirshipEventData(\n                body: event.eventData,\n                id: NSUUID().uuidString,\n                date: date,\n                sessionID: sessionID,\n                type: event.eventType\n            )\n\n            do {\n                AirshipLogger.debug(\"Adding event with type \\(eventData.type)\")\n                AirshipLogger.trace(\"Adding event \\(eventData)\")\n                try await self.eventManager.addEvent(eventData)\n                await Task { @MainActor in\n                    self.eventSubject.send(eventData)\n                }.value\n                await self.eventManager.scheduleUpload(\n                    eventPriority: event.priority\n                )\n            } catch {\n                AirshipLogger.error(\"Failed to save event \\(error)\")\n                return\n            }\n        }\n    }\n\n    /// Associates identifiers with the device. This call will add a special event\n    /// that will be batched and sent up with our other analytics events. Previous\n    /// associated identifiers will be replaced.\n    ///\n    /// For internal use only. :nodoc:\n    ///\n    /// - Parameter associatedIdentifiers: The associated identifiers.\n    public func associateDeviceIdentifiers(\n        _ associatedIdentifiers: AssociatedIdentifiers\n    ) {\n        guard self.isAnalyticsEnabled else {\n            AirshipLogger.warn(\n                \"Unable to associate identifiers \\(associatedIdentifiers.allIDs) when analytics is disabled\"\n            )\n            return\n        }\n\n        if let previous = self.dataStore.object(\n            forKey: DefaultAirshipAnalytics.associatedIdentifiers\n        ) as? [String: String] {\n            if previous == associatedIdentifiers.allIDs {\n                AirshipLogger.info(\n                    \"Skipping analytics event addition for duplicate associated identifiers.\"\n                )\n                return\n            }\n        }\n\n        do {\n            let event = try AirshipEvents.associatedIdentifiersEvent(\n                identifiers: associatedIdentifiers\n            )\n            self.recordEvent(event)\n            self.dataStore.setObject(\n                associatedIdentifiers.allIDs,\n                forKey: DefaultAirshipAnalytics.associatedIdentifiers\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to add associated idenfiers event \\(error)\")\n        }\n    }\n\n    /// The device's current associated identifiers.\n    /// - Returns: The device's current associated identifiers.\n    public func currentAssociatedDeviceIdentifiers() -> AssociatedIdentifiers {\n        let storedIDs =\n            self.dataStore.object(forKey: DefaultAirshipAnalytics.associatedIdentifiers)\n            as? [String: String]\n        return AssociatedIdentifiers(\n            identifiers: storedIDs ?? [:]\n        )\n    }\n\n    /// Initiates screen tracking for a specific app screen, must be called once per tracked screen.\n    /// - Parameter screen: The screen's identifier.\n    @MainActor\n    public func trackScreen(_ screen: String?) {\n        let date = self.date.now\n        // Prevent duplicate calls to track same screen\n        guard screen != self.screenState.value.current else {\n            return\n        }\n\n        Task {\n            await self.eventFeed.notifyEvent(.screen(screen: screen))\n        }\n\n        let currentScreen = self.screenState.value.current\n        let screenStartDate = self.screenState.value.startDate\n        let previousScreen = self.screenState.value.previous\n\n        self.screenState.update { state in\n            state.current = screen\n            state.startDate = date\n            state.previous = currentScreen\n        }\n\n        // If there's a screen currently being tracked set it's stop time and add it to analytics\n        if let currentScreen = currentScreen, let screenStartDate = screenStartDate {\n            do {\n                let ste = try AirshipEvents.screenTrackingEvent(\n                    screen: currentScreen,\n                    previousScreen: previousScreen,\n                    startDate: screenStartDate,\n                    duration: date.timeIntervalSince(screenStartDate)\n                )\n\n                // Add screen tracking event to next analytics batch\n                self.recordEvent(ste)\n            } catch {\n                AirshipLogger.error(\n                    \"Unable to create screen tracking event \\(error)\"\n                )\n            }\n        }\n    }\n\n    /// Registers an SDK extension with the analytics module.\n    /// For internal use only. :nodoc:\n    ///\n    ///  - Parameters:\n    ///   - ext: The SDK extension.\n    ///   - version: The version.\n    public func registerSDKExtension(\n        _ ext: AirshipSDKExtension,\n        version: String\n    ) {\n        let sanitizedVersion = version.replacingOccurrences(of: \",\", with: \"\")\n        self.sdkExtensions.value.append(\"\\(ext.name):\\(sanitizedVersion)\")\n    }\n\n    @objc\n    private func updateEnablement() {\n        guard self.isAnalyticsEnabled else {\n            self.eventManager.uploadsEnabled = false\n            Task {\n                do {\n                    try await self.eventManager.deleteEvents()\n                } catch {\n                    AirshipLogger.error(\"Failed to delete events \\(error)\")\n                }\n            }\n            return\n        }\n\n        let uploadsEnabled = self.isAirshipReady && self.channel.identifier != nil\n\n\n        if (self.eventManager.uploadsEnabled != uploadsEnabled) {\n            self.eventManager.uploadsEnabled = uploadsEnabled\n\n            if (uploadsEnabled) {\n                Task {\n                    await self.eventManager.scheduleUpload(\n                        eventPriority: .normal\n                    )\n                }\n            }\n        }\n    }\n}\n\n\nextension DefaultAirshipAnalytics: AirshipComponent, InternalAirshipAnalytics {\n    @MainActor\n    public func airshipReady() {\n        self.isAirshipReady = true\n        self.updateEnablement()\n\n        self.sessionTracker.airshipReady()\n    }\n\n    @MainActor\n    public var currentScreen: String? {\n        return self.screenState.value.current\n    }\n    \n    @MainActor\n    public var regionUpdates: AsyncStream<Set<String>> {\n        return self.regions.updates\n    }\n    \n    @MainActor\n    public var currentRegions: Set<String> {\n        return self.regions.value\n    }\n\n    @MainActor\n    public var screenUpdates: AsyncStream<String?> {\n        return AsyncStream { [screenState] continuation in\n            let updates = screenState.updates\n            let task = Task {\n                for await value in updates {\n                    continuation.yield(value.current)\n                }\n            }\n\n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n\n    /// Called to notify analytics the app was launched from a push notification.\n    /// For internal use only. :nodoc:\n    /// - Parameter notification: The push notification.\n    @MainActor\n    public func launched(fromNotification notification: [AnyHashable: Any]) {\n        if AirshipUtils.isAlertingPush(notification) {\n            let sendID = notification[DefaultAirshipAnalytics.pushSendID] as? String\n            let metadata = notification[DefaultAirshipAnalytics.pushMetadata] as? String\n\n            self.sessionTracker.launchedFromPush(\n                sendID: sendID ?? DefaultAirshipAnalytics.missingSendID,\n                metadata: metadata\n            )\n        }\n    }\n\n\n    @available(tvOS, unavailable)\n    @MainActor\n    public func onNotificationResponse(\n        response: UNNotificationResponse,\n        action: UNNotificationAction?\n    ) {\n        let userInfo = response.notification.request.content.userInfo\n\n        if response.actionIdentifier == UNNotificationDefaultActionIdentifier {\n            self.launched(fromNotification: userInfo)\n        } else if let action = action {\n            let categoryID = response.notification.request.content\n                .categoryIdentifier\n            let responseText = (response as? UNTextInputNotificationResponse)?\n                .userText\n\n            if action.options.contains(.foreground) == true {\n                self.launched(fromNotification: userInfo)\n            }\n\n            #if !os(tvOS)\n            recordEvent(\n                AirshipEvents.interactiveNotificationEvent(\n                    action: action,\n                    category: categoryID,\n                    notification: userInfo,\n                    responseText: responseText\n                )\n            )\n            #endif\n        }\n    }\n}\n\n\nfileprivate struct ScreenState {\n    var current: String?\n    var previous: String?\n    var startDate: Date?\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DefaultAirshipChannel.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@preconcurrency\nimport Combine\npublic import Foundation\nimport UserNotifications\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n\n/// This singleton provides an interface to the channel functionality.\nfinal class DefaultAirshipChannel: AirshipChannel, Sendable {\n    private static let tagsDataStoreKey: String = \"com.urbanairship.channel.tags\"\n    private static let legacyTagsSettingsKey: String = \"UAPushTags\"\n\n    private let dataStore: PreferenceDataStore\n    private let config: RuntimeConfig\n    private let privacyManager: any AirshipPrivacyManager\n    private let permissionsManager: any AirshipPermissionsManager\n    private let localeManager: any AirshipLocaleManager\n    private let audienceManager: any ChannelAudienceManagerProtocol\n    private let channelRegistrar: any ChannelRegistrarProtocol\n    private let notificationCenter: AirshipNotificationCenter\n    private let appStateTracker: any AppStateTrackerProtocol\n    private let tagsLock: AirshipLock = AirshipLock()\n    private let subscription: AirshipUnsafeSendableWrapper<AnyCancellable?> = AirshipUnsafeSendableWrapper(nil)\n\n    private let liveActivityQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n\n    @MainActor\n    private var extenders: [@Sendable (inout ChannelRegistrationPayload) async -> Void] = []\n\n    #if canImport(ActivityKit)\n    private let liveActivityRegistry: LiveActivityRegistry\n    #endif\n\n    private let isChannelCreationEnabled: AirshipAtomicValue<Bool>\n\n    public var identifier: String? {\n        return self.channelRegistrar.channelID\n    }\n\n    public var identifierUpdates: AsyncStream<String> {\n        return AsyncStream<String> { [weak self] continuation in\n            let task = Task { [weak self] in\n                guard let stream = await self?.channelRegistrar.registrationUpdates.makeStream() else {\n                    return\n                }\n\n                var current = self?.channelRegistrar.channelID\n                if let current {\n                    continuation.yield(current)\n                }\n\n                for await update in stream {\n                    let channelID =  switch update {\n                    case .created(let channelID, _): channelID\n                    case .updated(channelID: let channelID): channelID\n                    }\n                    if current != channelID {\n                        current = channelID\n                        continuation.yield(channelID)\n                    }\n                }\n            }\n\n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n\n    /// The channel tags.\n    public var tags: [String] {\n        get {\n            guard self.privacyManager.isEnabled(.tagsAndAttributes) else {\n                return []\n            }\n\n            var result: [String]?\n            tagsLock.sync {\n                result =\n                    self.dataStore.array(forKey: DefaultAirshipChannel.tagsDataStoreKey)\n                    as? [String]\n            }\n            return result ?? []\n        }\n\n        set {\n            guard self.privacyManager.isEnabled(.tagsAndAttributes) else {\n                AirshipLogger.warn(\n                    \"Unable to modify channel tags \\(tags) when data collection is disabled.\"\n                )\n                return\n            }\n\n            tagsLock.sync {\n                let normalized = AudienceUtils.normalizeTags(newValue)\n                self.dataStore.setObject(\n                    normalized,\n                    forKey: DefaultAirshipChannel.tagsDataStoreKey\n                )\n            }\n\n            self.updateRegistration()\n        }\n    }\n\n    private let isChannelTagRegistrationEnabledContainer: AirshipAtomicValue<Bool> = AirshipAtomicValue(true)\n    public var isChannelTagRegistrationEnabled: Bool {\n        get { return isChannelTagRegistrationEnabledContainer.value }\n        set { isChannelTagRegistrationEnabledContainer.value = newValue }\n    }\n\n    @MainActor\n    @inline(never)\n    init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        privacyManager: any AirshipPrivacyManager,\n        permissionsManager: any AirshipPermissionsManager,\n        localeManager: any AirshipLocaleManager,\n        audienceManager: any ChannelAudienceManagerProtocol,\n        channelRegistrar: any ChannelRegistrarProtocol,\n        notificationCenter: AirshipNotificationCenter,\n        appStateTracker: any AppStateTrackerProtocol\n    ) {\n\n        self.dataStore = dataStore\n        self.config = config\n        self.privacyManager = privacyManager\n        self.permissionsManager = permissionsManager\n        self.localeManager = localeManager\n        self.audienceManager = audienceManager\n        self.channelRegistrar = channelRegistrar\n        self.notificationCenter = notificationCenter\n        self.appStateTracker = appStateTracker\n\n        #if canImport(ActivityKit)\n        self.liveActivityRegistry = LiveActivityRegistry(\n            dataStore: dataStore\n        )\n        #endif\n\n        // Check config to see if user wants to delay channel creation\n        // If channel ID exists or channel creation delay is disabled then channelCreationEnabled\n        if self.channelRegistrar.channelID != nil\n            || !config.airshipConfig.isChannelCreationDelayEnabled\n        {\n            self.isChannelCreationEnabled = .init(true)\n        } else {\n            AirshipLogger.debug(\"Channel creation disabled.\")\n            self.isChannelCreationEnabled = .init(false)\n        }\n\n        self.migrateTags()\n\n        Task { @MainActor [weak self, weak channelRegistrar] in\n            guard let stream = await channelRegistrar?.registrationUpdates.makeStream() else {\n                return\n            }\n\n            for await update in stream {\n                self?.processChannelUpdate(update)\n            }\n        }\n\n        self.channelRegistrar.payloadCreateBlock = { [weak self] in\n            return await self?.makePayload()\n        }\n\n        self.audienceManager.channelID = self.channelRegistrar.channelID\n        self.audienceManager.enabled = true\n        \n        if let identifier = self.identifier {\n            AirshipLogger.importantInfo(\"Channel ID \\(identifier)\")\n        }\n\n        self.observeNotificationCenterEvents()\n        self.updateRegistration()\n\n\n\n        #if canImport(ActivityKit)\n        Task {\n            for await update in self.liveActivityRegistry.updates {\n                guard privacyManager.isEnabled(.push) || update.action == .remove else {\n                    AirshipLogger.error(\"Unable tot track set operation, push is disabled \\(update)\")\n                    return\n                }\n                self.audienceManager.addLiveActivityUpdate(update)\n            }\n        }\n\n        Task {\n            for await updates in self.audienceManager.liveActivityUpdates {\n                await self.liveActivityRegistry.updatesProcessed(updates: updates)\n            }\n        }\n        #endif\n    }\n\n    @MainActor\n    convenience init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        privacyManager: any AirshipPrivacyManager,\n        permissionsManager: any AirshipPermissionsManager,\n        localeManager: any AirshipLocaleManager,\n        audienceOverridesProvider: any AudienceOverridesProvider\n    ) {\n        self.init(\n            dataStore: dataStore,\n            config: config,\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager,\n            localeManager: localeManager,\n            audienceManager: ChannelAudienceManager(\n                dataStore: dataStore,\n                config: config,\n                privacyManager: privacyManager,\n                audienceOverridesProvider: audienceOverridesProvider\n            ),\n            channelRegistrar: ChannelRegistrar(\n                config: config,\n                dataStore: dataStore,\n                privacyManager: privacyManager\n            ),\n            notificationCenter: AirshipNotificationCenter.shared,\n            appStateTracker: AppStateTracker.shared\n        )\n    }\n\n    private func migrateTags() {\n        guard self.dataStore.keyExists(DefaultAirshipChannel.legacyTagsSettingsKey) else {\n            // Nothing to migrate\n            return\n        }\n\n        // Normalize tags for older SDK versions, and migrate to UAChannel as necessary\n        if let existingPushTags = self.dataStore.object(\n            forKey: DefaultAirshipChannel.legacyTagsSettingsKey\n        ) as? [String] {\n            let existingChannelTags = self.tags\n            if existingChannelTags.count > 0 {\n                let combinedTagsSet: Set<String> = Set(existingPushTags)\n                    .union(\n                        Set(existingChannelTags)\n                    )\n                self.tags = Array(combinedTagsSet)\n            } else {\n                self.tags = existingPushTags\n            }\n        }\n\n        self.dataStore.removeObject(forKey: DefaultAirshipChannel.legacyTagsSettingsKey)\n    }\n\n    private func observeNotificationCenterEvents() {\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationDidTransitionToForeground),\n            name: AppStateTracker.didTransitionToForeground\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(remoteConfigUpdated),\n            name: RuntimeConfig.configUpdatedEvent\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(onEnableFeaturesChanged),\n            name: AirshipNotifications.PrivacyManagerUpdated.name\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(localeUpdates),\n            name: AirshipNotifications.LocaleUpdated.name\n        )\n    }\n\n    @objc\n    private func localeUpdates() {\n        self.updateRegistration()\n    }\n\n    @objc\n    private func remoteConfigUpdated() {\n        guard self.isRegistrationAllowed else {\n            return\n        }\n\n        self.updateRegistration(forcefully: true)\n    }\n\n    @objc\n    private func onEnableFeaturesChanged() {\n        if !self.privacyManager.isEnabled(.tagsAndAttributes) {\n            self.dataStore.removeObject(forKey: DefaultAirshipChannel.tagsDataStoreKey)\n        }\n\n        self.updateRegistration()\n    }\n\n    @objc\n    private func applicationDidTransitionToForeground() {\n        if self.privacyManager.isAnyFeatureEnabled() {\n            AirshipLogger.trace(\n                \"Application did become active. Updating registration.\"\n            )\n            self.updateRegistration()\n        }\n    }\n\n    public func editTags() -> TagEditor {\n        return TagEditor { tagApplicator in\n            self.tagsLock.sync {\n                self.tags = tagApplicator(self.tags)\n            }\n        }\n    }\n\n    public func editTags(_ editorBlock: (TagEditor) -> Void) {\n        let editor = editTags()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func editTagGroups() -> TagGroupsEditor {\n        let allowDeviceTags = !self.isChannelTagRegistrationEnabled\n        return self.audienceManager.editTagGroups(\n            allowDeviceGroup: allowDeviceTags\n        )\n    }\n\n    public func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void) {\n        let editor = editTagGroups()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func editSubscriptionLists() -> SubscriptionListEditor {\n        return self.audienceManager.editSubscriptionLists()\n    }\n\n    public func editSubscriptionLists(\n        _ editorBlock: (SubscriptionListEditor) -> Void\n    ) {\n        let editor = editSubscriptionLists()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func fetchSubscriptionLists() async throws -> [String] {\n        return try await self.audienceManager.fetchSubscriptionLists()\n    }\n\n    public var subscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never>\n    {\n        audienceManager.subscriptionListEdits\n    }\n\n    public func editAttributes() -> AttributesEditor {\n        return self.audienceManager.editAttributes()\n    }\n\n    public func editAttributes(_ editorBlock: (AttributesEditor) -> Void) {\n        let editor = editAttributes()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func enableChannelCreation() {\n        if !self.isChannelCreationEnabled.value {\n            self.isChannelCreationEnabled.value = true\n            self.updateRegistration()\n        }\n    }\n\n    public func updateRegistration() {\n        updateRegistration(forcefully: false)\n    }\n\n    private var isRegistrationAllowed: Bool {\n        guard self.isChannelCreationEnabled.value else {\n            AirshipLogger.debug(\n                \"Channel creation is currently disabled, unable to update\"\n            )\n            return false\n        }\n\n        guard\n            self.identifier != nil || self.privacyManager.isAnyFeatureEnabled()\n        else {\n            AirshipLogger.trace(\n                \"Skipping channel create. All features are disabled.\"\n            )\n            return false\n        }\n\n        return true\n    }\n\n    public func updateRegistration(forcefully: Bool) {\n        guard self.isRegistrationAllowed else {\n            return\n        }\n\n        self.channelRegistrar.register(forcefully: forcefully)\n    }\n}\n\n/// - Note: for internal use only.  :nodoc:\nextension DefaultAirshipChannel: AirshipPushableComponent {\n    func receivedRemoteNotification(_ notification: AirshipJSON) async -> UABackgroundFetchResult {\n        if self.identifier == nil {\n            updateRegistration()\n        }\n        return .noData\n    }\n\n#if !os(tvOS)\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        // no-op\n    }\n#endif\n\n    private func processChannelUpdate(_ update: ChannelRegistrationUpdate) {\n        switch(update) {\n        case .created(let channelID, let isExisting):\n            AirshipLogger.importantInfo(\"Channel ID: \\(channelID)\")\n            self.audienceManager.channelID = channelID\n            self.notificationCenter.post(\n                name: AirshipNotifications.ChannelCreated.name,\n                object: nil,\n                userInfo: [\n                    AirshipNotifications.ChannelCreated.channelIDKey: channelID,\n                    AirshipNotifications.ChannelCreated.isExistingChannelKey: isExisting,\n                ]\n            )\n        case .updated(_):\n            AirshipLogger.info(\"Channel updated.\")\n        }\n    }\n\n    private func makePayload() async -> ChannelRegistrationPayload {\n        var payload = ChannelRegistrationPayload()\n\n        guard privacyManager.isAnyFeatureEnabled() else {\n            payload.channel.tags = []\n            payload.channel.setTags = true\n            payload.channel.isOptedIn = false\n            payload.channel.isBackgroundEnabled = false\n            return payload\n        }\n\n        for extender in await self.extenders {\n            await extender(&payload)\n        }\n        \n        if await self.appStateTracker.state == .active {\n            payload.channel.isActive = true\n        }\n\n        if self.isChannelTagRegistrationEnabled {\n            payload.channel.tags = self.tags\n            payload.channel.setTags = true\n        } else {\n            payload.channel.setTags = false\n        }\n\n        if self.privacyManager.isEnabled(.analytics) {\n            payload.channel.deviceModel = AirshipDevice.modelIdentifier\n            payload.channel.appVersion = AirshipUtils.bundleShortVersionString()\n            payload.channel.deviceOS = await AirshipDevice.osVersion\n        }\n\n        if self.privacyManager.isAnyFeatureEnabled() {\n            let currentLocale = self.localeManager.currentLocale\n            payload.channel.language = currentLocale.getLanguageCode()\n            payload.channel.country = currentLocale.getRegionCode()\n            payload.channel.timeZone = TimeZone.current.identifier\n            payload.channel.sdkVersion = AirshipVersion.version\n        }\n        \n        if self.privacyManager.isEnabled(.tagsAndAttributes) {\n            var permissions: [String: String] = [:]\n            \n            for permission in self.permissionsManager.configuredPermissions {\n                let status = await self.permissionsManager.checkPermissionStatus(\n                    permission\n                )\n                if status != .notDetermined {\n                    permissions[permission.rawValue] = status.rawValue\n                }\n            }\n            payload.channel.permissions = permissions\n        }\n\n        return payload\n    }\n}\n\nextension DefaultAirshipChannel: InternalAirshipChannel {\n    @MainActor\n    public func addRegistrationExtender(\n        _ extender: @Sendable @escaping (inout ChannelRegistrationPayload) async -> Void\n    ) {\n        self.extenders.append(extender)\n    }\n\n    public func clearSubscriptionListsCache() {\n        self.audienceManager.clearSubscriptionListCache()\n    }\n}\n\n#if canImport(ActivityKit) && !targetEnvironment(macCatalyst) && !os(macOS)\nimport ActivityKit\n@available(iOS 16.1, *)\nextension DefaultAirshipChannel {\n\n    /// Gets an AsyncSequence of `LiveActivityRegistrationStatus` updates for a given live acitvity name.\n    /// - Parameters:\n    ///     - name: The live activity name\n    /// - Returns A `LiveActivityRegistrationStatusUpdates`\n    @available(iOS 16.1, *)\n    public func liveActivityRegistrationStatusUpdates(\n        name: String\n    ) -> LiveActivityRegistrationStatusUpdates {\n        self.liveActivityRegistry.registrationUpdates(name: name, id: nil)\n    }\n\n    /// Gets an AsyncSequence of `LiveActivityRegistrationStatus` updates for a given live acitvity ID.\n    /// - Parameters:\n    ///     - activity: The live activity\n    /// - Returns A `LiveActivityRegistrationStatusUpdates`\n    @available(iOS 16.1, *)\n    public func liveActivityRegistrationStatusUpdates<T: ActivityAttributes>(\n        activity: Activity<T>\n    ) -> LiveActivityRegistrationStatusUpdates {\n        self.liveActivityRegistry.registrationUpdates(name: nil, id: activity.id)\n    }\n\n    /// Tracks a live activity with Airship for the given name.\n    /// Airship will monitor the push token and status and automatically\n    /// add and remove it from the channel for the App. If an activity is already\n    /// tracked with the given name it will be replaced with the new activity.\n    ///\n    /// The name will be used to send updates through Airship. It can be unique\n    /// for the device or shared across many devices.\n    ///\n    /// - Parameters:\n    ///     - activity: The live activity\n    ///     - name: The name of the activity\n    public func trackLiveActivity<T: ActivityAttributes>(\n        _ activity: Activity<T>,\n        name: String\n    ) {\n        guard privacyManager.isEnabled(.push) else {\n            AirshipLogger.error(\"Push is not enabled, unable to track live activity.\")\n            return\n        }\n\n        let liveActivity = LiveActivity(activity: activity)\n        liveActivityQueue.enqueue { [liveActivityRegistry] in\n            // This nested function is a workaround for a Swift compiler Sendable warning.\n            // It avoids the Task's @Sendable closure from directly capturing the generic type.\n            func run() async {\n                await liveActivityRegistry.addLiveActivity(liveActivity, name: name)\n            }\n            await run()\n        }\n    }\n\n    /// Called to restore live activity tracking. This method needs to be called exactly once\n    /// during `application(_:didFinishLaunchingWithOptions:)` right\n    /// after takeOff. Any activities not restored will stop being tracked by Airship.\n    /// - Parameters:\n    ///     - callback: Callback with the restorer.\n    public func restoreLiveActivityTracking(\n        callback: @escaping @Sendable (any LiveActivityRestorer) async -> Void\n    ) {\n        liveActivityQueue.enqueue { [liveActivityRegistry] in\n            let restorer = AirshipLiveActivityRestorer()\n            await callback(restorer)\n            await restorer.apply(registry: liveActivityRegistry)\n        }\n    }\n}\n\n#endif\n\nextension DefaultAirshipChannel: AirshipComponent {}\n\n\npublic extension AirshipNotifications {\n\n    /// NSNotification info when the channel is created.\n    final class ChannelCreated {\n\n        /// NSNotification name.\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.channel.channel_created\"\n        )\n\n        /// NSNotification userInfo key to get the channel ID.\n        public static let channelIDKey: String = \"channel_identifier\"\n\n        /// NSNotification userInfo key to get a boolean if the channel is existing or not.\n        public static let isExistingChannelKey: String = \"channel_existing\"\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DefaultAirshipContact.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@preconcurrency\npublic import Combine\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\npublic import UserNotifications\n\n/// Airship contact. A contact is distinct from a channel and  represents a \"user\"\n/// within Airship. Contacts may be named and have channels associated with it.\npublic final class DefaultAirshipContact: AirshipContact, @unchecked Sendable {\n    static let refreshContactPushPayloadKey: String = \"com.urbanairship.contact.update\"\n\n    public var contactChannelUpdates: AsyncStream<ContactChannelsResult> {\n        get {\n            return self.contactChannelsProvider.contactChannels(\n                stableContactIDUpdates: self.stableContactIDUpdates\n            )\n        }\n    }\n\n    public var contactChannelPublisher: AnyPublisher<ContactChannelsResult, Never> {\n        get {\n            let updates = self.contactChannelUpdates\n            let subject = CurrentValueSubject<ContactChannelsResult?, Never>(nil)\n\n            Task { @Sendable [weak subject] in\n                for await update in updates {\n                    subject?.send(update)\n                }\n            }\n\n            return subject.compactMap { $0 }.eraseToAnyPublisher()\n        }\n    }\n\n    private var stableContactIDUpdates: AsyncStream<String> {\n        AsyncStream { [contactIDUpdates] continuation in\n            let cancellable: AnyCancellable = contactIDUpdates\n                .filter { $0.isStable }\n                .map { $0.contactID }\n                .removeDuplicates()\n                .sink { value in\n                    continuation.yield(value)\n                }\n\n            continuation.onTermination = { _ in\n                cancellable.cancel()\n            }\n        }\n    }\n\n    private static let resolveDateKey: String = \"Contact.resolveDate\"\n    static let legacyPendingTagGroupsKey: String = \"com.urbanairship.tag_groups.pending_channel_tag_groups_mutations\"\n    static let legacyPendingAttributesKey: String = \"com.urbanairship.named_user_attributes.registrar_persistent_queue_key\"\n    static let legacyNamedUserKey: String = \"UANamedUserID\"\n\n\n    // Interval for how often we emit a resolve operation on foreground\n    static let defaultForegroundResolveInterval: TimeInterval = 3600.0 // 1 hour\n\n    // Max age of a contact ID update that we consider verified for CRA\n    static let defaultVerifiedContactIDAge: TimeInterval = 600.0 // 10 mins\n\n    // Subscription list cache age\n    private static let maxSubscriptionListCacheAge: TimeInterval = 600.0 // 10 mins\n\n    public static let maxNamedUserIDLength: Int = 128\n\n    private let dataStore: PreferenceDataStore\n    private let config: RuntimeConfig\n    private let privacyManager: any AirshipPrivacyManager\n    private let contactChannelsProvider: any ContactChannelsProviderProtocol\n    private let subscriptionListProvider: any SubscriptionListProviderProtocol\n    private let date: any AirshipDateProtocol\n    private let audienceOverridesProvider: any AudienceOverridesProvider\n    private let contactManager: any ContactManagerProtocol\n    private let cachedSubscriptionLists: CachedValue<(String, [String: [ChannelScope]])>\n    private var setupTask: Task<Void, Never>? = nil\n    private var subscriptions: Set<AnyCancellable> = Set()\n    private let serialQueue: AirshipAsyncSerialQueue\n\n    private var lastResolveDate: Date {\n         get {\n             let date = self.dataStore.object(forKey: DefaultAirshipContact.resolveDateKey) as? Date\n             return date ?? Date.distantPast\n         }\n         set {\n             self.dataStore.setObject(newValue, forKey: DefaultAirshipContact.resolveDateKey)\n         }\n     }\n\n    private let subscriptionListEditsSubject: PassthroughSubject<ScopedSubscriptionListEdit, Never> = PassthroughSubject<ScopedSubscriptionListEdit, Never>()\n\n    /// Publishes all edits made to the subscription lists through the  SDK\n    public var subscriptionListEdits: AnyPublisher<ScopedSubscriptionListEdit, Never> {\n        subscriptionListEditsSubject.eraseToAnyPublisher()\n    }\n\n    private let conflictEventSubject: PassthroughSubject<ContactConflictEvent, Never> = PassthroughSubject<ContactConflictEvent, Never>()\n    public var conflictEventPublisher: AnyPublisher<ContactConflictEvent, Never> {\n        conflictEventSubject.eraseToAnyPublisher()\n    }\n\n    private let contactIDUpdatesSubject: CurrentValueSubject<ContactIDInfo?, Never> = CurrentValueSubject<ContactIDInfo?, Never>(nil)\n    var contactIDUpdates: AnyPublisher<ContactIDInfo, Never> {\n        return self.contactIDUpdatesSubject\n            .compactMap { $0 }\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    private let namedUserUpdateSubject: CurrentValueSubject<NamedUserIDEvent?, Never> = CurrentValueSubject<NamedUserIDEvent?, Never>(nil)\n    public var namedUserIDPublisher: AnyPublisher<String?, Never> {\n        namedUserUpdateSubject\n            .compactMap { $0 }\n            .map { $0.identifier }\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    public var namedUserID: String? {\n        get async {\n            return await self.contactManager.currentNamedUserID()\n        }\n    }\n\n    private var foregroundInterval: TimeInterval {\n        let interval = self.config.remoteConfig.contactConfig?.foregroundInterval\n        return interval ?? Self.defaultForegroundResolveInterval\n    }\n\n    private var verifiedContactIDMaxAge: TimeInterval {\n        let age = self.config.remoteConfig.contactConfig?.channelRegistrationMaxResolveAge\n        return age ?? Self.defaultVerifiedContactIDAge\n    }\n\n    /**\n     * Internal only\n     * :nodoc:\n     */\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        channel: any InternalAirshipChannel,\n        privacyManager: any AirshipPrivacyManager,\n        contactChannelsProvider: any ContactChannelsProviderProtocol,\n        subscriptionListProvider: any SubscriptionListProviderProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        audienceOverridesProvider: any AudienceOverridesProvider,\n        contactManager: any ContactManagerProtocol,\n        serialQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue(priority: .high)\n    ) {\n\n        self.dataStore = dataStore\n        self.config = config\n        self.privacyManager = privacyManager\n        self.contactChannelsProvider = contactChannelsProvider\n        self.audienceOverridesProvider = audienceOverridesProvider\n        self.date = date\n        self.contactManager = contactManager\n        self.serialQueue = serialQueue\n        self.subscriptionListProvider = subscriptionListProvider\n        self.cachedSubscriptionLists = CachedValue(date: date)\n\n        self.setupTask = Task {\n            await self.migrateNamedUser()\n\n            await audienceOverridesProvider.setPendingContactOverridesProvider { contactID in\n\n                // Audience overrides will take any pending operations and updated operations. Since\n                // pending operations are added through the serialQueue to ensure order, some might still\n                // be in the queue. To avoid ignoring any of those, wait for current operations on the queue\n                // to finish\n                await self.serialQueue.waitForCurrentOperations()\n\n\n                return await contactManager.pendingAudienceOverrides(contactID: contactID)\n            }\n\n            await audienceOverridesProvider.setStableContactIDProvider {\n                return await self.getStableContactID()\n            }\n\n            await contactManager.onAudienceUpdated { update in\n                await audienceOverridesProvider.contactUpdated(\n                    contactID: update.contactID,\n                    tags: update.tags,\n                    attributes: update.attributes,\n                    subscriptionLists: update.subscriptionLists,\n                    channels: update.contactChannels\n                )\n            }\n\n            await self.contactManager.setEnabled(enabled: true)\n        }\n\n        self.serialQueue.enqueue {\n            await self.setupTask?.value\n        }\n\n        // Whenever the contact ID changes, ignoring stableness, notify the channel\n        self.contactIDUpdatesSubject\n            .receive(on: RunLoop.main)\n            .map { $0?.contactID }\n            .removeDuplicates()\n            .sink { _ in\n                channel.clearSubscriptionListsCache()\n                channel.updateRegistration()\n            }.store(in: &self.subscriptions)\n\n        // For obj-c compatibility\n        self.conflictEventPublisher\n            .receive(on: RunLoop.main)\n            .sink { event in\n                notificationCenter.post(\n                    name: AirshipNotifications.ContactConflict.name,\n                    object: nil,\n                    userInfo: [\n                        AirshipNotifications.ContactConflict.eventKey: event\n                    ]\n                )\n            }.store(in: &self.subscriptions)\n\n\n        channel.addRegistrationExtender { [weak self] payload in\n            await self?.setupTask?.value\n            if await self?.contactID == nil {\n                await self?.contactManager.generateDefaultContactIDIfNotSet()\n            }\n\n            if (channel.identifier != nil) {\n                payload.channel.contactID = await self?.getStableVerifiedContactID()\n            } else {\n                payload.channel.contactID = await self?.contactID\n            }\n        }\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(didBecomeActive),\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(channelCreated),\n            name: AirshipNotifications.ChannelCreated.name,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(checkPrivacyManager),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n\n        self.checkPrivacyManager()\n    }\n\n\n    public func airshipReady() {\n        Task { @MainActor [weak self] in\n            if let self = self {\n                let contactInfo = await self.contactManager.currentContactIDInfo()\n                self.contactIDUpdatesSubject.send(contactInfo)\n\n                let namedUserID = await self.contactManager.currentNamedUserID()\n                self.namedUserUpdateSubject.send(NamedUserIDEvent(identifier: namedUserID))\n            }\n\n            guard let updates = await self?.contactManager.contactUpdates else {\n                return\n            }\n\n            for await update in updates {\n                guard let self else {\n                    return\n                }\n\n                switch (update) {\n                case .conflict(let event):\n                    self.conflictEventSubject.send(event)\n                case .contactIDUpdate(let update):\n                    self.contactIDUpdatesSubject.send(update)\n                case .namedUserUpdate(let namedUserID):\n                    self.namedUserUpdateSubject.send(NamedUserIDEvent(identifier: namedUserID))\n                }\n            }\n        }\n    }\n\n    /**\n     * Internal only\n     * :nodoc:\n     */\n    @MainActor\n    convenience init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        channel: any InternalAirshipChannel,\n        privacyManager: any AirshipPrivacyManager,\n        audienceOverridesProvider: any AudienceOverridesProvider,\n        localeManager: any AirshipLocaleManager\n    ) {\n        self.init(\n            dataStore: dataStore,\n            config: config,\n            channel: channel,\n            privacyManager: privacyManager,\n            contactChannelsProvider: ContactChannelsProvider(\n                audienceOverrides: audienceOverridesProvider,\n                apiClient: ContactChannelsAPIClient(config: config),\n                privacyManager: privacyManager\n            ),\n            subscriptionListProvider: SubscriptionListProvider(\n                audienceOverrides: audienceOverridesProvider,\n                apiClient: ContactSubscriptionListAPIClient(config: config),\n                privacyManager: privacyManager),\n            audienceOverridesProvider: audienceOverridesProvider,\n            contactManager: ContactManager(\n                dataStore: dataStore,\n                channel: channel,\n                localeManager: localeManager,\n                apiClient: ContactAPIClient(config: config)\n            )\n        )\n    }\n\n    /// Identifies the contact.\n    /// - Parameter namedUserID: The named user ID.\n    @inline(never)\n    public func identify(_ namedUserID: String) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\"Contacts disabled. Enable to identify user.\")\n            return\n        }\n\n        do {\n            self.addOperation(\n                .identify(\n                    try namedUserID.normalizedNamedUserID\n                )\n            )\n        } catch {\n            AirshipLogger.error(\"Unable to set named user \\(error)\")\n        }\n    }\n\n    /// Resets the contact.\n    @inline(never)\n    public func reset() {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.trace(\"Contacts are disabled, ignoring reset request\")\n            return\n        }\n        self.addOperation(.reset)\n    }\n\n    /// Can be called after the app performs a remote named user association for the channel instead\n    /// of using `identify` or `reset` through the SDK. When called, the SDK will refresh the contact\n    /// data. Applications should only call this method when the user login has changed.\n    @inline(never)\n    public func notifyRemoteLogin() {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.trace(\"Contacts are disabled, ignoring notifyRemoteLogin request\")\n            return\n        }\n        self.addOperation(.verify(self.date.now, required: true))\n    }\n\n    /// Begins a tag groups editing session.\n    /// - Returns: A TagGroupsEditor\n    public func editTagGroups() -> TagGroupsEditor {\n        return TagGroupsEditor { updates in\n            guard !updates.isEmpty else {\n                AirshipLogger.trace(\"Empty tag group updates, ignoring\")\n                return\n            }\n\n            guard\n                self.privacyManager.isEnabled([.contacts, .tagsAndAttributes])\n            else {\n                AirshipLogger.warn(\n                    \"Contacts or tags are disabled. Enable to apply tag edits.\"\n                )\n                return\n            }\n\n            self.addOperation(.update(tagUpdates: updates))\n\n            self.notifyOverridesChanged()\n        }\n    }\n\n    private func notifyOverridesChanged() {\n        Task { [weak audienceOverridesProvider] in\n            await audienceOverridesProvider?.notifyPendingChanged()\n        }\n    }\n\n    /// Begins a tag groups editing session.\n    /// - Parameter editorBlock: A tag groups editor block.\n    /// - Returns: A TagGroupsEditor\n    public func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void) {\n        let editor = editTagGroups()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    /// Begins an attribute editing session.\n    /// - Returns: An AttributesEditor\n    public func editAttributes() -> AttributesEditor {\n        return AttributesEditor { updates in\n            guard !updates.isEmpty else {\n                AirshipLogger.trace(\"Empty attribute updates, ignoring\")\n                return\n            }\n\n            guard\n                self.privacyManager.isEnabled([.contacts, .tagsAndAttributes])\n            else {\n                AirshipLogger.warn(\n                    \"Contacts or tags are disabled. Enable to apply attribute edits.\"\n                )\n                return\n            }\n\n            self.addOperation(\n                .update(attributeUpdates: updates)\n            )\n            self.notifyOverridesChanged()\n        }\n    }\n\n    /// Begins an attribute editing session.\n    /// - Parameter editorBlock: An attributes editor block.\n    /// - Returns: An AttributesEditor\n    public func editAttributes(_ editorBlock: (AttributesEditor) -> Void) {\n        let editor = editAttributes()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    /**\n     * Associates an Email channel to the contact.\n     * - Parameters:\n     *   - address: The email address.\n     *   - options: The email channel registration options.\n     */\n    public func registerEmail(\n        _ address: String,\n        options: EmailRegistrationOptions\n    ) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\n                \"Contacts disabled. Enable to associate Email channel.\"\n            )\n            return\n        }\n\n        self.addOperation(.registerEmail(address: address, options: options))\n        self.notifyOverridesChanged()\n    }\n\n    /**\n     * Associates a SMS channel to the contact.\n     * - Parameters:\n     *   - msisdn: The SMS Mobile Station International Subscriber Directory Number..\n     *   - options: The SMS channel registration options.\n     */\n    public func registerSMS(_ msisdn: String, options: SMSRegistrationOptions) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\n                \"Contacts disabled. Enable to associate SMS channel.\"\n            )\n            return\n        }\n\n        self.addOperation(.registerSMS(msisdn: msisdn, options: options))\n        self.notifyOverridesChanged()\n    }\n\n    /// Associates an open channel to the contact.\n    /// - Parameter address: The open channel address.\n    /// - Parameter options: The open channel registration options.\n    public func registerOpen(\n        _ address: String,\n        options: OpenRegistrationOptions\n    ) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\n                \"Contacts disabled. Enable to associate Open channel.\"\n            )\n            return\n        }\n\n        self.addOperation(.registerOpen(address: address, options: options))\n    }\n\n    /**\n     * Associates a channel to the contact.\n     * - Parameters:\n     *   - channelID: The channel ID.\n     *   - type: The channel type.\n     *   - options: The SMS/email channel options\n     */\n    public func associateChannel(\n        _ channelID: String,\n        type: ChannelType\n    ) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\n                \"Contacts disabled. Enable to associate channel.\"\n            )\n            return\n        }\n\n        self.addOperation(\n            .associateChannel(\n                channelID: channelID,\n                channelType: type\n            )\n        )\n    }\n\n    /**\n     * Resends an opt-in message\n     * - Parameters:\n     *   - channelID: The channel ID.\n     *   - type: The channel type.\n     *   - options: The SMS/email channel options\n     */\n    public func resend(_ channel: ContactChannel) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\n                \"Contacts disabled. Enable to re-send double opt in to channel.\"\n            )\n            return\n        }\n\n        self.addOperation(.resend(channel: channel))\n    }\n\n    /**\n     * Disassociates a channel\n     * - Parameters:\n     *   - channel: The channel to disassociate.\n     */\n    public func disassociateChannel(_ channel: ContactChannel) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            AirshipLogger.warn(\n                \"Contacts disabled. Enable to disassociate channel.\"\n            )\n            return\n        }\n\n        self.addOperation(.disassociateChannel(channel: channel))\n\n        Task { [weak audienceOverridesProvider] in\n            await audienceOverridesProvider?.notifyPendingChanged()\n        }\n    }\n\n    /// Begins a subscription list editing session\n    /// - Returns: A Scoped subscription list editor\n    public func editSubscriptionLists() -> ScopedSubscriptionListEditor {\n        return ScopedSubscriptionListEditor(date: self.date) { updates in\n            guard !updates.isEmpty else {\n                return\n            }\n\n            guard\n                self.privacyManager.isEnabled([.contacts, .tagsAndAttributes])\n            else {\n                AirshipLogger.warn(\n                    \"Contacts or tags are disabled. Enable to apply subscription lists edits.\"\n                )\n                return\n            }\n\n            Task { @MainActor in\n                updates.forEach {\n                    switch $0.type {\n                    case .subscribe:\n                        self.subscriptionListEditsSubject.send(\n                            .subscribe($0.listId, $0.scope)\n                        )\n                    case .unsubscribe:\n                        self.subscriptionListEditsSubject.send(\n                            .unsubscribe($0.listId, $0.scope)\n                        )\n                    }\n                }\n            }\n\n            self.addOperation(.update(subscriptionListsUpdates: updates))\n        }\n    }\n\n    /// Begins a subscription list editing session\n    /// - Parameter editorBlock: A scoped subscription list editor block.\n    /// - Returns: A ScopedSubscriptionListEditor\n    public func editSubscriptionLists(\n        _ editorBlock: (ScopedSubscriptionListEditor) -> Void\n    ) {\n        let editor = editSubscriptionLists()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n\n    private func waitForContactIDInfo(filter: @Sendable @escaping (ContactIDInfo) -> Bool) async -> ContactIDInfo {\n        // Stableness is determined by a reset or identify operation.  Since\n        // pending operations are added through the serialQueue to ensure order, some might still\n        // be in the queue. To avoid ignoring any of those, wait for current operations on the queue\n        // to finish\n        await self.serialQueue.waitForCurrentOperations()\n\n        var subscription: AnyCancellable?\n        let result: ContactIDInfo = await withCheckedContinuation { continuation in\n            subscription = self.contactIDUpdates\n                .first { update in\n                    filter(update)\n                }\n                .sink { update in\n                    continuation.resume(returning: update)\n                }\n        }\n        subscription?.cancel()\n        return result\n    }\n\n    public func getStableContactID() async -> String {\n        return await waitForContactIDInfo { update in\n            update.isStable\n        }.contactID\n    }\n\n    public func getStableContactInfo() async -> StableContactInfo {\n        let info = await waitForContactIDInfo(filter: { $0.isStable })\n        return StableContactInfo(\n            contactID: info.contactID,\n            namedUserID: info.namedUserID\n        )\n    }\n\n    private func getStableVerifiedContactID() async -> String {\n        let now = self.date.now\n\n        let stableIDInfo = await waitForContactIDInfo { update in\n            update.isStable\n        }\n\n        let secondsSinceLastResolve = now.timeIntervalSince(stableIDInfo.resolveDate)\n        guard secondsSinceLastResolve >= self.verifiedContactIDMaxAge else {\n            return stableIDInfo.contactID\n        }\n\n        addOperation(.verify(now))\n        return await waitForContactIDInfo { update in\n            update.isStable && update.resolveDate >= now\n        }.contactID\n    }\n\n    public func fetchSubscriptionLists() async throws -> [String: [ChannelScope]] {\n        let contactID = await getStableContactID()\n        return try await subscriptionListProvider.fetch(contactID: contactID)\n    }\n\n    @objc\n    private func checkPrivacyManager() {\n        self.serialQueue.enqueue {\n            if self.privacyManager.isAnyFeatureEnabled() {\n                await self.contactManager.generateDefaultContactIDIfNotSet()\n            }\n\n            guard self.privacyManager.isEnabled(.contacts) else {\n                await self.contactManager.resetIfNeeded()\n                return\n            }\n        }\n    }\n\n    @objc\n    private func didBecomeActive() {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            return\n        }\n\n        let lastActive = self.date.now.timeIntervalSince(self.lastResolveDate)\n\n        if (lastActive >= self.foregroundInterval) {\n            self.lastResolveDate = self.date.now\n            self.addOperation(.resolve)\n        }\n\n        self.contactChannelsProvider.refreshAsync()\n    }\n\n    @objc\n    private func channelCreated(notification: NSNotification) {\n        guard self.privacyManager.isEnabled(.contacts) else {\n            return\n        }\n\n        let existing = notification.userInfo?[AirshipNotifications.ChannelCreated.isExistingChannelKey] as? Bool\n\n        if existing == true && self.config.airshipConfig.clearNamedUserOnAppRestore {\n            self.addOperation(.reset)\n        } else {\n            self.addOperation(.resolve)\n        }\n    }\n\n    private func addOperation(_ operation: ContactOperation) {\n        self.serialQueue.enqueue {\n            AirshipLogger.trace(\"Adding contact operation \\(operation.type)\")\n            await self.contactManager.addOperation(operation)\n        }\n    }\n\n    private func migrateNamedUser() async {\n        defer {\n            self.dataStore.removeObject(forKey: DefaultAirshipContact.legacyNamedUserKey)\n            self.dataStore.removeObject(\n                forKey: DefaultAirshipContact.legacyPendingTagGroupsKey\n            )\n            self.dataStore.removeObject(\n                forKey: DefaultAirshipContact.legacyPendingAttributesKey\n            )\n        }\n\n        guard self.privacyManager.isEnabled(.contacts) else {\n            return\n        }\n\n        guard\n            let legacyNamedUserID = try? self.dataStore.string(\n                forKey: DefaultAirshipContact.legacyNamedUserKey\n            )?.normalizedNamedUserID\n        else {\n            await self.contactManager.generateDefaultContactIDIfNotSet()\n            return\n        }\n\n        if await self.contactManager.currentContactIDInfo() == nil {\n            // Need to call through to contact manager directly to ensure operation order\n            await self.contactManager.addOperation(.identify(legacyNamedUserID))\n        }\n\n        if self.privacyManager.isEnabled(.tagsAndAttributes) {\n            var pendingTagUpdates: [TagGroupUpdate]?\n            var pendingAttributeUpdates: [AttributeUpdate]?\n\n            if let pendingTagGroupsData = self.dataStore.data(\n                forKey: DefaultAirshipContact.legacyPendingTagGroupsKey\n            ) {\n                let classes = [NSArray.self, TagGroupsMutation.self]\n                let pendingTagGroups = try? NSKeyedUnarchiver.unarchivedObject(\n                    ofClasses: classes,\n                    from: pendingTagGroupsData\n                )\n\n                if let pendingTagGroups = pendingTagGroups\n                    as? [TagGroupsMutation]\n                {\n                    pendingTagUpdates =\n                        pendingTagGroups.map { $0.tagGroupUpdates }\n                        .reduce([], +)\n                }\n            }\n\n            if let pendingAttributesData = self.dataStore.data(\n                forKey: DefaultAirshipContact.legacyPendingAttributesKey\n            ) {\n\n                let classes = [NSArray.self, AttributePendingMutations.self]\n                let pendingAttributes = try? NSKeyedUnarchiver.unarchivedObject(\n                    ofClasses: classes,\n                    from: pendingAttributesData\n                )\n\n                if let pendingAttributes = pendingAttributes\n                    as? [AttributePendingMutations]\n                {\n                    pendingAttributeUpdates =\n                        pendingAttributes.map {\n                            $0.attributeUpdates\n                        }\n                        .reduce([], +)\n                }\n            }\n\n            if !(pendingTagUpdates?.isEmpty ?? true\n                && pendingAttributeUpdates?.isEmpty ?? true)\n            {\n                // Need to call through to contact manager directly to ensure operation order\n                await self.contactManager.addOperation(\n                    .update(\n                        tagUpdates: pendingTagUpdates,\n                        attributeUpdates: pendingAttributeUpdates\n                    )\n                )\n            }\n        }\n    }\n}\n\nextension String {\n    var normalizedNamedUserID: String {\n        get throws {\n            let trimmedID = self.trimmingCharacters(\n                in: .whitespacesAndNewlines\n            )\n\n            guard\n                trimmedID.count > 0,\n                trimmedID.count <= DefaultAirshipContact.maxNamedUserIDLength\n            else {\n                throw AirshipErrors.error(\"Invalid named user ID \\(trimmedID). IDs must be between 1 and \\(DefaultAirshipContact.maxNamedUserIDLength) characters.\")\n            }\n\n            return trimmedID\n        }\n    }\n}\n\nextension DefaultAirshipContact : InternalAirshipContact {\n    var contactIDInfo: ContactIDInfo? {\n        get async {\n            return await self.contactManager.currentContactIDInfo()\n        }\n    }\n\n\n    var authTokenProvider: any AuthTokenProvider {\n        return self.contactManager\n    }\n\n    var contactID: String? {\n        get async {\n            return await self.contactManager.currentContactIDInfo()?.contactID\n        }\n    }\n}\n\nextension DefaultAirshipContact: AirshipPushableComponent {\n    public func receivedRemoteNotification(_ notification: AirshipJSON) async -> UABackgroundFetchResult {\n        guard\n            let userInfo = notification.unWrap() as? [AnyHashable: Any],\n            userInfo[Self.refreshContactPushPayloadKey] != nil else {\n            return .noData\n        }\n\n        self.contactChannelsProvider.refreshAsync()\n        return .newData\n    }\n\n#if !os(tvOS)\n    public func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        // no-op\n    }\n#endif\n\n}\n\n\n\nextension DefaultAirshipContact: AirshipComponent {}\n\n\npublic extension AirshipNotifications {\n\n    /// NSNotification info when a conflict event is emitted.\n    final class ContactConflict {\n\n        /// NSNotification name.\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.contact_conflict\"\n        )\n\n        /// NSNotification userInfo key to get the `ContactConflictEvent`.\n        public static let eventKey: String = \"event\"\n    }\n}\n\nfileprivate struct NamedUserIDEvent {\n    let identifier: String?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DefaultAirshipPush.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n@preconcurrency\npublic import UserNotifications\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n\n/// This singleton provides an interface to the functionality provided by the Airship iOS Push API.\nfinal class DefaultAirshipPush: AirshipPush, @unchecked Sendable {\n\n    private let pushTokenChannel: AirshipAsyncChannel<String> = AirshipAsyncChannel<String>()\n\n    private let notificationStatusChannel: AirshipAsyncChannel<AirshipNotificationStatus> = AirshipAsyncChannel<AirshipNotificationStatus>()\n\n    @MainActor\n    public var notificationStatusPublisher: AnyPublisher<AirshipNotificationStatus, Never> {\n        return notificationStatusUpdates\n            .airshipPublisher\n            .compactMap { $0 }\n            .eraseToAnyPublisher()\n    }\n\n    var notificationStatusUpdates: AsyncStream<AirshipNotificationStatus> {\n        return self.notificationStatusChannel.makeNonIsolatedDedupingStream(\n            initialValue: { [weak self] in await self?.notificationStatus }\n        )\n    }\n\n    private static let pushNotificationsOptionsKey: String = \"UAUserPushNotificationsOptions\"\n    private static let userPushNotificationsEnabledKey: String = \"UAUserPushNotificationsEnabled\"\n    private static let backgroundPushNotificationsEnabledKey: String = \"UABackgroundPushNotificationsEnabled\"\n    private static let requestExplicitPermissionWhenEphemeralKey: String = \"UAExtendedPushNotificationPermissionEnabled\"\n    private static let badgeSettingsKey: String = \"UAPushBadge\"\n    private static let deviceTokenKey: String = \"UADeviceToken\"\n    private static let quietTimeSettingsKey: String = \"UAPushQuietTime\"\n    private static let quietTimeEnabledSettingsKey: String = \"UAPushQuietTimeEnabled\"\n    private static let timeZoneSettingsKey: String = \"UAPushTimeZone\"\n    private static let typesAuthorizedKey: String = \"UAPushTypesAuthorized\"\n    private static let authorizationStatusKey: String = \"UAPushAuthorizationStatus\"\n    private static let userPromptedForNotificationsKey: String = \"UAPushUserPromptedForNotifications\"\n\n    // Old push enabled key\n    private static let oldPushEnabledKey: String = \"UAPushEnabled\"\n\n    // The default device tag group.\n    private static let defaultDeviceTagGroup: String = \"device\"\n\n    // The foreground presentation options that can be defined from API or dashboard\n    private static let presentationOptionBadge: String = \"badge\"\n    private static let presentationOptionAlert: String = \"alert\"\n    private static let presentationOptionSound: String = \"sound\"\n    private static let presentationOptionList: String = \"list\"\n    private static let presentationOptionBanner: String = \"banner\"\n\n    // Foreground presentation keys\n    private static let ForegroundPresentationLegacykey: String = \"foreground_presentation\"\n    private static let ForegroundPresentationkey: String = \"com.urbanairship.foreground_presentation\"\n    private static let deviceTokenRegistrationWaitTime: TimeInterval = 10\n\n    private let config: RuntimeConfig\n    private let dataStore: PreferenceDataStore\n    private let channel: any InternalAirshipChannel\n    private let privacyManager: any AirshipPrivacyManager\n    private let permissionsManager: any AirshipPermissionsManager\n    private let notificationCenter: AirshipNotificationCenter\n    private let notificationRegistrar: any NotificationRegistrar\n    private let apnsRegistrar: any APNSRegistrar\n    private let badger: any BadgerProtocol\n\n    @MainActor\n    private var waitForDeviceToken: Bool = false\n\n    @MainActor\n    private var pushEnabled: Bool = false\n\n    private let serialQueue: AirshipAsyncSerialQueue\n\n    @MainActor\n    public var onAPNSRegistrationFinished: (@MainActor @Sendable (APNSRegistrationResult) -> Void)?\n\n    @MainActor\n    public var onNotificationRegistrationFinished: (@MainActor @Sendable (NotificationRegistrationResult) -> Void)?\n\n    @MainActor\n    public var onNotificationAuthorizedSettingsDidChange: (@MainActor @Sendable (AirshipAuthorizedNotificationSettings) -> Void)?\n\n    // Notification callbacks\n    @MainActor\n    public var onForegroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> Void)?\n\n#if os(watchOS)\n    @MainActor\n    public var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> WKBackgroundFetchResult)?\n#elseif os(macOS)\n    @MainActor\n    public var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> Void)?\n#else\n    @MainActor\n    public var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> UIBackgroundFetchResult)?\n#endif\n\n#if !os(tvOS)\n    @MainActor\n    public var onNotificationResponseReceived: (@MainActor @Sendable (UNNotificationResponse) async -> Void)?\n#endif\n\n    @MainActor\n    public var onExtendPresentationOptions: (@MainActor @Sendable (UNNotificationPresentationOptions, UNNotification) async -> UNNotificationPresentationOptions)?\n\n    @MainActor\n    private var isRegisteredForRemoteNotifications: Bool {\n        return self.apnsRegistrar.isRegisteredForRemoteNotifications\n    }\n\n    @MainActor\n    private var isBackgroundRefreshStatusAvailable: Bool {\n        return self.apnsRegistrar.isBackgroundRefreshStatusAvailable\n    }\n\n    private var subscriptions: Set<AnyCancellable> = Set()\n\n    @MainActor\n    @inline(never)\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any InternalAirshipChannel,\n        analytics: any InternalAirshipAnalytics,\n        privacyManager: any AirshipPrivacyManager,\n        permissionsManager: any AirshipPermissionsManager,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        notificationRegistrar: any NotificationRegistrar =\n        UNNotificationRegistrar(),\n        apnsRegistrar: any APNSRegistrar,\n        badger: any BadgerProtocol,\n        serialQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n    ) {\n\n        self.config = config\n        self.dataStore = dataStore\n        self.channel = channel\n        self.privacyManager = privacyManager\n        self.permissionsManager = permissionsManager\n        self.notificationCenter = notificationCenter\n        self.notificationRegistrar = notificationRegistrar\n        self.apnsRegistrar = apnsRegistrar\n        self.badger = badger\n        self.serialQueue = serialQueue\n\n        let permissionDelegate = NotificationPermissionDelegate(\n            registrar: self.notificationRegistrar\n        ) {\n            let options = self.notificationOptions\n            let skipIfEphemeral = !self\n                .requestExplicitPermissionWhenEphemeral\n            return NotificationPermissionDelegate.Config(\n                options: options,\n                skipIfEphemeral: skipIfEphemeral\n            )\n        }\n\n        self.permissionsManager.setDelegate(\n            permissionDelegate,\n            permission: .displayNotifications\n        )\n\n        self.permissionsManager.addRequestExtender(\n            permission: .displayNotifications\n        ) { status in\n            await self.notificationRegistrationFinished()\n        }\n\n        self.permissionsManager.addAirshipEnabler(\n            permission: .displayNotifications\n        ) {\n            self.dataStore.setBool(\n                true,\n                forKey: DefaultAirshipPush.userPushNotificationsEnabledKey\n            )\n            self.privacyManager.enableFeatures(.push)\n            self.channel.updateRegistration()\n            self.updateNotificationStatus()\n        }\n\n        self.waitForDeviceToken = self.channel.identifier == nil\n        self.observeNotificationCenterEvents()\n\n        let checkAppRestoreTask = Task {\n            if await self.dataStore.isAppRestore {\n                self.resetDeviceToken()\n            }\n        }\n\n        self.channel.addRegistrationExtender { payload in\n            await checkAppRestoreTask.value\n            return await self.extendChannelRegistrationPayload(&payload)\n        }\n\n        analytics.addHeaderProvider {\n            await self.analyticsHeaders()\n        }\n\n        self.updatePushEnablement()\n\n        if !self.apnsRegistrar.isRemoteNotificationBackgroundModeEnabled {\n            AirshipLogger.impError(\n                \"Application is not configured for background notifications. Please enable remote notifications in the application's background modes.\",\n                skipLogLevelCheck: false\n            )\n        }\n    }\n\n    @MainActor\n    private func observeNotificationCenterEvents() {\n#if !os(watchOS) && !os(macOS)\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationBackgroundRefreshStatusChanged),\n            name: UIApplication\n                .backgroundRefreshStatusDidChangeNotification,\n            object: nil\n        )\n#endif\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationDidBecomeActive),\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationDidEnterBackground),\n            name: AppStateTracker.didEnterBackgroundNotification,\n            object: nil\n        )\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(onEnabledFeaturesChanged),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n    }\n\n    /// Enables/disables background remote notifications on this device through Airship.\n    /// Defaults to `true`.\n    public var backgroundPushNotificationsEnabled: Bool {\n        set {\n            let previous = self.backgroundPushNotificationsEnabled\n            self.dataStore.setBool(\n                newValue,\n                forKey: DefaultAirshipPush.backgroundPushNotificationsEnabledKey\n            )\n            if !previous == newValue {\n                self.channel.updateRegistration()\n            }\n        }\n        get {\n            return self.dataStore.bool(\n                forKey: DefaultAirshipPush.backgroundPushNotificationsEnabledKey,\n                defaultValue: true\n            )\n        }\n    }\n\n    /// Enables/disables user notifications on this device through Airship.\n    /// Defaults to `false`. Once set to `true`, the user will be prompted for remote notifications.\n    public var userPushNotificationsEnabled: Bool {\n        set {\n            let previous = self.userPushNotificationsEnabled\n            self.dataStore.setBool(\n                newValue,\n                forKey: DefaultAirshipPush.userPushNotificationsEnabledKey\n            )\n            if previous != newValue {\n                self.dispatchUpdateNotifications()\n            }\n\n            self.updateNotificationStatus()\n        }\n\n        get {\n            return self.dataStore.bool(\n                forKey: DefaultAirshipPush.userPushNotificationsEnabledKey\n            )\n        }\n    }\n\n\n    /// When enabled, if the user has ephemeral notification authorization the SDK will prompt the user for\n    /// notifications.  Defaults to `false`.\n    public var requestExplicitPermissionWhenEphemeral: Bool {\n        set {\n            let previous = self.requestExplicitPermissionWhenEphemeral\n            if previous != newValue {\n                self.dataStore.setBool(\n                    newValue,\n                    forKey: DefaultAirshipPush.requestExplicitPermissionWhenEphemeralKey\n                )\n                self.dispatchUpdateNotifications()\n            }\n        }\n        get {\n            return self.dataStore.bool(\n                forKey: DefaultAirshipPush.requestExplicitPermissionWhenEphemeralKey\n            )\n        }\n    }\n\n    /// The device token for this device, as a hex string.\n    @MainActor\n    public private(set) var deviceToken: String? {\n        set {\n            guard let deviceToken = newValue else {\n                self.dataStore.removeObject(forKey: DefaultAirshipPush.deviceTokenKey)\n                self.updateNotificationStatus()\n                return\n            }\n\n            do {\n                let regex = try NSRegularExpression(\n                    pattern: \"[^0-9a-fA-F]\",\n                    options: .caseInsensitive\n                )\n                if regex.numberOfMatches(\n                    in: deviceToken,\n                    options: [],\n                    range: NSRange(location: 0, length: deviceToken.count)\n                ) != 0 {\n                    AirshipLogger.error(\n                        \"Device token \\(deviceToken) contains invalid characters. Only hex characters are allowed\"\n                    )\n                    return\n                }\n\n                if deviceToken.count < 64 || deviceToken.count > 200 {\n                    AirshipLogger.warn(\n                        \"Device token \\(deviceToken) should be 64 to 200 hex characters (32 to 100 bytes) long.\"\n                    )\n                }\n\n                self.dataStore.setObject(\n                    deviceToken,\n                    forKey: DefaultAirshipPush.deviceTokenKey\n                )\n                AirshipLogger.importantInfo(\"Device token: \\(deviceToken)\")\n                Task {\n                    await self.pushTokenChannel.send(deviceToken)\n                }\n            } catch {\n                AirshipLogger.error(\"Unable to set device token\")\n            }\n\n            self.updateNotificationStatus()\n        }\n\n        get {\n            return self.dataStore.string(forKey: DefaultAirshipPush.deviceTokenKey)\n        }\n    }\n\n    /// User Notification options this app will request from APNS. Changes to this value\n    /// will not take effect until the next time the app registers with\n    /// updateRegistration.\n    ///\n    /// Defaults to alert, sound and badge.\n    public var notificationOptions: UNAuthorizationOptions {\n        set {\n            let previous = self.notificationOptions\n            self.dataStore.setObject(\n                newValue.rawValue,\n                forKey: DefaultAirshipPush.pushNotificationsOptionsKey\n            )\n            if previous != newValue {\n                self.dispatchUpdateNotifications()\n            }\n        }\n\n        get {\n            guard\n                let value = self.dataStore.object(\n                    forKey: DefaultAirshipPush.pushNotificationsOptionsKey\n                ) as? UInt\n            else {\n#if os(tvOS)\n                return .badge\n#else\n                guard self.authorizationStatus == .provisional else {\n                    return [.badge, .sound, .alert]\n                }\n                return [.badge, .sound, .alert, .provisional]\n#endif\n            }\n\n            return UNAuthorizationOptions(rawValue: value)\n        }\n    }\n\n#if !os(tvOS)\n    /// Custom notification categories. Airship default notification\n    /// categories will be unaffected by this field.\n    @MainActor\n    public var customCategories: Set<UNNotificationCategory> = Set() {\n        didSet {\n            self.updateCategories()\n        }\n    }\n\n    /// The combined set of notification categories from `customCategories` set by the app\n    /// and the Airship provided categories.\n    @MainActor\n    public var combinedCategories: Set<UNNotificationCategory> {\n        let defaultCategories = NotificationCategories.defaultCategories(\n            withRequireAuth: requireAuthorizationForDefaultCategories\n        )\n        return defaultCategories.union(self.customCategories)\n    }\n\n#endif\n\n    /// Sets authorization required for the default Airship categories. Only applies\n    /// to background user notification actions.\n    ///\n    /// Changes to this value will not take effect until the next time the app registers\n    /// with updateRegistration.\n    @MainActor\n    public var requireAuthorizationForDefaultCategories: Bool = true {\n        didSet {\n            self.updateCategories()\n        }\n    }\n\n    @MainActor\n    public weak var pushNotificationDelegate: (any PushNotificationDelegate)?\n\n    @MainActor\n    public weak var registrationDelegate: (any RegistrationDelegate)?\n\n#if !os(tvOS)\n    /// Notification response that launched the application.\n    public private(set) var launchNotificationResponse: UNNotificationResponse?\n#endif\n\n    public private(set) var authorizedNotificationSettings: AirshipAuthorizedNotificationSettings {\n        set {\n            self.dataStore.setInteger(\n                Int(newValue.rawValue),\n                forKey: DefaultAirshipPush.typesAuthorizedKey\n            )\n        }\n\n        get {\n            guard\n                let value = self.dataStore.object(\n                    forKey: DefaultAirshipPush.typesAuthorizedKey\n                )\n                    as? Int\n            else {\n                return []\n            }\n\n            return AirshipAuthorizedNotificationSettings(rawValue: UInt(value))\n        }\n    }\n\n    public private(set) var authorizationStatus: UNAuthorizationStatus {\n        set {\n            self.dataStore.setInteger(\n                newValue.rawValue,\n                forKey: DefaultAirshipPush.authorizationStatusKey\n            )\n        }\n\n        get {\n            guard\n                let value = self.dataStore.object(\n                    forKey: DefaultAirshipPush.authorizationStatusKey\n                )\n                    as? Int\n            else {\n                return .notDetermined\n            }\n\n            return UNAuthorizationStatus(rawValue: Int(value))\n            ?? .notDetermined\n        }\n    }\n\n    public private(set) var userPromptedForNotifications: Bool {\n        set {\n            self.dataStore.setBool(\n                newValue,\n                forKey: DefaultAirshipPush.userPromptedForNotificationsKey\n            )\n        }\n        get {\n            return self.dataStore.bool(\n                forKey: DefaultAirshipPush.userPromptedForNotificationsKey\n            )\n        }\n    }\n\n    public var defaultPresentationOptions: UNNotificationPresentationOptions =\n    []\n\n    @MainActor\n    private func updateAuthorizedNotificationTypes(\n        alwaysUpdateChannel: Bool = false\n    ) async -> (UNAuthorizationStatus, AirshipAuthorizedNotificationSettings) {\n        AirshipLogger.trace(\"Updating authorized types.\")\n        let (status, settings) = await self.notificationRegistrar.checkStatus()\n        var settingsChanged = false\n        if self.privacyManager.isEnabled(.push) {\n            if !self.userPromptedForNotifications {\n#if os(tvOS) || os(watchOS)\n                self.userPromptedForNotifications = status != .notDetermined\n#elseif os(macOS)\n                if status != .notDetermined{\n                    self.userPromptedForNotifications = true\n                }\n#else\n                if status != .notDetermined && status != .ephemeral {\n                    self.userPromptedForNotifications = true\n                }\n#endif\n            }\n            if status != self.authorizationStatus {\n                self.authorizationStatus = status\n                settingsChanged = true\n            }\n\n            if self.authorizedNotificationSettings != settings {\n                self.authorizedNotificationSettings = settings\n\n                if let onNotificationAuthorizedSettingsDidChange {\n                    onNotificationAuthorizedSettingsDidChange(settings)\n                } else {\n                    self.registrationDelegate?.notificationAuthorizedSettingsDidChange(\n                        settings\n                    )\n                }\n\n                settingsChanged = true\n            }\n        }\n\n        updateNotificationStatus()\n\n        if (settingsChanged || alwaysUpdateChannel) {\n            self.channel.updateRegistration()\n        }\n\n        return(status, settings)\n    }\n\n    public func enableUserPushNotifications() async -> Bool {\n        return await enableUserPushNotifications(fallback: .none)\n    }\n\n    public func enableUserPushNotifications(\n        fallback: PromptPermissionFallback\n    ) async -> Bool {\n        guard self.config.airshipConfig.requestAuthorizationToUseNotifications else {\n            self.userPushNotificationsEnabled = true\n\n            if !fallback.isNone {\n                AirshipLogger.error(\n                    \"Airship.push.enableUserPushNotifications(fallback:) called but the AirshipConfig.requestAuthorizationToUseNotifications is disabled. Unable to request permissions. Use Airship.permissionsManager.requestPermission(.displayNotifications, enableAirshipUsageOnGrant: true, fallback: fallback) instead.\"\n                )\n            }\n\n            return await self.permissionsManager.checkPermissionStatus(.displayNotifications) == .granted\n        }\n\n        self.dataStore.setBool(\n            true,\n            forKey: DefaultAirshipPush.userPushNotificationsEnabledKey\n        )\n\n        let result = await self.permissionsManager.requestPermission(\n            .displayNotifications,\n            enableAirshipUsageOnGrant: false,\n            fallback: fallback\n        )\n\n        return result.endStatus == .granted\n    }\n\n    @MainActor\n    private func waitForDeviceTokenRegistration() async {\n        guard self.waitForDeviceToken,\n              self.privacyManager.isEnabled(.push),\n              self.apnsRegistrar.isRegisteredForRemoteNotifications\n        else {\n            return\n        }\n\n        self.waitForDeviceToken = false\n\n        let updates = await pushTokenChannel.makeStream()\n        guard self.deviceToken == nil else {\n            return\n        }\n\n        let waitTask = Task {\n            for await _ in updates {\n                return\n            }\n        }\n\n        let cancelTask = Task { @MainActor in\n            try await Task.sleep(\n                nanoseconds: UInt64(DefaultAirshipPush.deviceTokenRegistrationWaitTime * 1_000_000_000)\n            )\n            try Task.checkCancellation()\n            waitTask.cancel()\n        }\n\n        await waitTask.value\n        cancelTask.cancel()\n    }\n\n    @MainActor\n    public var isPushNotificationsOptedIn: Bool {\n        var optedIn = true\n\n        if self.deviceToken == nil {\n            AirshipLogger.trace(\"Opted out: missing device token\")\n            optedIn = false\n        }\n\n        if !self.userPushNotificationsEnabled {\n            AirshipLogger.trace(\"Opted out: user push notifications disabled\")\n            optedIn = false\n        }\n\n        if self.authorizedNotificationSettings == [] {\n            AirshipLogger.trace(\n                \"Opted out: no authorized notification settings\"\n            )\n            optedIn = false\n        }\n\n        if !self.isRegisteredForRemoteNotifications {\n            AirshipLogger.trace(\n                \"Opted out: not registered for remote notifications\"\n            )\n            optedIn = false\n        }\n\n        if !self.privacyManager.isEnabled(.push) {\n            AirshipLogger.trace(\"Opted out: push is disabled\")\n            optedIn = false\n        }\n\n        return optedIn\n    }\n\n    public var notificationStatus: AirshipNotificationStatus {\n        get async {\n            let (status, settings) = await self.notificationRegistrar.checkStatus()\n            let isRegisteredForRemoteNotifications = await self.apnsRegistrar.isRegisteredForRemoteNotifications\n            let displayNotificationStatus = await self.permissionsManager.checkPermissionStatus(.displayNotifications)\n\n            return await AirshipNotificationStatus(\n                isUserNotificationsEnabled: self.userPushNotificationsEnabled,\n                areNotificationsAllowed: status != .denied && status != .notDetermined && settings != [],\n                isPushPrivacyFeatureEnabled: self.privacyManager.isEnabled(.push),\n                isPushTokenRegistered: self.deviceToken != nil && isRegisteredForRemoteNotifications,\n                displayNotificationStatus: displayNotificationStatus\n            )\n        }\n    }\n\n    @MainActor\n    private func backgroundPushNotificationsAllowed() -> Bool {\n        guard self.deviceToken != nil,\n              self.backgroundPushNotificationsEnabled,\n              self.apnsRegistrar.isRemoteNotificationBackgroundModeEnabled,\n              self.privacyManager.isEnabled(.push)\n        else {\n            return false\n        }\n\n        return self.isRegisteredForRemoteNotifications\n        && self.isBackgroundRefreshStatusAvailable\n    }\n\n    @MainActor\n    private func updatePushEnablement() {\n        if self.privacyManager.isEnabled(.push) {\n            if (!self.pushEnabled) {\n                self.pushEnabled = true\n                self.apnsRegistrar.registerForRemoteNotifications()\n                self.dispatchUpdateNotifications()\n                self.updateCategories()\n            }\n        } else {\n            self.pushEnabled = false\n        }\n\n        updateNotificationStatus()\n    }\n\n    private func updateNotificationStatus() {\n        Task {\n            await self.notificationStatusChannel.send(await self.notificationStatus)\n        }\n    }\n\n    @MainActor\n    private func notificationRegistrationFinished() async {\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n        if self.deviceToken == nil {\n            self.apnsRegistrar.registerForRemoteNotifications()\n        }\n\n        let (status, settings) = await self.updateAuthorizedNotificationTypes(\n            alwaysUpdateChannel: true\n        )\n\n#if !os(tvOS)\n        if let onNotificationRegistrationFinished {\n            onNotificationRegistrationFinished(\n                NotificationRegistrationResult(\n                    authorizedSettings: settings,\n                    status: status,\n                    categories: self.combinedCategories\n                )\n            )\n        } else {\n            self.registrationDelegate?\n                .notificationRegistrationFinished(\n                    withAuthorizedSettings: settings,\n                    categories: self.combinedCategories,\n                    status: status\n                )\n        }\n#else\n        if let onNotificationRegistrationFinished {\n            onNotificationRegistrationFinished(\n                NotificationRegistrationResult(\n                    authorizedSettings: settings,\n                    status: status\n                )\n            )\n        } else {\n            self.registrationDelegate?\n                .notificationRegistrationFinished(\n                    withAuthorizedSettings: settings,\n                    status: status\n                )\n        }\n#endif\n    }\n\n#if !os(watchOS)\n\n    public func setBadgeNumber(_ newBadgeNumber: Int) async throws {\n        try await self.badger.setBadgeNumber(newBadgeNumber)\n        if self.autobadgeEnabled, privacyManager.isEnabled(.push) {\n            self.channel.updateRegistration(forcefully: true)\n        }\n    }\n\n    /// deprecation warning\n    @MainActor\n    public var badgeNumber: Int {\n        get {\n            return self.badger.badgeNumber\n        }\n    }\n\n    public var autobadgeEnabled: Bool {\n        set {\n            if self.autobadgeEnabled != newValue {\n                self.dataStore.setBool(\n                    newValue,\n                    forKey: DefaultAirshipPush.badgeSettingsKey\n                )\n\n                if privacyManager.isEnabled(.push) {\n                    self.channel.updateRegistration(forcefully: true)\n                }\n            }\n        }\n\n        get {\n            return self.dataStore.bool(forKey: DefaultAirshipPush.badgeSettingsKey)\n        }\n    }\n\n    @MainActor\n    func resetBadge() async throws {\n        try await self.setBadgeNumber(0)\n    }\n\n#endif\n\n    public var quietTime: QuietTimeSettings? {\n        get {\n            guard let quietTime = self.dataStore.dictionary(forKey: Self.quietTimeSettingsKey) else {\n                return nil\n            }\n\n            return QuietTimeSettings(from: quietTime)\n        }\n        set {\n            if let newValue {\n                AirshipLogger.debug(\"Setting quiet time: \\(newValue)\")\n                self.dataStore.setObject(newValue.dictionary, forKey: Self.quietTimeSettingsKey)\n            } else {\n                AirshipLogger.debug(\"Clearing quiet time\")\n                self.dataStore.removeObject(forKey: Self.quietTimeSettingsKey)\n            }\n            self.channel.updateRegistration()\n        }\n    }\n\n    /// Time Zone for quiet time. If the time zone is not set, the current\n    /// local time zone is returned.\n    public var timeZone: NSTimeZone? {\n        set {\n            self.dataStore.setObject(\n                newValue?.name ?? nil,\n                forKey: DefaultAirshipPush.timeZoneSettingsKey\n            )\n        }\n\n        get {\n            let timeZoneName =\n            self.dataStore.string(forKey: DefaultAirshipPush.timeZoneSettingsKey) ?? \"\"\n            return NSTimeZone(name: timeZoneName) ?? NSTimeZone.default\n            as NSTimeZone\n        }\n    }\n\n    /// Enables/Disables quiet time\n    public var quietTimeEnabled: Bool {\n        set {\n            self.dataStore.setBool(\n                newValue,\n                forKey: DefaultAirshipPush.quietTimeEnabledSettingsKey\n            )\n        }\n\n        get {\n            return self.dataStore.bool(forKey: DefaultAirshipPush.quietTimeEnabledSettingsKey)\n        }\n    }\n\n    public func setQuietTimeStartHour(\n        _ startHour: Int,\n        startMinute: Int,\n        endHour: Int,\n        endMinute: Int\n    ) {\n        do {\n            self.quietTime = try QuietTimeSettings(\n                startHour: UInt(startHour),\n                startMinute: UInt(startMinute),\n                endHour: UInt(endHour),\n                endMinute: UInt(endMinute)\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Unable to set quiet time, invalid time: \\(error)\"\n            )\n        }\n    }\n\n\n\n    private func updateRegistration() {\n        self.dispatchUpdateNotifications()\n    }\n\n    @MainActor\n    private func updateCategories() {\n#if !os(tvOS)\n        guard\n            self.privacyManager.isEnabled(.push),\n            self.config.airshipConfig.requestAuthorizationToUseNotifications\n        else {\n            return\n        }\n\n        self.notificationRegistrar.setCategories(self.combinedCategories)\n#endif\n    }\n\n    private func dispatchUpdateNotifications() {\n        self.serialQueue.enqueue {\n            await self.updateNotifications()\n        }\n    }\n\n    @MainActor\n    private func updateNotifications() async {\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n        guard self.config.airshipConfig.requestAuthorizationToUseNotifications else {\n            self.channel.updateRegistration()\n            return\n        }\n\n        if self.userPushNotificationsEnabled {\n            _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        } else {\n            // If we are going from `ephemeral` to `[]` it will prompt the user to disable notifications...\n            // avoid that by just skipping if we have ephemeral.\n            await self.notificationRegistrar.updateRegistration(\n                options: [],\n                skipIfEphemeral: true\n            )\n\n            await self.notificationRegistrationFinished()\n        }\n    }\n\n    @objc\n    private func onEnabledFeaturesChanged() {\n        self.serialQueue.enqueue {\n            await self.updatePushEnablement()\n        }\n    }\n\n    @objc\n    private func applicationDidBecomeActive() {\n        if self.privacyManager.isEnabled(.push) {\n            self.dispatchUpdateAuthorizedNotificationTypes()\n        }\n    }\n\n    @objc\n    private func applicationDidEnterBackground() {\n#if !os(tvOS)\n        self.launchNotificationResponse = nil\n#endif\n        if self.privacyManager.isEnabled(.push) {\n            self.dispatchUpdateAuthorizedNotificationTypes()\n        }\n    }\n\n#if !os(watchOS) && !os(macOS)\n    @objc\n    @MainActor\n    private func applicationBackgroundRefreshStatusChanged() {\n        if self.privacyManager.isEnabled(.push) {\n            AirshipLogger.trace(\"Background refresh status changed.\")\n\n            if self.apnsRegistrar.isBackgroundRefreshStatusAvailable {\n                self.apnsRegistrar.registerForRemoteNotifications()\n            } else {\n                self.channel.updateRegistration()\n            }\n        }\n    }\n#endif\n\n    @MainActor\n    private func extendChannelRegistrationPayload(\n        _ payload: inout ChannelRegistrationPayload\n    ) async {\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n        await self.waitForDeviceTokenRegistration()\n\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n\n        payload.channel.pushAddress = self.deviceToken\n        payload.channel.isOptedIn = self.isPushNotificationsOptedIn\n#if !os(watchOS)\n        payload.channel.isBackgroundEnabled =\n        self.backgroundPushNotificationsAllowed()\n#endif\n\n        payload.channel.iOSChannelSettings =\n        payload.channel.iOSChannelSettings\n        ?? ChannelRegistrationPayload.iOSChannelSettings()\n\n#if !os(watchOS)\n        if self.autobadgeEnabled {\n            payload.channel.iOSChannelSettings?.badge = self.badgeNumber\n        }\n#endif\n\n        if let timeZoneName = self.timeZone?.name, let quietTime, self.quietTimeEnabled {\n            let quietTime = ChannelRegistrationPayload.QuietTime(\n                start: quietTime.startString,\n                end: quietTime.endString\n            )\n            payload.channel.iOSChannelSettings?.quietTimeTimeZone =\n            timeZoneName\n            payload.channel.iOSChannelSettings?.quietTime = quietTime\n        }\n\n        payload.channel.iOSChannelSettings?.isScheduledSummary =\n        (self.authorizedNotificationSettings.rawValue\n         & AirshipAuthorizedNotificationSettings.scheduledDelivery\n            .rawValue > 0)\n        payload.channel.iOSChannelSettings?.isTimeSensitive =\n        (self.authorizedNotificationSettings.rawValue\n         & AirshipAuthorizedNotificationSettings.timeSensitive.rawValue\n         > 0)\n\n        return\n    }\n\n    @MainActor\n    private func analyticsHeaders() -> [String: String] {\n        guard self.privacyManager.isEnabled(.push) else {\n            return [\n                \"X-UA-Channel-Opted-In\": \"false\",\n                \"X-UA-Channel-Background-Enabled\": \"false\",\n            ]\n        }\n        var headers: [String: String] = [:]\n        headers[\"X-UA-Channel-Opted-In\"] =\n        self.isPushNotificationsOptedIn ? \"true\" : \"false\"\n        headers[\"X-UA-Notification-Prompted\"] =\n        self.userPromptedForNotifications ? \"true\" : \"false\"\n        headers[\"X-UA-Channel-Background-Enabled\"] =\n        self.backgroundPushNotificationsAllowed() ? \"true\" : \"false\"\n        headers[\"X-UA-Push-Address\"] = self.deviceToken\n\n        return headers\n    }\n}\n\n/// - Note: For internal use only. :nodoc:\nextension DefaultAirshipPush: InternalAirshipPush {\n\n    public func dispatchUpdateAuthorizedNotificationTypes() {\n        self.serialQueue.enqueue {\n            _ = await self.updateAuthorizedNotificationTypes()\n        }\n    }\n\n    @MainActor\n    public func didRegisterForRemoteNotifications(_ deviceToken: Data) {\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n        let tokenString = AirshipUtils.deviceTokenStringFromDeviceToken(deviceToken)\n        AirshipLogger.info(\"Device token string: \\(tokenString)\")\n        self.deviceToken = tokenString\n        self.channel.updateRegistration()\n\n        if let onAPNSRegistrationFinished {\n            onAPNSRegistrationFinished(.success(deviceToken: tokenString))\n        } else {\n            self.registrationDelegate?.apnsRegistrationSucceeded(\n                withDeviceToken: deviceToken\n            )\n        }\n    }\n\n    public func didFailToRegisterForRemoteNotifications(_ error: any Error) {\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n        if let onAPNSRegistrationFinished {\n            onAPNSRegistrationFinished(.failure(error: error))\n        } else {\n            self.registrationDelegate?.apnsRegistrationFailedWithError(error)\n        }\n    }\n\n    public func presentationOptionsForNotification(_ notification: UNNotification) async -> UNNotificationPresentationOptions {\n        guard self.privacyManager.isEnabled(.push) else {\n            return []\n        }\n\n        var options: UNNotificationPresentationOptions = []\n\n        // get foreground presentation options defined from the push API/dashboard\n        if let payloadPresentationOptions = self.foregroundPresentationOptions(\n            notification: notification\n        ) {\n            if payloadPresentationOptions.count > 0 {\n                // build the options bitmask from the array\n                for presentationOption in payloadPresentationOptions {\n                    switch presentationOption {\n                    case DefaultAirshipPush.presentationOptionBadge:\n                        options.insert(.badge)\n                    case DefaultAirshipPush.presentationOptionAlert:\n                        options.insert(.list)\n                        options.insert(.banner)\n                    case DefaultAirshipPush.presentationOptionSound:\n                        options.insert(.sound)\n                    case DefaultAirshipPush.presentationOptionList:\n                        options.insert(.list)\n                    case DefaultAirshipPush.presentationOptionBanner:\n                        options.insert(.banner)\n                    default:\n                        break\n                    }\n                }\n            } else {\n                options = self.defaultPresentationOptions\n            }\n        } else {\n            options = self.defaultPresentationOptions\n        }\n\n        if let onExtendPresentationOptions = self.onExtendPresentationOptions {\n            options = await onExtendPresentationOptions(options, notification)\n        } else if let delegate = self.pushNotificationDelegate {\n            options = await delegate.extendPresentationOptions(options, notification: notification)\n        }\n\n        return options\n    }\n\n#if !os(tvOS)\n\n    public func didReceiveNotificationResponse(_ response: UNNotificationResponse) async {\n        guard self.privacyManager.isEnabled(.push) else {\n            return\n        }\n\n        if response.actionIdentifier == UNNotificationDefaultActionIdentifier {\n            self.launchNotificationResponse = response\n        }\n\n        self.notificationCenter.post(\n            name: AirshipNotifications.ReceivedNotificationResponse.name,\n            object: self,\n            userInfo: [\n                AirshipNotifications.ReceivedNotificationResponse.responseKey: response\n            ]\n        )\n\n        if let onNotificationResponseReceived = self.onNotificationResponseReceived {\n            await onNotificationResponseReceived(response)\n        } else {\n            await self.pushNotificationDelegate?.receivedNotificationResponse(response)\n        }\n    }\n\n#endif\n\n    @MainActor\n    public func didReceiveRemoteNotification(\n        _ notification: [AnyHashable: Any],\n        isForeground: Bool\n    ) async -> UABackgroundFetchResult {\n\n        guard self.privacyManager.isEnabled(.push) else {\n            return .noData\n        }\n\n        let delegate = self.pushNotificationDelegate\n\n        self.notificationCenter.post(\n            name: AirshipNotifications.RecievedNotification.name,\n            object: self,\n            userInfo: [\n                AirshipNotifications.RecievedNotification.isForegroundKey: isForeground,\n                AirshipNotifications.RecievedNotification.notificationKey: notification\n            ]\n        )\n\n        if isForeground {\n            if let onForegroundNotificationReceived = self.onForegroundNotificationReceived {\n                await onForegroundNotificationReceived(notification)\n            } else {\n                await delegate?.receivedForegroundNotification(notification)\n            }\n\n            return .noData\n        } else {\n#if os(watchOS)\n            let result: WKBackgroundFetchResult = if let onBackgroundNotificationReceived = self.onBackgroundNotificationReceived {\n                await onBackgroundNotificationReceived(notification)\n            } else if let result = await delegate?.receivedBackgroundNotification(notification) {\n                result\n            } else {\n                .noData\n            }\n\n            return UABackgroundFetchResult(from: result)\n#elseif os(macOS)\n            if let onBackgroundNotificationReceived = self.onBackgroundNotificationReceived {\n                await onBackgroundNotificationReceived(notification)\n            } else {\n                await delegate?.receivedBackgroundNotification(notification)\n            }\n            return .noData\n#else\n            let result: UIBackgroundFetchResult = if let onBackgroundNotificationReceived = self.onBackgroundNotificationReceived {\n                await onBackgroundNotificationReceived(notification)\n            } else if let result = await delegate?.receivedBackgroundNotification(notification) {\n                result\n            } else {\n                .noData\n            }\n\n            return UABackgroundFetchResult(from: result)\n#endif\n        }\n    }\n\n    private func foregroundPresentationOptions(notification: UNNotification)\n    -> [String]?\n    {\n        var presentationOptions: [String]? = nil\n#if !os(tvOS)\n        // get the presentation options from the the notification\n        presentationOptions =\n        notification.request.content.userInfo[\n            DefaultAirshipPush.ForegroundPresentationkey\n        ]\n        as? [String]\n\n        if presentationOptions == nil {\n            presentationOptions =\n            notification.request.content.userInfo[\n                DefaultAirshipPush.ForegroundPresentationLegacykey\n            ] as? [String]\n        }\n#endif\n        return presentationOptions\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    @MainActor\n    func resetDeviceToken() {\n        self.deviceToken = nil\n        self.apnsRegistrar.registerForRemoteNotifications()\n        self.waitForDeviceToken = true\n    }\n}\n\n#if !os(tvOS)\nextension UNNotification {\n    /// Checks if the push was sent from Airship.\n    /// - Returns: true if it's an Airship notification, otherwise false.\n    public func isAirshipPush() -> Bool {\n        return self.request.content.userInfo[\"com.urbanairship.metadata\"] != nil\n    }\n}\n#endif\n\n\nextension DefaultAirshipPush: AirshipComponent {}\n\n\npublic extension AirshipNotifications {\n\n    /// NSNotification info when a notification response is received.\n    final class ReceivedNotificationResponse {\n\n        /// NSNotification name.\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.push.received_notification_response\"\n        )\n\n        /// NSNotification userInfo key to get the response dictionary.\n        public static let responseKey: String = \"response\"\n    }\n\n\n    /// NSNotification info when a notification is received..\n    final class RecievedNotification {\n\n        /// NSNotification name.\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.push.received_notification\"\n        )\n\n        /// NSNotification userInfo key to get a boolean if the notification was received in the foreground or not.\n        public static let isForegroundKey: String = \"is_foreground\"\n\n        /// NSNotification userInfo key to get the notification user info.\n        public static let notificationKey: String = \"notification\"\n    }\n}\n\n/// Quiet time settings\npublic struct QuietTimeSettings: Sendable, Equatable {\n    private static let quietTimeStartKey: String = \"start\"\n    private static let quietTimeEndKey: String = \"end\"\n\n    /// Start hour\n    public let startHour: UInt\n    /// Start minute\n    public let startMinute: UInt\n    /// End hour\n    public let endHour: UInt\n    /// End minute\n    public let endMinute: UInt\n\n\n    var startString: String {\n        return \"\\(String(format: \"%02d\", startHour)):\\(String(format: \"%02d\", startMinute))\"\n    }\n\n    var endString: String {\n        return \"\\(String(format: \"%02d\", endHour)):\\(String(format: \"%02d\", endMinute))\"\n    }\n\n    var dictionary: [AnyHashable: Any] {\n        return [\n            Self.quietTimeStartKey: startString,\n            Self.quietTimeEndKey: endString,\n        ]\n    }\n\n    /// Default constructor.\n    /// - Parameters:\n    ///     - startHour: The starting hour. Must be between 0-23.\n    ///     - startMinute: The starting minute. Must be between 0-59.\n    ///     - endHour: The ending hour. Must be between 0-23.\n    ///     - endMinute: The ending minute. Must be between 0-59.\n    public init(startHour: UInt, startMinute: UInt, endHour: UInt, endMinute: UInt) throws {\n        guard startHour < 24, startMinute < 60 else {\n            throw AirshipErrors.error(\"Invalid start time\")\n        }\n\n        guard endHour < 24, endMinute < 60 else {\n            throw AirshipErrors.error(\"Invalid end time\")\n        }\n\n        self.startHour = startHour\n        self.startMinute = startMinute\n        self.endHour = endHour\n        self.endMinute = endMinute\n    }\n\n    fileprivate init?(from dictionary: [AnyHashable: Any])  {\n        guard\n            let startTime = dictionary[Self.quietTimeStartKey] as? String,\n            let endTime = dictionary[Self.quietTimeEndKey] as? String\n        else {\n            return nil\n        }\n\n        let startParts = startTime.components(separatedBy:\":\").compactMap { UInt($0) }\n        let endParts = endTime.components(separatedBy:\":\").compactMap { UInt($0) }\n\n        guard startParts.count == 2, endParts.count == 2 else { return nil }\n\n        self.startHour = startParts[0]\n        self.startMinute = startParts[1]\n        self.endHour = endParts[0]\n        self.endMinute = endParts[1]\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DefaultAppIntegrationDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n\n// NOTE: For internal use only. :nodoc:\nfinal class DefaultAppIntegrationDelegate: AppIntegrationDelegate, Sendable {\n\n    let push: any InternalAirshipPush\n    let analytics: any InternalAirshipAnalytics\n    let pushableComponents: [any AirshipPushableComponent]\n\n    init(\n        push: any InternalAirshipPush,\n        analytics: any InternalAirshipAnalytics,\n        pushableComponents: [any AirshipPushableComponent]\n    ) {\n        self.push = push\n        self.analytics = analytics\n        self.pushableComponents = pushableComponents\n    }\n\n    @MainActor\n    public func onBackgroundAppRefresh() {\n        AirshipLogger.info(\"Application received background app refresh\")\n        self.push.dispatchUpdateAuthorizedNotificationTypes()\n    }\n\n    @MainActor\n    public func didRegisterForRemoteNotifications(deviceToken: Data) {\n        let tokenString = AirshipUtils.deviceTokenStringFromDeviceToken(deviceToken)\n        AirshipLogger.info(\"Application registered device token: \\(tokenString)\")\n        self.push.didRegisterForRemoteNotifications(deviceToken)\n    }\n\n    @MainActor\n    public func didFailToRegisterForRemoteNotifications(error: any Error) {\n        AirshipLogger.error(\"Application failed to register for remote notifications with error \\(error)\")\n        self.push.didFailToRegisterForRemoteNotifications(error)\n    }\n\n    @MainActor\n    public func presentationOptions(for notification: UNNotification, completionHandler: @escaping @Sendable (UNNotificationPresentationOptions) -> Void) {\n        Task { @MainActor in\n            let options = await self.push.presentationOptionsForNotification(notification)\n            completionHandler(options)\n        }\n    }\n\n    @MainActor\n    public func willPresentNotification(notification: UNNotification, presentationOptions: UNNotificationPresentationOptions, completionHandler: @escaping @Sendable () -> Void) {\n        Task { @MainActor in\n#if !os(tvOS) && !os(watchOS)\n            _ = await self.processPush(\n                notification.request.content.userInfo,\n                isForeground: true,\n                presentationOptions: presentationOptions\n            )\n#endif\n            completionHandler()\n        }\n    }\n\n    // MARK: - Response Handling (Conditional for tvOS)\n\n#if !os(tvOS)\n    @MainActor\n    public func didReceiveNotificationResponse(response: UNNotificationResponse, completionHandler: @escaping @Sendable () -> Void) {\n        AirshipLogger.info(\"Application received notification response: \\(response)\")\n\n        let userInfo = response.notification.request.content.userInfo\n        let categoryID = response.notification.request.content.categoryIdentifier\n        let actionID = response.actionIdentifier\n        let action = self.notificationAction(categoryID: categoryID, actionID: actionID)\n\n        self.analytics.onNotificationResponse(response: response, action: action)\n\n        Task { @MainActor in\n            for component in pushableComponents {\n                await component.receivedNotificationResponse(response)\n            }\n\n            if let actionsPayload = self.actionsPayloadForNotification(userInfo: userInfo, actionID: actionID) {\n                let situation = self.situationFromAction(action) ?? .launchedFromPush\n                let metadata: [String: any Sendable] = [\n                    ActionArguments.userNotificationActionIDMetadataKey: actionID,\n                    ActionArguments.pushPayloadJSONMetadataKey: try? AirshipJSON.wrap(userInfo),\n                    ActionArguments.responseInfoMetadataKey: (response as? UNTextInputNotificationResponse)?.userText\n                ]\n\n                await ActionRunner.run(\n                    actionsPayload: actionsPayload,\n                    situation: situation,\n                    metadata: metadata\n                )\n            }\n\n            await self.push.didReceiveNotificationResponse(response)\n            completionHandler()\n        }\n    }\n#endif\n\n    // MARK: - Remote Notification Handling\n\n#if os(watchOS)\n    @MainActor\n    public func didReceiveRemoteNotification(userInfo: [AnyHashable : Any], isForeground: Bool, completionHandler: @escaping @Sendable (WKBackgroundFetchResult) -> Void) {\n        self.processRemoteNotification(userInfo: userInfo, isForeground: isForeground) { result in\n            completionHandler(result.osFetchResult)\n        }\n    }\n#elseif os(macOS)\n    @MainActor\n    public func didReceiveRemoteNotification(\n        userInfo: [AnyHashable : Any],\n        isForeground: Bool\n    ) {\n        self.processRemoteNotification(\n            userInfo: userInfo,\n            isForeground: isForeground\n        ) { _ in }\n    }\n#else\n    @MainActor\n    public func didReceiveRemoteNotification(userInfo: [AnyHashable : Any], isForeground: Bool, completionHandler: @escaping @Sendable (UIBackgroundFetchResult) -> Void) {\n        self.processRemoteNotification(userInfo: userInfo, isForeground: isForeground) { result in\n            completionHandler(result.osFetchResult)\n        }\n    }\n#endif\n\n    @MainActor\n    private func processRemoteNotification(userInfo: [AnyHashable : Any], isForeground: Bool, completionHandler: @escaping @Sendable (UABackgroundFetchResult) -> Void) {\n        guard !isForeground || AirshipUtils.isSilentPush(userInfo) else {\n            completionHandler(.noData)\n            return\n        }\n\n        Task { @MainActor in\n            let result = await self.processPush(\n                userInfo,\n                isForeground: isForeground,\n                presentationOptions: nil\n            )\n            completionHandler(result)\n        }\n    }\n\n    // MARK: - Private Processing\n    @MainActor\n    private func processPush(\n        _ userInfo: [AnyHashable: Any],\n        isForeground: Bool,\n        presentationOptions: UNNotificationPresentationOptions?\n    ) async -> UABackgroundFetchResult {\n        AirshipLogger.info(\"Application received remote notification: \\(userInfo)\")\n\n        // Start with .noData as the baseline\n        let finalResult = AirshipAtomicValue(UABackgroundFetchResult.noData)\n        let wrappedUserInfo = self.safeWrap(userInfo: userInfo) ?? .null\n\n        await withTaskGroup(of: UABackgroundFetchResult.self) { taskGroup in\n            for component in pushableComponents {\n                taskGroup.addTask {\n                    return await component.receivedRemoteNotification(wrappedUserInfo)\n                }\n            }\n\n            for await result in taskGroup {\n                finalResult.update { $0.merge(result) }\n            }\n        }\n\n        // Get and merge the platform-specific fetch result\n        let fetchResult = await getFetchResult(userInfo, isForeground: isForeground)\n        finalResult.update { $0.merge(fetchResult) }\n\n        if let pushJSON = self.safeWrap(userInfo: userInfo) {\n            let situation: ActionSituation = isForeground ? .foregroundPush : .backgroundPush\n            let isForegroundPresentation = self.isForegroundPresentation(presentationOptions)\n\n            let metadata: [String: any Sendable] = [\n                ActionArguments.pushPayloadJSONMetadataKey: pushJSON,\n                ActionArguments.isForegroundPresentationMetadataKey: isForegroundPresentation\n            ]\n\n            await ActionRunner.run(\n                actionsPayload: pushJSON,\n                situation: situation,\n                metadata: metadata\n            )\n        }\n\n        return finalResult.value\n    }\n\n    @MainActor\n    private func getFetchResult(\n        _ userInfo: [AnyHashable: Any],\n        isForeground: Bool\n    ) async -> UABackgroundFetchResult {\n        return await self.push.didReceiveRemoteNotification(\n            userInfo,\n            isForeground: isForeground\n        )\n    }\n\n    // MARK: - Helpers\n\n    private func isForegroundPresentation(_ presentationOptions: UNNotificationPresentationOptions?) -> Bool {\n        guard var options = presentationOptions else { return false }\n        options.remove(.sound)\n        options.remove(.badge)\n        return options != []\n    }\n\n#if !os(tvOS)\n    private func situationFromAction(_ action: UNNotificationAction?) -> ActionSituation? {\n        guard let options = action?.options else { return nil }\n        return options.contains(.foreground) ? .foregroundInteractiveButton : .backgroundInteractiveButton\n    }\n\n    private func actionsPayloadForNotification(userInfo: [AnyHashable: Any], actionID: String?) -> AirshipJSON? {\n        guard let actionID = actionID, actionID != UNNotificationDefaultActionIdentifier else {\n            return try? AirshipJSON.wrap(userInfo)\n        }\n        let interactive = userInfo[\"com.urbanairship.interactive_actions\"] as? [AnyHashable: Any]\n        return try? AirshipJSON.wrap(interactive?[actionID])\n    }\n\n    @MainActor\n    private func notificationAction(categoryID: String, actionID: String) -> UNNotificationAction? {\n        guard actionID != UNNotificationDefaultActionIdentifier else { return nil }\n\n        let category = self.push.combinedCategories.first { $0.identifier == categoryID }\n        if category == nil {\n            AirshipLogger.error(\"Unknown notification category identifier \\(categoryID)\")\n            return nil\n        }\n        let action = category?.actions.first { $0.identifier == actionID }\n        if action == nil {\n            AirshipLogger.error(\"Unknown notification action identifier \\(actionID)\")\n        }\n        return action\n    }\n#endif\n\n    private func safeWrap(userInfo: [AnyHashable: Any]?) -> AirshipJSON? {\n        guard let userInfo = userInfo else { return nil }\n        if let json = try? AirshipJSON.wrap(userInfo) { return json }\n\n        var parsed: [String: AirshipJSON] = [:]\n        userInfo.forEach { (key, value) in\n            if let stringKey = key as? String, let jsonValue = try? AirshipJSON.wrap(value) {\n                parsed[stringKey] = jsonValue\n            }\n        }\n        return try? AirshipJSON.wrap(parsed)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeferredAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol DeferredAPIClientProtocol: Sendable {\n    func resolve(\n        url: URL,\n        channelID: String,\n        contactID: String?,\n        stateOverrides: AirshipStateOverrides,\n        audienceOverrides: ChannelAudienceOverrides,\n        triggerContext: AirshipTriggerContext?\n    ) async throws -> AirshipHTTPResponse<Data>\n}\n\nfinal class DeferredAPIClient: DeferredAPIClientProtocol {\n\n    func resolve(\n        url: URL,\n        channelID: String,\n        contactID: String?,\n        stateOverrides: AirshipStateOverrides,\n        audienceOverrides: ChannelAudienceOverrides,\n        triggerContext: AirshipTriggerContext?\n    ) async throws -> AirshipHTTPResponse<Data> {\n        var tagOverrides: TagGroupOverrides?\n        if (!audienceOverrides.tags.isEmpty) {\n            tagOverrides = TagGroupOverrides.from(updates: audienceOverrides.tags)\n        }\n\n        var attributeOverrides: [AttributeOperation]?\n        if (!audienceOverrides.attributes.isEmpty) {\n            attributeOverrides = audienceOverrides.attributes.map { $0.operation }\n        }\n\n        let body = RequestBody(\n            channelID: channelID,\n            contactID: contactID,\n            stateOverrides: stateOverrides,\n            triggerContext: triggerContext,\n            tagOverrides: tagOverrides,\n            attributeOverrides: attributeOverrides\n        )\n\n        let request = AirshipRequest(\n            url: url,\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n            ],\n            method: \"POST\",\n            auth: .channelAuthToken(identifier: channelID),\n            body: try JSONEncoder().encode(body)\n        )\n\n        AirshipLogger.trace(\"Resolving deferred with request \\(request) body \\(body)\")\n\n        return try await session.performHTTPRequest(request) { data, response in\n            \n            AirshipLogger.debug(\"Resolving deferred response finished with response: \\(response)\")\n\n            if (response.statusCode == 200) {\n                return data\n            }\n\n            return nil\n        }\n    }\n\n\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    fileprivate struct RequestBody: Encodable {\n        let platform: String = \"ios\"\n        let channelID: String\n        let contactID: String?\n        let stateOverrides: AirshipStateOverrides\n        let triggerContext: AirshipTriggerContext?\n        let tagOverrides: TagGroupOverrides?\n        let attributeOverrides: [AttributeOperation]?\n\n\n        enum CodingKeys: String, CodingKey {\n            case platform\n            case channelID = \"channel_id\"\n            case contactID = \"contact_id\"\n            case stateOverrides = \"state_overrides\"\n            case triggerContext = \"trigger\"\n            case tagOverrides = \"tag_overrides\"\n            case attributeOverrides = \"attribute_overrides\"\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeferredResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic enum AirshipDeferredResult<T : Sendable&Equatable>: Sendable, Equatable {\n    case success(T)\n    case timedOut\n    case outOfDate\n    case notFound\n    case retriableError(retryAfter: TimeInterval? = nil, statusCode: Int? = nil)\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic struct DeferredRequest: Sendable, Equatable {\n    public var url: URL\n    public var channelID: String\n    public var contactID: String?\n    var triggerContext: AirshipTriggerContext?\n    var locale: Locale\n    var notificationOptIn: Bool\n    var appVersion: String\n    var sdkVersion: String\n\n    public init(\n        url: URL,\n        channelID: String,\n        contactID: String? = nil,\n        triggerContext: AirshipTriggerContext? = nil,\n        locale: Locale,\n        notificationOptIn: Bool,\n        appVersion: String = AirshipUtils.bundleShortVersionString() ?? \"\",\n        sdkVersion: String = AirshipVersion.version\n    ) {\n        self.url = url\n        self.channelID = channelID\n        self.contactID = contactID\n        self.triggerContext = triggerContext\n        self.locale = locale\n        self.notificationOptIn = notificationOptIn\n        self.appVersion = appVersion\n        self.sdkVersion = sdkVersion\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol AirshipDeferredResolverProtocol : Sendable {\n    func resolve<T: Sendable>(\n        request: DeferredRequest,\n        resultParser: @escaping @Sendable (Data) async throws -> T\n    ) async -> AirshipDeferredResult<T>\n}\n\nactor AirshipDeferredResolver : AirshipDeferredResolverProtocol {\n\n    private final let audienceOverridesProvider: any AudienceOverridesProvider\n    private final let client: any DeferredAPIClientProtocol\n    private var locationMap: [URL: URL] = [:]\n    private var outdatedURLs: Set<URL> = Set()\n\n    init(\n        config: RuntimeConfig,\n        audienceOverrides: any AudienceOverridesProvider\n    ) {\n        self.init(\n            client: DeferredAPIClient(config: config),\n            audienceOverrides: audienceOverrides\n        )\n    }\n    \n    init(\n        client: any DeferredAPIClientProtocol,\n        audienceOverrides: any AudienceOverridesProvider\n    ) {\n        self.client = client\n        self.audienceOverridesProvider = audienceOverrides\n    }\n\n    func resolve<T: Sendable>(\n        request: DeferredRequest,\n        resultParser: @escaping @Sendable (Data) async throws -> T\n    ) async -> AirshipDeferredResult<T> {\n        let audienceOverrides = await audienceOverridesProvider.channelOverrides(\n            channelID: request.channelID,\n            contactID: request.contactID\n        )\n\n        let stateOverrides = AirshipStateOverrides(\n            appVersion: request.appVersion,\n            sdkVersion: request.sdkVersion,\n            notificationOptIn: request.notificationOptIn,\n            localeLangauge: request.locale.getLanguageCode(),\n            localeCountry: request.locale.getRegionCode()\n        )\n\n        return await resolve(\n            request: request,\n            stateOverrides: stateOverrides,\n            audienceOverrides: audienceOverrides,\n            resultParser: resultParser,\n            allowRetry: true\n        )\n    }\n\n    private func resolve<T: Sendable>(\n        request: DeferredRequest,\n        stateOverrides: AirshipStateOverrides,\n        audienceOverrides: ChannelAudienceOverrides,\n        resultParser: @escaping @Sendable (Data) async throws -> T,\n        allowRetry: Bool\n    ) async -> AirshipDeferredResult<T> {\n        let resolvedURL = self.locationMap[request.url] ?? request.url\n        AirshipLogger.trace(\"Resolving deferred \\(resolvedURL)\")\n\n        guard !outdatedURLs.contains(resolvedURL) else {\n            AirshipLogger.trace(\"Deferred out of date \\(resolvedURL)\")\n            return .outOfDate\n        }\n        \n        var result: AirshipHTTPResponse<Data>?\n        do {\n            result = try await client.resolve(\n                url: self.locationMap[request.url] ?? request.url,\n                channelID: request.channelID,\n                contactID: request.contactID,\n                stateOverrides: stateOverrides,\n                audienceOverrides: audienceOverrides,\n                triggerContext: request.triggerContext\n            )\n        } catch {\n            AirshipLogger.trace(\"Failed to resolve deferred: \\(resolvedURL) error: \\(error)\")\n        }\n\n        guard let result = result else {\n            AirshipLogger.trace(\"Resolving deferred timed out \\(resolvedURL)\")\n            return .timedOut\n        }\n        \n        AirshipLogger.trace(\"Resolving deferred result: \\(result)\")\n\n        switch (result.statusCode) {\n        case 200:\n            do {\n                guard let body = result.result else {\n                    return .retriableError(statusCode: result.statusCode)\n                }\n                let parsed = try await resultParser(body)\n\n                AirshipLogger.trace(\"Deferred result body: \\(parsed)\")\n\n                return .success(parsed)\n            } catch {\n                AirshipLogger.error(\"Failed to parse deferred body \\(error) with status code: \\(result.statusCode)\")\n                return .retriableError(statusCode: result.statusCode)\n            }\n        case 404: return .notFound\n        case 409:\n            outdatedURLs.insert(resolvedURL)\n            return .outOfDate\n        case 429:\n            if let location = result.locationHeader {\n                locationMap[request.url] = location\n            }\n            return .retriableError(retryAfter: result.retryAfter, statusCode: result.statusCode)\n        case 307:\n            if let location = result.locationHeader {\n                locationMap[request.url] = location\n\n                if let retry = result.retryAfter, retry > 0 {\n                    return .retriableError(retryAfter: retry, statusCode: result.statusCode)\n                }\n\n                if (allowRetry) {\n                    return await resolve(\n                        request: request,\n                        stateOverrides: stateOverrides,\n                        audienceOverrides: audienceOverrides,\n                        resultParser: resultParser,\n                        allowRetry: false\n                    )\n                }\n            }\n            return .retriableError(statusCode: result.statusCode)\n        default:\n            return .retriableError(statusCode: result.statusCode)\n        }\n    }\n}\n\nextension AirshipHTTPResponse {\n    var locationHeader: URL? {\n        guard let location = self.headers[\"Location\"] else {\n            return nil\n        }\n\n        return URL(string: location)\n    }\n\n    var retryAfter: TimeInterval? {\n        guard let retryAfter = self.headers[\"Retry-After\"] else {\n            return nil\n        }\n\n        if let seconds = Double(retryAfter) {\n            return seconds\n        }\n\n        return AirshipDateFormatter.date(fromISOString: retryAfter)?.timeIntervalSince1970\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeviceAudienceChecker.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol DeviceAudienceChecker: Sendable {\n    func evaluate(\n        audienceSelector: CompoundDeviceAudienceSelector?,\n        newUserEvaluationDate: Date,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> AirshipDeviceAudienceResult\n}\n\n/// NOTE: For internal use only. :nodoc:\nstruct DefaultDeviceAudienceChecker: DeviceAudienceChecker {\n    private let hashChecker: HashChecker\n\n    public init(cache: any AirshipCache) {\n        self.hashChecker = HashChecker(cache: cache)\n    }\n\n    public func evaluate(\n        audienceSelector: CompoundDeviceAudienceSelector?,\n        newUserEvaluationDate: Date,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> AirshipDeviceAudienceResult {\n        guard let audienceSelector else {\n            return .match\n        }\n\n        return try await audienceSelector.evaluate(\n            newUserEvaluationDate: newUserEvaluationDate,\n            deviceInfoProvider: deviceInfoProvider,\n            hashChecker: hashChecker\n        )\n    }\n}\n\n\nextension Array where Element == AirshipDeviceAudienceResult {\n\n    func reducedResult(reducer: (Bool, Bool) -> Bool) -> AirshipDeviceAudienceResult {\n        var isMatch: Bool?\n        var reportingMetadata: [AirshipJSON]? = nil\n\n        self.forEach {\n            isMatch = if let isMatch {\n                reducer(isMatch, $0.isMatch)\n            } else {\n                $0.isMatch\n            }\n\n            if let reporting = $0.reportingMetadata {\n                if (reportingMetadata == nil) {\n                    reportingMetadata = []\n                }\n                reportingMetadata?.append(contentsOf: reporting)\n            }\n        }\n\n        return AirshipDeviceAudienceResult(\n            isMatch: isMatch ?? true,\n            reportingMetadata: reportingMetadata\n        )\n    }\n}\n\nextension CompoundDeviceAudienceSelector {\n    func evaluate(\n        newUserEvaluationDate: Date = Date.distantPast,\n        deviceInfoProvider: any AudienceDeviceInfoProvider = DefaultAudienceDeviceInfoProvider(),\n        hashChecker: HashChecker\n    ) async throws -> AirshipDeviceAudienceResult {\n        switch self {\n        case .atomic(let audience):\n            return try await audience.evaluate(\n                newUserEvaluationDate: newUserEvaluationDate,\n                deviceInfoProvider: deviceInfoProvider,\n                hashChecker: hashChecker\n            )\n\n        case .not(let selector):\n            var result = try await selector.evaluate(\n                newUserEvaluationDate: newUserEvaluationDate,\n                deviceInfoProvider: deviceInfoProvider,\n                hashChecker: hashChecker\n            )\n\n            result.negate()\n            return result\n\n        case .and(let selectors):\n            guard !selectors.isEmpty else {\n                return AirshipDeviceAudienceResult.match\n            }\n\n            var results: [AirshipDeviceAudienceResult] = []\n            for selector in selectors {\n                let selectorResult = try await selector.evaluate(\n                    newUserEvaluationDate: newUserEvaluationDate,\n                    deviceInfoProvider: deviceInfoProvider,\n                    hashChecker: hashChecker\n                )\n                results.append(selectorResult)\n                if !selectorResult.isMatch {\n                    break\n                }\n            }\n            return results.reducedResult { first, second in first && second }\n\n        case .or(let selectors):\n            guard !selectors.isEmpty else {\n                return AirshipDeviceAudienceResult.miss\n            }\n\n            var results: [AirshipDeviceAudienceResult] = []\n            for selector in selectors {\n                let selectorResult = try await selector.evaluate(\n                    newUserEvaluationDate: newUserEvaluationDate,\n                    deviceInfoProvider: deviceInfoProvider,\n                    hashChecker: hashChecker\n                )\n\n                results.append(selectorResult)\n                if selectorResult.isMatch {\n                    break\n                }\n            }\n\n            return results.reducedResult { first, second in first || second }\n        }\n    }\n}\n\n\n/// NOTE: For internal use only. :nodoc:\nextension DeviceAudienceSelector {\n\n    func evaluate(\n        newUserEvaluationDate: Date = Date.distantPast,\n        deviceInfoProvider: any AudienceDeviceInfoProvider = DefaultAudienceDeviceInfoProvider(),\n        hashChecker: HashChecker\n    ) async throws -> AirshipDeviceAudienceResult {\n\n        AirshipLogger.trace(\"Evaluating audience conditions \\(self)\")\n\n        guard deviceInfoProvider.isAirshipReady else {\n            throw AirshipErrors.error(\"Airship not ready, unable to check audience\")\n        }\n\n        guard checkNewUser(deviceInfoProvider: deviceInfoProvider, newUserEvaluationDate: newUserEvaluationDate) else {\n            AirshipLogger.trace(\"Locale condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard checkDeviceTypes() else {\n            AirshipLogger.trace(\"Device type condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard checkLocale(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"Locale condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard await checkTags(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"Tags condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard try await checkTestDevices(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"Test device condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard try checkVersion(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"App version condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard checkAnalytics(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"Analytics condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard await checkNotificationOptIn(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"Notification opt-in condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        guard try await checkPermissions(deviceInfoProvider: deviceInfoProvider) else {\n            AirshipLogger.trace(\"Permission condition not met for audience: \\(self)\")\n            return .miss\n        }\n\n        let hashCheckerResult = try await hashChecker.evaluate(\n            hashSelector: self.hashSelector,\n            deviceInfoProvider: deviceInfoProvider\n        )\n\n        if !hashCheckerResult.isMatch {\n            AirshipLogger.trace(\"Hash condition not met for audience: \\(self)\")\n        }\n\n        return hashCheckerResult\n    }\n\n    private func checkNewUser(deviceInfoProvider: any AudienceDeviceInfoProvider, newUserEvaluationDate: Date) -> Bool {\n        guard let newUser = self.newUser else {\n            return true\n        }\n\n        return newUser == (deviceInfoProvider.installDate >= newUserEvaluationDate)\n    }\n\n    private func checkDeviceTypes() -> Bool {\n        return deviceTypes?.contains(\"ios\") ?? true\n    }\n\n    private func checkPermissions(deviceInfoProvider: any AudienceDeviceInfoProvider) async throws -> Bool {\n        guard self.permissionPredicate != nil || self.locationOptIn != nil else {\n            return true\n        }\n\n        let permissions = await deviceInfoProvider.permissions\n        if let permissionPredicate = self.permissionPredicate {\n            var map: [String: AirshipJSON] = [:]\n            for entry in permissions {\n                map[entry.key.rawValue] = .string(entry.value.rawValue)\n            }\n\n            guard permissionPredicate.evaluate(json: .object(map)) else {\n                return false\n            }\n        }\n\n        if let locationOptIn = self.locationOptIn {\n            let isLocationPermissionGranted = permissions[.location] == .granted\n            return locationOptIn == isLocationPermissionGranted\n        }\n\n        return true\n    }\n\n    private func checkAnalytics(deviceInfoProvider: any AudienceDeviceInfoProvider) -> Bool {\n        guard let requiresAnalytics = self.requiresAnalytics else {\n            return true\n        }\n\n        return requiresAnalytics == false || deviceInfoProvider.analyticsEnabled\n    }\n\n    private func checkVersion(deviceInfoProvider: any AudienceDeviceInfoProvider) throws -> Bool {\n        guard let versionPredicate = self.versionPredicate else {\n            return true\n        }\n\n        guard let appVersion = deviceInfoProvider.appVersion else {\n            AirshipLogger.trace(\"Unable to query app version for audience: \\(self)\")\n            return false\n        }\n\n        let versionObject: AirshipJSON = [\n            \"ios\": [\n                \"version\": .string(appVersion)\n            ]\n        ]\n        \n        return versionPredicate.evaluate(json: versionObject)\n    }\n\n\n    private func checkTags(deviceInfoProvider: any AudienceDeviceInfoProvider) async -> Bool {\n        guard let tagSelector = self.tagSelector else {\n            return true\n        }\n\n        return tagSelector.evaluate(tags: deviceInfoProvider.tags)\n    }\n\n    private func checkNotificationOptIn(deviceInfoProvider: any AudienceDeviceInfoProvider) async -> Bool {\n        guard let notificationOptIn = self.notificationOptIn else {\n            return true\n        }\n\n        return await deviceInfoProvider.isUserOptedInPushNotifications == notificationOptIn\n    }\n\n\n    private func checkTestDevices(deviceInfoProvider: any AudienceDeviceInfoProvider) async throws -> Bool {\n        guard let testDevices = self.testDevices else {\n            return true\n        }\n\n\n        guard deviceInfoProvider.isChannelCreated else {\n            return false\n        }\n\n        let channelID = try await deviceInfoProvider.channelID\n        let digest = AirshipUtils.sha256Digest(input: channelID).subdata(with: NSMakeRange(0, 16))\n        return testDevices.contains { testDevice in\n            AirshipBase64.data(from: testDevice) == digest\n        }\n    }\n\n    private func checkLocale(deviceInfoProvider: any AudienceDeviceInfoProvider) -> Bool {\n        guard let languageIDs = self.languageIDs else {\n            return true\n        }\n\n        let currentLocale = deviceInfoProvider.locale\n        return languageIDs.contains { languageID in\n            let locale = Locale(identifier: languageID)\n\n            if currentLocale.getLanguageCode() != locale.getLanguageCode() {\n                return false\n            }\n\n            if (!locale.getRegionCode().isEmpty && locale.getRegionCode() != currentLocale.getRegionCode()) {\n                return false\n            }\n\n            return true\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeviceAudienceSelector.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// A collection of properties defining an automation audience\npublic struct DeviceAudienceSelector: Sendable, Codable, Equatable {\n    var newUser: Bool?\n    var notificationOptIn: Bool?\n    var locationOptIn: Bool?\n    var languageIDs: [String]?\n    var tagSelector: DeviceTagSelector?\n    var requiresAnalytics: Bool?\n    var permissionPredicate: JSONPredicate?\n    var versionPredicate: JSONPredicate?\n    var testDevices: [String]?\n    var hashSelector: AudienceHashSelector?\n    var deviceTypes: [String]?\n\n    enum CodingKeys: String, CodingKey {\n        case newUser = \"new_user\"\n        case notificationOptIn = \"notification_opt_in\"\n        case locationOptIn = \"location_opt_in\"\n        case languageIDs = \"locale\"\n        case tagSelector = \"tags\"\n        case requiresAnalytics = \"requires_analytics\"\n        case permissionPredicate = \"permissions\"\n        case versionPredicate = \"app_version\"\n        case testDevices = \"test_devices\"\n        case hashSelector = \"hash\"\n        case deviceTypes = \"device_types\"\n    }\n\n\n    /// Audience selector initializer\n    /// - Parameters:\n    ///   - newUser: Flag indicating if audience consists of new users\n    ///   - notificationOptIn: Flag indicating if audience consists of users opted into notifications\n    ///   - locationOptIn: Flag indicating if audience consists of users that have opted into location\n    ///   - languageIDs: Array of language IDs representing a given audience\n    ///   - tagSelector: Internal-only selector\n    ///   - versionPredicate: Version predicate representing a given audience\n    ///   - requiresAnalytics: Flag indicating if audience consists of users that require analytics tracking\n    ///   - permissionPredicate: Flag indicating if audience consists of users that require certain permissions\n    ///   - testDevices:  Array of test device identifiers representing a given audience\n    ///   - hashSelector: Internal-only selector\n    ///   - deviceTypes: Array of device types representing a given audience\n    public init(\n        newUser: Bool? = nil,\n        notificationOptIn: Bool? = nil,\n        locationOptIn: Bool? = nil,\n        languageIDs: [String]? = nil,\n        tagSelector: DeviceTagSelector? = nil,\n        versionPredicate: JSONPredicate? = nil,\n        requiresAnalytics: Bool? = nil,\n        permissionPredicate: JSONPredicate? = nil,\n        testDevices: [String]? = nil,\n        hashSelector: AudienceHashSelector? = nil,\n        deviceTypes: [String]? = nil\n    ) {\n        self.newUser = newUser\n        self.notificationOptIn = notificationOptIn\n        self.locationOptIn = locationOptIn\n        self.languageIDs = languageIDs\n        self.tagSelector = tagSelector\n        self.versionPredicate = versionPredicate\n        self.requiresAnalytics = requiresAnalytics\n        self.permissionPredicate = permissionPredicate\n        self.testDevices = testDevices\n        self.hashSelector = hashSelector\n        self.deviceTypes = deviceTypes\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/DeviceTagSelector.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/*\n * <tag_selector>   := <tag> | <not> | <and> | <or>\n * <tag>            := { \"tag\": string }\n * <not>            := { \"not\": <tag_selector> }\n * <and>            := { \"and\": [<tag_selector>, <tag_selector>, ...] }\n * <or>             := { \"or\": [<tag_selector>, <tag_selector>, ...] }\n */\n\n/// NOTE: For internal use only. :nodoc:\npublic indirect enum DeviceTagSelector: Codable, Sendable, Equatable {\n    case or([DeviceTagSelector])\n    case not(DeviceTagSelector)\n    case and([DeviceTagSelector])\n    case tag(String)\n\n\n    public func evaluate(tags: Set<String>) -> Bool {\n        switch (self) {\n        case .tag(let tag):\n            return tags.contains(tag)\n        case .or(let selectors):\n            return selectors.contains { selector in\n                selector.evaluate(tags: tags)\n            }\n        case .not(let selector):\n            return !selector.evaluate(tags: tags)\n        case .and(let selectors):\n            return selectors.allSatisfy { selector in\n                selector.evaluate(tags: tags)\n            }\n        }\n    }\n\n    enum CodingKeys: CodingKey {\n        case or\n        case not\n        case and\n        case tag\n    }\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        var allKeys = ArraySlice(container.allKeys)\n        guard let selectorType = allKeys.popFirst(), allKeys.isEmpty else {\n            throw DecodingError.typeMismatch(\n                DeviceTagSelector.self,\n                DecodingError.Context(codingPath: container.codingPath, debugDescription: \"Invalid number of keys found, expected one.\", underlyingError: nil)\n            )\n        }\n\n        switch selectorType {\n        case .or:\n            self = .or(try container.decode([DeviceTagSelector].self, forKey: .or))\n        case .not:\n            self = .not(try container.decode(DeviceTagSelector.self, forKey: .not))\n        case .and:\n            self = .and(try container.decode([DeviceTagSelector].self, forKey: .and))\n        case .tag:\n            self = .tag(try container.decode(String.self, forKey: .tag))\n        }\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch self {\n        case .or(let selectors):\n            try container.encode(selectors, forKey: .or)\n        case .not(let selector):\n            try container.encode(selector, forKey: .not)\n        case .and(let selectors):\n            try container.encode(selectors, forKey: .and)\n        case .tag(let tag):\n            try container.encode(tag, forKey: .tag)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Dispatcher.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol UADispatcher: AnyObject, Sendable {\n    func doSync(_ block: @Sendable @escaping () -> Void)\n    func dispatchAsyncIfNecessary(_ block:  @Sendable @escaping () -> Void)\n    func dispatchAsync(_ block:  @Sendable  @escaping () -> Void)\n}\n\nfinal class DefaultDispatcher: UADispatcher, Sendable {\n    static let main: DefaultDispatcher = DefaultDispatcher(\n        queue: DispatchQueue.main\n    )\n\n    private static let dispatchKey: DispatchSpecificKey<DefaultDispatcher> = DispatchSpecificKey<DefaultDispatcher>()\n\n    private let queue: DispatchQueue\n\n    private init(queue: DispatchQueue) {\n        self.queue = queue\n        queue.setSpecific(key: DefaultDispatcher.dispatchKey, value: self)\n    }\n\n    class func serial(_ qos: DispatchQoS = .default) -> DefaultDispatcher {\n        let queue = DispatchQueue(\n            label: \"com.urbanairship.dispatcher.serial_queue\",\n            qos: qos\n        )\n        return DefaultDispatcher(queue: queue)\n    }\n\n    func doSync(_ block: @escaping () -> Void) {\n        if isCurrentQueue() {\n            block()\n        } else {\n            queue.sync(execute: block)\n        }\n    }\n\n    func dispatchAsyncIfNecessary(_ block: @Sendable @escaping () -> Void) {\n        if isCurrentQueue() {\n            block()\n        } else {\n            dispatchAsync(block)\n        }\n    }\n\n    func dispatchAsync(_ block: @Sendable @escaping () -> Void) {\n        queue.async(execute: block)\n    }\n\n    private func isCurrentQueue() -> Bool {\n        if DispatchQueue.getSpecific(key: DefaultDispatcher.dispatchKey) === self {\n            return true\n        } else if self === DefaultDispatcher.main && Thread.isMainThread {\n            return true\n        } else {\n            return false\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EmailRegistrationOptions.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Email registration options\npublic struct EmailRegistrationOptions: Codable, Sendable, Equatable, Hashable {\n\n    /**\n     * Transactional opted-in value\n     */\n    let transactionalOptedIn: Date?\n\n    /**\n     * Commercial opted-in value - used to determine the email opt-in state during double opt-in\n     */\n    let commercialOptedIn: Date?\n\n    /**\n     * Properties\n     */\n    let properties: AirshipJSON?\n\n    /**\n     * Double opt-in value\n     */\n    let doubleOptIn: Bool\n\n    private init(\n        transactionalOptedIn: Date?,\n        commercialOptedIn: Date? = nil,\n        properties: [String: Any]?,\n        doubleOptIn: Bool = false\n    ) {\n        self.transactionalOptedIn = transactionalOptedIn\n        self.commercialOptedIn = commercialOptedIn\n        self.properties = try? AirshipJSON.wrap(properties)\n        self.doubleOptIn = doubleOptIn\n    }\n\n    /// Returns an Email registration options with double opt-in value to false\n    /// - Parameter transactionalOptedIn: The transactional opted-in value\n    /// - Parameter commercialOptedIn: The commercial opted-in value\n    /// - Parameter properties: The properties. They must be JSON serializable.\n    /// - Returns: An Email registration options.\n    public static func commercialOptions(\n        transactionalOptedIn: Date?,\n        commercialOptedIn: Date?,\n        properties: [String: Any]?\n    ) -> EmailRegistrationOptions {\n        return EmailRegistrationOptions(\n            transactionalOptedIn: transactionalOptedIn,\n            commercialOptedIn: commercialOptedIn,\n            properties: properties\n        )\n    }\n\n    /// Returns an Email registration options.\n    /// - Parameter transactionalOptedIn: The transactional opted-in date.\n    /// - Parameter properties: The properties. They must be JSON serializable.\n    /// - Parameter doubleOptIn: The double opt-in value\n    /// - Returns: An Email registration options.\n    public static func options(\n        transactionalOptedIn: Date?,\n        properties: [String: Any]?,\n        doubleOptIn: Bool\n    ) -> EmailRegistrationOptions {\n        return EmailRegistrationOptions(\n            transactionalOptedIn: transactionalOptedIn,\n            properties: properties,\n            doubleOptIn: doubleOptIn\n        )\n    }\n\n    /// Returns an Email registration options.\n    /// - Parameter properties: The properties. They must be JSON serializable.\n    /// - Parameter doubleOptIn: The double opt-in value\n    /// - Returns: An Email registration options.\n    public static func options(\n        properties: [String: Any]?,\n        doubleOptIn: Bool\n    ) -> EmailRegistrationOptions {\n        return EmailRegistrationOptions(\n            transactionalOptedIn: nil,\n            properties: properties,\n            doubleOptIn: doubleOptIn\n        )\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case transactionalOptedIn\n        case commercialOptedIn\n        case properties = \"properties\"\n        case doubleOptIn\n    }\n\n\n    public init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.transactionalOptedIn = try container.decodeIfPresent(Date.self, forKey: .transactionalOptedIn)\n        self.commercialOptedIn = try container.decodeIfPresent(Date.self, forKey: .commercialOptedIn)\n\n\n        self.doubleOptIn = try container.decode(Bool.self, forKey: .doubleOptIn)\n\n        do {\n            self.properties = try container.decodeIfPresent(AirshipJSON.self, forKey: .properties)\n        } catch {\n            let legacy = try? container.decodeIfPresent(JsonValue.self, forKey: .properties)\n            guard let legacy = legacy else {\n                throw error\n            }\n\n            if let decoder = decoder as? JSONDecoder {\n                self.properties = try AirshipJSON.from(\n                    json: legacy.jsonEncodedValue,\n                    decoder: decoder\n                )\n            } else {\n                self.properties = try AirshipJSON.from(\n                    json: legacy.jsonEncodedValue\n                )\n            }\n        }\n    }\n\n\n    // Migration purposes\n    fileprivate struct JsonValue: Decodable {\n        let jsonEncodedValue: String?\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EmbeddedView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n@available(iOS 16, tvOS 16, watchOS 9.0, *)\nstruct AdoptLayout: SwiftUI.Layout {\n\n    let placement: ThomasPresentationInfo.Embedded.Placement\n\n    @Binding var viewConstraints: ViewConstraints?\n    let embeddedSize: AirshipEmbeddedSize?\n\n    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {\n        let viewSize = subviews.first?.sizeThatFits(proposal)\n\n        let height = size(\n            constraint: placement.size.height,\n            parent: self.embeddedSize?.parentHeight,\n            proposal: proposal.height,\n            sizeThataFits: viewSize?.height\n        )\n\n        let width = size(\n            constraint: placement.size.width,\n            parent: self.embeddedSize?.parentWidth,\n            proposal: proposal.width,\n            sizeThataFits: viewSize?.width\n        )\n\n\n        /// proposal.replacingUnspecifiedDimensions() uses `10`, so we shall as well\n        let size = CGSize(width: width ?? 10, height: height ?? 10)\n\n        let constraintWidth: CGFloat? = if placement.size.width.isAuto {\n            nil\n        } else {\n            size.width\n        }\n\n        let constraintHeight: CGFloat? = if placement.size.height.isAuto {\n            nil\n        } else {\n            size.height\n        }\n\n        let viewConstraints = ViewConstraints(\n            width: constraintWidth,\n            height: constraintHeight\n        )\n\n        DispatchQueue.main.async {\n            if (self.viewConstraints != viewConstraints) {\n                self.viewConstraints = viewConstraints\n            }\n        }\n\n        return size\n    }\n\n    private func size(constraint: ThomasSizeConstraint, parent: CGFloat?, proposal: CGFloat?, sizeThataFits: CGFloat? = nil) -> CGFloat? {\n        switch (constraint) {\n        case .auto:\n            return sizeThataFits ?? proposal\n        case .percent(let percent):\n            if let parent = parent {\n                return parent * percent/100.0\n            }\n            return proposal ?? sizeThataFits\n        case .points(let size):\n            return size\n        }\n    }\n\n    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {\n        let viewProposal = ProposedViewSize(\n            width: size(\n                constraint: placement.size.width,\n                parent: embeddedSize?.parentWidth ?? bounds.width,\n                proposal: proposal.width\n            ),\n            height: size(\n                constraint: placement.size.height,\n                parent: embeddedSize?.parentHeight ?? bounds.height,\n                proposal: proposal.height\n            )\n        )\n\n        let center = CGPoint(x: bounds.midX, y: bounds.midY)\n        subviews.forEach { layout in\n            layout.place(at: center, anchor: .center, proposal: viewProposal)\n        }\n    }\n}\n\nstruct EmbeddedView: View {\n    private let presentation: ThomasPresentationInfo.Embedded\n    private let layout: AirshipLayout\n    private let thomasEnvironment: ThomasEnvironment\n\n    private let embeddedSize: AirshipEmbeddedSize?\n    @State private var viewConstraints: ViewConstraints?\n    @Environment(\\.isVoiceOverRunning) private var isVoiceOverRunning\n\n    init(\n        presentation: ThomasPresentationInfo.Embedded,\n        layout: AirshipLayout,\n        thomasEnvironment: ThomasEnvironment,\n        embeddedSize: AirshipEmbeddedSize?\n    ) {\n        self.presentation = presentation\n        self.layout = layout\n        self.thomasEnvironment = thomasEnvironment\n        self.embeddedSize = embeddedSize\n    }\n\n    var body: some View {\n        RootView(thomasEnvironment: thomasEnvironment, layout: layout) { orientation, windowSize in\n            let placement = resolvePlacement(\n                orientation: orientation,\n                windowSize: windowSize\n            )\n\n            AdoptLayout(placement: placement, viewConstraints: $viewConstraints, embeddedSize: embeddedSize) {\n                if let constraints = viewConstraints {\n                    createView(constraints: constraints, placement: placement)\n                } else {\n                    Color.clear\n                }\n            }\n        }\n\n#if os(macOS)\n        .onAppear {\n            if isVoiceOverRunning {\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n                    NSAccessibility.post(\n                        element: (NSApp.mainWindow ?? NSApp) as Any,\n                        notification: .layoutChanged\n                    )\n                }\n            }\n        }\n#elseif !os(watchOS)\n        // iOS, tvOS, visionOS\n        .onAppear {\n            if isVoiceOverRunning {\n                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {\n                    UIAccessibility.post(notification: .screenChanged, argument: nil)\n                }\n            }\n        }\n#endif\n    }\n\n    @MainActor\n    private func createView(\n        constraints: ViewConstraints,\n        placement: ThomasPresentationInfo.Embedded.Placement\n    ) -> some View {\n        return ViewFactory\n            .createView(layout.view, constraints: constraints)\n            .thomasBackground(\n                color: placement.backgroundColor,\n                border: placement.border\n            )\n            .margin(placement.margin)\n            .constraints(constraints)\n    }\n\n    private func resolvePlacement(\n        orientation: ThomasOrientation,\n        windowSize: ThomasWindowSize\n    ) -> ThomasPresentationInfo.Embedded.Placement {\n        var placement = self.presentation.defaultPlacement\n        for placementSelector in self.presentation.placementSelectors ?? [] {\n            if placementSelector.windowSize != nil\n                && placementSelector.windowSize != windowSize\n            {\n                continue\n            }\n\n            if placementSelector.orientation != nil\n                && placementSelector.orientation != orientation\n            {\n                continue\n            }\n\n            // its a match!\n            placement = placementSelector.placement\n        }\n\n        return placement\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EmbeddedViewSelector.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@MainActor\nfinal class EmbeddedViewSelector {\n    @MainActor\n    static let shared: EmbeddedViewSelector = EmbeddedViewSelector()\n\n    private var lastDisplayed: [String: String] = [:]\n\n    func onViewDisplayed(_ info: AirshipEmbeddedInfo) {\n        AirshipLogger.trace(\"Updating last displayed for \\(info.embeddedID): \\(info.instanceID)\")\n        lastDisplayed[info.embeddedID] = info.instanceID\n    }\n\n    func selectView(embeddedID: String, views: [AirshipEmbeddedContentView]) -> AirshipEmbeddedContentView? {\n        guard\n            let lastInstanceID = lastDisplayed[embeddedID],\n            let last = views.first(where: { view in\n                view.embeddedInfo.instanceID == lastInstanceID\n            })\n        else {\n            let view =  views.sorted(by: { f, s in\n                f.embeddedInfo.priority < s.embeddedInfo.priority\n            }).first\n\n            if let view {\n                AirshipLogger.trace(\"Selecting priority sorted view for \\(embeddedID): \\(view.embeddedInfo)\")\n            }\n\n            return view\n        }\n        \n        AirshipLogger.trace(\"Selecting previously displayed view for \\(embeddedID): \\(last.embeddedInfo.instanceID)\")\n        return last\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EmptyAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Action that produces an empty result.\npublic final class EmptyAction: AirshipAction {\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        return true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EmptyView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Empty View\n\nstruct AirshipEmptyView: View {\n\n    let info: ThomasViewInfo.EmptyView\n    let constraints: ViewConstraints\n\n    var body: some View {\n        Color.clear\n            .constraints(constraints)\n            .thomasCommon(self.info)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EnableBehaviorModifiers.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\ninternal struct FormSubmissionEnableBehavior: ViewModifier {\n    let onApply: ((Bool, ThomasEnableBehavior) -> Void)?\n\n    @EnvironmentObject var formState: ThomasFormState\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        if let onApply = onApply {\n            content.onReceive(self.formState.$status) { value in\n                onApply(value != .submitted, .formSubmission)\n            }\n        } else {\n            content.disabled(formState.status == .submitted)\n        }\n    }\n}\n\ninternal struct ValidFormButtonEnableBehavior: ViewModifier {\n    let onApply: ((Bool, ThomasEnableBehavior) -> Void)?\n\n    @EnvironmentObject var formState: ThomasFormState\n    @Environment(\\.isVisible) private var isVisible\n\n    @State var isEnabled: Bool?\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        if isVisible {\n            content.airshipOnChangeOf(\n                self.formState.status,\n                initial: true\n            ) { status in\n                let isEnabled = switch(formState.validationMode) {\n                case .onDemand:\n                    switch(status) {\n                    case .error, .valid, .pendingValidation: true\n                    case .invalid, .validating, .submitted: false\n                    }\n                case .immediate:\n                    switch(status) {\n                    case .error, .valid: true\n                    case .pendingValidation, .invalid, .validating, .submitted: false\n                    }\n                }\n\n                if let onApply = onApply {\n                    onApply(!isEnabled, .formValidation)\n                } else {\n                    DispatchQueue.main.async {\n                        self.isEnabled = isEnabled\n                    }\n                }\n            }\n            .disabled(isEnabled == false)\n        } else {\n            content\n        }\n    }\n\n}\n\ninternal struct PagerNextButtonEnableBehavior: ViewModifier {\n    let onApply: ((Bool, ThomasEnableBehavior) -> Void)?\n\n    @EnvironmentObject var pagerState: PagerState\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        if let onApply = onApply {\n            content.airshipOnChangeOf(pagerState.canGoForward, initial: true) { canGoForward in\n                onApply(canGoForward, .pagerNext)\n            }\n        } else {\n            content.disabled(!pagerState.canGoForward)\n        }\n    }\n}\n\nstruct PagerPreviousButtonEnableBehavior: ViewModifier {\n    let onApply: ((Bool, ThomasEnableBehavior) -> Void)?\n\n    @EnvironmentObject var pagerState: PagerState\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        if let onApply = onApply {\n            content.airshipOnChangeOf(pagerState.canGoBack, initial: true) { canGoBack in\n                onApply(canGoBack, .pagerPrevious)\n            }\n        } else {\n            content.disabled(!pagerState.canGoBack)\n        }\n    }\n}\n\ninternal struct AggregateEnableBehavior: ViewModifier {\n    let behaviors: [ThomasEnableBehavior]\n    let onApply: ((Bool) -> Void)\n\n    @State private var enabledBehaviors: [ThomasEnableBehavior: Bool] = [:]\n    @State private var enabled: Bool?\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        content.addBehaviorModifiers(behaviors) { behaviorEnabled, behavior in\n            enabledBehaviors[behavior] = behaviorEnabled\n            let updated = !enabledBehaviors.contains(where: { _, value in\n                !value\n            })\n\n            if updated != enabled {\n                enabled = updated\n                onApply(updated)\n            }\n        }\n    }\n}\n\nextension View {\n    @ViewBuilder\n    fileprivate func addBehaviorModifiers(\n        _ behaviors: [ThomasEnableBehavior]?,\n        onApply: ((Bool, ThomasEnableBehavior) -> Void)? = nil\n    ) -> some View {\n        if let behaviors = behaviors {\n            self.viewModifiers {\n                if behaviors.contains(.formValidation) {\n                    ValidFormButtonEnableBehavior(onApply: onApply)\n                }\n\n                if behaviors.contains(.pagerNext) {\n                    PagerNextButtonEnableBehavior(onApply: onApply)\n                }\n\n                if behaviors.contains(.pagerPrevious) {\n                    PagerPreviousButtonEnableBehavior(onApply: onApply)\n                }\n\n                if behaviors.contains(.formSubmission) {\n                    FormSubmissionEnableBehavior(onApply: onApply)\n                }\n            }\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func thomasEnableBehaviors(\n        _ behaviors: [ThomasEnableBehavior]?,\n        onApply: @escaping (Bool) -> Void\n    ) -> some View {\n\n        if let behaviors = behaviors {\n            self.modifier(\n                AggregateEnableBehavior(\n                    behaviors: behaviors,\n                    onApply: onApply\n                )\n            )\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EnableFeatureAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Enables an Airship feature.\n///\n/// Expected argument values:\n/// - \"user_notifications\": To enable user notifications.\n/// - \"location\": To enable location updates.\n/// - \"background_location\": To enable location and allow background updates.\n///\n/// Valid situations:  `ActionSituation.launchedFromPush`,\n/// `ActionSituation.webViewInvocation`, `ActionSituation.manualInvocation`,\n/// `ActionSituation.foregroundInteractiveButton`, and `ActionSituation.automation`\npublic final class EnableFeatureAction: AirshipAction {\n    /// Default names - \"enable_feature\", \"^ef\"\n    public static let defaultNames: [String] = [\"enable_feature\", \"^ef\"]\n    \n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n\n    /// Metadata key for a block that takes the permission results`(PermissionStatus, PermissionStatus) -> Void`.\n    /// - Note: For internal use only. :nodoc:\n    public static let resultReceiverMetadataKey: String = PromptPermissionAction\n        .resultReceiverMetadataKey\n\n    public static let userNotificationsActionValue: String = \"user_notifications\"\n    public static let locationActionValue: String = \"location\"\n    public static let backgroundLocationActionValue: String = \"background_location\"\n\n    private let permissionPrompter: @Sendable () -> any PermissionPrompter\n\n\n    public convenience init() {\n           self.init {\n               return AirshipPermissionPrompter(\n                   permissionsManager: Airship.permissionsManager\n               )\n           }\n       }\n    \n    required init(permissionPrompter: @escaping @Sendable () -> any PermissionPrompter) {\n        self.permissionPrompter = permissionPrompter\n    }\n    \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .automation, .manualInvocation, .launchedFromPush,\n            .webViewInvocation,\n            .foregroundPush, .foregroundInteractiveButton:\n            return (try? self.parsePermission(arguments: arguments)) != nil\n        case .backgroundPush: fallthrough\n        case .backgroundInteractiveButton: fallthrough\n        @unknown default:\n            return false\n        }\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let permission = try parsePermission(arguments: arguments)\n\n        let result = await self.permissionPrompter()\n            .prompt(\n                permission: permission,\n                enableAirshipUsage: true,\n                fallbackSystemSettings: true\n            )\n\n        let resultReceiver = arguments.metadata[\n            EnableFeatureAction.resultReceiverMetadataKey\n        ] as? PermissionResultReceiver\n\n        await resultReceiver?(permission, result.startStatus, result.endStatus)\n\n        return nil\n    }\n\n    private func parsePermission(\n        arguments: ActionArguments\n    ) throws -> AirshipPermission {\n        let unwrapped = arguments.value.unWrap()\n        let value = unwrapped as? String ?? \"\"\n        switch value {\n        case EnableFeatureAction.userNotificationsActionValue:\n            return .displayNotifications\n        case EnableFeatureAction.locationActionValue:\n            return .location\n        case EnableFeatureAction.backgroundLocationActionValue:\n            return .location\n        default:\n            throw AirshipErrors.error(\"Invalid argument \\(value)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EnvironmentValues.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nprivate struct OrientationKey: EnvironmentKey {\n    static let defaultValue: ThomasOrientation? = nil\n}\n\nprivate struct WindowSizeKey: EnvironmentKey {\n    static let defaultValue: ThomasWindowSize? = nil\n}\n\nprivate struct VoiceOverRunningKey: EnvironmentKey {\n    static let defaultValue: Bool = false\n}\n\nprivate struct VisibleEnvironmentKey: EnvironmentKey {\n    static let defaultValue: Bool = false\n}\n\nprivate struct ButtonActionsEnabledKey: EnvironmentKey {\n    static let defaultValue: Bool = true\n}\n\nprivate struct PageIdentifierKey: EnvironmentKey {\n    static let defaultValue: String? = nil\n}\n\nprivate struct ThomasAssociatedLabelResolverKey: EnvironmentKey {\n    static let defaultValue: ThomasAssociatedLabelResolver? = nil\n}\n\nprivate struct LayoutStateEnvironmentKey: EnvironmentKey {\n    static let defaultValue: LayoutState = LayoutState.empty\n}\n\nextension EnvironmentValues {\n    var orientation: ThomasOrientation? {\n        get { self[OrientationKey.self] }\n        set { self[OrientationKey.self] = newValue }\n    }\n\n    var windowSize: ThomasWindowSize? {\n        get { self[WindowSizeKey.self] }\n        set { self[WindowSizeKey.self] = newValue }\n    }\n\n    var isVoiceOverRunning: Bool {\n        get { self[VoiceOverRunningKey.self] }\n        set { self[VoiceOverRunningKey.self] = newValue }\n    }\n\n    var isVisible: Bool {\n        get { self[VisibleEnvironmentKey.self] }\n        set { self[VisibleEnvironmentKey.self] = newValue }\n    }\n\n    var isButtonActionsEnabled: Bool {\n        get { self[ButtonActionsEnabledKey.self] }\n        set { self[ButtonActionsEnabledKey.self] = newValue }\n    }\n\n    var pageIdentifier: String? {\n        get { self[PageIdentifierKey.self] }\n        set { self[PageIdentifierKey.self] = newValue }\n    }\n\n    var thomasAssociatedLabelResolver: ThomasAssociatedLabelResolver? {\n        get { self[ThomasAssociatedLabelResolverKey.self] }\n        set { self[ThomasAssociatedLabelResolverKey.self] = newValue }\n    }\n\n\n    internal var layoutState: LayoutState {\n        get { self[LayoutStateEnvironmentKey.self] }\n        set { self[LayoutStateEnvironmentKey.self] = newValue }\n    }\n}\n\nextension View {\n    func setVisible(_ visible: Bool) -> some View {\n        environment(\\.isVisible, visible)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\nimport Foundation\n\nprotocol EventAPIClientProtocol: Sendable {\n    func uploadEvents(\n        _ events: [AirshipEventData],\n        channelID: String,\n        headers: [String: String]\n    ) async throws -> AirshipHTTPResponse<EventUploadTuningInfo>\n}\n\nfinal class EventAPIClient: EventAPIClientProtocol {\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    func uploadEvents(\n        _ events: [AirshipEventData],\n        channelID: String,\n        headers: [String: String]\n    ) async throws -> AirshipHTTPResponse<EventUploadTuningInfo> {\n\n        guard let analyticsURL = config.analyticsURL else {\n            throw AirshipErrors.error(\"The analyticsURL is nil\")\n        }\n\n        var allHeaders = headers\n        allHeaders[\"X-UA-Sent-At\"] = \"\\(Date().timeIntervalSince1970)\"\n        allHeaders[\"Content-Type\"] = \"application/json\"\n\n        let request = AirshipRequest(\n            url: URL(string: \"\\(analyticsURL)/warp9/\"),\n            headers: allHeaders,\n            method: \"POST\",\n            auth: .channelAuthToken(identifier: channelID),\n            body: try self.requestBody(fromEvents: events),\n            contentEncoding: .deflate\n        )\n\n        AirshipLogger.trace(\"Sending to server: \\(config.analyticsURL ?? \"\")\")\n        AirshipLogger.trace(\"Sending analytics headers: \\(allHeaders)\")\n        AirshipLogger.trace(\"Sending analytics events: \\(events)\")\n\n        // Perform the upload\n        return try await self.session.performHTTPRequest(request) { _, response in\n            \n            AirshipLogger.debug(\"Upload event finished with response: \\(response)\")\n            \n            return EventUploadTuningInfo(\n                maxTotalStoreSizeKB: response.unsignedInt(\n                    forHeader: \"X-UA-Max-Total\"\n                ),\n                maxBatchSizeKB: response.unsignedInt(\n                    forHeader: \"X-UA-Max-Batch\"\n                ),\n                minBatchInterval: response.double(\n                    forHeader: \"X-UA-Min-Batch-Interval\"\n                )\n            )\n        }\n    }\n\n    private func requestBody(fromEvents events: [AirshipEventData]) throws -> Data {\n        let preparedEvents: [[String: Any]] = events.compactMap { eventData in\n            var eventBody: [String: Any] = [:]\n            eventBody[\"event_id\"] =  eventData.id\n            eventBody[\"time\"] = String(\n                format: \"%f\",\n                eventData.date.timeIntervalSince1970\n            )\n            eventBody[\"type\"] = eventData.type.reportingName\n            \n        \n            guard\n                var data = eventData.body.unWrap() as? [String: Any]\n            else {\n                AirshipLogger.error(\"Failed to deserialize event body \\(eventData)\")\n                return nil\n            }\n            \n            data[\"session_id\"] = eventData.sessionID\n            eventBody[\"data\"] = data\n            return eventBody\n        }\n\n        return try AirshipJSONUtils.data(preparedEvents, options: [])\n    }\n}\n\n\nfileprivate extension HTTPURLResponse {\n    func double(forHeader header: String) -> Double? {\n        guard let value = self.allHeaderFields[header] else {\n            return nil\n        }\n\n        if let value = value as? Double {\n            return value\n        }\n\n        if let value = value as? String {\n            return Double(value)\n        }\n\n        if let value = value as? NSNumber {\n            return value.doubleValue\n        }\n\n        return nil\n    }\n\n    func unsignedInt(forHeader header: String) -> UInt? {\n        guard let value = self.allHeaderFields[header] else {\n            return nil\n        }\n\n        if let value = value as? UInt {\n            return value\n        }\n\n        if let value = value as? String {\n            return UInt(value)\n        }\n\n        if let value = value as? NSNumber {\n            return value.uintValue\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventHandlerViewModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n\ninternal struct EventHandlerViewModifier: ViewModifier {\n    @EnvironmentObject var thomasEnvironment: ThomasEnvironment\n    @EnvironmentObject var thomasState: ThomasState\n    @EnvironmentObject var formState: ThomasFormState\n    @EnvironmentObject var pagerState: PagerState\n\n    @Environment(\\.layoutState) private var layoutState\n\n    let eventHandlers: [ThomasEventHandler]\n    let formInputID: String?\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        let types = eventHandlers.map { $0.type }\n\n        content.airshipApplyIf(types.contains(.tap)) { view in\n            view.addTapGesture {\n                handleEvent(type: .tap)\n            }\n        }\n        .airshipApplyIf(types.contains(.formInput)) { view in\n            if let formInputID {\n                view.airshipOnChangeOf(self.formState.field(identifier: formInputID)?.input) { input in\n                    handleEvent(type: .formInput, formFieldValue: input)\n                }\n\n            }\n        }\n    }\n\n    private func handleEvent(\n        type: ThomasEventHandler.EventType,\n        formFieldValue: ThomasFormField.Value? = nil\n    ) {\n        let handlers = eventHandlers.filter { $0.type == type }\n\n        // Process\n        handlers.forEach { handler in\n            handleStateAction(handler.stateActions, formFieldValue: formFieldValue)\n        }\n    }\n\n    private func handleStateAction(\n        _ stateActions: [ThomasStateAction],\n        formFieldValue: ThomasFormField.Value?\n    ) {\n        thomasState.processStateActions(stateActions, formFieldValue: formFieldValue)\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventManager.swift",
    "content": "import Foundation\n\nprotocol EventManagerProtocol: AnyObject, Sendable {\n    var uploadsEnabled: Bool { get set }\n    func addEvent(_ event: AirshipEventData) async throws\n    func deleteEvents() async throws\n    func scheduleUpload(eventPriority: AirshipEventPriority) async\n\n    @MainActor\n    func addHeaderProvider(\n        _ headerProvider: @Sendable @escaping () async -> [String: String]\n    )\n}\n\nfinal class EventManager: EventManagerProtocol {\n\n    private let headerBlocks: AirshipMainActorValue<[@Sendable () async -> [String: String]]> = AirshipMainActorValue([])\n\n    private let _uploadsEnabled: AirshipAtomicValue<Bool> = AirshipAtomicValue<Bool>(false)\n    var uploadsEnabled: Bool  {\n        get {\n            _uploadsEnabled.value\n        }\n        set {\n            _uploadsEnabled.value = newValue\n        }\n    }\n\n    private let eventStore: EventStore\n    private let eventAPIClient: any EventAPIClientProtocol\n    private let eventScheduler: any EventUploadSchedulerProtocol\n    private let state: EventManagerState\n    private let channel: any AirshipChannel\n\n    @MainActor\n    convenience init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel\n    ) {\n        self.init(\n            dataStore: dataStore,\n            channel: channel,\n            eventStore: EventStore(appKey: config.appCredentials.appKey),\n            eventAPIClient: EventAPIClient(config: config)\n        )\n    }\n\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        channel: any AirshipChannel,\n        eventStore: EventStore,\n        eventAPIClient: any EventAPIClientProtocol,\n        eventScheduler: (any EventUploadSchedulerProtocol)? = nil\n    ) {\n        self.channel = channel\n        self.eventStore = eventStore\n        self.eventAPIClient = eventAPIClient\n        self.eventScheduler = eventScheduler ?? EventUploadScheduler()\n        self.state = EventManagerState(dataStore: dataStore)\n\n        Task {\n            await self.eventScheduler.setWorkBlock { [weak self] in\n                try await self?.uploadEvents() ?? .success\n            }\n        }\n    }\n\n    func addEvent(_ event: AirshipEventData) async throws {\n        try await self.eventStore.save(event: event)\n    }\n\n    func deleteEvents() async throws {\n        try await self.eventStore.deleteAllEvents()\n    }\n\n    @MainActor\n    func addHeaderProvider(\n        _ headerProvider: @Sendable @escaping () async -> [String : String]\n    ) {\n        self.headerBlocks.update { $0.append(headerProvider) }\n    }\n\n    func scheduleUpload(eventPriority: AirshipEventPriority) async {\n        guard self.uploadsEnabled else { return }\n\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: eventPriority,\n            minBatchInterval: await self.state.minBatchInterval\n        )\n    }\n\n    private func uploadEvents() async throws -> AirshipWorkResult {\n        guard self.uploadsEnabled else {\n            return .success\n        }\n\n        guard let channelID = channel.identifier else {\n            return .success\n        }\n\n        let events = try await self.prepareEvents()\n        guard !events.isEmpty else {\n            AirshipLogger.trace(\n                \"Analytic upload finished, no events to upload.\"\n            )\n            return .success\n        }\n\n        let headers = await self.prepareHeaders()\n        let response = try await self.eventAPIClient.uploadEvents(\n            events,\n            channelID: channelID,\n            headers: headers\n        )\n\n        guard response.isSuccess else {\n            AirshipLogger.trace(\n                \"Analytics upload request failed with status: \\(response.statusCode)\"\n            )\n            return .failure\n        }\n\n        AirshipLogger.trace(\"Analytic upload success\")\n        try await self.eventStore.deleteEvents(\n            eventIDs: events.map { event in\n                return event.id\n            }\n        )\n\n        await self.state.updateTuniningInfo(response.result)\n\n        if (try? await self.eventStore.hasEvents()) == true {\n            await self.scheduleUpload(eventPriority: .normal)\n        }\n\n        return .success\n    }\n\n    private func prepareEvents() async throws -> [AirshipEventData] {\n        do {\n            try await self.eventStore.trimEvents(\n                maxStoreSizeKB: await self.state.maxTotalStoreSizeKB\n            )\n        } catch {\n            AirshipLogger.warn(\"Unable to trim database: \\(error)\")\n        }\n\n        return try await self.eventStore.fetchEvents(\n            maxBatchSizeKB: await self.state.maxBatchSizeKB\n        )\n    }\n\n    @MainActor\n    private func prepareHeaders() async -> [String: String] {\n        let providers = self.headerBlocks.value\n\n        var allHeaders: [String: String] = [:]\n        for headerBlock in providers {\n            let headers = await headerBlock()\n            allHeaders.merge(headers) { (_, new) in\n                AirshipLogger.warn(\"Analytic header merge conflict \\(new)\")\n                return new\n            }\n        }\n        return allHeaders\n    }\n}\n\nfileprivate actor EventManagerState {\n\n    // Max database size\n    private static let maxTotalDBSizeKB: UInt = 5120\n    private static let minTotalDBSizeKB: UInt = 10\n    private static let tuninigInfoDefaultsKey: String = \"Analytics.tuningInfo\"\n\n    // Total size in bytes that a given event post is allowed to send.\n    private static let maxBatchSizeKB: UInt = 500\n    private static let minBatchSizeKB: UInt = 10\n\n    // The actual amount of time in seconds that elapse between event-server posts\n    private static let minBatchInterval: TimeInterval = 60\n    private static let maxBatchInterval: TimeInterval = 604800 // 7 days\n\n    private var _tuningInfo: EventUploadTuningInfo?\n    private var tuningInfo: EventUploadTuningInfo? {\n        get {\n            if let tuningInfo = self._tuningInfo {\n                return tuningInfo\n            }\n\n            self._tuningInfo = try? self.dataStore.codable(\n                forKey: EventManagerState.tuninigInfoDefaultsKey\n            )\n\n            return self._tuningInfo\n        }\n\n        set {\n            self._tuningInfo = newValue\n            try? self.dataStore.setCodable(\n                newValue,\n                forKey: EventManagerState.tuninigInfoDefaultsKey\n            )\n        }\n    }\n\n\n    var minBatchInterval: TimeInterval {\n        Self.clamp(\n            self.tuningInfo?.minBatchInterval ?? EventManagerState.minBatchInterval,\n            min: EventManagerState.minBatchInterval,\n            max: EventManagerState.maxBatchInterval\n        )\n    }\n\n    var maxTotalStoreSizeKB: UInt {\n        Self.clamp(\n            self.tuningInfo?.maxTotalStoreSizeKB ?? EventManagerState.maxTotalDBSizeKB,\n            min: EventManagerState.minTotalDBSizeKB,\n            max: EventManagerState.maxTotalDBSizeKB\n        )\n    }\n\n    var maxBatchSizeKB: UInt {\n        Self.clamp(\n            self.tuningInfo?.maxBatchSizeKB ?? EventManagerState.maxBatchSizeKB,\n            min: EventManagerState.minBatchSizeKB,\n            max: EventManagerState.maxBatchSizeKB\n        )\n    }\n\n    let dataStore: PreferenceDataStore\n\n    init(dataStore: PreferenceDataStore) {\n        self.dataStore = dataStore\n    }\n\n    func updateTuniningInfo(_ tuningInfo: EventUploadTuningInfo?) {\n        self.tuningInfo = tuningInfo\n    }\n\n\n    static func clamp<T>(_ value: T, min: T, max: T) -> T where T: Comparable {\n        if value < min {\n            return min\n        }\n\n        if value > max {\n            return max\n        }\n\n        return value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\nimport Foundation\n\nactor EventStore {\n    private static let eventDataEntityName: String = \"UAEventData\"\n    private static let fetchEventLimit: Int = 500\n\n    private var coreData: UACoreData\n    private var storeName: String?\n    private nonisolated let inMemory: Bool\n\n    init(appKey: String, inMemory: Bool = false) {\n        self.inMemory = inMemory\n        let modelURL = AirshipCoreResources.bundle.url(\n            forResource: \"UAEvents\",\n            withExtension: \"momd\"\n        )\n        self.coreData = UACoreData(\n            name: Self.eventDataEntityName,\n            modelURL: modelURL!,\n            inMemory: inMemory,\n            stores: [\"Events-\\(appKey).sqlite\"]\n        )\n    }\n\n    func save(\n        event: AirshipEventData\n    ) async throws {\n        try await self.coreData.perform { context in\n            try self.saveEvent(event: event, context: context)\n        }\n    }\n\n    func fetchEvents(\n        maxBatchSizeKB: UInt\n    ) async throws -> [AirshipEventData] {\n        return try await self.coreData.performWithResult { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: EventStore.eventDataEntityName\n            )\n            request.fetchLimit = EventStore.fetchEventLimit\n            request.sortDescriptors = [\n                NSSortDescriptor(key: \"storeDate\", ascending: true)\n            ]\n\n            let fetchResult = try context.fetch(request) as? [EventData] ?? []\n            let batchSizeBytesLimit = maxBatchSizeKB * 1024\n            var batchSize = 0\n            var events: [AirshipEventData] = []\n            for eventData in fetchResult {\n                let bytes = eventData.bytes?.intValue ?? 0\n                if ((batchSize + bytes) > batchSizeBytesLimit) {\n                    break\n                }\n\n                do {\n                    events.append(\n                        try self.convert(internalEventData: eventData)\n                    )\n                    batchSize += bytes\n                } catch {\n                    AirshipLogger.error(\"Unable to read event, deleting. \\(error)\")\n                    context.delete(eventData)\n                }\n            }\n            return events\n        }\n    }\n\n    func hasEvents() async throws -> Bool {\n        return try await self.coreData.performWithResult { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: EventStore.eventDataEntityName\n            )\n            return try context.count(for: request) > 0\n        }\n    }\n\n    func deleteEvents(eventIDs: [String]) async throws {\n        try await self.coreData.perform { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: EventStore.eventDataEntityName\n            )\n            \n            request.predicate = NSPredicate(\n                format: \"identifier IN %@\",\n                eventIDs\n            )\n            \n            do {\n                if self.inMemory {\n                    request.includesPropertyValues = false\n                    let events = try context.fetch(request) as? [NSManagedObject]\n                    events?.forEach { event in\n                        context.delete(event)\n                    }\n                } else {\n                    let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n                    try context.execute(deleteRequest)\n                }\n            } catch {\n                AirshipLogger.error(\"Error deleting analytics events: \\(error)\")\n            }\n        }\n    }\n\n    func deleteAllEvents() async throws {\n        try await self.coreData.perform(skipIfStoreNotCreated: true) { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: EventStore.eventDataEntityName\n            )\n\n            do {\n                if self.inMemory {\n                    request.includesPropertyValues = false\n                    let events = try context.fetch(request) as? [NSManagedObject]\n                    events?.forEach { event in\n                        context.delete(event)\n                    }\n                } else {\n                    let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n                    try context.execute(deleteRequest)\n                }\n            } catch {\n                AirshipLogger.error(\"Error deleting analytics events: \\(error)\")\n            }\n        }\n    }\n\n    func trimEvents(maxStoreSizeKB: UInt) async throws {\n        let maxBytes = maxStoreSizeKB * 1024\n        try await self.coreData.perform { context in\n            while self.fetchTotalEventSize(with: context) > maxBytes {\n                guard let sessionID = self.fetchOldestSessionID(with: context),\n                    self.deleteSession(sessionID, context: context)\n                else {\n                    return\n                }\n            }\n        }\n    }\n\n    nonisolated private func deleteSession(\n        _ sessionID: String,\n        context: NSManagedObjectContext\n    ) -> Bool {\n        let request = NSFetchRequest<any NSFetchRequestResult>(\n            entityName: EventStore.eventDataEntityName\n        )\n        request.predicate = NSPredicate(format: \"sessionID == %@\", sessionID)\n\n        do {\n            let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n            try context.execute(deleteRequest)\n            return true\n        } catch {\n            AirshipLogger.error(\"Error deleting session: \\(sessionID)\")\n            return false\n        }\n    }\n\n    nonisolated private func fetchOldestSessionID(with context: NSManagedObjectContext)\n        -> String?\n    {\n        let request = NSFetchRequest<any NSFetchRequestResult>(\n            entityName: EventStore.eventDataEntityName\n        )\n        request.fetchLimit = 1\n        request.sortDescriptors = [\n            NSSortDescriptor(key: \"storeDate\", ascending: true)\n        ]\n        request.propertiesToFetch = [\"sessionID\"]\n\n        do {\n            let result = try context.fetch(request) as? [EventData] ?? []\n            return result.first?.sessionID\n        } catch {\n            AirshipLogger.error(\"Error fetching oldest sessionID: \\(error)\")\n            return nil\n        }\n\n    }\n\n    nonisolated private func fetchTotalEventSize(with context: NSManagedObjectContext)\n        -> Int\n    {\n        guard !self.inMemory else {\n            return 0\n        }\n\n        let sumDescription = NSExpressionDescription()\n        sumDescription.name = \"sum\"\n        sumDescription.expression = NSExpression(\n            forFunction: \"sum:\",\n            arguments: [NSExpression(forKeyPath: \"bytes\")]\n        )\n        sumDescription.expressionResultType = .doubleAttributeType\n\n        let request = NSFetchRequest<any NSFetchRequestResult>(\n            entityName: EventStore.eventDataEntityName\n        )\n        request.resultType = .dictionaryResultType\n        request.propertiesToFetch = [sumDescription]\n\n        do {\n            let result = try context.fetch(request) as? [[String: Int]] ?? []\n            return result.first?[\"sum\"] ?? 0\n        } catch {\n            AirshipLogger.error(\"Error trimming analytic event store: \\(error)\")\n            return 0\n        }\n    }\n\n    nonisolated private func saveEvent(\n        event: AirshipEventData,\n        context: NSManagedObjectContext\n    ) throws {\n        if let eventData = NSEntityDescription.insertNewObject(\n            forEntityName: EventStore.eventDataEntityName,\n            into: context\n        ) as? EventData {\n            eventData.sessionID = event.sessionID\n            eventData.type = event.type.reportingName\n            eventData.identifier = event.id\n            eventData.data = try event.body.toData()\n            eventData.storeDate = event.date\n\n            // Approximate size\n            var count = 0\n            count += eventData.sessionID?.count ?? 0\n            count += eventData.type?.count ?? 0\n            count += eventData.time?.count ?? 0\n            count += eventData.identifier?.count ?? 0\n            count += eventData.data?.count ?? 0\n            eventData.bytes = NSNumber(value: count)\n\n            AirshipLogger.debug(\"Event saved: \\(event)\")\n        } else {\n            AirshipLogger.error(\"Failed to save event: \\(event)\")\n        }\n    }\n\n    nonisolated private func date(internalEventData: EventData) -> Date? {\n        // Stopped using the time field on new events. Will remove\n        // in a future SDK version.\n        if let time = internalEventData.time, let time = Double(time) {\n            return Date(timeIntervalSince1970: time)\n        }\n\n        return internalEventData.storeDate\n    }\n\n    nonisolated private func convert(\n        internalEventData: EventData\n    ) throws -> AirshipEventData {\n        guard let sessionID = internalEventData.sessionID,\n              let id = internalEventData.identifier,\n              let type = internalEventData.type,\n              let convertedType = EventType.allCases.first(where: { $0.reportingName == type }),\n              let date = date(internalEventData: internalEventData)\n        else {\n            throw AirshipErrors.error(\"Invalid event data\")\n        }\n\n        return AirshipEventData(\n            body: try AirshipJSON.from(data: internalEventData.data),\n            id: id,\n            date: date,\n            sessionID: sessionID,\n            type: convertedType \n        )\n    }\n}\n\n// Internal core data entity\n@objc(UAEventData)\nfileprivate class EventData: NSManagedObject {\n\n    /// The event's session ID.\n    @objc\n    @NSManaged public dynamic var sessionID: String?\n\n    /// The event's Data.\n    @NSManaged public dynamic var data: Data?\n\n    /// The event's creation time.\n    @objc\n    @NSManaged public dynamic var time: String?\n\n    /// The event's number of bytes.\n    @objc\n    @NSManaged public dynamic var bytes: NSNumber?\n\n    /// The event's type.\n    @objc\n    @NSManaged public dynamic var type: String?\n\n    /// The event's identifier.\n    @objc\n    @NSManaged public dynamic var identifier: String?\n\n    /// The event's store date.\n    @objc\n    @NSManaged public dynamic var storeDate: Date?\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventUploadScheduler.swift",
    "content": "import Foundation\n\nprotocol EventUploadSchedulerProtocol: Sendable {\n    func scheduleUpload(\n        eventPriority: AirshipEventPriority,\n        minBatchInterval: TimeInterval\n    ) async\n\n    func setWorkBlock(\n        _ workBlock: @Sendable @escaping () async throws -> AirshipWorkResult\n    ) async\n}\n\nactor EventUploadScheduler: EventUploadSchedulerProtocol {\n    private static let foregroundWorkBatchDelay: TimeInterval = 5\n    private static let backgroundWorkBatchDelay: TimeInterval = 1\n    private static let uploadScheduleDelay: TimeInterval = 15\n    private static let workID: String = \"EventUploadScheduler.upload\"\n\n    private var lastWorkDate: Date = .distantPast\n    private var nextScheduleDate: Date = .distantFuture\n    private var isScheduled: Bool = false\n    private var workBlock: (() async throws -> AirshipWorkResult)?\n\n    private let workManager: any AirshipWorkManagerProtocol\n    private let appStateTracker: any AppStateTrackerProtocol\n    private let date: any AirshipDateProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n\n    @MainActor\n    init(\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        workManager: any AirshipWorkManagerProtocol = AirshipWorkManager.shared,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = DefaultAirshipTaskSleeper.shared\n    ) {\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n        self.workManager = workManager\n        self.date = date\n        self.taskSleeper = taskSleeper\n\n        self.workManager.registerWorker(\n            EventUploadScheduler.workID\n        ) { [weak self] _ in\n            guard let self else {\n                return .success\n            }\n            return try await self.performWork()\n        }\n    }\n\n    private func performWork() async throws -> AirshipWorkResult {\n        self.lastWorkDate = self.date.now\n        self.isScheduled = false\n\n        var batchDelay = EventUploadScheduler.backgroundWorkBatchDelay\n        if (await self.appStateTracker.state == .active) {\n            batchDelay = EventUploadScheduler.foregroundWorkBatchDelay\n        }\n        \n        try await self.taskSleeper.sleep(timeInterval: batchDelay)\n\n        guard let workBlock = self.workBlock else {\n            return .success\n        }\n        try Task.checkCancellation()\n        return try await workBlock()\n    }\n\n    func scheduleUpload(\n        eventPriority: AirshipEventPriority,\n        minBatchInterval: TimeInterval\n    ) async {\n        let delay = await self.calculateNextUploadDelay(\n            eventPriority: eventPriority,\n            minBatchInterval: minBatchInterval\n        )\n\n        let proposedScheduleDate = self.date.now.advanced(by: delay)\n        guard !self.isScheduled || self.nextScheduleDate >= proposedScheduleDate else {\n            AirshipLogger.trace(\n                \"Upload already scheduled for an earlier time.\"\n            )\n            return\n        }\n\n        self.nextScheduleDate = proposedScheduleDate\n        self.isScheduled = true\n        self.workManager.dispatchWorkRequest(\n            AirshipWorkRequest(\n                workID: EventUploadScheduler.workID,\n                initialDelay: delay,\n                requiresNetwork: true,\n                conflictPolicy: .replace\n            )\n        )\n    }\n\n    private func calculateNextUploadDelay(\n        eventPriority: AirshipEventPriority,\n        minBatchInterval: TimeInterval\n    ) async -> TimeInterval {\n\n        switch(eventPriority) {\n        case .high:\n            return 0\n        case .normal: fallthrough\n        default:\n            if await self.appStateTracker.state == .background {\n                return 0\n            } else {\n                var delay: TimeInterval = 0\n                let timeSincelastSend = self.date.now.timeIntervalSince(self.lastWorkDate)\n                if timeSincelastSend < minBatchInterval {\n                    delay = minBatchInterval - timeSincelastSend\n                }\n                return max(delay, EventUploadScheduler.uploadScheduleDelay)\n            }\n        }\n    }\n\n    func setWorkBlock(\n        _ workBlock: @Sendable @escaping () async throws -> AirshipWorkResult\n    ) {\n        self.workBlock = workBlock\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventUploadTuningInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct EventUploadTuningInfo: Codable {\n    let maxTotalStoreSizeKB: UInt?\n    let maxBatchSizeKB: UInt?\n    let minBatchInterval: TimeInterval?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/EventUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\nclass EventUtils {\n\n    class func isValid(latitude: Double) -> Bool {\n        guard latitude >= -90 && latitude <= 90 else {\n            AirshipLogger.error(\n                \"Invalid latitude \\(latitude). Must be between -90 and 90\"\n            )\n            return false\n        }\n        return true\n    }\n\n    class func isValid(longitude: Double) -> Bool {\n        guard longitude >= -180 && longitude <= 180 else {\n            AirshipLogger.error(\n                \"Invalid longitude \\(longitude). Must be between -180 and 180\"\n            )\n            return false\n        }\n        return true\n    }\n\n    class func notificationTypes(\n        authorizedSettings: AirshipAuthorizedNotificationSettings\n    ) -> [String]? {\n        var notificationTypes: [String] = []\n\n        if (AirshipAuthorizedNotificationSettings.badge.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"badge\")\n        }\n\n        #if !os(tvOS)\n        if (AirshipAuthorizedNotificationSettings.sound.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"sound\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.alert.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"alert\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.carPlay.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"car_play\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.lockScreen.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"lock_screen\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.notificationCenter.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"notification_center\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.criticalAlert.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"critical_alert\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.scheduledDelivery.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"scheduled_summary\")\n        }\n\n        if (AirshipAuthorizedNotificationSettings.timeSensitive.rawValue\n            & authorizedSettings.rawValue) > 0\n        {\n            notificationTypes.append(\"time_sensitive\")\n        }\n\n        #endif\n\n        return notificationTypes\n    }\n\n    class func notificationAuthorization(\n        authorizationStatus: UNAuthorizationStatus\n    ) -> String? {\n        switch authorizationStatus {\n        case .notDetermined:\n            return \"not_determined\"\n        case .denied:\n            return \"denied\"\n        case .authorized:\n            return \"authorized\"\n        case .provisional:\n            return \"provisional\"\n        case .ephemeral:\n            return \"ephemeral\"\n        default:\n            return \"not_determined\"\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Experiment.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ExperimentType: String, Codable, Sendable, Equatable {\n    case holdoutGroup = \"holdout\"\n}\n\nenum ResultionType: String, Codable, Sendable, Equatable {\n    case `static` = \"static\"\n}\n\nstruct ExperimentCompoundAudience: Codable, Sendable, Equatable {\n    var selector: CompoundDeviceAudienceSelector\n}\n\n\nstruct Experiment: Codable, Sendable, Equatable {\n\n    let id: String\n    let type: ExperimentType\n    let resolutionType: ResultionType\n    let lastUpdated: Date\n    let created: Date\n    let reportingMetadata: AirshipJSON\n    let audienceSelector: DeviceAudienceSelector?\n    let compoundAudience: ExperimentCompoundAudience?\n    let exclusions: [MessageCriteria]?\n    let timeCriteria: AirshipTimeCriteria?\n\n    enum CodingKeys: String, CodingKey {\n        case id = \"experiment_id\"\n        case created\n        case lastUpdated = \"last_updated\"\n        case experimentDefinition = \"experiment_definition\"\n    }\n    \n    enum ExperimentDefinitionKeys: String, CodingKey {\n        case type = \"experiment_type\"\n        case resolutionType = \"type\"\n        case reportingMetadata = \"reporting_metadata\"\n        case audienceSelector = \"audience_selector\"\n        case compoundAudience = \"compound_audience\"\n        case exclusions = \"message_exclusions\"\n        case timeCriteria = \"time_criteria\"\n    }\n\n    init(\n        id: String,\n        type: ExperimentType = .holdoutGroup,\n        resolutionType: ResultionType = ResultionType.static,\n        lastUpdated: Date,\n        created: Date,\n        reportingMetadata: AirshipJSON,\n        audienceSelector: DeviceAudienceSelector? = nil,\n        compoundAudience: ExperimentCompoundAudience? = nil,\n        exclusions: [MessageCriteria]? = nil,\n        timeCriteria: AirshipTimeCriteria? = nil\n    ) {\n        self.id = id\n        self.type = type\n        self.resolutionType = resolutionType\n        self.lastUpdated = lastUpdated\n        self.created = created\n        self.reportingMetadata = reportingMetadata\n        self.audienceSelector = audienceSelector\n        self.compoundAudience = compoundAudience\n        self.exclusions = exclusions\n        self.timeCriteria = timeCriteria\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.id = try container.decode(String.self, forKey: .id)\n        self.created = try container.decode(Date.self, forKey: .created)\n        self.lastUpdated = try container.decode(Date.self, forKey: .lastUpdated)\n\n        let definitionContainer = try container.nestedContainer(keyedBy: ExperimentDefinitionKeys.self, forKey: .experimentDefinition)\n        self.type = try definitionContainer.decode(ExperimentType.self, forKey: .type)\n        self.resolutionType = try definitionContainer.decode(ResultionType.self, forKey: .resolutionType)\n        self.reportingMetadata = try definitionContainer.decode(AirshipJSON.self, forKey: .reportingMetadata)\n        self.audienceSelector = try definitionContainer.decodeIfPresent(DeviceAudienceSelector.self, forKey: .audienceSelector)\n        self.exclusions = try definitionContainer.decodeIfPresent([MessageCriteria].self, forKey: .exclusions)\n        self.timeCriteria = try definitionContainer.decodeIfPresent(AirshipTimeCriteria.self, forKey: .timeCriteria)\n        self.compoundAudience = try definitionContainer.decodeIfPresent(ExperimentCompoundAudience.self, forKey: .compoundAudience)\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.id, forKey: .id)\n        \n        try container.encode(Self.dateFormatter.string(from: self.created), forKey: .created)\n        try container.encode(Self.dateFormatter.string(from: self.lastUpdated), forKey: .lastUpdated)\n        \n        var definition = container.nestedContainer(keyedBy: ExperimentDefinitionKeys.self, forKey: .experimentDefinition)\n        try definition.encode(self.type, forKey: .type)\n        try definition.encode(self.resolutionType, forKey: .resolutionType)\n        try definition.encode(self.reportingMetadata, forKey: .reportingMetadata)\n        try definition.encodeIfPresent(self.audienceSelector, forKey: .audienceSelector)\n        try definition.encodeIfPresent(self.compoundAudience, forKey: .compoundAudience)\n        try definition.encodeIfPresent(self.exclusions, forKey: .exclusions)\n        try definition.encodeIfPresent(self.timeCriteria, forKey: .timeCriteria)\n    }\n    \n    private static let dateFormatter: DateFormatter = {\n        let result = DateFormatter()\n        result.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSS\"\n        return result\n    }()\n    \n    static let decoder: JSONDecoder = {\n        var decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .formatted(Self.dateFormatter)\n        return decoder\n    }()\n\n\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ExperimentDataProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol ExperimentDataProvider: Sendable {\n    func evaluateExperiments(\n        info: MessageInfo,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> ExperimentResult?\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic struct MessageInfo: Equatable, Hashable {\n    let messageType: String\n    let campaigns: AirshipJSON?\n\n    public init(messageType: String, campaigns: AirshipJSON? = nil) {\n        self.messageType = messageType\n        self.campaigns = try? AirshipJSON.wrap(campaigns)\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ExperimentResult: Codable, Sendable, Hashable {\n    public let channelID: String\n    public let contactID: String\n    public let isMatch: Bool\n    public let reportingMetadata: [AirshipJSON]\n\n    public init(channelID: String, contactID: String, isMatch: Bool, reportingMetadata: [AirshipJSON]) {\n        self.channelID = channelID\n        self.contactID = contactID\n        self.isMatch = isMatch\n        self.reportingMetadata = reportingMetadata\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ExperimentManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nfinal class ExperimentManager: ExperimentDataProvider {\n    private static let payloadType: String = \"experiments\"\n    \n    private let dataStore: PreferenceDataStore\n    private let remoteData: any RemoteDataProtocol\n    private let audienceChecker: any DeviceAudienceChecker\n    private let date: any AirshipDateProtocol\n\n    init(\n        dataStore: PreferenceDataStore,\n        remoteData: any RemoteDataProtocol,\n        audienceChecker: any DeviceAudienceChecker,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.dataStore = dataStore\n        self.remoteData = remoteData\n        self.audienceChecker = audienceChecker\n        self.date = date\n    }\n    \n    public func evaluateExperiments(\n        info: MessageInfo,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> ExperimentResult? {\n        let experiments = await getExperiments(info: info)\n        guard !experiments.isEmpty else {\n            return nil\n        }\n\n        \n        let contactID = await deviceInfoProvider.stableContactInfo.contactID\n        let channelID = try await deviceInfoProvider.channelID\n\n        var evaluatedMetadata: [AirshipJSON] = []\n        var isMatch: Bool = false\n\n        for experiment in experiments {\n            isMatch = try await self.audienceChecker.evaluate(\n                audienceSelector: .combine(\n                    compoundSelector: experiment.compoundAudience?.selector,\n                    deviceSelector: experiment.audienceSelector\n                ),\n                newUserEvaluationDate: experiment.created,\n                deviceInfoProvider: deviceInfoProvider\n            ).isMatch\n\n            evaluatedMetadata.append(experiment.reportingMetadata)\n\n            if (isMatch) {\n                break\n            }\n        }\n        \n        return ExperimentResult(\n            channelID: channelID,\n            contactID: contactID,\n            isMatch: isMatch,\n            reportingMetadata: evaluatedMetadata\n        )\n    }\n\n    func getExperiments(info: MessageInfo) async -> [Experiment] {\n        return await remoteData\n            .payloads(types: [Self.payloadType])\n            .compactMap { payload in\n                payload.data.object?[Self.payloadType]?.array\n            }\n            .flatMap { $0 }\n            .compactMap { json in\n                do {\n                    let experiment: Experiment = try json.decode(decoder: Experiment.decoder)\n                    return experiment\n                } catch {\n                    AirshipLogger.error(\"Failed to parse experiment \\(error)\")\n                    return nil\n                }\n            }\n            .filter { $0.isActive(date: self.date.now) }\n            .filter { !$0.isExcluded(info: info) }\n    }\n    \n}\n\nprivate extension Experiment {\n    func isExcluded(info: MessageInfo) -> Bool {\n        return self.exclusions?.contains { criteria in\n            let messageType = criteria.messageTypePredicate?.evaluate(json: .string(info.messageType)) ?? false\n            let campaigns = criteria.campaignsPredicate?.evaluate(json: info.campaigns ?? .null) ?? false\n            return messageType || campaigns\n        } ?? false\n    }\n    func isActive(date: Date) -> Bool {\n        return self.timeCriteria?.isActive(date: date) ?? true\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ExternalURLProcessor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n/// Protocol for opening URLs and settings across different platforms.\npublic protocol URLOpenerProtocol: Sendable {\n    /// Opens a URL asynchronously.\n    @MainActor\n    @discardableResult\n    func openURL(_ url: URL) async -> Bool\n\n    /// Opens a URL with a completion handler.\n    @MainActor\n    func openURL(_ url: URL, completionHandler: (@MainActor @Sendable (Bool) -> Void)?)\n\n    /// Opens the app or system settings.\n    @MainActor\n    @discardableResult\n    func openSettings() async -> Bool\n}\n\n/// Default implementation of the URLOpenerProtocol.\npublic struct DefaultURLOpener: URLOpenerProtocol {\n\n    @MainActor\n    public static let shared = DefaultURLOpener()\n\n    @MainActor\n    @discardableResult\n    public func openURL(_ url: URL) async -> Bool {\n#if os(macOS)\n        return NSWorkspace.shared.open(url)\n#elseif os(watchOS)\n        WKExtension.shared().openSystemURL(url)\n        return true\n#else\n        return await UIApplication.shared.open(url, options: [:])\n#endif\n    }\n\n    @MainActor\n    public func openURL(_ url: URL, completionHandler: (@MainActor @Sendable (Bool) -> Void)?) {\n#if os(macOS)\n        let success = NSWorkspace.shared.open(url)\n        completionHandler?(success)\n#elseif os(watchOS)\n        WKExtension.shared().openSystemURL(url)\n        completionHandler?(true)\n#else\n        UIApplication.shared.open(url, options: [:], completionHandler: completionHandler)\n#endif\n    }\n\n    @MainActor\n    public func openSettings() async -> Bool {\n#if os(macOS)\n        // Parity: Opens the app's own Settings window (Command + ,)\n        // macOS users expect this for \"App Settings\" deep links.\n        NSApp.sendAction(Selector((\"showSettingsWindow:\")), to: nil, from: nil)\n        return true\n#elseif os(watchOS)\n        // watchOS does not support opening settings via URL\n        return false\n#else\n        // iOS, tvOS, visionOS\n        guard let url = URL(string: UIApplication.openSettingsURLString) else {\n            return false\n        }\n        return await self.openURL(url)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/FarmHashFingerprint64.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/**\n * Implementation of FarmHash Fingerprint64, an open-source fingerprinting algorithm for strings.\n *\n * Based on https://github.com/google/guava/blob/master/guava/src/com/google/common/hash/FarmHashFingerprint64.java\n * \n */\nstruct FarmHashFingerprint64 {\n\n    // some fun primes\n    private static let k0: UInt64 = 0xc3a5c85c97cb3127\n    private static let k1: UInt64 = 0xb492b66fbe98f273\n    private static let k2: UInt64 = 0x9ae16a3b2f90404f\n\n    public static func fingerprint(_ value: String) -> UInt64 {\n        let bytes: [UInt8] = Array(value.utf8)\n        return fingerprint(bytes)\n    }\n\n    public static func fingerprint(_ bytes: [UInt8]) -> UInt64 {\n        if (bytes.count <= 32) {\n            if (bytes.count <= 16) {\n                return hashLength0to16(bytes)\n            } else {\n                return hashLength17to32(bytes)\n            }\n        } else if (bytes.count <= 64) {\n            return hashLength33To64(bytes)\n        } else {\n            return hashLength65Plus(bytes)\n        }\n    }\n\n    public static func fingerprint(_ bytes: [UInt8], _ length: Int) -> UInt64 {\n        let trimmedBytes : [UInt8] = Array(bytes.prefix(length))\n        return fingerprint(trimmedBytes)\n    }\n\n    private static func load64(_ bytes: [UInt8], _ offset: Int) -> UInt64 {\n        var result: UInt64 = 0\n        for i in 0...7 {\n            let value: UInt64 = UInt64(bytes[offset + i]) << (i * 8)\n            result = result | value\n        }\n\n        return result\n    }\n\n    private static func load32(_ bytes: [UInt8], _ offset: Int) -> UInt64 {\n        var result: UInt64 = 0\n        for i in 0...3 {\n            let value: UInt64 = UInt64(bytes[offset + i]) << (i * 8)\n            result = result | value\n        }\n\n        return result\n    }\n\n    private static func rotateRight(_ value: UInt64, _ distance: Int) -> UInt64 {\n        return (value >> UInt64(distance)) | (value << (value.bitWidth - distance))\n    }\n\n    private static func hashLength16(_ u: UInt64, _ v: UInt64, _ mul: UInt64) -> UInt64 {\n        var a = (u ^ v) &* mul\n        a ^= (a >> 47)\n        var b = (v ^ a) &* mul\n        b ^= (b >> 47)\n        b = b &* mul\n        return b\n    }\n\n    private static func shiftMix(_ value: UInt64) -> UInt64 {\n        return value ^ (value >> 47)\n    }\n\n    private static func hashLength0to16(_ bytes: [UInt8]) -> UInt64 {\n        let length: Int = bytes.count\n        if (length >= 8) {\n            let mul = k2 + UInt64(length) &* 2\n            let a = load64(bytes, 0) &+ k2\n            let b = load64(bytes, length - 8)\n            let c = rotateRight(b, 37) &* mul &+ a\n            let d = (rotateRight(a, 25) &+ b) &* mul\n            return hashLength16(c, d, mul)\n        }\n\n        if (length >= 4) {\n            let mul: UInt64 = k2 + UInt64(length) &* 2\n            let a: UInt64 = load32(bytes, 0)\n            return hashLength16(\n                UInt64(length) &+ (a << 3),\n                load32(bytes, length - 4),\n                mul\n            )\n        }\n\n        if (length > 0) {\n            let a = Int(bytes[0])\n            let b = Int(bytes[Int(length >> 1)])\n            let c = bytes[Int(length - 1)]\n            let y = a + (b << 8)\n            let z = length + ((Int(c) << 2))\n            return shiftMix(UInt64(y) &* k2 ^ UInt64(z) &* k0) &* k2\n        }\n\n        return k2\n    }\n\n    static func hashLength17to32(_ bytes: [UInt8]) -> UInt64 {\n        let length = bytes.count\n        let mul: UInt64 = k2 + UInt64(length) &* 2\n        let a: UInt64 = load64(bytes, 0) &* k1\n        let b: UInt64 = load64(bytes, 8)\n        let c: UInt64 = load64(bytes, length - 8) &* mul\n        let d: UInt64 = load64(bytes, length - 16) &* k2\n        return hashLength16(\n            rotateRight(a &+ b, 43) &+ rotateRight(c, 30) &+ d,\n            a &+ rotateRight(b &+ k2, 18) &+ c,\n            mul\n        )\n    }\n\n    static func hashLength33To64(_ bytes: [UInt8]) -> UInt64 {\n        let length: UInt64 = UInt64(bytes.count)\n        let mul: UInt64 = k2 &+ length &* 2\n        let a: UInt64 = load64(bytes, 0) &* k2\n        let b: UInt64 = load64(bytes, 8)\n        let c: UInt64 = load64(bytes, Int(length) - 8) &* mul\n        let d: UInt64 = load64(bytes, Int(length) - 16) &* k2\n        let y: UInt64 = rotateRight(a &+ b, 43) &+ rotateRight(c, 30) &+ d\n        let z: UInt64 = hashLength16(y, a &+ rotateRight(b &+ k2, 18) &+ c, mul)\n        let e: UInt64 = load64(bytes, 16) &* mul\n        let f: UInt64 = load64(bytes, 24)\n        let g: UInt64 = (y &+ load64(bytes, Int(length) - 32)) &* mul\n        let h: UInt64 = (z &+ load64(bytes, Int(length) - 24)) &* mul\n        return hashLength16(\n            rotateRight(e &+ f, 43) &+ rotateRight(g, 30) &+ h,\n            e &+ rotateRight(f &+ a, 18) &+ g,\n            mul\n        )\n    }\n\n    /**\n     * Computes intermediate hash of 32 bytes of byte array from the given offset.\n     */\n    static func weakHashLength32WithSeeds(\n        _ bytes: [UInt8],\n        _ offset: Int,\n        _ seedA: UInt64,\n        _ seedB: UInt64\n    ) -> [UInt64] {\n        let part1 = load64(bytes, offset)\n        let part2 = load64(bytes, offset + 8)\n        let part3 = load64(bytes, offset + 16)\n        let part4 = load64(bytes, offset + 24)\n\n        var mutableSeedA = seedA &+ part1;\n        var mutableSeedB = rotateRight(seedB &+ mutableSeedA &+ part4, 21)\n        let c = mutableSeedA\n        mutableSeedA &+= part2\n        mutableSeedA &+= part3\n        mutableSeedB &+= rotateRight(mutableSeedA, 44)\n\n        return [\n            mutableSeedA &+ part4,\n            mutableSeedB &+ c\n        ]\n    }\n\n    private static func hashLength65Plus(_ bytes: [UInt8]) -> UInt64 {\n        let length: Int = bytes.count\n\n        let seed: UInt64 = 81\n        // For strings over 64 bytes we loop. Internal state consists of 56 bytes: v, w, x, y, and z.\n        var x: UInt64 = seed\n\n        var offset = 0\n\n        var y: UInt64 = seed &* k1 &+ 113\n        var z: UInt64 = shiftMix(y &* k2 &+ 113) &* k2\n        var v: [UInt64] = [0,0]\n        var w: [UInt64] = [0,0]\n        x = x &* k2 &+ load64(bytes, offset)\n\n        // Set end so that after the loop we have 1 to 64 bytes left to process.\n        let end = offset + ((length - 1) / 64) * 64\n        let last64offset = end + ((length - 1) & 63) - 63\n        repeat {\n            x = rotateRight(x &+ y &+ v[0] &+ load64(bytes, offset + 8), 37) &* k1\n            y = rotateRight(y &+ v[1] &+ load64(bytes, offset + 48), 42) &* k1\n            x ^= w[1]\n            y &+= v[0] &+ load64(bytes, offset + 40)\n            z = rotateRight(z &+ w[0], 33) &* k1\n            v = weakHashLength32WithSeeds(bytes, offset, v[1] &* k1, x &+ w[0])\n            w = weakHashLength32WithSeeds(\n                bytes,\n                offset + 32,\n                z &+ w[1],\n                y &+ load64(bytes, offset + 16)\n            )\n            let tmp: UInt64 = x\n            x = z\n            z = tmp\n            offset += 64\n        } while (offset != end)\n\n        let mul : UInt64 = k1 &+ ((z & 0xFF) << 1)\n\n        // Operate on the last 64 bytes of input.\n        offset = last64offset\n        w[0] &+= UInt64((length - 1) & 63)\n        v[0] &+= w[0]\n        w[0] &+= v[0]\n        x = rotateRight(x &+ y &+ v[0] &+ load64(bytes, offset + 8), 37) &* mul\n        y = rotateRight(y &+ v[1] &+ load64(bytes, offset + 48), 42) &* mul\n        x ^= w[1] &* 9\n        y &+= v[0] &* 9 &+ load64(bytes, offset + 40)\n        z = rotateRight(z &+ w[0], 33) &* mul\n        v = weakHashLength32WithSeeds(bytes, offset, v[1] &* mul, x &+ w[0])\n        w = weakHashLength32WithSeeds(\n            bytes,\n            offset + 32,\n            z &+ w[1],\n            y &+ load64(bytes, offset + 16)\n        )\n        return hashLength16(\n            hashLength16(v[0], w[0], mul) &+ shiftMix(y) &* k0 &+ x,\n            hashLength16(v[1], w[1], mul) &+ z,\n            mul\n        )\n    }\n}\n\nextension String {\n    var farmHashFingerprint64: UInt64 {\n        return FarmHashFingerprint64.fingerprint(self)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/FetchDeviceInfoAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Fetches device info.\n///\n/// Expected argument values: none.\n///\n/// Valid situations: `ActionSituation.launchedFromPush`,\n/// `ActionSituation.webViewInvocation`, `ActionSituation.manualInvocation`,\n/// `ActionSituation.foregroundInteractiveButton`, `ActionSituation.backgroundInteractiveButton`,\n/// and `ActionSituation.automation`\n///\n/// Result value: JSON payload containing the device's channel ID, named user ID, push opt-in status,\n/// location enabled status, and tags. An example response as JSON:\n/// {\n///     \"channel_id\": \"9c36e8c7-5a73-47c0-9716-99fd3d4197d5\",\n///     \"push_opt_in\": true,\n///     \"location_enabled\": true,\n///     \"named_user\": \"cool_user\",\n///     \"tags\": [\"tag1\", \"tag2, \"tag3\"]\n/// }\n///\npublic final class FetchDeviceInfoAction: AirshipAction {\n\n    /// Default names - \"fetch_device_info\", \"^+fdi\"\n    public static let defaultNames: [String] = [\"fetch_device_info\", \"^+fdi\"]\n\n    /// Default predicate - only accepts `ActionSituation.manualInvocation` and `ActionSituation.webViewInvocation`\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.situation == .manualInvocation\n            || args.situation == .webViewInvocation\n    }\n\n    // Channel ID key\n    public static let channelID: String = \"channel_id\"\n\n    // Named user key\n    public static let namedUser: String = \"named_user\"\n\n    // Tags key\n    public static let tags: String = \"tags\"\n\n    // Push opt-in key\n    public static let pushOptIn: String = \"push_opt_in\"\n\n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n    private let push: @Sendable () -> any AirshipPush\n\n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier(),\n            push: Airship.componentSupplier()\n        )\n    }\n\n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact,\n        push: @escaping @Sendable () -> any AirshipPush\n    ) {\n        self.channel = channel\n        self.contact = contact\n        self.push = push\n    }\n\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        return true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let info = DeviceInfo(\n            channelID: channel().identifier,\n            pushOptIn: await push().isPushNotificationsOptedIn,\n            namedUser: await contact().namedUserID,\n            tags: channel().tags\n        )\n\n        return try AirshipJSON.wrap(info)\n    }\n}\n\n\nfileprivate struct DeviceInfo: Encodable {\n    let channelID: String?\n    let pushOptIn: Bool\n    let namedUser: String?\n    let tags: [String]\n\n    init(channelID: String?, pushOptIn: Bool, namedUser: String?, tags: [String]) {\n        self.channelID = channelID\n        self.pushOptIn = pushOptIn\n        self.namedUser = namedUser\n        self.tags = tags\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case channelID = \"channel_id\"\n        case pushOptIn = \"push_opt_in\"\n        case namedUser = \"named_user\"\n        case tags = \"tags\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/FontViewModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct TextAppearanceViewModifier: ViewModifier\n{\n    let textAppearance: ThomasTextAppearance\n\n    // Needed for dynamic font size\n    @Environment(\\.sizeCategory) var sizeCategory\n    \n    @ViewBuilder\n    func body(content: Content) -> some View {\n        let baseFontSize = textAppearance.fontSize\n        let scaledFontSize = AirshipFont.scaledSize(baseFontSize)\n        let scaleFactor = Double(scaledFontSize) / baseFontSize\n        content\n            .font(self.textAppearance.font)\n            .applyLineHeightMultiplier(\n                textAppearance.lineHeightMultiplier,\n                scaledFontSize: scaledFontSize\n            )\n            .applyKerning(\n                textAppearance.kerning,\n                scaleFactor: scaleFactor\n            )\n    }\n}\n\nextension Text {\n    \n    private func applyTextStyles(styles: [ThomasTextAppearance.TextStyle]?) -> Text {\n        var text = self\n        if let styles = styles {\n            if styles.contains(.bold) {\n                text = text.bold()\n            }\n            \n            if styles.contains(.italic) {\n                text = text.italic()\n            }\n            \n            if styles.contains(.underlined) {\n                text = text.underline()\n            }\n        }\n        return text\n    }\n    \n    @ViewBuilder\n    @MainActor\n    func textAppearance(\n        _ textAppearance: ThomasTextAppearance?,\n        colorScheme: ColorScheme\n    ) -> some View {\n        if let textAppearance = textAppearance {\n            self.applyTextStyles(styles: textAppearance.styles)\n                .multilineTextAlignment(\n                    textAppearance.alignment?.toSwiftTextAlignment() ?? .center\n                )\n                .modifier(\n                    TextAppearanceViewModifier(textAppearance: textAppearance)\n                )\n                .foreground(textAppearance.color, colorScheme: colorScheme)\n        } else {\n            self\n        }\n    }\n}\n\nextension View {\n\n    @ViewBuilder\n    @MainActor\n    func applyLineHeightMultiplier(\n        _ multiplier: Double?,\n        scaledFontSize: Double\n    ) -> some View {\n        if let multiplier {\n            if #available(iOS 26.0, macOS 26.0, tvOS 26.0, watchOS 26.0, visionOS 26.0, *) {\n                self.lineHeight(.multiple(factor: multiplier))\n            } else {\n                // Fallback: approximate using scaled font size as base line height.\n                //\n                // Natural line height ~= scaledFontSize * (font's internal multiplier).\n                // We don't know that exact internal multiplier in SwiftUI,\n                // but using scaledFontSize as the \"1.0\" baseline is a reasonable approximation.\n                let baseLineHeight = scaledFontSize\n                let effective = baseLineHeight * multiplier\n                let extra = max(effective - baseLineHeight, 0)\n                self.lineSpacing(extra)\n            }\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    fileprivate func applyKerning(\n        _ kerning: Double?,\n        scaleFactor: Double\n    ) -> some View {\n        if let kerning {\n            self.kerning(kerning * scaleFactor)\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func applyViewAppearance(\n        _ textAppearance: ThomasTextAppearance?,\n        colorScheme: ColorScheme\n    ) -> some View {\n        if let textAppearance = textAppearance {\n            self\n                .multilineTextAlignment(\n                    textAppearance.alignment?.toSwiftTextAlignment() ?? .center\n                )\n                .modifier(\n                    TextAppearanceViewModifier(textAppearance: textAppearance)\n                )\n                .foreground(textAppearance.color, colorScheme: colorScheme)\n        } else {\n            self\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/FormController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct FormController: View {\n\n    enum FormInfo {\n        case nps(ThomasViewInfo.NPSController)\n        case form(ThomasViewInfo.FormController)\n    }\n\n    private let info: FormInfo\n    private let constraints: ViewConstraints\n\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n    @EnvironmentObject private var environment: ThomasEnvironment\n    @EnvironmentObject private var state: ThomasState\n\n    init(info: FormInfo, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: constraints,\n            environment: environment,\n            parentFormState: formState,\n            parentFormDataCollector: formDataCollector,\n            parentState: state\n        )\n        .id(info.identifier)\n    }\n\n    private struct Content: View {\n\n        private let info: FormController.FormInfo\n        private let constraints: ViewConstraints\n\n        @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n        @Environment(\\.layoutState) private var layoutState\n\n        @ObservedObject private var formState: ThomasFormState\n        @StateObject private var formDataCollector: ThomasFormDataCollector\n        @StateObject private var state: ThomasState\n\n        init(\n            info: FormController.FormInfo,\n            constraints: ViewConstraints,\n            environment: ThomasEnvironment,\n            parentFormState: ThomasFormState,\n            parentFormDataCollector: ThomasFormDataCollector,\n            parentState: ThomasState\n        ) {\n            self.info = info\n            self.constraints = constraints\n\n            // Use the environment to create or retrieve the state in case the view\n            // stack changes and we lose our state.\n            let formState = environment.retrieveState(identifier: info.identifier) {\n                ThomasFormState(\n                    identifier: info.identifier,\n                    formType: info.formType,\n                    formResponseType: info.responseType,\n                    validationMode: info.validationMode ?? .immediate,\n                    parentFormState: info.isParent ? nil : parentFormState\n                )\n            }\n\n            if info.isParent {\n                formState.onSubmit = { [weak environment] identifier, result, layoutState in\n                    guard let environment else { throw AirshipErrors.error(\"Missing environment\") }\n                    environment.submitForm(\n                        result: ThomasFormResult(\n                            identifier: identifier,\n                            formData: try ThomasFormPayloadGenerator.makeFormEventPayload(\n                                identifier: identifier,\n                                formValue: result.value\n                            )\n                        ),\n                        channels: result.channels ?? [],\n                        attributes: result.attributes ?? [],\n                        layoutState: layoutState\n                    )\n                }\n            } else {\n                formState.onSubmit = { [weak parentFormDataCollector] identifier, result, layoutState in\n                    guard let parentFormDataCollector else { throw AirshipErrors.error(\"Missing form collector\") }\n                    let field = ThomasFormField.validField(identifier: identifier, input: result.value, result: result)\n                    parentFormDataCollector.updateField(field, pageID: layoutState.pagerState?.currentPageId)\n                }\n            }\n\n            self._formState = ObservedObject(wrappedValue: formState)\n            self._formDataCollector = StateObject(\n                wrappedValue: parentFormDataCollector.with(formState: formState)\n            )\n            self._state = StateObject(\n                wrappedValue: parentState.with(formState: formState)\n            )\n        }\n\n        var body: some View {\n            ViewFactory.createView(self.info.view, constraints: constraints)\n                .thomasCommon(self.info.thomasInfo, formInputID: self.info.identifier)\n                .thomasEnableBehaviors(self.info.formEnableBehaviors) { enabled in\n                    self.formState.isEnabled = enabled\n                }\n                .environmentObject(formState)\n                .environmentObject(formDataCollector)\n                .environmentObject(state)\n                .airshipOnChangeOf(formState.isVisible) { [weak formState, weak thomasEnvironment] incoming in\n                    guard info.isParent, incoming, let formState, let thomasEnvironment else {\n                        return\n                    }\n                    thomasEnvironment.formDisplayed(\n                        formState,\n                        layoutState: layoutState.override(\n                            formState: formState\n                        )\n                    )\n                }\n        }\n    }\n}\n\nstruct FormControllerDebug: View {\n    @EnvironmentObject var state: ThomasFormState\n\n    var body: some View {\n        Text(String(describing: state))\n    }\n}\n\nfileprivate extension FormController.FormInfo {\n    var responseType: String? {\n        switch(self) {\n        case .nps(let info): info.properties.responseType\n        case .form(let info): info.properties.responseType\n        }\n    }\n\n    var formType: ThomasFormState.FormType {\n        switch(self) {\n        case .nps(let info): .nps(info.properties.npsIdentifier)\n        case .form: .form\n        }\n    }\n\n    var formEnableBehaviors: [ThomasEnableBehavior]? {\n        switch(self) {\n        case .nps(let info): info.properties.formEnableBehaviors\n        case .form(let info): info.properties.formEnableBehaviors\n        }\n    }\n\n    var identifier: String {\n        switch(self) {\n        case .nps(let info): info.properties.identifier\n        case .form(let info): info.properties.identifier\n        }\n    }\n\n    var isParent: Bool {\n        switch(self) {\n        case .nps(let info): info.properties.submit != nil\n        case .form(let info): info.properties.submit != nil\n        }\n    }\n\n    var validationMode: ThomasFormValidationMode? {\n        switch(self) {\n        case .nps(let info): info.properties.validationMode\n        case .form(let info): info.properties.validationMode\n        }\n    }\n\n    var view: ThomasViewInfo {\n        switch(self) {\n        case .nps(let info): info.properties.view\n        case .form(let info): info.properties.view\n        }\n    }\n\n    var thomasInfo: any ThomasViewInfo.BaseInfo {\n        switch(self) {\n        case .nps(let info): info\n        case .form(let info): info\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/FormInputViewModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n\nstruct FormVisibilityViewModifier: ViewModifier {\n    @Environment(\\.isVisible) private var isVisible\n    @EnvironmentObject var formState: ThomasFormState\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        content\n            .onAppear {\n                if isVisible {\n                    formState.markVisible()\n                }\n            }\n            .airshipOnChangeOf(isVisible) { [weak formState] newValue in\n                if newValue {\n                    formState?.markVisible()\n                }\n            }\n    }\n}\n\nstruct FormInputEnabledViewModifier: ViewModifier {\n    @EnvironmentObject var formState: ThomasFormState\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        content.disabled(\n            !formState.isFormInputEnabled\n        )\n    }\n}\n\nextension View {\n    @ViewBuilder\n    @MainActor\n    func formElement() -> some View {\n        self.viewModifiers {\n            FormVisibilityViewModifier()\n            FormInputEnabledViewModifier()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/HashChecker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct HashChecker {\n    private let queue: AirshipSerialQueue = AirshipSerialQueue()\n    private let cache: any AirshipCache\n\n    init(cache: any AirshipCache) {\n        self.cache = cache\n    }\n\n    func evaluate(\n        hashSelector: AudienceHashSelector?,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> AirshipDeviceAudienceResult {\n        guard let hashSelector else {\n            return .match\n        }\n\n        return try await self.queue.run {\n            let contactID = await deviceInfoProvider.stableContactInfo.contactID\n            let channelID = try await deviceInfoProvider.channelID\n\n            let result = await self.resolveResult(\n                hashSelector: hashSelector,\n                contactID: contactID,\n                channelID: channelID\n            )\n\n            await self.cacheResult(\n                selector: hashSelector,\n                result: result,\n                contactID: contactID,\n                channelID: channelID\n            )\n\n            return result\n        }\n    }\n\n    private func resolveResult(\n        hashSelector: AudienceHashSelector,\n        contactID: String,\n        channelID: String\n    ) async -> AirshipDeviceAudienceResult {\n        guard\n            let cached = await self.getCachedResult(\n                selector: hashSelector,\n                contactID: contactID,\n                channelID: channelID\n            )\n        else {\n            let isMatch = hashSelector.evaluate(\n                channelID: channelID,\n                contactID: contactID\n            )\n\n            let reportingMetadata: [AirshipJSON]? = if let reporting = hashSelector.sticky?.reportingMetadata {\n                [reporting]\n            } else {\n                nil\n            }\n\n            return AirshipDeviceAudienceResult(\n                isMatch: isMatch,\n                reportingMetadata: reportingMetadata\n            )\n        }\n\n        return cached\n    }\n\n    private func cacheResult(\n        selector: AudienceHashSelector,\n        result: AirshipDeviceAudienceResult,\n        contactID: String,\n        channelID: String\n    ) async {\n        guard let sticky = selector.sticky else {\n            return\n        }\n\n        let key = Self.makeCacheKey(\n            sticky.id,\n            contactID: contactID,\n            channelID: channelID\n        )\n\n        await cache.setCachedValue(result, key: key, ttl: sticky.lastAccessTTL)\n    }\n\n    private func getCachedResult(\n        selector: AudienceHashSelector,\n        contactID: String,\n        channelID: String\n    ) async -> AirshipDeviceAudienceResult? {\n        guard let sticky = selector.sticky else {\n            return nil\n        }\n\n        let key = Self.makeCacheKey(\n            sticky.id,\n            contactID: contactID,\n            channelID: channelID\n        )\n\n        return await cache.getCachedValue(key: key)\n    }\n\n    private static func makeCacheKey(\n        _ id: String,\n        contactID: String,\n        channelID: String\n    ) -> String {\n        return \"StickyHash:\\(contactID):\\(channelID):\\(id)\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/IconView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n// Icon view that can be used to display icons inside a toggle layout\nstruct IconView: View {\n    @Environment(\\.colorScheme) private var colorScheme\n    @EnvironmentObject private var thomasState: ThomasState\n\n    private let info: ThomasViewInfo.IconView\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.IconView, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var resolvedIcon: ThomasIconInfo {\n        return ThomasPropertyOverride.resolveRequired(\n            state: self.thomasState,\n            overrides: self.info.overrides?.icon,\n            defaultValue: self.info.properties.icon\n        )\n    }\n\n    var body: some View {\n        Icons.icon(info: resolvedIcon, colorScheme: colorScheme)\n            .constraints(constraints, fixedSize: true)\n            .background(Color.airshipTappableClear)\n            .thomasCommon(self.info)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Icons.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n\nstruct Icons {\n\n    @MainActor\n    static func makeSystemImageIcon(\n        name: String,\n        resizable: Bool,\n        color: Color\n    ) -> some View {\n        Image(systemName: name)\n            .airshipApplyIf(resizable) { view in view.resizable() }\n            .foregroundColor(color)\n    }\n    \n\n    @MainActor\n    @ViewBuilder\n    private static func makeView(\n        icon: ThomasIconInfo.Icon,\n        resizable: Bool,\n        color: Color\n    ) -> some View {\n        switch icon {\n        case .asterisk:\n            makeSystemImageIcon(\n                name: \"asterisk\",\n                resizable: resizable,\n                color: color\n            )\n        case .asteriskCicleFill:\n            makeSystemImageIcon(\n                name: \"asterisk.circle.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .checkmark:\n            makeSystemImageIcon(\n                name: \"checkmark\",\n                resizable: resizable,\n                color: color\n            )\n        case .close:\n            makeSystemImageIcon(\n                name: \"xmark\",\n                resizable: resizable,\n                color: color\n            )\n        case .backArrow:\n            makeSystemImageIcon(\n                name: \"arrow.backward\",\n                resizable: resizable,\n                color: color\n            )\n        case .forwardArrow:\n            makeSystemImageIcon(\n                name: \"arrow.forward\",\n                resizable: resizable,\n                color: color\n            )\n        case .chevronForward:\n            makeSystemImageIcon(\n                name: \"chevron.forward\",\n                resizable: resizable,\n                color: color\n            )\n        case .chevronBackward:\n            makeSystemImageIcon(\n                name: \"chevron.backward\",\n                resizable: resizable,\n                color: color\n            )\n        case .play:\n            makeSystemImageIcon(\n                name: \"play.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .pause:\n            makeSystemImageIcon(\n                name: \"pause\",\n                resizable: resizable,\n                color: color\n            )\n        case .mute:\n            makeSystemImageIcon(\n                name: \"speaker.slash.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .unmute:\n            makeSystemImageIcon(\n                name: \"speaker.wave.2.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .exclamationmarkCircleFill:\n            makeSystemImageIcon(\n                name: \"exclamationmark.circle.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .star:\n            makeSystemImageIcon(\n                name: \"star\",\n                resizable: resizable,\n                color: color\n            )\n        case .starFill:\n            makeSystemImageIcon(\n                name: \"star.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .heart:\n            makeSystemImageIcon(\n                name: \"heart\",\n                resizable: resizable,\n                color: color\n            )\n        case .heartFill:\n            makeSystemImageIcon(\n                name: \"heart.fill\",\n                resizable: resizable,\n                color: color\n            )\n        case .progressSpinner:\n            ProgressSpinnerIconView(\n                resizable: resizable,\n                color: color\n            )\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    static func icon(\n        info: ThomasIconInfo,\n        colorScheme: ColorScheme,\n        resizable: Bool = true\n    ) -> some View {\n        makeView(\n            icon: info.icon,\n            resizable: resizable,\n            color: info.color.toColor(colorScheme)\n        )\n        .aspectRatio(contentMode: .fit)\n        .airshipApplyIf(info.scale != nil) { view in\n            view.scaleEffect(info.scale ?? 1)\n        }\n    }\n}\n\n@MainActor\nprivate struct ProgressSpinnerIconView: View {\n    let resizable: Bool\n    let color: Color\n    // Only used for < 18\n    @State private var isSpinning: Bool = false\n    \n    var body: some View {\n        if #available(iOS 18.0, visionOS 2.0, *) {\n            Icons.makeSystemImageIcon(\n                name: \"progress.indicator\",\n                resizable: resizable,\n                color: color\n            )\n            .symbolEffect(.variableColor.iterative, options: .repeat(.continuous))\n        } else {\n            Icons.makeSystemImageIcon(\n                name: \"rays\",\n                resizable: resizable,\n                color: color\n            )\n            .rotationEffect(.degrees(isSpinning ? 360 : 0))\n            .onAppear {\n                withAnimation(.linear(duration: 1.2).repeatForever(autoreverses: false)) {\n                    isSpinning = true\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Image.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(UIKit)\npublic import UIKit\npublic typealias AirshipNativeImage = UIImage\n#elseif canImport(AppKit)\npublic import AppKit\npublic typealias AirshipNativeImage = NSImage\n#endif\n\n@preconcurrency\nimport ImageIO\n\n\n/// - Note: for internal use only.  :nodoc:\npublic final class AirshipImageData: Sendable {\n    // Image frame\n    struct Frame {\n        let image: AirshipNativeImage\n        let duration: TimeInterval\n    }\n\n    static let minFrameDuration: TimeInterval = 0.01\n    private let source: CGImageSource\n    private let imageActor: AirshipImageDataFrameActor\n    \n    let isAnimated: Bool\n    let imageFramesCount: Int\n    let loopCount: Int?\n\n    init(_ source: CGImageSource) throws {\n        self.source = source\n        imageFramesCount = CGImageSourceGetCount(source)\n        if imageFramesCount < 1 {\n            throw AirshipErrors.error(\"Invalid image, no frames.\")\n        }\n\n        self.loopCount = source.gifLoopCount()\n        self.isAnimated = imageFramesCount > 1\n        self.imageActor = AirshipImageDataFrameActor(source: source)\n    }\n\n    public convenience init(data: Data) throws {\n        guard let source = CGImageSourceCreateWithData(data as CFData, nil)\n        else {\n            throw AirshipErrors.error(\"Invalid image data\")\n        }\n\n        try self.init(source)\n    }\n\n    func loadFrames() async -> [Frame] {\n        await withCheckedContinuation { continuation in\n            DispatchQueue.global(qos: .userInitiated).async {\n                let frames = Self.frames(from: self.source)\n                DispatchQueue.main.async {\n                    continuation.resume(returning: frames)\n                }\n            }\n        }\n    }\n\n    func getActor() -> AirshipImageDataFrameActor {\n        return self.imageActor\n    }\n\n    private class func frames(from source: CGImageSource) -> [Frame] {\n        let count = CGImageSourceGetCount(source)\n        guard count > 1 else {\n            guard let image = AirshipImageDataFrameActor.frameImage(0, source: source) else {\n                return []\n            }\n            return [Frame(image: image, duration: 0.0)]\n        }\n\n        var frames: [Frame] = []\n        for i in 0..<count {\n            guard let image = AirshipImageDataFrameActor.frameImage(i, source: source) else {\n                continue\n            }\n\n            frames.append(\n                Frame(\n                    image: image,\n                    duration: AirshipImageDataFrameActor.frameDuration(i, source: source)\n                )\n            )\n        }\n        return frames\n    }\n}\n\nactor AirshipImageDataFrameActor {\n    private let source: CGImageSource\n\n    let framesCount: Int\n\n    init(source: CGImageSource) {\n        self.source = source\n        framesCount = CGImageSourceGetCount(source)\n    }\n\n    func loadFrame(at index: Int) -> AirshipImageData.Frame? {\n        guard index >= 0, index < framesCount else { return nil }\n\n        guard let image = Self.frameImage(index, source: source) else {\n            return nil\n        }\n\n        return AirshipImageData.Frame(\n            image: image,\n            duration: Self.frameDuration(index, source: source)\n        )\n    }\n\n    fileprivate static func frameImage(\n        _ index: Int,\n        source: CGImageSource\n    ) -> AirshipNativeImage? {\n        guard let imageRef = CGImageSourceCreateImageAtIndex(source, index, nil)\n        else {\n            return nil\n        }\n        // Use a cross-platform initializer\n        return AirshipNativeImage.make(with: imageRef)\n    }\n\n    fileprivate static func frameDuration(\n        _ index: Int,\n        source: CGImageSource\n    ) -> TimeInterval {\n\n        guard\n            let properties = imageProperties(index: index, source: source)\n        else {\n            return AirshipImageData.minFrameDuration\n        }\n\n        let delayTime = properties[kCGImageAnimationDelayTime as String] as? TimeInterval\n        let gifDelayTime = properties[[kCGImagePropertyGIFUnclampedDelayTime as String]] as? TimeInterval\n\n        return max(gifDelayTime ?? delayTime ?? 0.0, AirshipImageData.minFrameDuration)\n    }\n\n    fileprivate static func imageProperties(\n        index: Int,\n        source: CGImageSource\n    ) -> [AnyHashable: Any]? {\n        guard\n            let properties = CGImageSourceCopyPropertiesAtIndex(\n                source,\n                index,\n                nil\n            ) as? [AnyHashable: Any]\n        else {\n            return nil\n        }\n\n        let gif = properties[\n            kCGImagePropertyGIFDictionary as String\n        ] as? [AnyHashable: Any]\n\n        let webp = properties[\n            kCGImagePropertyWebPDictionary as String\n        ] as? [AnyHashable: Any]\n\n        return gif ?? webp\n    }\n}\n\n\nextension CGImageSource {\n    func gifLoopCount() -> Int? {\n        guard let properties = CGImageSourceCopyProperties(self, nil) as NSDictionary?,\n              let gifDictionary = properties[kCGImagePropertyGIFDictionary] as? NSDictionary else {\n            return nil\n        }\n\n        let loopCount = gifDictionary[kCGImagePropertyGIFLoopCount] as? Int\n        return loopCount\n    }\n}\n\nfileprivate extension AirshipNativeImage {\n    static func make(with cgImage: CGImage) -> AirshipNativeImage {\n#if os(macOS)\n        return NSImage(cgImage: cgImage, size: .zero) // .zero size uses the pixel dimensions\n#else\n        return UIImage(cgImage: cgImage)\n#endif\n    }\n}\n\npublic extension Image {\n    /// Bridges UIImage and NSImage into a single SwiftUI Image initializer\n    init(airshipNativeImage: AirshipNativeImage) {\n        #if os(macOS)\n        self.init(nsImage: airshipNativeImage)\n        #else\n        self.init(uiImage: airshipNativeImage)\n        #endif\n    }\n}\n\n\npublic extension AirshipNativeImage {\n    /// Cross-platform initializer for SF Symbols\n    static func airshipSystemImage(name: String) -> AirshipNativeImage? {\n        #if os(macOS)\n        return NSImage(systemSymbolName: name, accessibilityDescription: nil)\n        #else\n        return UIImage(systemName: name)\n        #endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ImageButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n/// Image Button view.\nstruct ImageButton : View {\n \n    /// Image Button model.\n    private let info: ThomasViewInfo.ImageButton\n\n    /// View constraints.\n    private let constraints: ViewConstraints\n  \n    @Environment(\\.colorScheme) private var colorScheme\n    @Environment(\\.layoutState) private var layoutState\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    init(info: ThomasViewInfo.ImageButton, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .imageButton,\n            thomasState: thomasState\n        )\n    }\n\n    @ViewBuilder\n    var body: some View {\n        AirshipButton(\n            identifier: self.info.properties.identifier,\n            reportingMetadata: self.info.properties.reportingMetadata,\n            description: self.info.accessible.resolveContentDescription,\n            clickBehaviors: self.info.properties.clickBehaviors,\n            eventHandlers: self.info.commonProperties.eventHandlers,\n            actions: self.info.properties.actions,\n            tapEffect: self.info.properties.tapEffect\n        ) {\n            makeInnerButton()\n                .constraints(constraints, fixedSize: true)\n                .thomasCommon(self.info, scope: [.background])\n                .accessible(\n                    self.info.accessible,\n                    associatedLabel: self.associatedLabel,\n                    hideIfDescriptionIsMissing: false\n                )\n                .background(Color.airshipTappableClear)\n        }\n        .thomasCommon(self.info, scope: [.enableBehaviors, .visibility])\n        .environment(\n            \\.layoutState,\n             layoutState.override(\n                buttonState: ButtonState(identifier: self.info.properties.identifier)\n             )\n        )\n        .accessibilityHidden(info.accessible.accessibilityHidden ?? false)\n    }\n    \n    @ViewBuilder\n    private func makeInnerButton() -> some View {\n        switch(self.info.properties.image) {\n        case .url(let info):\n            ThomasAsyncImage(\n                url: info.url,\n                imageLoader: thomasEnvironment.imageLoader,\n                image: { image, imageSize in\n                    image.fitMedia(\n                        mediaFit: info.mediaFit ?? .centerInside,\n                        cropPosition: info.cropPosition,\n                        constraints: constraints,\n                        imageSize: imageSize\n                    )\n                },\n                placeholder: {\n                    AirshipProgressView()\n                }\n            )\n        case .icon(let info):\n            Icons.icon(info: info, colorScheme: colorScheme)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JSONMatcher.swift",
    "content": "// Copyright Airship and Contributors\n\npublic import Foundation\n\n/// A matcher for evaluating a JSON payload against a set of criteria.\n///\n/// `JSONMatcher` allows you to specify conditions for a JSON value, optionally at a specific key or nested path (`scope`),\n/// and then evaluate if a given JSON object meets those conditions.\npublic final class JSONMatcher: NSObject, Sendable, Codable {\n\n    /// The key to look for in the JSON object.\n    private let key: String?\n\n    /// The path to the value within the JSON object.\n    private let scope: [String]?\n\n    /// The matcher to apply to the found JSON value.\n    private let valueMatcher: JSONValueMatcher\n\n    /// If `true`, string comparisons will ignore case.\n    private let ignoreCase: Bool?\n\n    /// Private designated initializer.\n    init(\n        valueMatcher: JSONValueMatcher,\n        key: String?,\n        scope: [String]?,\n        ignoreCase: Bool?\n    ) {\n        self.valueMatcher = valueMatcher\n        self.key = key\n        self.scope = scope\n        self.ignoreCase = ignoreCase\n        super.init()\n    }\n\n    /// Coding keys for backward compatibility.\n    private enum CodingKeys: String, CodingKey {\n        case key\n        case scope\n        case valueMatcher = \"value\"\n        case ignoreCase = \"ignore_case\"\n    }\n\n    /// Creates a new `JSONMatcher`.\n    /// - Parameter valueMatcher: The `JSONValueMatcher` to apply to the value.\n    /// - Returns: A new `JSONMatcher` instance.\n    public convenience init(valueMatcher: JSONValueMatcher) {\n        self.init(\n            valueMatcher: valueMatcher,\n            key: nil,\n            scope: nil,\n            ignoreCase: nil\n        )\n    }\n\n    /// Creates a new `JSONMatcher` with a specified scope.\n    /// - Parameters:\n    ///   - valueMatcher: The `JSONValueMatcher` to apply to the value.\n    ///   - scope: An array of keys representing the path to the value.\n    /// - Returns: A new `JSONMatcher` instance.\n    public convenience init(valueMatcher: JSONValueMatcher, scope: [String]) {\n        self.init(\n            valueMatcher: valueMatcher,\n            key: nil,\n            scope: scope,\n            ignoreCase: nil\n        )\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public convenience init(\n        valueMatcher: JSONValueMatcher,\n        scope: [String],\n        ignoreCase: Bool\n    ) {\n        self.init(\n            valueMatcher: valueMatcher,\n            key: nil,\n            scope: scope,\n            ignoreCase: ignoreCase\n        )\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public convenience init(\n        valueMatcher: JSONValueMatcher,\n        ignoreCase: Bool\n    ) {\n        self.init(\n            valueMatcher: valueMatcher,\n            key: nil,\n            scope: nil,\n            ignoreCase: ignoreCase\n        )\n    }\n\n    /// Evaluates the given `AirshipJSON` value against the matcher's criteria.\n    ///\n    /// This method traverses the JSON object using the `scope` and `key` to find the target value,\n    /// then uses the `valueMatcher` to perform the evaluation.\n    ///\n    /// - Parameter json: The `AirshipJSON` object to evaluate.\n    /// - Returns: `true` if the value matches the criteria; otherwise, `false`.\n    public func evaluate(json: AirshipJSON) -> Bool {\n        var paths: [String] = []\n        if let scope = scope {\n            paths.append(contentsOf: scope)\n        }\n\n        if let key = key {\n            paths.append(key)\n        }\n\n        var object = json\n        for path in paths {\n            guard let obj = object.object else {\n                object = .null\n                break\n            }\n            object = obj[path] ?? .null\n        }\n\n        return valueMatcher.evaluate(json: object, ignoreCase: self.ignoreCase ?? false)\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public override func isEqual(_ other: Any?) -> Bool {\n        guard let matcher = other as? JSONMatcher else {\n            return false\n        }\n\n        if self === matcher {\n            return true\n        }\n\n        return isEqual(to: matcher)\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public func isEqual(to matcher: JSONMatcher) -> Bool {\n        guard self.valueMatcher == matcher.valueMatcher,\n              self.key == matcher.key,\n              self.scope == matcher.scope,\n              self.ignoreCase ?? false == matcher.ignoreCase ?? false\n        else {\n            return false\n        }\n\n        return true\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public override var hash: Int {\n        var hasher = Hasher()\n        hasher.combine(valueMatcher)\n        hasher.combine(key)\n        hasher.combine(scope)\n        hasher.combine(ignoreCase)\n        return hasher.finalize()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JSONPredicate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Defines a predicate for evaluating a JSON payload.\n///\n/// `JSONPredicate` can be used to build complex logical conditions (`AND`, `OR`, `NOT`)\n/// composed of multiple `JSONMatcher` objects.\npublic final class JSONPredicate: NSObject, Sendable, Codable {\n    /// Key for the 'AND' logical operator.\n    private static let andTypeKey: String = \"and\"\n    /// Key for the 'OR' logical operator.\n    private static let orTypeKey: String = \"or\"\n    /// Key for the 'NOT' logical operator.\n    private static let notTypeKey: String = \"not\"\n\n    /// The type of logical operation (e.g., \"and\", \"or\", \"not\").\n    private let type: String?\n\n    /// The collection of sub-predicates for logical operations.\n    private let subpredicates: [JSONPredicate]?\n\n    /// The matcher to apply if this is a leaf predicate.\n    private let jsonMatcher: JSONMatcher?\n\n    /// Designated initializer.\n    required init(\n        type: String?,\n        jsonMatcher: JSONMatcher?,\n        subpredicates: [JSONPredicate]?\n    ) {\n        self.type = type\n        self.jsonMatcher = jsonMatcher\n        self.subpredicates = subpredicates\n        super.init()\n    }\n\n    /// Coding keys for serialization.\n    private enum CodingKeys: String, CodingKey, CaseIterable {\n        case keyAnd = \"and\"\n        case keyOr = \"or\"\n        case keyNot = \"not\"\n    }\n\n    /// Creates a new predicate from a JSON payload.\n    ///\n    /// - Parameter json: The JSON payload representing the predicate.\n    /// - Throws: An error if the JSON is invalid or cannot be decoded.\n    public convenience init(json: Any?) throws {\n        let value: JSONPredicate = try AirshipJSON.wrap(json).decode()\n        self.init(type: value.type, jsonMatcher: value.jsonMatcher, subpredicates: value.subpredicates)\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public convenience init(from decoder: any Decoder) throws {\n        // This implementation is for backward compatibility and may be refactored.\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        if let key = CodingKeys.allCases.first(where: { container.contains($0) }) {\n            let subpredicates: [JSONPredicate]\n\n            if key == CodingKeys.keyNot {\n                // Handle 'not' which can contain a single predicate or an array with one predicate\n                if let singlePredicate = try? container.decode(JSONPredicate.self, forKey: key) {\n                    subpredicates = [singlePredicate]\n                } else {\n                    let predicates = try container.decode([JSONPredicate].self, forKey: key)\n                    guard predicates.count == 1 else {\n                        throw AirshipErrors.error(\"A `not` predicate must contain a single sub-predicate.\")\n                    }\n                    subpredicates = predicates\n                }\n            } else {\n                subpredicates = try container.decode([JSONPredicate].self, forKey: key)\n            }\n\n            self.init(\n                type: key.rawValue,\n                jsonMatcher: nil,\n                subpredicates: subpredicates\n            )\n        } else {\n            let matcher = try decoder.singleValueContainer().decode(JSONMatcher.self)\n            self.init(jsonMatcher: matcher)\n        }\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public func encode(to encoder: any Encoder) throws {\n        if let jsonMatcher {\n            var container = encoder.singleValueContainer()\n            try container.encode(jsonMatcher)\n            return\n        }\n\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        let key: CodingKeys\n        switch(type) {\n        case CodingKeys.keyAnd.rawValue: key = .keyAnd\n        case CodingKeys.keyOr.rawValue: key = .keyOr\n        case CodingKeys.keyNot.rawValue: key = .keyNot\n        default: throw AirshipErrors.error(\"Invalid predicate type \\(type ?? \"n/a\")\")\n        }\n\n        try container.encodeIfPresent(self.subpredicates, forKey: key)\n    }\n\n    /// Evaluates the given `AirshipJSON` value against the predicate.\n    /// - Parameter json: The `AirshipJSON` object to evaluate.\n    /// - Returns: `true` if the value matches the predicate; otherwise, `false`.\n    public func evaluate(json: AirshipJSON) -> Bool {\n        switch type {\n        case JSONPredicate.andTypeKey:\n            // All sub-predicates must be true\n            return subpredicates?.allSatisfy { $0.evaluate(json: json) } ?? true\n        case JSONPredicate.orTypeKey:\n            // At least one sub-predicate must be true\n            return subpredicates?.contains { $0.evaluate(json: json) } ?? false\n        case JSONPredicate.notTypeKey:\n            // The single sub-predicate must be false\n            return !(subpredicates?.first?.evaluate(json: json) ?? false)\n        default:\n            // Evaluate using the JSON matcher\n            return jsonMatcher?.evaluate(json: json) ?? false\n        }\n    }\n\n    /// Creates a predicate from a `JSONMatcher`.\n    /// - Parameter matcher: The `JSONMatcher` to base the predicate on.\n    public convenience init(jsonMatcher matcher: JSONMatcher) {\n        self.init(type: nil, jsonMatcher: matcher, subpredicates: nil)\n    }\n\n    /// Creates a predicate by AND-ing an array of sub-predicates.\n    /// - Parameter subpredicates: An array of predicates to combine.\n    /// - Returns: A new `JSONPredicate` instance.\n    public class func andPredicate(subpredicates: [JSONPredicate]) -> JSONPredicate {\n        return JSONPredicate(\n            type: JSONPredicate.andTypeKey,\n            jsonMatcher: nil,\n            subpredicates: subpredicates\n        )\n    }\n\n    /// Creates a predicate by OR-ing an array of sub-predicates.\n    /// - Parameter subpredicates: An array of predicates to combine.\n    /// - Returns: A new `JSONPredicate` instance.\n    public class func orPredicate(subpredicates: [JSONPredicate]) -> JSONPredicate {\n        return JSONPredicate(\n            type: JSONPredicate.orTypeKey,\n            jsonMatcher: nil,\n            subpredicates: subpredicates\n        )\n    }\n\n    /// Creates a predicate by NOT-ing a single sub-predicate.\n    /// - Parameter subpredicate: The predicate to negate.\n    /// - Returns: A new `JSONPredicate` instance.\n    public class func notPredicate(subpredicate: JSONPredicate) -> JSONPredicate {\n        return JSONPredicate(\n            type: JSONPredicate.notTypeKey,\n            jsonMatcher: nil,\n            subpredicates: [subpredicate]\n        )\n    }\n\n    /// Creates a predicate from a JSON payload.\n    /// - Parameter json: The JSON payload.\n    /// - Returns: A predicate or `nil` if the JSON is invalid.\n    /// - Throws: An error if the JSON cannot be parsed.\n    class func fromJson(json: Any?) throws -> JSONPredicate {\n        return try JSONPredicate(json: json)\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    func isEqual(to predicate: JSONPredicate) -> Bool {\n        return type == predicate.type\n            && jsonMatcher == predicate.jsonMatcher\n            && subpredicates == predicate.subpredicates\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public override func isEqual(_ object: Any?) -> Bool {\n        guard let predicate = object as? JSONPredicate else {\n            return false\n        }\n\n        if self === predicate {\n            return true\n        }\n\n        return isEqual(to: predicate)\n    }\n\n    /// - Note: For internal use only. :nodoc:\n    public override var hash: Int {\n        var hasher = Hasher()\n        hasher.combine(type)\n        hasher.combine(jsonMatcher)\n        hasher.combine(subpredicates)\n        return hasher.finalize()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JSONValueMatcher.swift",
    "content": "// Copyright Airship and Contributors\n\npublic import Foundation\n\n/// A `JSONValueMatcher` is used to match a JSON value against a set of constraints.\n///\n/// This class provides a flexible way to define conditions for JSON values, such as checking for equality,\n/// numerical ranges, presence of a value, version constraints, and conditions on array elements.\n/// It is `Codable`, allowing it to be easily serialized and deserialized.\npublic final class JSONValueMatcher: NSObject, Sendable, Codable {\n\n    /// A protocol for defining the specific logic of a JSON value matcher.\n    /// Each predicate implementation checks a JSON value against a specific condition.\n    public protocol Predicate: Codable, Sendable, Hashable, Equatable {\n        /// Evaluates the predicate against a given JSON value.\n        /// - Parameters:\n        ///   - json: The `AirshipJSON` value to evaluate.\n        ///   - ignoreCase: If `true`, string comparisons will be case-insensitive.\n        /// - Returns: `true` if the JSON value matches the predicate's conditions, otherwise `false`.\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool\n\n        /// Checks if this predicate is equal to another predicate.\n        /// - Parameter other: The other predicate to compare against.\n        /// - Returns: `true` if the predicates are equal, otherwise `false`.\n        func isEqual(to other: any Predicate) -> Bool\n    }\n\n    private let predicate: any Predicate\n\n    public init(from decoder: any Decoder) throws {\n        // Decode the predicate using a helper function to avoid compiler timeouts.\n        guard let predicate = Self.decodePredicate(from: decoder) else {\n            throw AirshipErrors.parseError(\"Unsupported JSONValueMatcher predicate\")\n        }\n        self.predicate = predicate\n    }\n\n    /// A helper function to decode one of the possible predicate types.\n    private static func decodePredicate(from decoder: any Decoder) -> (any JSONValueMatcher.Predicate)? {\n        // The decoding order matters and is designed to match Android/Backend implementations.\n        if let predicate = try? JSONValueMatcher.EqualsPredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.NumberRangePredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.PresencePredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.VersionPredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.ArrayLengthPredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.ArrayContainsPredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.StringBeginsPredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.StringEndsPredicate(from: decoder) { return predicate }\n        if let predicate = try? JSONValueMatcher.StringContainsPredicate(from: decoder) { return predicate }\n        return nil\n    }\n\n    init(predicate: any Predicate) {\n        self.predicate = predicate\n    }\n    \n    /// Creates a matcher that requires a number to be at least a minimum value.\n    /// - Parameter atLeast: The minimum acceptable value.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereNumberAtLeast(\n        _ atLeast: Double\n    )-> JSONValueMatcher {\n        return .init(\n            predicate: NumberRangePredicate(atLeast: atLeast)\n        )\n    }\n\n    /// Creates a matcher that requires a number to be within a specified range.\n    /// - Parameters:\n    ///   - atLeast: The minimum acceptable value (inclusive).\n    ///   - atMost: The maximum acceptable value (inclusive).\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereNumberAtLeast(\n        _ atLeast: Double,\n        atMost: Double\n    ) -> JSONValueMatcher {\n        return .init(\n            predicate: NumberRangePredicate(\n                atLeast: atLeast,\n                atMost: atMost\n            )\n        )\n    }\n    \n    /// Creates a matcher that requires a number to be at most a maximum value.\n    /// - Parameter atMost: The maximum acceptable value.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereNumberAtMost(\n        _ atMost: Double\n    ) -> JSONValueMatcher {\n        return .init(\n            predicate: NumberRangePredicate(\n                atMost: atMost\n            )\n        )\n    }\n\n    /// Creates a matcher for an exact number value.\n    /// - Parameter number: The exact number to match.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereNumberEquals(\n        to number: Double\n    ) -> JSONValueMatcher {\n        return .init(\n            predicate: EqualsPredicate(\n                equals: .number(number)\n            )\n        )\n    }\n    \n    /// Creates a matcher for an exact boolean value.\n    /// - Parameter boolean: The exact boolean to match.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereBooleanEquals(\n        _ boolean: Bool\n    ) -> JSONValueMatcher {\n        return .init(\n            predicate: EqualsPredicate(\n                equals: .bool(boolean)\n            )\n        )\n    }\n\n    /// Creates a matcher for an exact string value.\n    /// - Parameter string: The exact string to match.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereStringEquals(\n        _ string: String\n    ) -> JSONValueMatcher {\n        return .init(\n            predicate: EqualsPredicate(\n                equals: .string(string)\n            )\n        )\n    }\n\n    /// Creates a matcher that checks for the presence or absence of a value.\n    /// - Parameter present: If `true`, the value must exist (not be null). If `false`, it must not.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWhereValueIsPresent(\n        _ present: Bool\n    ) -> JSONValueMatcher {\n        return .init(\n            predicate: PresencePredicate(\n                isPresent: present\n            )\n        )\n    }\n\n    /// Creates a matcher that checks a value against a version constraint.\n    /// The value being checked is expected to be a string representing a version.\n    /// - Parameter versionConstraint: The version constraint string (e.g., \"1.0.0+\", \"[1.0, 2.0)\").\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWithVersionConstraint(\n        _ versionConstraint: String\n    ) -> JSONValueMatcher? {\n        return .init(\n            predicate: VersionPredicate(\n                versionConstraint: versionConstraint\n            )\n        )\n    }\n\n    /// Creates a matcher that checks if an array contains an element that matches a `JSONPredicate`.\n    /// - Parameter predicate: The predicate to apply to elements in the array.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWithArrayContainsPredicate(\n        _ predicate: JSONPredicate\n    ) -> JSONValueMatcher? {\n        return .init(\n            predicate: ArrayContainsPredicate(arrayContains: predicate)\n        )\n    }\n\n    /// Creates a matcher that checks if an array element at a specific index matches a `JSONPredicate`.\n    /// - Parameters:\n    ///   - predicate: The predicate to apply to the element at the specified index.\n    ///   - index: The index of the array element to check.\n    /// - Returns: A `JSONValueMatcher` for the specified condition.\n    public class func matcherWithArrayContainsPredicate(\n        _ predicate: JSONPredicate,\n        at index: Int\n    ) -> JSONValueMatcher? {\n        return .init(\n            predicate: ArrayContainsPredicate(\n                arrayContains: predicate,\n                index: index\n            )\n        )\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        try self.predicate.encode(to: encoder)\n    }\n\n    /// Evaluates the given `AirshipJSON` value against the matcher.\n    /// - Parameters:\n    ///   - json: The `AirshipJSON` value to evaluate.\n    ///   - ignoreCase: If `true`, string comparisons will be case-insensitive.\n    /// - Returns: `true` if the value matches, otherwise `false`.\n    public func evaluate(json: AirshipJSON, ignoreCase: Bool = false) -> Bool {\n        self.predicate.evaluate(json: json, ignoreCase: ignoreCase)\n    }\n\n    public override func isEqual(_ other: Any?) -> Bool {\n        guard let matcher = other as? JSONValueMatcher else {\n            return false\n        }\n\n        if self === matcher {\n            return true\n        }\n\n        return predicate.isEqual(to: matcher.predicate)\n    }\n\n    public func hash() -> Int {\n        return predicate.hashValue\n    }\n}\n\nextension JSONValueMatcher.Predicate {\n    func isEqual(to other: any JSONValueMatcher.Predicate) -> Bool {\n        // Attempt to cast the `other` existential to our own concrete type (`Self`).\n        guard let otherAsSelf = other as? Self else {\n            // If the types are different (e.g., comparing EqualsPredicate to\n            // NumberRangePredicate), the cast will fail and they are not equal.\n            return false\n        }\n\n        // If the types are the same, we can now use the concrete `==`\n        // implementation provided by the Equatable conformance.\n        return self == otherAsSelf\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JSONValueMatcherPredicates.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n\nextension JSONValueMatcher {\n\n    struct VersionPredicate: Predicate {\n        let versionConstraint: String\n\n        init(versionConstraint: String) {\n            self.versionConstraint = versionConstraint\n        }\n        \n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let version = json.string else {\n                return false\n            }\n\n            do {\n                let versionMatcher = try AirshipIvyVersionMatcher(versionConstraint: versionConstraint)\n                return versionMatcher.evaluate(version: version)\n            } catch {\n                AirshipLogger.error(\"Invalid constraint \\(versionConstraint)\")\n            }\n\n            return false\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case versionMatches = \"version_matches\"\n            case deprecatedVersionMatches = \"version\"\n        }\n\n        func encode(to encoder: any Encoder) throws {\n            var containter = encoder.container(keyedBy: CodingKeys.self)\n            try containter.encode(versionConstraint, forKey: .versionMatches)\n        }\n\n        init(from decoder: any Decoder) throws {\n            let containter = try decoder.container(keyedBy: CodingKeys.self)\n            if let value = try containter.decodeIfPresent(String.self, forKey: .versionMatches) {\n                self.versionConstraint = value\n            } else {\n                self.versionConstraint = try containter.decode(String.self, forKey: .deprecatedVersionMatches)\n            }\n        }\n    }\n\n    struct PresencePredicate: Predicate {\n        var isPresent: Bool\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            return json.isNull != isPresent\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case isPresent = \"is_present\"\n        }\n    }\n\n    struct EqualsPredicate: Predicate {\n        var equals: AirshipJSON\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            return if ignoreCase {\n                isEqualIgnoreCase(valueOne: equals, valueTwo: json)\n            } else {\n                equals == json\n            }\n        }\n\n\n        func isEqualIgnoreCase(valueOne: AirshipJSON, valueTwo: AirshipJSON) -> Bool {\n            if let string = valueOne.string, let otherString = valueTwo.string {\n                return string.normalizedIgnoreCaseComparison() == otherString.normalizedIgnoreCaseComparison()\n            }\n\n            if let array = valueOne.array, let otherArray = valueTwo.array {\n                guard array.count == otherArray.count else {\n                    return false\n                }\n\n                for (index, element) in array.enumerated() {\n                    guard isEqualIgnoreCase(valueOne: element, valueTwo: otherArray[index]) else {\n                        return false\n                    }\n                }\n\n                return true\n            }\n\n            if let object = valueOne.object, let otherObject = valueTwo.object {\n                guard object.count == otherObject.count else {\n                    return false\n                }\n\n                for (key, value) in object {\n                    guard\n                        let otherValue = otherObject[key],\n                        isEqualIgnoreCase(valueOne: value, valueTwo: otherValue)\n                    else {\n                        return false\n                    }\n                }\n\n                return true\n            }\n\n            // Remaining types - bool, number, mismatch types\n            return valueOne == valueTwo\n        }\n    }\n\n    struct NumberRangePredicate: Predicate {\n        var atLeast: Double?\n        var atMost: Double?\n\n        init (atLeast: Double? = nil, atMost: Double? = nil) {\n            self.atLeast = atLeast\n            self.atMost = atMost\n        }\n        \n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let number = json.number else {\n                return false\n            }\n\n            if let atLeast, number < atLeast {\n                return false\n            }\n\n            if let atMost, number > atMost {\n                return false\n            }\n            \n            return true\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.atLeast = try container.decodeIfPresent(Double.self, forKey: .atLeast)\n            self.atMost = try container.decodeIfPresent(Double.self, forKey: .atMost)\n            guard self.atLeast != nil || self.atMost != nil else {\n                throw AirshipErrors.parseError(\"Invalid number range predicate\")\n            }\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case atLeast = \"at_least\"\n            case atMost = \"at_most\"\n        }\n    }\n\n    struct ArrayContainsPredicate: Predicate {\n        var arrayContains: JSONPredicate\n        var index: Int?\n\n        enum CodingKeys: String, CodingKey {\n            case arrayContains = \"array_contains\"\n            case index\n        }\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let array = json.array else {\n                return false\n            }\n\n            if let index {\n                guard array.count > index else {\n                    return false\n                }\n\n                return arrayContains.evaluate(json: array[index])\n            } else {\n                return array.contains { value in\n                    arrayContains.evaluate(json: value)\n                }\n            }\n        }\n    }\n\n    struct ArrayLengthPredicate: Predicate {\n        var arrayLength: JSONPredicate\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let length = json.array?.count else {\n                return false\n            }\n\n            return arrayLength.evaluate(json: .number(Double(length)))\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case arrayLength = \"array_length\"\n        }\n    }\n\n    struct StringBeginsPredicate: Predicate {\n        var stringBegins: String\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let string = json.string else { return false }\n\n            return if ignoreCase {\n                string.normalizedIgnoreCaseComparison().hasPrefix(\n                    stringBegins.normalizedIgnoreCaseComparison()\n                )\n            } else {\n                string.hasPrefix(stringBegins)\n            }\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case stringBegins = \"string_begins\"\n        }\n    }\n\n    struct StringEndsPredicate: Predicate {\n        var stringEnds: String\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let string = json.string else { return false }\n            return if ignoreCase {\n                string.normalizedIgnoreCaseComparison().hasSuffix(\n                    stringEnds.normalizedIgnoreCaseComparison()\n                )\n            } else {\n                string.hasSuffix(stringEnds)\n            }\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case stringEnds = \"string_ends\"\n        }\n    }\n\n    struct StringContainsPredicate: Predicate {\n        var stringContains: String\n\n        func evaluate(json: AirshipJSON, ignoreCase: Bool) -> Bool {\n            guard let string = json.string else { return false }\n            return if ignoreCase {\n                string.normalizedIgnoreCaseComparison().contains(\n                    stringContains.normalizedIgnoreCaseComparison()\n                )\n            } else {\n                string.contains(stringContains)\n            }\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case stringContains = \"string_contains\"\n        }\n    }\n}\n\nfileprivate extension String {\n    /// Returns a normalized representation of the string for case- and diacritic-insensitive comparisons.\n    ///\n    /// This method \"folds\" the string into a simplified form by removing case distinctions (e.g., \"a\" vs. \"A\")\n    /// and diacritical marks (e.g., \"é\" vs. \"e\"). The resulting string is suitable for reliable,\n    /// locale-agnostic comparisons where variations in case or accents should be ignored.\n    ///\n    /// - Returns: A normalized string, ready for comparison.\n    func normalizedIgnoreCaseComparison() -> String {\n        return self.folding(\n            options: [.caseInsensitive, .diacriticInsensitive],\n            locale: nil\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JSONValueTransformer.swift",
    "content": "/* Copyright Airship and Contributors */\n\n// Legacy transformers. We still need these around for coredata migrations.\n// Do not use these anymore, its almost always a better idea to use Codables\n// instead.\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic class JSONValueTransformer: ValueTransformer {\n\n    public override class func transformedValueClass() -> AnyClass {\n        return NSData.self\n    }\n\n    public override class func allowsReverseTransformation() -> Bool {\n        return true\n    }\n\n    public override func transformedValue(_ value: Any?) -> Any? {\n\n        guard let value = value else {\n            return nil\n        }\n\n        do {\n            return try AirshipJSONUtils.data(\n                value,\n                options: JSONSerialization.WritingOptions.prettyPrinted\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n\n    public override func reverseTransformedValue(_ value: Any?) -> Any? {\n\n        guard let value = value as? Data else {\n            return nil\n        }\n\n        do {\n            return try JSONSerialization.jsonObject(\n                with: value,\n                options: .mutableContainers\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to reverse transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic class NSDictionaryValueTransformer: ValueTransformer {\n\n    public override class func transformedValueClass() -> AnyClass {\n        return NSData.self\n    }\n\n    public override class func allowsReverseTransformation() -> Bool {\n        return true\n    }\n\n    public override func transformedValue(_ value: Any?) -> Any? {\n        guard let value = value else {\n            return nil\n        }\n\n        do {\n            return try NSKeyedArchiver.archivedData(\n                withRootObject: value,\n                requiringSecureCoding: true\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n\n    public override func reverseTransformedValue(_ value: Any?) -> Any? {\n        guard let value = value as? Data else {\n            return nil\n        }\n\n        do {\n            let classes = [\n                NSString.self, NSDictionary.self, NSArray.self, NSSet.self,\n                NSData.self,\n                NSNumber.self, NSDate.self, NSURL.self, NSUUID.self,\n                NSNull.self,\n            ]\n            return try NSKeyedUnarchiver.unarchivedObject(\n                ofClasses: classes,\n                from: value\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to reverse transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n}\n\n// NOTE: For internal use only. :nodoc:\npublic class NSURLValueTransformer: ValueTransformer {\n\n    public override class func transformedValueClass() -> AnyClass {\n        return NSData.self\n    }\n\n    public override class func allowsReverseTransformation() -> Bool {\n        return true\n    }\n\n    public override func transformedValue(_ value: Any?) -> Any? {\n        guard let value = value else {\n            return nil\n        }\n\n        do {\n            return try NSKeyedArchiver.archivedData(\n                withRootObject: value,\n                requiringSecureCoding: true\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n\n    public override func reverseTransformedValue(_ value: Any?) -> Any? {\n        guard let value = value as? Data else {\n            return nil\n        }\n\n        do {\n            return try NSKeyedUnarchiver.unarchivedObject(\n                ofClass: NSURL.self,\n                from: value\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to reverse transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n}\n\n// NOTE: For internal use only. :nodoc:\npublic class NSArrayValueTransformer: ValueTransformer {\n\n    public override class func transformedValueClass() -> AnyClass {\n        return NSData.self\n    }\n\n    public override class func allowsReverseTransformation() -> Bool {\n        return true\n    }\n\n    public override func transformedValue(_ value: Any?) -> Any? {\n        guard let value = value else {\n            return nil\n        }\n\n        do {\n            return try NSKeyedArchiver.archivedData(\n                withRootObject: value,\n                requiringSecureCoding: true\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n\n    public override func reverseTransformedValue(_ value: Any?) -> Any? {\n        guard let value = value as? Data else {\n            return nil\n        }\n\n        do {\n            let classes = [\n                NSString.self, NSDictionary.self, NSArray.self, NSSet.self,\n                NSData.self,\n                NSNumber.self, NSDate.self, NSURL.self, NSUUID.self,\n                NSNull.self,\n            ]\n            return try NSKeyedUnarchiver.unarchivedObject(\n                ofClasses: classes,\n                from: value\n            )\n        } catch {\n            AirshipLogger.error(\n                \"Failed to reverse transform value: \\(value), error: \\(error)\"\n            )\n            return nil\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JavaScriptCommand.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\npublic import Foundation\n\n// Model object for holding data associated with JS delegate calls\npublic struct JavaScriptCommand: Sendable, CustomDebugStringConvertible {\n\n    // A name, derived from the host passed in the delegate call URL.\n    public let name: String?\n\n    // The argument strings passed in the call.\n    public let arguments: [String]\n\n    // The query options passed in the call.\n    public let options: [String: [String]]\n\n    // The original URL that initiated the call.\n    public let url: URL\n\n    public init(url: URL) {\n        self.url = url\n\n        let components = URLComponents(url: url, resolvingAgainstBaseURL: false)\n        var encodedUrlPath = components?.percentEncodedPath\n        if let path = encodedUrlPath, path.hasPrefix(\"/\") {\n            encodedUrlPath = String(path.dropFirst())\n        }\n\n        // Put the arguments into an array\n        // NOTE: we special case an empty array as componentsSeparatedByString\n        // returns an array with a copy of the input in the first position when passed\n        // a string without any delimiters\n        var args: [String] = []\n        if let encodedUrlPath = encodedUrlPath, !encodedUrlPath.isEmpty {\n            let encodedArgs = encodedUrlPath.components(separatedBy: \"/\")\n\n            args = encodedArgs.compactMap { encoded in\n                encoded.removingPercentEncoding\n            }\n        }\n\n        self.arguments = args\n\n        var options: [String: [String]] = [:]\n        components?.queryItems?.forEach { item in\n            if (options[item.name] == nil) {\n                options[item.name] = []\n            }\n\n            options[item.name]?.append(item.value ?? \"\")\n        }\n\n        self.options = options\n        self.name = url.host\n    }\n\n    public var debugDescription: String {\n        \"JavaScriptCommand{name=\\(String(describing: name)), options=\\(options)}, arguments=\\(arguments), url=\\(url)})\"\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JavaScriptCommandDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\npublic import WebKit\n\n/// A standard protocol for handling commands from the NativeBridge..\npublic protocol JavaScriptCommandDelegate: AnyObject, Sendable {\n    /// Delegates must implement this method. Implementations take a model object representing\n    /// the JavaScript command which includes the command name, an array of string arguments,\n    /// and a dictionary of key-value pairs (all strings).\n    ///\n    /// If the passed command name is not one the delegate responds to return `NO`. If the command is handled, return\n    /// `YES` and the command will not be handled by another delegate.\n    ///\n    /// To pass information to the delegate from a webview, insert links with a \"uairship\" scheme,\n    /// args in the path and key-value option pairs in the query string. The host\n    /// portion of the URL is treated as the command name.\n    ///\n    /// The basic URL format:\n    /// uairship:///command-name/<args>?<key/value options>\n    ///\n    /// For example, to invoke a command named \"foo\", and pass in three args (arg1, arg2 and arg3)\n    /// and three key-value options {option1:one, option2:two, option3:three}:\n    ///\n    /// uairship:///foo/arg1/arg2/arg3?option1=one&amp;option2=two&amp;option3=three\n    ///\n    /// - Parameter command: The javascript command\n    /// - Parameter webView: The web view\n    /// - Returns: `true` if the command was handled, otherwise `false`\n    @MainActor\n    func performCommand(_ command: JavaScriptCommand, webView: WKWebView) -> Bool\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/JavaScriptEnvironment.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\n\n/// Protocol for building the Airship JavaScript environment injected into web views.\npublic protocol JavaScriptEnvironmentProtocol: Sendable {\n\n    /// Adds a string getter to the Airship JavaScript environment.\n    /// - Parameter getter: The getter's name.\n    /// - Parameter string: The getter's value.\n    func add(_ getter: String, string: String?)\n\n    /// Adds a number getter to the Airship JavaScript environment.\n    /// - Parameter getter: The getter's name.\n    /// - Parameter number: The getter's value.\n    func add(_ getter: String, number: Double?)\n    \n    /// Adds a dictionary getter to the Airship JavaScript environment.\n    /// - Parameter getter: The getter's name.\n    /// - Parameter dictionary: The getter's value.\n    func add(_ getter: String, dictionary: [AnyHashable: Any]?)\n\n    /**\n     * Builds the script that can be injected into a web view.\n     * - Returns: The script.\n     */\n    func build() async -> String\n}\n\n\n/// The JavaScript environment builder that is used by the native bridge.\npublic final class JavaScriptEnvironment: JavaScriptEnvironmentProtocol, Sendable {\n\n    private let extensions: AirshipAtomicValue<[String]> = AirshipAtomicValue([String]())\n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n\n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier()\n        )\n    }\n\n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact\n    ) {\n        self.channel = channel\n        self.contact = contact\n    }\n\n    public func add(_ getter: String, string: String?) {\n        self.addExtension(\n            makeGetter(name: getter, string: string)\n        )\n    }\n    \n    public func add(_ getter: String, number: Double?) {\n        self.addExtension(\n            makeGetter(name: getter, number: number)\n        )\n    }\n\n    public func add(_ getter: String, dictionary: [AnyHashable: Any]?) {\n        self.addExtension(\n            makeGetter(name: getter, dictionary: dictionary)\n        )\n    }\n\n    public func build() async -> String {\n        var js = \"var _UAirship = {};\"\n        var extensions: [String] = await self.makeDefaultExtensions()\n        extensions += self.extensions.value\n\n        for ext in extensions {\n            js = js.appending(ext)\n        }\n\n        guard\n            let path = AirshipCoreResources.bundle.path(\n                forResource: \"UANativeBridge\",\n                ofType: \"\"\n            )\n        else {\n            AirshipLogger.impError(\n                \"UANativeBridge resource file is missing.\"\n            )\n            return js\n        }\n\n        let bridge: String\n\n        do {\n            bridge = try String(contentsOfFile: path, encoding: .utf8)\n        } catch {\n            AirshipLogger.impError(\n                \"UANativeBridge resource file is missing.\"\n            )\n            return js\n        }\n\n        return js.appending(bridge)\n    }\n\n    private func makeDefaultExtensions() async -> [String] {\n        return await [\n            makeGetter(\n                name: \"getDeviceModel\",\n                string: AirshipDevice.model\n            ),\n\n            makeGetter(\n                name: \"getNamedUser\",\n                string: self.contact().namedUserID\n            ),\n\n            makeGetter(\n                name: \"getChannelId\",\n                string: self.channel().identifier\n            ),\n\n            makeGetter(\n                name: \"getAppKey\",\n                string: Airship.config.appCredentials.appKey\n            )\n        ]\n    }\n\n    private func addExtension(_ ext: String) {\n        self.extensions.update { current in\n            var mutable = current\n            mutable.append(ext)\n            return mutable\n        }\n    }\n\n    private func makeGetter(\n        name: String,\n        string: String?\n    ) -> String {\n        guard let value = string else {\n            return String(\n                format: \"_UAirship.%@ = function() {return null;};\",\n                name\n            )\n        }\n\n        let encodedValue = value.addingPercentEncoding(\n            withAllowedCharacters: CharacterSet.urlHostAllowed\n        )\n        return String(\n            format:\n                \"_UAirship.%@ = function() {return decodeURIComponent(\\\"%@\\\");};\",\n            name,\n            encodedValue ?? \"\"\n        )\n    }\n\n    private func makeGetter(\n        name: String,\n        number: Double?\n    ) -> String {\n        String(\n            format: \"_UAirship.%@ = function() {return %@;};\",\n            name,\n            String(number ?? -1)\n        )\n    }\n\n    private func makeGetter(name: String, dictionary: [AnyHashable: Any]?) -> String {\n        guard let value = dictionary,\n              JSONSerialization.isValidJSONObject(value),\n              let jsonData: Data = try? JSONSerialization.data(\n                  withJSONObject: value,\n                  options: []\n              ),\n              let jsonString = String.init(data: jsonData, encoding: .utf8)\n        else {\n            return String(\n                format: \"_UAirship.%@ = function() {return null;};\",\n                name\n            )\n        }\n\n        return String(\n            format: \"_UAirship.%@ = function() {return %@;};\",\n            name,\n            jsonString\n        )\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Label.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n/// Text/Label view\n\nstruct Label: View {\n    private let info: ThomasViewInfo.Label\n\n    /// View constraints.\n    private let constraints: ViewConstraints\n\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.colorScheme) private var colorScheme\n    @Environment(\\.sizeCategory) private var sizeCategory\n\n    static let defaultHighlightColor: Color = Color(\n        red: 1.0,\n        green: 0.84,\n        blue: 0.04,\n        opacity: 0.3\n    )\n\n    init(info: ThomasViewInfo.Label, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var scaledFontSize: Double {\n        AirshipFont.scaledSize(self.info.properties.textAppearance.fontSize)\n    }\n\n    private var resolvedEndIcon: ThomasViewInfo.Label.LabelIcon? {\n        return ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: self.info.overrides?.iconEnd,\n            defaultValue: self.info.properties.iconEnd\n        )\n    }\n\n    private var resolvedStartIcon: ThomasViewInfo.Label.LabelIcon? {\n        return ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: self.info.overrides?.iconStart,\n            defaultValue: self.info.properties.iconStart\n        )\n    }\n\n    private var resolvedTextAppearance: ThomasTextAppearance {\n        return ThomasPropertyOverride.resolveRequired(\n            state: thomasState,\n            overrides: self.info.overrides?.textAppearance,\n            defaultValue: self.info.properties.textAppearance\n        )\n    }\n\n    private var textView: Text {\n        guard\n            self.info.properties.markdown?.disabled != true\n        else {\n            return Text(verbatim: info.resolveLabelString(thomasState: thomasState))\n        }\n\n        do {\n            return try markdownText\n        } catch {\n            let resolved = info.resolveLabelString(thomasState: thomasState)\n            AirshipLogger.error(\"Failed to parse markdown text \\(error) text \\(resolved)\")\n            return Text(verbatim: resolved)\n        }\n    }\n\n    var body: some View {\n        HStack(spacing: 0) {\n            if let icon = resolvedStartIcon {\n                let size = scaledFontSize\n                Icons.icon(info: icon.icon, colorScheme: colorScheme)\n                    .frame(width: size, height: size)\n                    .padding(.trailing, icon.space)\n                    .accessibilityHidden(true)\n            }\n\n            if #available(iOS 26.0, visionOS 26.0, *) {\n                self.textView\n                    .textAppearance(resolvedTextAppearance, colorScheme: colorScheme)\n                    .truncationMode(.tail)\n                    .textRenderer(HighlightRenderer())\n            } else {\n                self.textView\n                    .textAppearance(resolvedTextAppearance, colorScheme: colorScheme)\n                    .truncationMode(.tail)\n            }\n\n\n            if let icon = resolvedEndIcon {\n                // Add a spacer if we are not auto to push the icon to the edge\n                if constraints.width != nil {\n                    Spacer()\n                }\n\n                let size = scaledFontSize\n                Icons.icon(info: icon.icon, colorScheme: colorScheme)\n                    .frame(width: size, height: size)\n                    .padding(.leading, icon.space)\n                    .accessibilityHidden(true)\n            }\n        }\n        .constraints(\n            constraints,\n            alignment: self.info.properties.textAppearance.alignment?\n                .toFrameAlignment()\n                ?? Alignment.center\n        )\n        .fixedSize(\n            horizontal: false,\n            vertical: self.constraints.height == nil\n        )\n        .thomasCommon(self.info)\n        .accessible(self.info.accessible, associatedLabel: nil, hideIfDescriptionIsMissing: true)\n        .accessibilityRole(self.info.properties.accessibilityRole)\n        .onAppear {\n            if self.info.properties.isAccessibilityAlert == true {\n                let message = self.info.resolveLabelString(thomasState: self.thomasState)\n#if !os(watchOS) && !os(macOS)\n                UIAccessibility.post(notification: .announcement, argument: message)\n#endif\n            }\n        }\n    }\n}\n\nextension ThomasTextAppearance.TextAlignement {\n    func toFrameAlignment() -> Alignment {\n        switch self {\n        case .start:\n            return Alignment.leading\n        case .end:\n            return Alignment.trailing\n        case .center:\n            return Alignment.center\n        }\n    }\n\n    func toSwiftTextAlignment() -> SwiftUI.TextAlignment {\n        switch self {\n        case .start:\n            return SwiftUI.TextAlignment.leading\n        case .end:\n            return SwiftUI.TextAlignment.trailing\n        case .center:\n            return SwiftUI.TextAlignment.center\n        }\n    }\n}\n\nextension View {\n    fileprivate func headingLevel(_ int: Int) -> AccessibilityHeadingLevel {\n        switch int {\n        case 1:\n            return .h1\n        case 2:\n            return .h2\n        case 3:\n            return .h3\n        case 4:\n            return .h4\n        case 5:\n            return .h5\n        case 6:\n            return .h6\n        default:\n            return .unspecified\n        }\n    }\n\n    @ViewBuilder\n    fileprivate func accessibilityRole(_ role: ThomasViewInfo.Label.AccessibilityRole?) -> some View  {\n        switch role {\n        case .heading(let level):\n            self.accessibilityAddTraits(.isHeader)\n                .accessibilityHeading(headingLevel(level))\n        case .none:\n            self\n        }\n    }\n}\n\nextension ThomasViewInfo.Label {\n    @MainActor\n    func resolveLabelString(thomasState: ThomasState) -> String {\n        let resolvedRefs = ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: overrides?.refs,\n            defaultValue: properties.refs\n        )\n\n        let resolvedRef = ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: overrides?.ref,\n            defaultValue: properties.ref\n        )\n\n        if let refs = resolvedRefs {\n            for ref in refs {\n                if let string = AirshipResources.localizedString(key: ref) {\n                    return string\n                }\n            }\n        } else if let ref = resolvedRef {\n            if let string = AirshipResources.localizedString(key: ref) {\n                return string\n            }\n        }\n\n        return ThomasPropertyOverride.resolveRequired(\n            state: thomasState,\n            overrides: overrides?.text,\n            defaultValue: properties.text\n        )\n    }\n}\n\n\nextension Label {\n    private static let scriptFontScale: Double = 0.65\n    private static let superscriptBaselineScale: Double = 0.4\n    private static let subscriptBaselineScale: Double = 0.2\n\n    private struct Segment {\n        let range: Range<AttributedString.Index>\n        let isHighlight: Bool\n        let isSuperscript: Bool\n        let isSubscript: Bool\n    }\n\n    private struct DelimitedRange {\n        /// Full range including the delimiter characters themselves.\n        let outer: Range<AttributedString.Index>\n        /// Inner content range, excluding the delimiter characters.\n        let inner: Range<AttributedString.Index>\n    }\n\n    private func findDelimitedRanges(\n        in chars: AttributedString.CharacterView,\n        open: (Character, Character),\n        close: (Character, Character)\n    ) -> [DelimitedRange] {\n        var results: [DelimitedRange] = []\n        let end = chars.endIndex\n        var i = chars.startIndex\n\n        while i < end {\n            let next = chars.index(after: i)\n            guard next < end else { break }\n\n            if chars[i] == open.0, chars[next] == open.1 {\n                let outerStart = i\n                let openEnd = chars.index(after: next)\n                var j = openEnd\n                var foundClose = false\n\n                while j < end {\n                    let jNext = chars.index(after: j)\n                    if jNext < end, chars[j] == close.0, chars[jNext] == close.1 {\n                        let innerStart = openEnd\n                        let innerEnd = j\n                        let outerEnd = chars.index(after: jNext)\n                        if innerStart < innerEnd {\n                            results.append(DelimitedRange(\n                                outer: outerStart..<outerEnd,\n                                inner: innerStart..<innerEnd\n                            ))\n                        }\n                        i = outerEnd\n                        foundClose = true\n                        break\n                    }\n                    j = chars.index(after: j)\n                }\n\n                if !foundClose {\n                    i = chars.index(after: i)\n                }\n            } else {\n                i = next\n            }\n        }\n\n        return results\n    }\n\n    private func formatSegments(in attributed: AttributedString) -> [Segment] {\n        let chars = attributed.characters\n        let start = chars.startIndex\n        let end = chars.endIndex\n\n        let highlightRanges   = findDelimitedRanges(in: chars, open: (\"=\", \"=\"), close: (\"=\", \"=\"))\n        let superscriptRanges = findDelimitedRanges(in: chars, open: (\"^\", \"^\"), close: (\"^\", \"^\"))\n        let subscriptRanges   = findDelimitedRanges(in: chars, open: (\",\", \"{\"), close: (\"}\", \",\"))\n\n        // Build boundaries from both outer and inner edges so delimiter\n        // characters form their own sub-segments and can be skipped.\n        var boundarySet: [AttributedString.Index] = [start, end]\n        for r in highlightRanges + superscriptRanges + subscriptRanges {\n            boundarySet.append(r.outer.lowerBound)\n            boundarySet.append(r.inner.lowerBound)\n            boundarySet.append(r.inner.upperBound)\n            boundarySet.append(r.outer.upperBound)\n        }\n        let boundaries = boundarySet\n            .sorted { $0 < $1 }\n            .reduce(into: [AttributedString.Index]()) { result, idx in\n                if result.last != idx { result.append(idx) }\n            }\n\n        var segments: [Segment] = []\n        for idx in 0..<(boundaries.count - 1) {\n            let segStart = boundaries[idx]\n            let segEnd = boundaries[idx + 1]\n            guard segStart < segEnd else { continue }\n\n            // Use midpoint to determine which ranges contain this segment.\n            let mid = chars.index(segStart, offsetBy: chars.distance(from: segStart, to: segEnd) / 2)\n\n            // A segment is a \"delimiter\" if it falls inside an outer range but\n            // outside that range's inner content — skip it so delimiters are invisible.\n            let isDelimiter =\n                highlightRanges.contains   { $0.outer.contains(mid) && !$0.inner.contains(mid) } ||\n                superscriptRanges.contains { $0.outer.contains(mid) && !$0.inner.contains(mid) } ||\n                subscriptRanges.contains   { $0.outer.contains(mid) && !$0.inner.contains(mid) }\n\n            if isDelimiter { continue }\n\n            let isHighlight   = highlightRanges.contains   { $0.inner.contains(mid) }\n            let isSuperscript = superscriptRanges.contains { $0.inner.contains(mid) }\n            let isSubscript   = subscriptRanges.contains   { $0.inner.contains(mid) }\n\n            segments.append(\n                Segment(\n                    range: segStart..<segEnd,\n                    isHighlight: isHighlight,\n                    isSuperscript: isSuperscript,\n                    isSubscript: isSubscript\n                )\n            )\n        }\n\n        return segments\n    }\n\n    private var markdownText: Text {\n        get throws {\n            let resolved = info.resolveLabelString(thomasState: thomasState)\n\n            // Parse markdown into attributed\n            let attributed = try AttributedString(\n                markdown: resolved,\n                options: .init(interpretedSyntax: .inlineOnlyPreservingWhitespace)\n            )\n\n            let highlightOptions = self.info.properties.markdown?.appearance?.highlight\n            let fontSize = scaledFontSize\n\n            // Find format segments INSIDE the attributed string\n            let segments = formatSegments(in: attributed)\n\n            // Build Text by slicing\n            var result: Text?\n\n            for seg in segments {\n                var slice = attributed[seg.range]\n                let piece: Text\n\n                if seg.isSuperscript || seg.isSubscript {\n                    slice.baselineOffset = seg.isSuperscript\n                        ? fontSize * Self.superscriptBaselineScale\n                        : -(fontSize * Self.subscriptBaselineScale)\n                    slice.font = AirshipFont.resolveFont(\n                        size: resolvedTextAppearance.fontSize * Self.scriptFontScale,\n                        families: resolvedTextAppearance.fontFamilies,\n                        weight: resolvedTextAppearance.fontWeight,\n                        isItalic: resolvedTextAppearance.hasStyle(.italic),\n                        isBold: resolvedTextAppearance.hasStyle(.bold)\n                    )\n                }\n\n                if seg.isHighlight {\n                    // For custom cornerRadius we have to use a custom attribute and renderer\n                    if #available(iOS 18.0, visionOS 2.0, *), let cornerRadius = highlightOptions?.cornerRadius {\n                        let highlight = HighlightAttribute(\n                            color: highlightOptions?.color?.toColor(colorScheme) ?? Self.defaultHighlightColor,\n                            cornerRadius: cornerRadius\n                        )\n                        piece = Text(AttributedString(slice))\n                            .customAttribute(highlight)\n                    } else {\n                        slice.backgroundColor = highlightOptions?.color?.toColor(colorScheme) ?? Self.defaultHighlightColor\n                        piece = Text(AttributedString(slice))\n                    }\n                } else {\n                    piece = Text(AttributedString(slice))\n                }\n\n                result = result.map { $0 + piece } ?? piece\n            }\n\n            return result ?? Text(attributed)\n        }\n    }\n}\n\n@available(iOS 18.0, *)\nstruct HighlightAttribute: TextAttribute {\n    let color: Color\n    let cornerRadius: CGFloat\n}\n\n@available(iOS 18.0, *)\nstruct HighlightRenderer: TextRenderer {\n    struct Cluster {\n        var rect: CGRect\n        var attr: HighlightAttribute\n    }\n\n    func draw(layout: Text.Layout, in context: inout GraphicsContext) {\n        for line in layout {\n            var clusters: [Cluster] = []\n            var currentRect: CGRect?\n            var currentAttr: HighlightAttribute?\n\n            for run in line {\n                if let highlight = run[HighlightAttribute.self] {\n                    let runRect = run.typographicBounds.rect\n\n                    if var rect = currentRect, let attr = currentAttr,\n                       attr.color == highlight.color,\n                       attr.cornerRadius == highlight.cornerRadius {\n                        rect = rect.union(runRect)\n                        currentRect = rect\n                        currentAttr = highlight\n                    } else {\n                        // flush previous cluster\n                        if let rect = currentRect, let attr = currentAttr {\n                            clusters.append(Cluster(rect: rect, attr: attr))\n                        }\n                        currentRect = runRect\n                        currentAttr = highlight\n                    }\n                } else {\n                    // end of a cluster\n                    if let rect = currentRect, let attr = currentAttr {\n                        clusters.append(Cluster(rect: rect, attr: attr))\n                        currentRect = nil\n                        currentAttr = nil\n                    }\n                }\n            }\n\n            if let rect = currentRect, let attr = currentAttr {\n                clusters.append(Cluster(rect: rect, attr: attr))\n            }\n\n            for cluster in clusters {\n                let path = Path(\n                    roundedRect: cluster.rect,\n                    cornerRadius: cluster.attr.cornerRadius\n                )\n                context.fill(path, with: .color(cluster.attr.color))\n            }\n\n            for run in line {\n                context.draw(run)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LabelButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Button view.\nstruct LabelButton : View {\n\n    private let info: ThomasViewInfo.LabelButton\n    private let constraints: ViewConstraints\n    @Environment(\\.layoutState) private var layoutState\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .labelButton,\n            thomasState: thomasState\n        )\n    }\n\n    init(info: ThomasViewInfo.LabelButton, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    @ViewBuilder\n    private var labelContent: some View {\n        Label(\n            info: self.info.properties.label,\n            constraints: ViewConstraints()\n        )\n        .airshipApplyIf(self.constraints.height == nil) { view in\n            view.padding([.bottom, .top], 12)\n        }\n        .airshipApplyIf(self.constraints.width == nil) { view in\n            view.padding([.leading, .trailing], 12)\n        }\n        .constraints(constraints)\n        .thomasCommon(info, scope: [.background])\n        .accessible(\n            self.info.accessible,\n            associatedLabel: associatedLabel,\n            hideIfDescriptionIsMissing: false\n        )\n        .background(Color.airshipTappableClear)\n    }\n\n    var body: some View {\n        AirshipButton(\n            identifier: self.info.properties.identifier,\n            reportingMetadata: self.info.properties.reportingMetadata,\n            description: self.info.accessible.contentDescription ?? self.info.properties.label.properties.text,\n            clickBehaviors: self.info.properties.clickBehaviors,\n            eventHandlers: self.info.commonProperties.eventHandlers,\n            actions: self.info.properties.actions,\n            tapEffect: self.info.properties.tapEffect\n        ) {\n            labelContent\n        }\n        .thomasCommon(info, scope: [.enableBehaviors, .visibility])\n        .environment(\n            \\.layoutState,\n             layoutState.override(\n                buttonState: ButtonState(identifier: self.info.properties.identifier)\n             )\n        )\n        .accessibilityHidden(info.accessible.accessibilityHidden ?? false)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LayoutState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct LayoutState: Sendable {\n    static let empty = LayoutState(\n        pagerState: nil,\n        formState: nil,\n        buttonState: nil\n    )\n\n    var pagerState: PagerState?\n    var formState: ThomasFormState?\n    var buttonState: ButtonState?\n\n    func override(pagerState: PagerState?) -> LayoutState {\n        var context = self\n        context.pagerState = pagerState\n        return context\n    }\n\n    func override(formState: ThomasFormState?) -> LayoutState {\n        var context = self\n        context.formState = formState\n        return context\n    }\n\n    func override(buttonState: ButtonState?) -> LayoutState {\n        var context = self\n        context.buttonState = buttonState\n        return context\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LinearLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Linear Layout - either a VStack or HStack depending on the direction.\n\nstruct LinearLayout: View {\n\n    /// LinearLayout model.\n    private let info: ThomasViewInfo.LinearLayout\n\n    /// View constraints.\n    private let constraints: ViewConstraints\n\n    @State\n    private var numberGenerator: RepeatableNumberGenerator = RepeatableNumberGenerator()\n\n    init(info: ThomasViewInfo.LinearLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    @ViewBuilder\n    @MainActor\n    private func makeVStack(\n        items: [ThomasViewInfo.LinearLayout.Item],\n        parentConstraints: ViewConstraints\n    ) -> some View {\n\n        VStack(alignment: .center, spacing: 0) {\n            ForEach(0..<items.count, id: \\.self) { index in\n#if os(tvOS)\n                HStack {\n                    childItem(items[index], parentConstraints: parentConstraints)\n                }\n                .frame(maxWidth: .infinity)\n                .focusSection()\n                .airshipApplyIf(\n                    items[index].hasPerItemAlignment(stackDirection: .vertical)\n                ) {\n                    $0.frame(\n                        maxWidth: .infinity,\n                        alignment: items[index].position?.alignment ?? .center\n                    )\n                }\n#else\n                childItem(items[index], parentConstraints: parentConstraints)\n                    .airshipApplyIf(\n                        items[index].hasPerItemAlignment(stackDirection: .vertical)\n                    ) {\n                        $0.frame(\n                            maxWidth: .infinity,\n                            alignment: items[index].position?.alignment ?? .center\n                        )\n                    }\n#endif\n            }\n        }\n        .airshipGeometryGroupCompat()\n        .accessibilityElement(children: .contain)\n        .constraints(self.constraints, alignment: .top)\n        .applyFixedSizeForAlignment(info: self.info, constraints: constraints)\n    }\n\n\n    @ViewBuilder\n    @MainActor\n    private func makeHStack(\n        items: [ThomasViewInfo.LinearLayout.Item],\n        parentConstraints: ViewConstraints\n    ) -> some View {\n\n        HStack(alignment: .center, spacing: 0) {\n            ForEach(0..<items.count, id: \\.self) { index in\n#if os(tvOS)\n                VStack {\n                    childItem(items[index], parentConstraints: parentConstraints)\n                }\n                .frame(maxHeight: .infinity)\n                .focusSection()\n                .airshipApplyIf(\n                    items[index].hasPerItemAlignment(stackDirection: .horizontal)\n                ) {\n                    $0.frame(\n                        maxHeight: .infinity,\n                        alignment: items[index].position?.alignment ?? .center\n                    )\n                }\n#else\n                childItem(items[index], parentConstraints: parentConstraints)\n                    .airshipApplyIf(\n                        items[index].hasPerItemAlignment(stackDirection: .horizontal)\n                    ) {\n                        $0.frame(\n                            maxHeight: .infinity,\n                            alignment: items[index].position?.alignment ?? .center\n                        )\n                    }\n#endif\n            }\n        }\n        .constraints(constraints, alignment: .leading)\n        .applyFixedSizeForAlignment(info: self.info, constraints: constraints)\n    }\n\n\n    @ViewBuilder\n    @MainActor\n    private func makeStack() -> some View {\n        if self.info.properties.direction == .vertical {\n            makeVStack(\n                items: orderedItems(),\n                parentConstraints: parentConstraints()\n            )\n        } else {\n            makeHStack(\n                items: orderedItems(),\n                parentConstraints: parentConstraints()\n            )\n        }\n    }\n\n    var body: some View {\n        makeStack()\n            .clipped()\n            .thomasCommon(self.info)\n    }\n\n    @ViewBuilder\n    @MainActor\n    private func childItem(\n        _ item: ThomasViewInfo.LinearLayout.Item,\n        parentConstraints: ViewConstraints\n    ) -> some View {\n        let constraints = parentConstraints.childConstraints(\n            item.size,\n            margin: item.margin,\n            padding: self.info.commonProperties.border?.strokeWidth ?? 0,\n            safeAreaInsetsMode: .consume\n        )\n\n        ViewFactory.createView(item.view, constraints: constraints)\n            .margin(item.margin)\n#if os(tvOS)\n            .focusSection()\n#endif\n    }\n\n    private func parentConstraints() -> ViewConstraints {\n        var constraints = self.constraints\n\n        if self.info.properties.direction == .vertical {\n            constraints.isVerticalFixedSize = false\n        } else {\n            constraints.isHorizontalFixedSize = false\n        }\n\n        return constraints\n    }\n\n    private func orderedItems() -> [ThomasViewInfo.LinearLayout.Item] {\n        guard self.info.properties.randomizeChildren == true else {\n            return self.info.properties.items\n        }\n        var generator = self.numberGenerator\n        generator.repeatNumbers()\n        return self.info.properties.items.shuffled(using: &generator)\n    }\n}\n\nclass RepeatableNumberGenerator: RandomNumberGenerator {\n    private var numbers: [UInt64] = []\n    private var index: Int = 0\n    private var numberGenerator: SystemRandomNumberGenerator = SystemRandomNumberGenerator()\n\n    func next() -> UInt64 {\n        defer {\n            self.index += 1\n        }\n\n        guard index < numbers.count else {\n            let next = numberGenerator.next()\n            numbers.append(next)\n            return next\n        }\n        return numbers[index]\n    }\n\n    func repeatNumbers() {\n        index = 0\n    }\n}\n\nfileprivate extension ThomasViewInfo.LinearLayout.Item {\n    func hasPerItemAlignment(stackDirection: ThomasDirection) -> Bool {\n        guard let position = self.position else { return false }\n        switch(stackDirection) {\n        case .horizontal: return position.vertical != .center\n        case .vertical: return position.horizontal != .center\n        }\n    }\n}\n\nfileprivate extension View {\n    @ViewBuilder\n    func applyFixedSizeForAlignment(\n        info: ThomasViewInfo.LinearLayout,\n        constraints: ViewConstraints\n    ) -> some View {\n        if info.properties.direction == .horizontal {\n            let hasPerItemAlignment = info.properties.items.contains {\n                $0.hasPerItemAlignment(stackDirection: .horizontal)\n            }\n\n            if hasPerItemAlignment, constraints.height == nil {\n                self.fixedSize(horizontal: false, vertical: true)\n            } else {\n                self\n            }\n        } else {\n            let hasPerItemAlignment = info.properties.items.contains {\n                $0.hasPerItemAlignment(stackDirection: .vertical)\n            }\n\n            if hasPerItemAlignment, constraints.width == nil {\n                self.fixedSize(horizontal: true, vertical: false)\n            } else {\n                self\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LiveActivity.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n\n/// LiveActivity protocol. Makes everything testable.\nprotocol LiveActivityPushToStartTrackerProtocol: Sendable {\n    var attributeType: String { get }\n    func track(tokenUpdates: @Sendable @escaping (String) async -> Void) async\n}\n\n/// LiveActivity protocol. Makes everything testable.\nprotocol LiveActivityProtocol: Sendable {\n    /// The activity's ID\n    var id: String { get }\n\n    var isUpdatable: Bool { get }\n\n    var pushTokenString: String? { get }\n\n    /// Awaits for the activity to be finished.\n    /// - Parameters:\n    ///     - tokenUpdates: A closure that is called whenever the token changes\n    func track(tokenUpdates: @Sendable @escaping (String) async -> Void) async\n}\n\n\n#if canImport(ActivityKit) && !targetEnvironment(macCatalyst) && !os(macOS)\n\npublic import ActivityKit\n\n@available(iOS 17.2, *)\nstruct LiveActivityPushToStartTracker<T: ActivityAttributes>: LiveActivityPushToStartTrackerProtocol {\n    var attributeType: String {\n        return String(describing: T.self)\n    }\n\n\n    func track(tokenUpdates: @escaping @Sendable (String) async -> Void) async {\n        var tokenString: String? = Activity<T>.pushToStartToken?.tokenString\n\n        if let tokenString = tokenString {\n            await tokenUpdates(tokenString)\n        }\n\n        for await token in Activity<T>.pushToStartTokenUpdates {\n            if Task.isCancelled {\n               break\n            }\n\n            let newTokenString = token.tokenString\n            if tokenString != newTokenString {\n                tokenString = newTokenString\n                await tokenUpdates(newTokenString)\n            }\n        }\n    }\n}\n\n\n@available(iOS 16.1, *)\nfileprivate struct ActivityProvider<T : ActivityAttributes>: Sendable {\n    public let id: String\n\n    func getActivity() -> Activity<T>? {\n        Activity<T>.activities.first { activity in\n            activity.id == id\n        }\n    }\n}\n\n\n@available(iOS 16.1, *)\nstruct LiveActivity<T: ActivityAttributes>: LiveActivityProtocol {\n    public let id: String\n\n    public var isUpdatable: Bool {\n        return provider.getActivity()?.activityState.isStaleOrActive ?? false\n    }\n\n    public var pushTokenString: String? {\n        return provider.getActivity()?.pushToken?.tokenString\n    }\n\n\n    fileprivate let provider: ActivityProvider<T>\n\n    init(activity: Activity<T>) {\n        self.id = activity.id\n        self.provider = ActivityProvider(id: activity.id)\n    }\n\n    /// Awaits for the activity to be finished.\n    /// - Parameters:\n    ///     - tokenUpdates: A closure that is called whenever the token changes\n    func track(tokenUpdates: @Sendable @escaping (String) async -> Void) async {\n        guard let activity = provider.getActivity(),\n              activity.activityState.isStaleOrActive\n        else {\n            return\n        }\n\n        // Use a background task to wait for the first token update\n        let backgroundTask = await AirshipBackgroundTask(\n            name: \"live_activity: \\(self.id)\",\n            expiry: 30.0\n        )\n\n        let task = Task {\n            // This nested function is a workaround for a Swift compiler Sendable warning.\n            // It avoids the Task's @Sendable closure from directly capturing the generic type.\n            func run() async throws {\n                guard let activity = provider.getActivity(),\n                      activity.activityState.isStaleOrActive\n                else {\n                    return\n                }\n\n                for await token in activity.pushTokenUpdates {\n                    if Task.isCancelled {\n                        await backgroundTask.stop()\n                        try Task.checkCancellation()\n                    }\n\n                    await tokenUpdates(token.tokenString)\n                    await backgroundTask.stop()\n                }\n            }\n\n            try await run()\n        }\n\n        /// If the push token is already created it does not cause an update above,\n        /// so we will call the tokenUpdate callback directly if we have a token.\n        if let token = activity.pushToken {\n            await tokenUpdates(token.tokenString)\n            await backgroundTask.stop()\n        }\n\n        for await update in activity.activityStateUpdates {\n            if !update.isStaleOrActive || Task.isCancelled {\n                await backgroundTask.stop()\n                task.cancel()\n                break\n            }\n        }\n    }\n}\n\n@available(iOS 16.1, *)\nextension ActivityState {\n    public var isStaleOrActive: Bool {\n        if #available(iOS 16.2, *) {\n            return self == .active || self == .stale\n        } else {\n            return self == .active\n        }\n    }\n}\n\n\nextension Data {\n    fileprivate var tokenString: String {\n        AirshipUtils.deviceTokenStringFromDeviceToken(self)\n    }\n}\n\n\n@MainActor\n@available(iOS 16.1, *)\nfileprivate class AirshipBackgroundTask {\n\n    private var taskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier.invalid\n\n    private let name: String\n\n    init(name: String, expiry: TimeInterval) {\n        self.name = name\n\n        taskID = UIApplication.shared.beginBackgroundTask(withName: name) {\n            Task { @MainActor [weak self] in\n                self?.stop()\n            }\n        }\n\n        if (taskID != UIBackgroundTaskIdentifier.invalid) {\n            AirshipLogger.trace(\"Background task started: \\(name)\")\n\n            Task {\n                try await Task.sleep(\n                    nanoseconds: UInt64(expiry * 1_000_000_000)\n                )\n                self.stop()\n            }\n        }\n    }\n\n    func stop() {\n        if (taskID != UIBackgroundTaskIdentifier.invalid) {\n            UIApplication.shared.endBackgroundTask(taskID)\n            AirshipLogger.trace(\"Background task ended: \\(name)\")\n        }\n\n        taskID = UIBackgroundTaskIdentifier.invalid\n    }\n}\n\n@available(iOS 16.1, *)\nextension Activity where Attributes : ActivityAttributes {\n\n    fileprivate class func _airshipCheckActivities(activityBlock: @escaping @Sendable (Activity<Attributes>) -> Void) {\n        self.activities.filter { activity in\n            if #available(iOS 16.2, *) {\n                return activity.activityState == .active || activity.activityState == .stale\n            } else {\n                return activity.activityState == .active\n            }\n        }.forEach { activity in\n            activityBlock(activity)\n        }\n    }\n\n    /// Calls `checkActivity` on every active activity on the first call and on each `pushToStartTokenUpdates` update.\n    /// - Parameters:\n    ///     - activityBlock: Block that is called with the activity\n    public class func airshipWatchActivities(activityBlock: @escaping @Sendable (Activity<Attributes>) -> Void) {\n        Task {\n            // This nested function is a workaround for a Swift compiler Sendable warning.\n            // It avoids the Task's @Sendable closure from directly capturing the generic type.\n            func run() async {\n                _airshipCheckActivities(activityBlock: activityBlock)\n                if #available(iOS 17.2, *) {\n                    for await _ in Activity<Attributes>.pushToStartTokenUpdates {\n                        _airshipCheckActivities(activityBlock: activityBlock)\n                    }\n                }\n            }\n            await run()\n        }\n    }\n}\n\n\n#endif\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LiveActivityRegistrationStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Airship live activity registration status.\npublic enum LiveActivityRegistrationStatus: String, Codable, Sendable {\n    /// The live activity is either waiting for a token to be generated and/or waiting for its registration to be sent\n    /// to Airship.\n    case pending\n\n    /// The live activity is registered with Airship and is now able to be updated through APNS.\n    case registered\n\n    /// Airship is not actively tracking the live activity. Usually this means it has ended, been replaced\n    /// with another activity using the same name, or was not tracked with Airship.\n    case notTracked\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LiveActivityRegistrationStatusUpdates.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// AsyncSequence of `LiveActivityRegistrationStatus` updates.\npublic struct LiveActivityRegistrationStatusUpdates: AsyncSequence {\n    public typealias Element = LiveActivityRegistrationStatus\n\n    private let statusProducer: @Sendable (LiveActivityRegistrationStatus?) async -> LiveActivityRegistrationStatus?\n\n    init(statusProducer: @escaping @Sendable (LiveActivityRegistrationStatus?) async -> LiveActivityRegistrationStatus?) {\n        self.statusProducer = statusProducer\n    }\n\n    public struct AsyncIterator: AsyncIteratorProtocol {\n        private let statusProducer: @Sendable (LiveActivityRegistrationStatus?) async -> LiveActivityRegistrationStatus?\n        private var lastStatus: LiveActivityRegistrationStatus?\n\n        init(statusProducer: @escaping @Sendable (LiveActivityRegistrationStatus?) async -> LiveActivityRegistrationStatus?) {\n            self.statusProducer = statusProducer\n        }\n\n        public mutating func next() async -> Element? {\n            let status = await statusProducer(lastStatus)\n            self.lastStatus = status\n            return status\n        }\n    }\n    \n    public func makeAsyncIterator() -> AsyncIterator {\n        return AsyncIterator(statusProducer: statusProducer)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LiveActivityRegistry.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(ActivityKit)\n\nimport Foundation\n\n@preconcurrency\nimport Combine\n\n/// Registers and watches live activities\nactor LiveActivityRegistry {\n\n    private nonisolated let liveActivityUpdatesSubject = CurrentValueSubject<Void, Never>(())\n\n    /// A stream of registry updates\n    let updates: AsyncStream<LiveActivityUpdate>\n\n    private static let maxActiveTime: TimeInterval = 8 * 60 * 60\n    private static let staleTokenAge: TimeInterval = 48 * 60 * 60\n\n    private let liveActivityKey: String = \"LiveaActivityRegister#tracked\"\n    private let startTokensKey: String = \"LiveaActivityRegister#trackedStartTokens\"\n\n    private var restoreCalled: Bool = false\n\n    private var liveActivityInfos: [LiveActivityInfo] {\n        get {\n            return self.dataStore.safeCodable(forKey: liveActivityKey) ?? []\n        }\n        set {\n            self.dataStore.setSafeCodable(newValue, forKey: liveActivityKey)\n        }\n\n    }\n    \n    private var startTokenInfos: [String: StartTokenInfo] {\n        get {\n            return self.dataStore.safeCodable(forKey: startTokensKey) ?? [:]\n        }\n        set {\n            self.dataStore.setSafeCodable(newValue, forKey: startTokensKey)\n        }\n    }\n\n    private var liveActivityTaskMap: [String: Task<Void, Never>] = [:]\n    private let updatesContinuation: AsyncStream<LiveActivityUpdate>.Continuation\n    private let dataStore: PreferenceDataStore\n    private let date: any AirshipDateProtocol\n\n    init(\n        dataStore: PreferenceDataStore,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.date = date\n        self.dataStore = dataStore\n        (self.updates, self.updatesContinuation) = AsyncStream<LiveActivityUpdate>.airshipMakeStreamWithContinuation()\n    }\n\n    /// For tests\n    func stop() {\n        self.updatesContinuation.finish()\n        self.liveActivityTaskMap.values.forEach { task in\n            task.cancel()\n        }\n    }\n\n    /// Should be called for all live activities right after takeOff.\n    /// - Parameters:\n    ///     - activities: An array of activities\n    func restoreTracking(\n        activities: [any LiveActivityProtocol],\n        startTokenTrackers: [any LiveActivityPushToStartTrackerProtocol]\n    ) {\n        guard !restoreCalled else {\n            AirshipLogger.error(\"Restore called mulitple times. Ignoring.\")\n            return\n        }\n        restoreCalled = true\n\n        /// Track push to start tokens\n        startTokenTrackers.forEach { tracker in\n            Task {\n                await tracker.track { token in\n                    await self.updateStartToken(attributeType: tracker.attributeType, token: token)\n                }\n            }\n        }\n\n        // Watch activities\n        activities.forEach { activity in\n            findLiveActivityInfos(id: activity.id).forEach { info in\n                AirshipLogger.debug(\n                    \"Live activity restore: \\(activity.id) name: \\(info.name)\"\n                )\n                watchActivity(activity, name: info.name)\n            }\n        }\n\n        clearUntrackedActivities(\n            currentActivityIDs: Set(activities.map { $0.id })\n        )\n\n        clearUntrackedStartTokens(\n            currentAttributeTypes: Set(startTokenTrackers.map { $0.attributeType })\n        )\n\n        resendStaleStartTokens()\n    }\n\n    func updatesProcessed(updates: [LiveActivityUpdate]) {\n        let setIds = updates.compactMap { update in\n            return if case .liveActivity(id: let id, name: _, startTimeMS: _) = update.source {\n                id\n            } else {\n                nil\n            }\n        }\n\n        guard !setIds.isEmpty else {\n            return\n        }\n\n        var infos = liveActivityInfos\n        setIds.forEach { updateId in\n            let index = infos.firstIndex(where: { info in\n                info.id == updateId\n            })\n\n            if let index = index {\n                infos[index].status = .registered\n            }\n        }\n\n        self.liveActivityInfos = infos\n        liveActivityUpdatesSubject.send()\n    }\n\n    @available(iOS 16.1, *)\n    public nonisolated func registrationUpdates(\n        name: String?,\n        id: String?\n    ) -> LiveActivityRegistrationStatusUpdates {\n\n        return LiveActivityRegistrationStatusUpdates { previous in\n            var async = self.liveActivityUpdatesSubject.values.map { _ in\n                return await self.findLiveActivityInfos(id: id, name: name).last?.status ?? .notTracked\n            }.makeAsyncIterator()\n\n            while !Task.isCancelled {\n                let status = await async.next()\n                if status != previous {\n                    return status\n                }\n            }\n\n            return nil\n        }\n    }\n\n\n    /// Adds a live activity to the registry. The activity will be monitored and\n    /// automatically removed after its finished.\n    func addLiveActivity(\n        _ liveActivity: any LiveActivityProtocol,\n        name: String\n    ) {\n        guard liveActivity.isUpdatable else {\n            return\n        }\n\n        guard findLiveActivityInfos(id: liveActivity.id, name: name).isEmpty else {\n            return\n        }\n\n        findLiveActivityInfos(name: name)\n            .forEach { info in\n                self.removeLiveActivity(id: info.id, name: info.name)\n            }\n\n        let info = LiveActivityInfo(\n            id: liveActivity.id,\n            name: name,\n            token: liveActivity.pushTokenString,\n            startDate: self.date.now,\n            status: .pending\n        )\n\n        self.liveActivityInfos.append(info)\n\n        if info.token != nil {\n            yieldLiveActivityUpdate(\n                info: info,\n                action: .set\n            )\n        }\n\n        watchActivity(liveActivity, name: info.name)\n        liveActivityUpdatesSubject.send()\n    }\n\n    private func watchActivity(\n        _ liveActivity: any LiveActivityProtocol,\n        name: String\n    ) {\n        let task: Task<Void, Never> = Task {\n\n            /// This will wait until the activity is no longer active\n            await liveActivity.track { token in\n                await self.updateLiveActivityToken(id: liveActivity.id, name: name, token: token)\n            }\n\n            self.removeLiveActivity(id: liveActivity.id, name: name)\n        }\n\n        liveActivityTaskMap[makeTaskID(id: liveActivity.id, name: name)] = task\n    }\n\n\n    private func clearUntrackedStartTokens(currentAttributeTypes: Set<String>) {\n        let shouldClear = self.startTokenInfos.values.filter { info in\n            !currentAttributeTypes.contains(info.attributeType)\n        }\n\n        shouldClear.forEach { info in\n            self.updateStartToken(attributeType: info.attributeType, token: nil)\n        }\n    }\n\n    /// Should be called after all activities have been restored.\n    private func clearUntrackedActivities(currentActivityIDs: Set<String>) {\n        let shouldClear = liveActivityInfos.filter { info in\n            !currentActivityIDs.contains(info.id)\n        }\n\n\n        shouldClear.forEach { info in\n            var date = self.date.now\n            let maxActiveDate = info.startDate.advanced(by: Self.maxActiveTime)\n\n            if date > maxActiveDate {\n                date = maxActiveDate\n            }\n\n            removeLiveActivity(\n                id: info.id,\n                name: info.name,\n                date: date\n            )\n        }\n\n        liveActivityUpdatesSubject.send()\n    }\n\n    private func resendStaleStartTokens() {\n        self.startTokenInfos.values.filter { info in\n            self.date.now.timeIntervalSince(info.sentDate) > Self.staleTokenAge\n        }.forEach { info in\n            yieldStartTokenUpdate(attributeType: info.attributeType, token: info.token)\n        }\n    }\n\n    private func updateStartToken(attributeType: String, token: String?) {\n        let existing = self.startTokenInfos[attributeType]\n\n        guard let token = token else {\n            if (existing != nil) {\n                self.startTokenInfos[attributeType] = nil\n                yieldStartTokenUpdate(attributeType: attributeType, token: nil)\n            }\n            return\n        }\n\n        guard token != existing?.token else { return }\n\n        self.startTokenInfos[attributeType] = StartTokenInfo(\n            attributeType: attributeType,\n            token: token,\n            sentDate: self.date.now\n        )\n\n        yieldStartTokenUpdate(attributeType: attributeType, token: token)\n    }\n\n    private func updateLiveActivityToken(id: String, name: String, token: String) {\n        var tracked = self.liveActivityInfos\n\n        for index in 0..<tracked.count {\n            if tracked[index].id == id && tracked[index].name == name {\n                if tracked[index].token != token {\n                    tracked[index].token = token\n                    yieldLiveActivityUpdate(info: tracked[index], action: .set)\n                }\n                break\n            }\n        }\n\n        self.liveActivityInfos = tracked\n    }\n    \n    private func removeLiveActivity(\n        id: String,\n        name: String,\n        date: Date? = nil\n    ) {\n        let taskID = makeTaskID(id: id, name: name)\n        liveActivityTaskMap[taskID]?.cancel()\n        liveActivityTaskMap[taskID] = nil\n\n        self.liveActivityInfos.removeAll { info in\n            if info.name == name && info.id == id {\n                if info.token != nil {\n                    yieldLiveActivityUpdate(info: info, action: .remove, date: date)\n                }\n                return true\n            }\n\n            return false\n        }\n\n        liveActivityUpdatesSubject.send()\n    }\n\n    private func yieldLiveActivityUpdate(\n        info: LiveActivityInfo,\n        action: LiveActivityUpdate.Action,\n        date: Date? = nil\n    ) {\n        let actionDate = date ?? self.date.now\n        \n        let update = LiveActivityUpdate(\n            action: action,\n            source: .liveActivity(id: info.id, name: info.name, startTimeMS: info.startDate.millisecondsSince1970),\n            actionTimeMS: actionDate.millisecondsSince1970, \n            token: action == .set ? info.token : nil)\n        \n        updatesContinuation.yield(update)\n    }\n    \n    private func yieldStartTokenUpdate(\n        attributeType: String,\n        token: String?\n    ) {\n        let update = LiveActivityUpdate(\n            action: token == nil ? .remove : .set,\n            source: .startToken(attributeType: attributeType),\n            actionTimeMS: self.date.now.millisecondsSince1970,\n            token: token\n        )\n\n        updatesContinuation.yield(update)\n    }\n    \n\n    private func findLiveActivityInfos(\n        id: String? = nil,\n        name: String? = nil\n    ) -> [LiveActivityInfo] {\n        return self.liveActivityInfos.filter { info in\n            if id != nil && info.id != id {\n                return false\n            }\n\n            if name != nil && info.name != name {\n                return false\n            }\n\n            return true\n        }\n    }\n\n    private func makeTaskID(id: String, name: String) -> String {\n        return id + name\n    }\n}\n\nprivate struct LiveActivityInfo: Codable, Sendable {\n    var id: String\n    var name: String\n    var token: String?\n    var startDate: Date\n    var status: LiveActivityRegistrationStatus?\n}\n\nprivate struct StartTokenInfo: Codable, Sendable, Equatable {\n    var attributeType: String\n    var token: String\n    var sentDate: Date\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LiveActivityRestorer.swift",
    "content": "#if canImport(ActivityKit) && !targetEnvironment(macCatalyst) && !os(macOS)\n\npublic import ActivityKit\n\n/// Restores live activity tracking\n@available(iOS 16.1, *)\npublic protocol LiveActivityRestorer: Actor {\n    /// Should be called for every live activity type that you track with Airship.\n    /// This method will  track a push to start token for the activity and check if any previousl\n    /// tracked live activities are still available by comparing the activity's ID. If we previously tracked\n    /// the activity, Airship will resume tracking the status and push token.\n    /// - Parameters:\n    ///     - forType: The live activity type\n    func restore<T: ActivityAttributes>(forType: Activity<T>.Type) async\n    \n}\n\n@available(iOS 16.1, *)\nactor AirshipLiveActivityRestorer: LiveActivityRestorer {\n\n    var liveActivities: [any LiveActivityProtocol] = []\n    var pushToStartTokenTrackers: [any LiveActivityPushToStartTrackerProtocol] = []\n\n    public func restore<T: ActivityAttributes>(\n        forType: Activity<T>.Type\n    ) {\n        self.liveActivities.append(\n            contentsOf: forType.activities.map { LiveActivity(activity: $0) }\n        )\n\n        if #available(iOS 17.2, *) {\n            self.pushToStartTokenTrackers.append(\n                LiveActivityPushToStartTracker<T>()\n            )\n        }\n    }\n\n    func apply(registry: LiveActivityRegistry) async {\n        await registry.restoreTracking(\n            activities: liveActivities,\n            startTokenTrackers: pushToStartTokenTrackers\n        )\n    }\n}\n\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LiveActivityUpdate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// An update to a live activity\nstruct LiveActivityUpdate: Codable, Equatable {\n    enum Action: String, Codable {\n        case set\n        case remove\n    }\n    \n    enum Source: Equatable {\n        enum TokenType: String, Codable {\n            case start = \"start_token\"\n            case update = \"update_token\"\n        }\n        \n        case liveActivity(id: String, name: String, startTimeMS: Int64)\n        case startToken(attributeType: String)\n    }\n\n    /// Update action\n    var action: Action\n    \n    /// Update source\n    let source: Source\n\n    /// The token, should be available on a set\n    var token: String?\n\n    /// The update start time in milliseconds\n    var actionTimeMS: Int64\n\n    enum CodingKeys: String, CodingKey {\n        case action = \"action\"\n        case id = \"id\"\n        case name = \"name\"\n        case token = \"token\"\n        case actionTimeMS = \"action_ts_ms\"\n        case startTimeMS = \"start_ts_ms\"\n        case type = \"type\"\n        case attributeType = \"attributes_type\"\n    }\n    \n    init(action: Action,\n         source: Source,\n         actionTimeMS: Int64,\n         token: String? = nil\n    ) {\n        self.action = action\n        self.source = source\n        self.token = token\n        self.actionTimeMS = actionTimeMS\n    }\n    \n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        \n        let type: Source.TokenType\n        if container.contains(.type) {\n            type = try container.decode(Source.TokenType.self, forKey: .type)\n        } else {\n            type = .update\n        }\n        \n        switch type {\n        case .start:\n            self.source = .startToken(attributeType: try container.decode(String.self, forKey: .attributeType))\n        case .update:\n            self.source = .liveActivity(\n                id: try container.decode(String.self, forKey: .id),\n                name: try container.decode(String.self, forKey: .name),\n                startTimeMS: try container.decode(Int64.self, forKey: .startTimeMS))\n        }\n        \n        self.action = try container.decode(Action.self, forKey: .action)\n        self.token = try container.decodeIfPresent(String.self, forKey: .token)\n        self.actionTimeMS = try container.decode(Int64.self, forKey: .actionTimeMS)\n    }\n    \n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(action, forKey: .action)\n        try container.encodeIfPresent(token, forKey: .token)\n        try container.encode(actionTimeMS, forKey: .actionTimeMS)\n        \n        switch source {\n        case .liveActivity(let id, let name, let startTimeMS):\n            try container.encode(id, forKey: .id)\n            try container.encode(name, forKey: .name)\n            try container.encode(startTimeMS, forKey: .startTimeMS)\n            try container.encode(Source.TokenType.update, forKey: .type)\n        case .startToken(let attributeType):\n            try container.encode(attributeType, forKey: .attributeType)\n            try container.encode(Source.TokenType.start, forKey: .type)\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/LocaleManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Airship locale manager.\npublic protocol AirshipLocaleManager: AnyObject, Sendable {\n    /**\n     * Resets the current locale.\n     */\n    func clearLocale()\n\n    /**\n     * The current locale used by Airship. Defaults to `autoupdatingCurrent` or the user preferred lanaguage, depending on\n     * `AirshipConfig.useUserPreferredLocale`.\n     */\n    var currentLocale: Locale { get set }\n}\n\n\nfinal class DefaultAirshipLocaleManager: AirshipLocaleManager {\n\n    fileprivate static let storeKey: String = \"com.urbanairship.locale.locale\"\n\n    private let dataStore: PreferenceDataStore\n    private let config: RuntimeConfig\n    private let notificationCenter: AirshipNotificationCenter\n\n    /**\n     * The current locale used by Airship. Defaults to `autoupdatingCurrent`.\n     */\n    public var currentLocale: Locale {\n        get {\n            if self.config.airshipConfig.useUserPreferredLocale {\n                let preferredLanguage = Locale.preferredLanguages[0]\n                let preferredLocale = Locale(identifier: preferredLanguage)\n                return dataStore.localeOverride ?? preferredLocale\n            } else {\n                return dataStore.localeOverride ?? Locale.autoupdatingCurrent\n            }\n        }\n        set {\n            dataStore.localeOverride = newValue\n            dispatchUpdate()\n        }\n    }\n    \n    /**\n     * - Note: For internal use only. :nodoc:\n     */\n    init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared\n    ) {\n        self.dataStore = dataStore\n        self.config = config\n        self.notificationCenter = notificationCenter\n\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(autoLocaleChanged),\n            name: NSLocale.currentLocaleDidChangeNotification\n        )\n    }\n\n    /**\n     * Resets the current locale.\n     */\n    public func clearLocale() {\n        dataStore.localeOverride = nil\n        dispatchUpdate()\n    }\n\n    @objc\n    private func autoLocaleChanged() {\n        if (dataStore.localeOverride == nil) {\n            dispatchUpdate()\n        }\n    }\n\n    private func dispatchUpdate() {\n        notificationCenter.postOnMain(\n            name: AirshipNotifications.LocaleUpdated.name,\n            object: [AirshipNotifications.LocaleUpdated.localeKey: self.currentLocale]\n        )\n    }\n}\n\n\nfileprivate extension PreferenceDataStore {\n    var localeOverride: Locale? {\n        get {\n            guard\n                let encodedLocale = object(forKey: DefaultAirshipLocaleManager.storeKey) as? Data\n            else {\n                return nil\n            }\n\n            return try? NSKeyedUnarchiver.unarchivedObject(ofClass: NSLocale.self, from: encodedLocale) as? Locale\n        }\n        set {\n            guard let locale = newValue else {\n                removeObject(forKey: DefaultAirshipLocaleManager.storeKey)\n                return\n            }\n\n            guard\n                let encodedLocale: Data = try? NSKeyedArchiver.archivedData(\n                    withRootObject: locale,\n                    requiringSecureCoding: true\n                )\n            else {\n                AirshipLogger.error(\"Failed to encode locale!\")\n                return\n            }\n\n            setValue(\n                encodedLocale,\n                forKey: DefaultAirshipLocaleManager.storeKey\n            )\n        }\n    }\n}\n\n\npublic extension AirshipNotifications {\n\n    /// NSNotification info when the locale is updated.\n    final class LocaleUpdated: NSObject {\n\n        /// NSNotification name.\n        public static let name: NSNotification.Name = NSNotification.Name(\n            \"com.urbanairship.locale.locale_updated\"\n        )\n        \n        /// NSNotification userInfo key to get the locale.\n        public static let localeKey: String = \"locale\"\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Media.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport AVFoundation\n\n/// Media view.\n\nstruct Media: View {\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n\n    private let info: ThomasViewInfo.Media\n    private let constraints: ViewConstraints\n    @State\n    private var mediaID: UUID = UUID()\n    private let defaultAspectRatio: Double = 16.0 / 9.0\n    @EnvironmentObject private var pagerState: PagerState\n    @Environment(\\.pageIdentifier) private var pageIdentifier\n\n    init(info: ThomasViewInfo.Media, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var videoAspectRatio: CGFloat {\n        CGFloat(self.info.properties.video?.aspectRatio ?? defaultAspectRatio)\n    }\n\n    var body: some View {\n        switch self.info.properties.mediaType {\n        case .image:\n            ThomasAsyncImage(\n                url: self.info.properties.url,\n                imageLoader: thomasEnvironment.imageLoader\n            ) { image, imageSize in\n                image.fitMedia(\n                    mediaFit: self.info.properties.mediaFit,\n                    cropPosition: self.info.properties.cropPosition,\n                    constraints: constraints,\n                    imageSize: imageSize\n                ).allowsHitTesting(false)\n            } placeholder: {\n                AirshipProgressView()\n            }\n            .constraints(constraints)\n            .thomasCommon(self.info)\n            .accessible(\n                self.info.accessible,\n                associatedLabel: nil,\n                hideIfDescriptionIsMissing: true\n            )\n        case .video:\n#if !os(watchOS) && !os(macOS)\n            VideoMediaNativeView(\n                info: self.info,\n                videoIdentifier: self.info.properties.identifier ?? mediaID.uuidString,\n                constraints: constraints,\n                videoAspectRatio: videoAspectRatio,\n                onMediaReady: {\n                    pagerState.setMediaReady(\n                        pageId: pageIdentifier ?? \"\",\n                        id: mediaID,\n                        isReady: true\n                    )\n                }\n            )\n            .onAppear {\n                pagerState.registerMedia(pageId: pageIdentifier ?? \"\", id: mediaID)\n            }\n            .thomasCommon(self.info)\n#endif\n        case .youtube, .vimeo:\n#if !os(tvOS) && !os(watchOS)\n            VideoMediaWebView(\n                info: self.info,\n                videoIdentifier: self.info.properties.identifier ?? mediaID.uuidString\n            ) {\n                pagerState.setMediaReady(\n                    pageId: pageIdentifier ?? \"\",\n                    id: mediaID,\n                    isReady: true\n                )\n            }\n            .airshipApplyIf(self.constraints.width == nil || self.constraints.height == nil) {\n                $0.aspectRatio(videoAspectRatio, contentMode: ContentMode.fit)\n            }\n            .constraints(constraints)\n            .onAppear {\n                pagerState.registerMedia(pageId: pageIdentifier ?? \"\", id: mediaID)\n            }\n            .thomasCommon(self.info)\n#endif\n        }\n    }\n}\n\nextension Image {\n\n    @ViewBuilder\n    @MainActor\n    func fitMedia(\n        mediaFit: ThomasMediaFit,\n        cropPosition: ThomasPosition?,\n        constraints: ViewConstraints,\n        imageSize: CGSize\n    ) -> some View {\n        switch mediaFit {\n        case .center:\n            cropAligned(constraints: constraints, imageSize: imageSize)\n        case .fitCrop:\n            cropAligned(constraints: constraints, imageSize: imageSize, alignment: cropPosition?.alignment ?? .center)\n        case .centerCrop:\n            cropAligned(constraints: constraints, imageSize: imageSize)\n        case .centerInside:\n            centerInside(constraints: constraints)\n        }\n    }\n\n    private func shouldCenterInside(constraints: ViewConstraints, imageSize: CGSize) -> Bool {\n        guard constraints.height == nil || constraints.width == nil else {\n            return false\n        }\n\n        let aspectRatio = imageSize.height > 0 ? imageSize.width/imageSize.height : 1.0\n\n        if let height = constraints.height, let maxWidth = constraints.maxWidth {\n            let fitWidth = height * aspectRatio\n            return fitWidth <= maxWidth\n        }\n\n        if let width = constraints.width, let maxHeight = constraints.maxHeight {\n            let fitHeight = width / aspectRatio\n            return fitHeight <= maxHeight\n        }\n\n        return false\n    }\n\n    @ViewBuilder\n    @MainActor\n    private func cropAligned(constraints: ViewConstraints, imageSize: CGSize, alignment: Alignment = .center) -> some View {\n        // If we have an auto bound constraint and we can fit the image then centerInside\n        if shouldCenterInside(constraints: constraints, imageSize: imageSize) {\n            centerInside(constraints: constraints)\n        } else {\n            self.resizable()\n                .scaledToFill()\n                .constraints(constraints, alignment: alignment)\n                .frame(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight)\n                .clipped()\n        }\n    }\n\n    @MainActor\n    private func centerInside(constraints: ViewConstraints) -> some View {\n        self.resizable()\n            .scaledToFit()\n            .constraints(constraints)\n            .ignoresSafeArea()\n            .clipped()\n    }\n}\n\n// Basically mirror the Image.fitMedia functionality\nextension View {\n    @ViewBuilder\n    @MainActor\n    func fitVideo(\n        mediaFit: ThomasMediaFit,\n        cropPosition: ThomasPosition?,\n        constraints: ViewConstraints,\n        videoAspectRatio: CGFloat\n    ) -> some View {\n        switch mediaFit {\n        case .center:\n            cropAlignedVideo(constraints: constraints, videoAspectRatio: videoAspectRatio)\n        case .fitCrop:\n            cropAlignedVideo(constraints: constraints, videoAspectRatio: videoAspectRatio, alignment: cropPosition?.alignment ?? .center)\n        case .centerCrop:\n            cropAlignedVideo(constraints: constraints, videoAspectRatio: videoAspectRatio)\n        case .centerInside:\n            centerInsideVideo(constraints: constraints, videoAspectRatio: videoAspectRatio)\n        }\n    }\n\n    private func shouldCenterInsideVideo(constraints: ViewConstraints, videoAspectRatio: CGFloat) -> Bool {\n        guard constraints.height == nil || constraints.width == nil else {\n            return false\n        }\n\n        if let height = constraints.height, let maxWidth = constraints.maxWidth {\n            let fitWidth = height * videoAspectRatio\n            return fitWidth <= maxWidth\n        }\n\n        if let width = constraints.width, let maxHeight = constraints.maxHeight {\n            let fitHeight = width / videoAspectRatio\n            return fitHeight <= maxHeight\n        }\n\n        return false\n    }\n\n    @ViewBuilder\n    @MainActor\n    private func cropAlignedVideo(constraints: ViewConstraints, videoAspectRatio: CGFloat, alignment: Alignment = .center) -> some View {\n        if shouldCenterInsideVideo(constraints: constraints, videoAspectRatio: videoAspectRatio) {\n            centerInsideVideo(constraints: constraints, videoAspectRatio: videoAspectRatio)\n        } else {\n            self.aspectRatio(videoAspectRatio, contentMode: .fill)\n                .constraints(constraints, alignment: alignment)\n                .frame(maxWidth: constraints.maxWidth, maxHeight: constraints.maxHeight)\n                .clipped()\n        }\n    }\n\n    @MainActor\n    private func centerInsideVideo(constraints: ViewConstraints, videoAspectRatio: CGFloat ) -> some View {\n        self.aspectRatio(videoAspectRatio, contentMode: .fit)\n            .constraints(constraints)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/MediaEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\npublic extension CustomEvent {\n\n    /// Media event types\n    enum MediaTemplate: Sendable {\n        /// Browsed media\n        case browsed\n\n        /// Consumed media\n        case consumed\n\n        /// Shared media\n        /// - Parameters:\n        ///     - source: Optional source.\n        ///     - medium: Optional medium.\n        case shared(source: String? = nil, medium: String? = nil)\n\n        /// Starred media\n        case starred\n\n        fileprivate static let templateName: String = \"media\"\n\n        fileprivate var eventName: String {\n            return switch self {\n            case .browsed: \"browsed_content\"\n            case .consumed: \"consumed_content\"\n            case .shared: \"shared_content\"\n            case .starred: \"starred_content\"\n            }\n        }\n    }\n\n    /// Additional media template properties\n    struct MediaProperties: Encodable, Sendable {\n        /// The event's ID.\n        public var id: String?\n\n        /// The event's category.\n        public var category: String?\n\n        /// The event's type.\n        public var type: String?\n\n        /// The event's description.\n        public var eventDescription: String?\n\n        /// The event's author.\n        public var author: String?\n\n        /// The event's published date.\n        public var publishedDate: Date?\n\n        /// If the event is a feature\n        public var isFeature: Bool?\n\n        /// If the value is a lifetime value or not.\n        public var isLTV: Bool\n\n        var source: String? = nil\n        var medium: String? = nil\n\n        public init(\n            id: String? = nil,\n            category: String? = nil,\n            type: String? = nil,\n            eventDescription: String? = nil,\n            isLTV: Bool = false,\n            author: String? = nil,\n            publishedDate: Date? = nil,\n            isFeature: Bool? = nil\n        ) {\n            self.id = id\n            self.category = category\n            self.type = type\n            self.eventDescription = eventDescription\n            self.isLTV = isLTV\n            self.author = author\n            self.publishedDate = publishedDate\n            self.isFeature = isFeature\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case isLTV = \"ltv\"\n            case isFeature = \"feature\"\n            case id\n            case category\n            case type\n            case source\n            case medium\n            case eventDescription = \"description\"\n            case author\n            case publishedDate = \"published_date\"\n        }\n    }\n\n    /// Constructs a custom event using the media template.\n    /// - Parameters:\n    ///     - mediaTemplate: The media template.\n    ///     - properties: Media properties.\n    ///     - encoder: Encoder used to encode the additional properties. Defaults to `CustomEvent.defaultEncoder`.\n    init(\n        mediaTemplate: MediaTemplate,\n        properties: MediaProperties = MediaProperties(),\n        encoder: @autoclosure () -> JSONEncoder = CustomEvent.defaultEncoder()\n    ) {\n        self = .init(name: mediaTemplate.eventName)\n        self.templateType = MediaTemplate.templateName\n\n        var mutableProperties = properties\n\n        switch (mediaTemplate) {\n        case .browsed: break\n        case .starred: break\n        case .consumed: break\n        case .shared(source: let source, medium: let medium):\n            mutableProperties.source = source\n            mutableProperties.medium = medium\n        }\n\n        do {\n            try self.setProperties(mutableProperties, encoder: encoder())\n        } catch {\n            /// Should never happen so we are just catching the exception and logging\n            AirshipLogger.error(\"Failed to generate event \\(error)\")\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/MessageCriteria.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct MessageCriteria: Codable, Sendable, Equatable {\n    let messageTypePredicate: JSONPredicate?\n    let campaignsPredicate: JSONPredicate?\n    \n    enum CodingKeys: String, CodingKey {\n        case messageTypePredicate = \"message_type\"\n        case campaignsPredicate = \"campaigns\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/MessageDisplayHistory.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct MessageDisplayHistory: Codable, Equatable, Sendable {\n    public var lastImpression: LastImpression?\n    public var lastDisplay: LastDisplay?\n\n    public init(lastImpression: LastImpression? = nil, lastDisplay: LastDisplay? = nil) {\n        self.lastImpression = lastImpression\n        self.lastDisplay = lastDisplay\n    }\n    \n    public struct LastImpression: Codable, Equatable, Sendable {\n        public var date: Date\n        public var triggerSessionID: String\n        \n        public init(date: Date, triggerSessionID: String) {\n            self.date = date\n            self.triggerSessionID = triggerSessionID\n        }\n    }\n\n    public struct LastDisplay: Codable, Equatable, Sendable {\n        public var triggerSessionID: String\n        \n        public init(triggerSessionID: String) {\n            self.triggerSessionID = triggerSessionID\n        }\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol MessageDisplayHistoryStoreProtocol: Sendable {\n    func set(\n        _ history: MessageDisplayHistory,\n        scheduleID: String\n    )\n\n    func get(\n        scheduleID: String\n    ) async -> MessageDisplayHistory\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic final class MessageDisplayHistoryStore: MessageDisplayHistoryStoreProtocol {\n\n    private let storageGetter: @Sendable (String) async throws -> Data?\n    private let storageSetter: @Sendable (String, MessageDisplayHistory) async throws -> Void\n    private let queue: AirshipAsyncSerialQueue\n    \n    public init(\n        storageGetter: @escaping @Sendable (String) async throws -> Data?,\n        storageSetter: @escaping @Sendable (String, MessageDisplayHistory) async throws -> Void,\n        queue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n    ) {\n        self.storageGetter = storageGetter\n        self.storageSetter = storageSetter\n        self.queue = queue\n    }\n\n    public func set(_ history: MessageDisplayHistory, scheduleID: String) {\n        queue.enqueue { [weak self] in\n            do {\n                try await self?.storageSetter(scheduleID, history)\n            } catch {\n                AirshipLogger.error(\"Failed to save message history \\(error)\")\n            }\n        }\n    }\n\n    public func get(scheduleID: String) async -> MessageDisplayHistory {\n        return await withCheckedContinuation { continuation in\n            queue.enqueue { [weak self] in\n                do {\n                    guard let data = try await self?.storageGetter(scheduleID) else {\n                        continuation.resume(returning: MessageDisplayHistory())\n                        return\n                    }\n\n                    let history = try JSONDecoder().decode(MessageDisplayHistory.self, from: data)\n                    continuation.resume(returning: history)\n                } catch {\n                    AirshipLogger.error(\"Failed to save message history \\(error)\")\n                    continuation.resume(returning: MessageDisplayHistory())\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/MeteredUsageAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol MeteredUsageAPIClientProtocol: Sendable {\n    func uploadEvents(\n        _ events: [AirshipMeteredUsageEvent],\n        channelID: String?\n    ) async throws -> AirshipHTTPResponse<Void>\n}\n\n\nfinal class MeteredUsageAPIClient : MeteredUsageAPIClientProtocol {\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    private var encoder: JSONEncoder {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .custom({ date, encoder in\n            var container = encoder.singleValueContainer()\n            try container.encode(\n                AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter)\n            )\n        })\n        return encoder\n    }\n\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    func uploadEvents(\n        _ events: [AirshipMeteredUsageEvent], \n        channelID: String?\n    ) async throws -> AirshipHTTPResponse<Void> {\n        guard let meteredUsageURL = config.meteredUsageURL else {\n            throw AirshipErrors.error(\"Missing metered usage URL\")\n        }\n\n        var headers: [String: String] = [\n            \"X-UA-Lib-Version\": AirshipVersion.version,\n            \"X-UA-Device-Family\": \"ios\",\n            \"Content-Type\": \"application/json\",\n            \"Accept\":  \"application/vnd.urbanairship+json; version=3;\"\n        ]\n\n        if let channelID = channelID {\n            headers[\"X-UA-Channel-ID\"] = channelID\n        }\n\n        let body = try encoder.encode(RequestBody(usage: events))\n\n        let request = AirshipRequest(\n            url: URL(string: \"\\(meteredUsageURL)/api/metered-usage\"),\n            headers: headers,\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: body\n        )\n\n        AirshipLogger.trace(\"Sending usage: \\(events), request: \\(request)\")\n\n        // Perform the upload\n        let result = try await self.session.performHTTPRequest(request)\n        AirshipLogger.debug(\"Usage result: \\(result)\")\n\n        return result\n    }\n\n\n    fileprivate struct RequestBody: Encodable {\n        let usage: [AirshipMeteredUsageEvent]\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/MeteredUsageStore.swift",
    "content": "import Foundation\nimport CoreData\n\nfinal class MeteredUsageStore: Sendable {\n    private static let fileFormat: String = \"MeteredUsage-%@.sqlite\"\n    private static let eventDataEntityName: String = \"UAMeteredUsageEventData\"\n    private static let fetchEventLimit: Int = 500\n\n    private let coreData: UACoreData\n    private let inMemory: Bool\n\n    init(appKey: String,\n         inMemory: Bool = false\n    ) {\n        self.inMemory = inMemory\n        let storeName = String(\n            format: MeteredUsageStore.fileFormat,\n            appKey\n        )\n        let modelURL = AirshipCoreResources.bundle.url(\n            forResource: \"UAMeteredUsage\",\n            withExtension: \"momd\"\n        )\n        self.coreData = UACoreData(\n            name: Self.eventDataEntityName,\n            modelURL: modelURL!,\n            inMemory: inMemory,\n            stores: [storeName]\n        )\n    }\n\n    func saveEvent(_ event: AirshipMeteredUsageEvent) async throws {\n        try await self.coreData.perform { context in\n            let eventData = NSEntityDescription.insertNewObject(\n                forEntityName: MeteredUsageStore.eventDataEntityName,\n                into: context\n            ) as? MeteredUsageEventData\n\n            guard let eventData = eventData else {\n                throw AirshipErrors.error(\"Failed to MeteredUsageEventData\")\n            }\n\n            eventData.identifier = event.eventID\n            eventData.data = try JSONEncoder().encode(event)\n        }\n    }\n\n    func deleteAll() async throws {\n        try await self.coreData.perform(skipIfStoreNotCreated: true) { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: MeteredUsageStore.eventDataEntityName\n            )\n\n            if self.inMemory {\n                request.includesPropertyValues = false\n                let events = try context.fetch(request) as? [NSManagedObject]\n                events?.forEach { event in\n                    context.delete(event)\n                }\n            } else {\n                let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n                try context.execute(deleteRequest)\n            }\n        }\n    }\n\n    func deleteEvents(_ events: [AirshipMeteredUsageEvent]) async throws {\n        let eventIDs = events.map { $0.eventID }\n        try await self.coreData.perform { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: MeteredUsageStore.eventDataEntityName\n            )\n\n            request.predicate = NSPredicate(\n                format: \"identifier IN %@\",\n                eventIDs\n            )\n\n            if self.inMemory {\n                request.includesPropertyValues = false\n                let events = try context.fetch(request) as? [NSManagedObject]\n                events?.forEach { event in\n                    context.delete(event)\n                }\n            } else {\n                let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n                try context.execute(deleteRequest)\n            }\n        }\n    }\n\n    func getEvents() async throws -> [AirshipMeteredUsageEvent] {\n        return try await self.coreData.performWithResult { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: MeteredUsageStore.eventDataEntityName\n            )\n            request.fetchLimit = MeteredUsageStore.fetchEventLimit\n            let fetchResult = try context.fetch(request) as? [MeteredUsageEventData] ?? []\n\n            let events: [AirshipMeteredUsageEvent] = fetchResult.compactMap { eventData in\n                guard let data = eventData.data else {\n                    AirshipLogger.error(\"Unable to read event, deleting. \\(eventData)\")\n                    context.delete(eventData)\n                    return nil\n                }\n\n                do {\n                    return try JSONDecoder().decode(AirshipMeteredUsageEvent.self, from: data)\n                } catch {\n                    AirshipLogger.error(\"Unable to read event, deleting. \\(error)\")\n                    context.delete(eventData)\n                    return nil\n                }\n            }\n            return events\n        }\n    }\n}\n\n// Internal core data entity\n@objc(UAMeteredUsageEventData)\nfileprivate class MeteredUsageEventData: NSManagedObject {\n    /// The event's Data.\n    @NSManaged public dynamic var data: Data?\n\n    /// The event's identifier.\n    @objc\n    @NSManaged public dynamic var identifier: String?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ModalView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n\nstruct ModalView: View {\n\n    @Environment(\\.colorScheme) var colorScheme\n\n    let presentation: ThomasPresentationInfo.Modal\n    let layout: AirshipLayout\n    @ObservedObject\n    var thomasEnvironment: ThomasEnvironment\n    #if !os(watchOS)\n    let viewControllerOptions: ThomasViewControllerOptions\n    #endif\n\n    @State private var contentSize: CGSize? = nil\n\n    var body: some View {\n        GeometryReader { metrics in\n            RootView(\n                thomasEnvironment: thomasEnvironment,\n                layout: layout\n            ) { orientation, windowSize in\n                let placement = resolvePlacement(\n                    orientation: orientation,\n                    windowSize: windowSize\n                )\n                createModal(placement: placement, metrics: metrics)\n            }\n        }\n        .ignoresSafeArea(ignoreKeyboardSafeArea ? [.keyboard] : [])\n    }\n\n    private var ignoreKeyboardSafeArea: Bool {\n        presentation.ios?.keyboardAvoidance == .overTheTop\n    }\n\n    private func createModal(\n        placement: ThomasPresentationInfo.Modal.Placement,\n        metrics: GeometryProxy\n    ) -> some View {\n        let ignoreSafeArea = placement.ignoreSafeArea == true\n        let safeAreaInsets =\n            ignoreSafeArea\n            ? metrics.safeAreaInsets : ViewConstraints.emptyEdgeSet\n\n        let alignment = Alignment(\n            horizontal: placement.position?.horizontal.alignment ?? .center,\n            vertical: placement.position?.vertical.alignment ?? .center\n        )\n\n        let windowConstraints = ViewConstraints(\n            size: metrics.size,\n            safeAreaInsets: safeAreaInsets\n        )\n\n        let contentConstraints = windowConstraints.contentConstraints(\n            placement.size,\n            contentSize: self.contentSize,\n            margin: placement.margin\n        )\n\n        let safeAreasToIgnore: SafeAreaRegions = if ignoreSafeArea {\n            [.container, .keyboard]\n        } else {\n            []\n        }\n        \n        return VStack {\n            ViewFactory.createView(\n                self.layout.view,\n                constraints: contentConstraints\n            )\n            .background(\n                GeometryReader { contentMetrics -> Color in\n                    DispatchQueue.main.async {\n                        self.contentSize = contentMetrics.size\n                    }\n                    return Color.clear\n                }\n            )\n            .thomasBackground(\n                color: placement.backgroundColor,\n                border: placement.border,\n                shadow: placement.shadow\n            )\n            .margin(placement.margin)\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: alignment)\n        .background(\n            modalBackground(placement)\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n        )\n        .ignoresSafeArea(safeAreasToIgnore)\n        .opacity(self.contentSize == nil ? 0 : 1)\n        .animation(nil, value: self.contentSize)\n        .accessibilityElement(children: .contain)\n    }\n\n    @ViewBuilder\n    private func modalBackground(_ placement: ThomasPresentationInfo.Modal.Placement) -> some View {\n        GeometryReader { reader in\n            VStack(spacing: 0) {\n                if placement.isFullscreen, placement.ignoreSafeArea != true {\n                    statusBarShimColor()\n                        .frame(height: reader.safeAreaInsets.top)\n                }\n\n                Rectangle()\n                    .foreground(placement.shade, colorScheme: colorScheme)\n                    .ignoresSafeArea(.all)\n                    .airshipApplyIf(self.presentation.dismissOnTouchOutside == true) {\n                        view in\n                        // Add tap gesture outside of view to dismiss\n                        view.addTapGesture {\n                            self.thomasEnvironment.dismiss()\n                        }\n                    }\n\n                if placement.isFullscreen, placement.ignoreSafeArea != true {\n                    statusBarShimColor()\n                        .frame(height: reader.safeAreaInsets.bottom)\n                }\n            }\n            .ignoresSafeArea(.all)\n        }\n    }\n\n\n    private func resolvePlacement(\n        orientation: ThomasOrientation,\n        windowSize: ThomasWindowSize\n    ) -> ThomasPresentationInfo.Modal.Placement {\n        var placement = self.presentation.defaultPlacement\n\n        #if !os(watchOS)\n        let resolvedOrientation =\n            viewControllerOptions.orientation ?? orientation\n        #else\n        let resolvedOrientation = orientation\n        #endif\n\n        for placementSelector in self.presentation.placementSelectors ?? [] {\n            if placementSelector.windowSize != nil\n                && placementSelector.windowSize != windowSize\n            {\n                continue\n            }\n\n            if placementSelector.orientation != nil\n                && placementSelector.orientation != resolvedOrientation\n            {\n                continue\n            }\n\n            // its a match!\n            placement = placementSelector.placement\n            break\n        }\n\n        #if !os(watchOS)\n        self.viewControllerOptions.orientation =\n            placement.device?.orientationLock\n        #endif\n        return placement\n    }\n\n    private func statusBarShimColor() -> Color {\n        #if os(tvOS) || os(watchOS) || os(macOS)\n        return Color.clear\n        #else\n\n        var statusBarStyle = UIStatusBarStyle.default\n\n        if let scene = try? AirshipSceneManager.shared.lastActiveScene,\n           let sceneStyle = scene.statusBarManager?.statusBarStyle\n        {\n            statusBarStyle = sceneStyle\n        }\n\n        switch statusBarStyle {\n        case .darkContent:\n            return Color.white\n        case .lightContent:\n            return Color.black\n        case .default:\n            return self.colorScheme == .dark ? Color.black : Color.white\n        @unknown default:\n            return Color.black\n        }\n        #endif\n    }\n}\n\n\nextension ThomasPresentationInfo.Modal.Placement {\n    fileprivate var isFullscreen: Bool {\n        if let horiztonalMargins = self.margin?.horiztonalMargins, horiztonalMargins > 0 {\n            return false\n        }\n\n        if let verticalMargins = self.margin?.verticalMargins, verticalMargins > 0 {\n            return false\n        }\n\n        if case let .percent(height) = self.size.height, height >= 100.0,\n            case let .percent(width) = self.size.width, width >= 100.0\n        {\n            return true\n        }\n        return false\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ModifyAttributesAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Modifies attributes.\n///\n/// An example JSON payload:\n///\n/// {\n///     \"channel\": {\n///         set: {\"key\": value, ... },\n///         remove: [\"attribute\", ....]\n///     },\n///     \"named_user\": {\n///         set: {\"key\": value, ... },\n///         remove: [\"attribute\", ....]\n///     }\n/// }\n///\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.backgroundInteractiveButton`, `ActionSituation.manualInvocation`, and\n/// `ActionSituation.automation`\npublic final class ModifyAttributesAction: AirshipAction {\n\n    /// Default names - \"modify_attributes_action\", \"^a\"\n    public static let defaultNames: [String] = [\"modify_attributes_action\", \"set_attributes_action\", \"^a\"]\n    \n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n    \n\n    private static let namedUserKey: String = \"named_user\"\n    private static let channelsKey: String = \"channel\"\n    private static let setActionKey: String = \"set\"\n    private static let removeActionKey: String = \"remove\"\n\n\n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n\n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact\n    ) {\n        self.channel = channel\n        self.contact = contact\n    }\n\n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier()\n        )\n    }\n   \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        guard arguments.situation != .backgroundPush else {\n            return false\n        }\n        \n        do {\n            let changes = try parse(value: arguments.value)\n            return !changes.isEmpty\n        } catch {\n            return false\n        }\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let actions = try parse(value: arguments.value)\n        \n        let channelEditor = channel().editAttributes()\n        let contactEditor = contact().editAttributes()\n        \n        for modification in actions {\n            let editor = switch(modification.editor) {\n            case .channel: channelEditor\n            case .contact: contactEditor\n            }\n            \n            switch(modification) {\n            case .set(_, let name, let value):\n                switch value {\n                case .string(let value):\n                    editor.set(string: value, attribute: name)\n                case .number(let value):\n                    editor.set(number: value, attribute: name)\n                case .date(let value):\n                    editor.set(date: value, attribute: name)\n                case .json(let value):\n                    try editor.set(\n                        json: value.value,\n                        attribute: value.name,\n                        instanceID: value.instanceId,\n                        expiration: value.expiration\n                    )\n                    break\n                }\n                \n            case .remove(_, let name):\n                editor.remove(name)\n            }\n        }\n        \n        let editors: Set<AttributeActionArgs.TargetEditor> = Set(actions.compactMap { $0.editor })\n        if editors.contains(.channel) {\n            channelEditor.apply()\n        }\n        \n        if editors.contains(.contact) {\n            contactEditor.apply()\n        }\n\n        return nil\n    }\n    \n    private func parse(value: AirshipJSON) throws -> [AttributeActionArgs] {\n        if let unwrapped: [AttributeActionArgs] = try? value.decode() {\n            return unwrapped\n        }\n        \n        guard\n            let unwrapped = value.unWrap(),\n            let dict = unwrapped as? [String: [String: Any]]\n        else {\n            throw AirshipErrors.error(\"invalid arguments\")\n        }\n        \n        let convertEdits: (AttributeActionArgs.TargetEditor, [String: Any]) throws -> [AttributeActionArgs] = { editor, input in\n            let sets: [AttributeActionArgs] = input[ModifyAttributesAction.setActionKey]\n                .map { items in\n                    guard let items = items as? [String: Any] else {\n                        return []\n                    }\n                    \n                    return items\n                        .compactMapValues { try? AttributeActionArgs.Value(value: $0) }\n                        .map { AttributeActionArgs.set(editor, $0.key, $0.value) }\n                }\n            ?? []\n            \n            if input.keys.contains(ModifyAttributesAction.setActionKey) && sets.isEmpty {\n                throw AirshipErrors.error(\"failed to parse set arguments\")\n            }\n            \n            let removes: [AttributeActionArgs] = input[ModifyAttributesAction.removeActionKey]\n                .map { items in\n                    guard let items = items as? [String] else {\n                        return []\n                    }\n                    \n                    return items.map { AttributeActionArgs.remove(editor, $0) }\n                }\n            ?? []\n            \n            if input.keys.contains(ModifyAttributesAction.removeActionKey) && removes.isEmpty {\n                throw AirshipErrors.error(\"failed to parse remove arguments\")\n            }\n            \n            return sets + removes\n        }\n        \n        return (try convertEdits(.contact, dict[ModifyAttributesAction.namedUserKey] ?? [:])) +\n               (try convertEdits(.channel, dict[ModifyAttributesAction.channelsKey] ?? [:]))\n    }\n    \n    private enum AttributeActionArgs: Codable, Hashable, Sendable {\n        case set(TargetEditor, String, Value)\n        case remove(TargetEditor, String)\n        \n        enum CodingKeys: String, CodingKey {\n            case actionType = \"action\"\n            case target = \"type\"\n            case name\n            case value\n        }\n        \n        var editor: TargetEditor {\n            switch self {\n            case .set(let editor, _, _):\n                return editor\n            case .remove(let editor, _):\n                return editor\n            }\n        }\n        \n        var name: String {\n            switch self {\n            case .set(_, let name, _):\n                return name\n            case .remove(_, let name):\n                return name\n            }\n        }\n        \n        var value: Value? {\n            switch self {\n            case .set(_, _, let value):\n                return value\n            case .remove:\n                return nil\n            }\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            \n            let type = try container.decode(ActionType.self, forKey: .actionType)\n            \n            let editor = try container.decode(TargetEditor.self, forKey: .target)\n            let name = try container.decode(String.self, forKey: .name)\n            \n            switch(type) {\n            case .set:\n                self = .set(editor, name, try container.decode(Value.self, forKey: .value))\n            case .remove:\n                self = .remove(editor, name)\n            }\n        }\n        \n        func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            \n            try container.encode(editor, forKey: .target)\n            try container.encode(name, forKey: .name)\n            \n            switch(self) {\n            case .set(_, _, let value):\n                try container.encode(ActionType.set, forKey: .actionType)\n                try container.encode(value, forKey: .value)\n            case .remove(_, _):\n                try container.encode(ActionType.remove, forKey: .actionType)\n            }\n        }\n        \n        enum ActionType: String, Codable, Sendable {\n            case set\n            case remove\n        }\n        \n        enum TargetEditor: String, Codable, Sendable {\n            case channel\n            case contact\n        }\n        \n        enum Value: Codable, Sendable, Hashable {\n            case string(String)\n            case number(Double)\n            case date(Date)\n            case json(JsonValue)\n            \n            init(from decoder: any Decoder) throws {\n                let container = try decoder.singleValueContainer()\n                if let string = try? container.decode(String.self) {\n                    self = .string(string)\n                } else if let double = try? container.decode(Double.self) {\n                    self = .number(double)\n                } else if let date = try? container.decode(Date.self) {\n                    self = .date(date)\n                } else if let json = try? JsonValue(from: decoder) {\n                    self = .json(json)\n                } else {\n                    throw DecodingError.dataCorruptedError(in: container, debugDescription: \"unsupported type\")\n                }\n            }\n            \n            init(value: Any) throws {\n                if let string = value as? String {\n                    self = .string(string)\n                } else if let number = value as? NSNumber {\n                    self = .number(number.doubleValue)\n                } else if let date = value as? Date {\n                    self = .date(date)\n                } else {\n                    throw AirshipErrors.error(\"Unsupported value type for attribute modification\")\n                }\n            }\n            \n            func encode(to encoder: any Encoder) throws {\n                var container = encoder.singleValueContainer()\n                switch self {\n                case .string(let string):\n                    try container.encode(string)\n                case .number(let number):\n                    try container.encode(number)\n                case .date(let date):\n                    try container.encode(date)\n                case .json(let json):\n                    try container.encode(json)\n                }\n            }\n        }\n        \n        struct JsonValue: Codable, Sendable, Hashable {\n            private static let keyExpiration: String = \"exp\"\n            \n            let name: String\n            let instanceId: String\n            let expiration: Date?\n            let value: [String: AirshipJSON]\n            \n            init(from decoder: any Decoder) throws {\n                let json = try AirshipJSON(from: decoder)\n                \n                guard\n                    case .object(let dict) = json,\n                    dict.count == 1,\n                    let keyInstanceId = dict.first?.key, keyInstanceId.contains(\"#\"),\n                    let contentJson = dict.first?.value,\n                    case .object(var content) = contentJson\n                else {\n                    throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: \"Expected JSON object but found \\(json)\"))\n                }\n                \n                let components = keyInstanceId.split(separator: \"#\")\n                guard components.count == 2 else {\n                    throw DecodingError.dataCorrupted(DecodingError.Context(codingPath: decoder.codingPath, debugDescription: \"Invalid instance ID format: \\(keyInstanceId)\"))\n                }\n                \n                self.name = String(components[0])\n                self.instanceId = String(components[1])\n                self.expiration = Self.convertToDate(content.removeValue(forKey: Self.keyExpiration))\n                self.value = content\n            }\n            \n            func encode(to encoder: any Encoder) throws {\n                var content = value\n                if let expiration {\n                    content[Self.keyExpiration] = .number(expiration.timeIntervalSince1970)\n                }\n                \n                let source = [\n                    \"\\(name)#\\(instanceId)\": content\n                ]\n                \n                try AirshipJSON.wrap(source).encode(to: encoder)\n            }\n            \n            private static func convertToDate(_ value: AirshipJSON?) -> Date? {\n                guard let value = value else { return nil }\n                \n                switch value {\n                case .number(let interval):\n                    return Date(timeIntervalSince1970: interval)\n                default:\n                    return nil\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ModifyTagsAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n\nimport Foundation\n\n/// Modify channel or contact tags.\n///\n/// Expected argument values: an array of mutations.\n/// An example add channel tags JSON payload:\n/// [\n///     {\n///       \"action\": \"add\",\n///       \"tags\": [\n///         \"channel_tag_1\",\n///         \"channel_tag_2\"\n///       ],\n///       \"type\": \"channel\"\n///     },\n///     {\n///       \"action\": \"remove\",\n///       \"group\": \"tag_group\"\n///       \"tags\": [\n///         \"contact_tag_1\",\n///         \"contact_tag_2\"\n///       ],\n///       \"type\": \"contact\"\n///     }\n/// ]\n///\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.backgroundInteractiveButton`, `ActionSituation.manualInvocation`, and\n/// `ActionSituation.automation`\npublic final class ModifyTagsAction: AirshipAction {\n    \n    /// Default names - \"tag_action\", \"^t\"\n    public static let defaultNames: [String] = [\"tag_action\", \"^t\"]\n    \n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n    \n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n    \n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact\n    ) {\n        self.channel = channel\n        self.contact = contact\n    }\n    \n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier()\n        )\n    }\n    \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        guard arguments.situation != .backgroundPush else {\n            return false\n        }\n        return true\n    }\n    \n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let data: [Arguments] = try arguments.value.decode()\n        \n        let channelEditor = self.channel().editTags()\n        let channelGroupEditor = self.channel().editTagGroups()\n        let contactGroupEditor = self.contact().editTagGroups()\n        var onDoneCallbacks: [EditorType: () -> Void] = [:]\n        \n        for data in data {\n            performAction(\n                data: data,\n                channelEditor: {\n                    if !onDoneCallbacks.keys.contains(.channel) {\n                        onDoneCallbacks[.channel] = channelEditor.apply\n                    }\n                    return channelEditor\n                }) { target in\n                    let key: EditorType\n                    let editor: TagGroupsEditor\n                    \n                    switch target {\n                    case .channel:\n                        key = .channelGroup\n                        editor = channelGroupEditor\n                    case .contact:\n                        key = .contactGroup\n                        editor = contactGroupEditor\n                    }\n                    \n                    if !onDoneCallbacks.keys.contains(key) {\n                        onDoneCallbacks[key] = editor.apply\n                    }\n                    \n                    return editor\n                }\n        }\n        \n        onDoneCallbacks.values.forEach { $0() }\n        \n        return nil\n    }\n    \n    private func performAction(\n        data: Arguments,\n        channelEditor: () -> TagEditor,\n        groupEditor: (Arguments.Target) -> TagGroupsEditor\n    ) {\n        switch data {\n        case .channel(let args):\n            if let group = args.group {\n                let editor = groupEditor(.channel)\n                \n                switch args.action {\n                case .add: editor.add(args.tags, group: group)\n                case .remove: editor.remove(args.tags, group: group)\n                }\n            } else {\n                let editor = channelEditor()\n                \n                switch args.action {\n                case .add: editor.add(args.tags)\n                case .remove: editor.remove(args.tags)\n                }\n            }\n        case .contact(let args):\n            let editor = groupEditor(.contact)\n            \n            switch args.action {\n            case .add: editor.add(args.tags, group: args.group)\n            case .remove: editor.remove(args.tags, group: args.group)\n            }\n        }\n    }\n    \n    private enum EditorType: Hashable {\n        case channel, channelGroup, contactGroup\n    }\n    \n    private enum Arguments: Codable, Sendable {\n        \n        case channel(ChannelTags)\n        case contact(ContactTags)\n        \n        private enum CodingKeys : String, CodingKey, Sendable {\n            case type\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            \n            switch try container.decode(Target.self, forKey: .type) {\n            case .channel:\n                self = try .channel(.init(from: decoder))\n            case .contact:\n                self = try .contact(.init(from: decoder))\n            }\n        }\n        \n        func encode(to encoder: any Encoder) throws {\n            switch self {\n            case .channel(let value):\n                try value.encode(to: encoder)\n            case .contact(let value):\n                try value.encode(to: encoder)\n            }\n        }\n        \n        enum Target: String, Codable, Sendable {\n            case channel = \"channel\"\n            case contact = \"contact\"\n        }\n        \n        enum ActionType: String, Codable, Sendable {\n            case add = \"add\"\n            case remove = \"remove\"\n        }\n        \n        struct ChannelTags: Codable, Sendable {\n            let type: Target = .channel\n            let group: String?\n            let action: ActionType\n            let tags: [String]\n            \n            enum CodingKeys : String, CodingKey, Sendable {\n                case group\n                case action\n                case tags\n                case type\n            }\n        }\n        \n        struct ContactTags: Codable, Sendable {\n            let type: Target = .contact\n            let group: String\n            let action: ActionType\n            let tags: [String]\n            \n            enum CodingKeys : String, CodingKey, Sendable {\n                case group\n                case action\n                case tags\n                case type\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ModuleLoader.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct AirshiopModuleLoaderArgs {\n    public let config: RuntimeConfig\n    public let dataStore: PreferenceDataStore\n    public let channel: any InternalAirshipChannel\n    public let contact: any AirshipContact\n    public let push: any AirshipPush\n    public let remoteData: any RemoteDataProtocol\n    public let analytics: any InternalAirshipAnalytics\n    public let privacyManager: any AirshipPrivacyManager\n    public let permissionsManager: any AirshipPermissionsManager\n    public let experimentsManager: any ExperimentDataProvider\n    public let meteredUsage: any AirshipMeteredUsage\n    public let deferredResolver: any AirshipDeferredResolverProtocol\n    public let cache: any AirshipCache\n    public let audienceChecker: any DeviceAudienceChecker\n    public let workManager: any AirshipWorkManagerProtocol\n    public let inputValidator: any AirshipInputValidation.Validator\n\n}\n\n/// NOTE: For internal use only. :nodoc:\nenum SDKModuleNames: String, CaseIterable {\n    case messageCenter = \"UAMessageCenterSDKModule\"\n    case preferenceCenter = \"UAPreferenceCenterSDKModule\"\n    case debug = \"UADebugSDKModule\"\n    case featureFlags = \"UAFeatureFlagsSDKModule\"\n    case automation = \"UAAutomationSDKModule\"\n}\n\n/// NOTE: For internal use only. :nodoc:\nclass ModuleLoader {\n\n    public let components: [any AirshipComponent]\n\n    public let actionManifests: [any ActionsManifest]\n\n    @MainActor\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any InternalAirshipChannel,\n        contact: any AirshipContact,\n        push: any AirshipPush,\n        remoteData: any RemoteDataProtocol,\n        analytics: any InternalAirshipAnalytics,\n        privacyManager: any AirshipPrivacyManager,\n        permissionsManager: any AirshipPermissionsManager,\n        audienceOverrides: any AudienceOverridesProvider,\n        experimentsManager: any ExperimentDataProvider,\n        meteredUsage: any AirshipMeteredUsage,\n        deferredResolver: any AirshipDeferredResolverProtocol,\n        cache: any AirshipCache,\n        audienceChecker: any DeviceAudienceChecker,\n        inputValidator: any AirshipInputValidation.Validator\n    ) {\n\n        let args = AirshiopModuleLoaderArgs(\n            config: config,\n            dataStore: dataStore,\n            channel: channel,\n            contact: contact,\n            push: push,\n            remoteData: remoteData,\n            analytics: analytics,\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager,\n            experimentsManager: experimentsManager,\n            meteredUsage: meteredUsage,\n            deferredResolver: deferredResolver,\n            cache: cache,\n            audienceChecker: audienceChecker,\n            workManager: AirshipWorkManager.shared,\n            inputValidator: inputValidator\n        )\n\n        let modules = ModuleLoader.loadModules(args)\n        self.components = modules.compactMap { $0.components }.reduce([], +)\n        self.actionManifests = modules.compactMap { $0.actionsManifest }\n    }\n\n    @MainActor\n    private class func loadModules(_ args: AirshiopModuleLoaderArgs) -> [any AirshipSDKModule]\n    {\n        let sdkModules: [any AirshipSDKModule] = SDKModuleNames.allCases.compactMap {\n            guard\n                let moduleClass = NSClassFromString($0.rawValue) as? any AirshipSDKModule.Type\n            else {\n                return nil\n            }\n\n            AirshipLogger.debug(\"Loading module \\($0)\")\n            return moduleClass.load(args)\n        }\n\n        return sdkModules\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NativeBridge.swift",
    "content": "/* Copyright Airship and Contributors */\n\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\n@preconcurrency\npublic import WebKit\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\ntypealias WebViewForwardHandler = (WKWebView, WKNavigationAction, @Sendable @escaping @MainActor (WKNavigationActionPolicy) -> Void) -> Void\n\n/// The native bridge will automatically load the Airship JavaScript environment into whitlelisted sites. The native\n/// bridge must be assigned as the navigation delegate on a `WKWebView` in order to function.\npublic final class NativeBridge: NSObject, WKNavigationDelegate {\n\n    static let airshipScheme: String = \"uairship\"\n    private static let closeCommand: String = \"close\"\n    private static let dismissCommand: String = \"dismiss\"\n    private static let setNamedUserCommand: String = \"named_user\"\n    private static let multiCommand: String = \"multi\"\n\n    @MainActor\n    private var jsRequests: [JSBridgeLoadRequest] = []\n\n    private static let forwardSchemes: [String] = [\n        \"itms-apps\", \"maps\", \"sms\", \"tel\", \"mailto\",\n    ]\n\n    private static let forwardHosts: [String] = [\n        \"maps.google.com\",\n        \"www.youtube.com\",\n        \"phobos.apple.com\",\n        \"itunes.apple.com\",\n    ]\n\n    /// Delegate to support additional native bridge features such as `close`.\n    public weak var nativeBridgeDelegate: (any NativeBridgeDelegate)?\n\n    /// Optional delegate to forward any WKNavigationDelegate calls.\n    public weak var forwardNavigationDelegate: (any AirshipWKNavigationDelegate)?\n\n    /// Optional delegate to support custom JavaScript commands.\n    public weak var javaScriptCommandDelegate: (any JavaScriptCommandDelegate)?\n\n    /// Optional delegate to extend the native bridge.\n    public weak var nativeBridgeExtensionDelegate: (any NativeBridgeExtensionDelegate)?\n\n    private let actionHandler: any NativeBridgeActionHandlerProtocol\n    private let javaScriptEnvironmentFactoryBlock: () -> any JavaScriptEnvironmentProtocol\n    private let challengeResolver: ChallengeResolver\n\n    /// NativeBridge initializer.\n    /// - Note: For internal use only. :nodoc:\n    /// - Parameter actionHandler: An action handler.\n    /// - Parameter javaScriptEnvironmentFactoryBlock: A factory block producing a JavaScript environment.\n    init(\n        actionHandler: any NativeBridgeActionHandlerProtocol,\n        javaScriptEnvironmentFactoryBlock: @escaping () -> any JavaScriptEnvironmentProtocol,\n        resolver: ChallengeResolver = .shared\n    ) {\n        self.actionHandler = actionHandler\n        self.javaScriptEnvironmentFactoryBlock =\n        javaScriptEnvironmentFactoryBlock\n        self.challengeResolver = resolver\n        super.init()\n    }\n\n    /// NativeBridge initializer.\n    public convenience override init() {\n        self.init(\n            actionHandler: NativeBridgeActionHandler(),\n            javaScriptEnvironmentFactoryBlock: {\n                return JavaScriptEnvironment()\n            }\n        )\n    }\n\n    /// NativeBridge initializer.\n    /// - Parameter actionRunner: An action runner to run actions triggered from the web view\n    public convenience init(actionRunner: any NativeBridgeActionRunner) {\n        self.init(\n            actionHandler: NativeBridgeActionHandler(actionRunner: actionRunner),\n            javaScriptEnvironmentFactoryBlock: {\n                return JavaScriptEnvironment()\n            }\n        )\n    }\n\n    /**\n     * Decide whether to allow or cancel a navigation. :nodoc:\n     *\n     * If a uairship:// URL, process it ourselves\n     */\n    public func webView(\n        _ webView: WKWebView,\n        decidePolicyFor navigationAction: WKNavigationAction,\n        decisionHandler: @escaping @MainActor (WKNavigationActionPolicy) -> Void\n    ) {\n        let navigationType = navigationAction.navigationType\n        let originatingURL = webView.url\n        let requestURL = navigationAction.request.url\n\n        let isAirshipJSAllowed =\n        originatingURL?.isAllowed(scope: .javaScriptInterface) ?? false\n\n        // Airship commands\n        if let requestURL = requestURL, isAirshipJSAllowed, requestURL.isAirshipCommand {\n            if navigationType == .linkActivated || navigationType == .other {\n                let command = JavaScriptCommand(url: requestURL)\n                Task { @MainActor in\n                    await self.handleAirshipCommand(\n                        command: command,\n                        webView: webView\n                    )\n                }\n            }\n            decisionHandler(.cancel)\n            return\n        }\n\n        let forward = self.forwardNavigationDelegate?.webView as WebViewForwardHandler?\n\n        // Forward\n        if let forward = forward {\n            forward(webView, navigationAction) { policyForThisURL in\n                if policyForThisURL == WKNavigationActionPolicy.allow\n                    && navigationType == WKNavigationType.linkActivated\n                {\n                    let decisionHandlerWrapper = AirshipUnsafeSendableWrapper(decisionHandler)\n                    Task { @MainActor in\n                        // Try to override any special link handling\n                        self.handle(requestURL) { success in\n                            decisionHandlerWrapper.value(success ? .cancel : .allow)\n                        }\n                    }\n                } else {\n                    decisionHandler(policyForThisURL)\n                }\n            }\n            return\n        }\n\n        // Default\n        guard let requestURL = requestURL else {\n            decisionHandler(.allow)\n            return\n        }\n\n        // Default\n        let handleLink: () -> Void = {\n            /// If target frame is a new window navigation, have OS handle it\n            if navigationAction.targetFrame == nil {\n                DefaultURLOpener.shared.openURL(requestURL) { success in\n                    decisionHandler(success ? .cancel : .allow)\n                }\n            } else {\n                decisionHandler(.allow)\n            }\n        }\n\n        if navigationType == WKNavigationType.linkActivated {\n            let decisionHandlerWrapper = AirshipUnsafeSendableWrapper(decisionHandler)\n            let handleLinkWrapper = AirshipUnsafeSendableWrapper(handleLink)\n\n            Task { @MainActor in\n                self.handle(requestURL) { success in\n                    if success {\n                        decisionHandlerWrapper.value(.cancel)\n                    } else {\n                        handleLinkWrapper.value()\n                    }\n                }\n            }\n\n        } else {\n            handleLink()\n        }\n    }\n\n    /**\n     * Decide whether to allow or cancel a navigation after its response is known. :nodoc:\n     */\n    public func webView(\n        _ webView: WKWebView,\n        decidePolicyFor navigationResponse: WKNavigationResponse,\n        decisionHandler: @Sendable @escaping @MainActor (WKNavigationResponsePolicy) -> Void\n    ) {\n\n        guard\n            let forward = self.forwardNavigationDelegate?.webView\n                as (\n                    (\n                        WKWebView, WKNavigationResponse,\n                        @Sendable @MainActor @escaping (WKNavigationResponsePolicy) -> Void\n                    ) -> Void\n                )?\n        else {\n            decisionHandler(.allow)\n            return\n        }\n\n        forward(webView, navigationResponse, decisionHandler)\n    }\n\n    /**\n     * Called when the navigation is complete. :nodoc:\n     */\n    @MainActor\n    public func webView(\n        _ webView: WKWebView,\n        didFinish navigation: WKNavigation!\n    ) {\n        AirshipLogger.trace(\n            \"Webview finished navigation: \\(String(describing: webView.url))\"\n        )\n\n        cancelJSRequest(webView: webView)\n\n        if let url = webView.url, url.isAllowed(scope: .javaScriptInterface) {\n            AirshipLogger.trace(\"Loading Airship JS bridge: \\(url)\")\n\n            let request = JSBridgeLoadRequest(webView: webView) { [weak self] in\n                return await self?.makeJSEnvironment(webView: webView)\n            }\n            self.jsRequests.append(request)\n            request.start()\n        }\n\n        self.forwardNavigationDelegate?.webView?(\n            webView,\n            didFinish: navigation\n        )\n    }\n\n    /**\n     * Called when the web view begins to receive web content. :nodoc:\n     */\n    public func webView(\n        _ webView: WKWebView,\n        didCommit navigation: WKNavigation!\n    ) {\n        self.forwardNavigationDelegate?.webView?(\n            webView,\n            didCommit: navigation\n        )\n    }\n\n    /**\n     * Called when the web view’s web content process is terminated. :nodoc:\n     */\n    public func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {\n        self.forwardNavigationDelegate?\n            .webViewWebContentProcessDidTerminate?(\n                webView\n            )\n    }\n\n    /**\n     * Called when web content begins to load in a web view. :nodoc:\n     */\n    @MainActor\n    public func webView(\n        _ webView: WKWebView,\n        didStartProvisionalNavigation navigation: WKNavigation!\n    ) {\n        self.cancelJSRequest(webView: webView)\n        self.forwardNavigationDelegate?.webView?(\n            webView,\n            didStartProvisionalNavigation: navigation\n        )\n    }\n\n    /**\n     * Called when a web view receives a server redirect. :nodoc:\n     */\n    public func webView(\n        _ webView: WKWebView,\n        didReceiveServerRedirectForProvisionalNavigation navigation:\n        WKNavigation!\n    ) {\n        self.forwardNavigationDelegate?.webView?(\n            webView,\n            didReceiveServerRedirectForProvisionalNavigation: navigation\n        )\n    }\n\n    /**\n     * Called when an error occurs during navigation. :nodoc:\n     */\n    public func webView(\n        _ webView: WKWebView,\n        didFail navigation: WKNavigation!,\n        withError error: any Error\n    ) {\n        self.forwardNavigationDelegate?.webView?(\n            webView,\n            didFail: navigation,\n            withError: error\n        )\n    }\n\n    /**\n     * Called when an error occurs while the web view is loading content. :nodoc:\n     */\n    public func webView(\n        _ webView: WKWebView,\n        didFailProvisionalNavigation navigation: WKNavigation!,\n        withError error: any Error\n    ) {\n        self.forwardNavigationDelegate?.webView?(\n            webView,\n            didFailProvisionalNavigation: navigation,\n            withError: error\n        )\n    }\n\n    /**\n     * Called when the web view needs to respond to an authentication challenge. :nodoc:\n     */\n    public func webView(\n        _ webView: WKWebView,\n        didReceive challenge: URLAuthenticationChallenge,\n        completionHandler: @Sendable @escaping @MainActor (\n            URLSession.AuthChallengeDisposition, URLCredential?\n        ) -> Void\n    ) {\n\n        guard\n            let forward = self.forwardNavigationDelegate?.webView\n                as (\n                    (\n                        WKWebView, URLAuthenticationChallenge,\n                        @Sendable @MainActor @escaping (\n                            URLSession.AuthChallengeDisposition,\n                            URLCredential?\n                        ) ->\n                        Void\n                    ) -> Void\n                )?\n        else {\n            Task {\n                let (disposition, creds) = await challengeResolver.resolve(challenge)\n                completionHandler(disposition, creds)\n            }\n            return\n        }\n\n        forward(webView, challenge, completionHandler)\n    }\n\n    @MainActor\n    private func makeJSEnvironment(webView: WKWebView) async -> String {\n        let jsEnvironment: any JavaScriptEnvironmentProtocol = self.javaScriptEnvironmentFactoryBlock()\n\n        await self.nativeBridgeExtensionDelegate?.extendJavaScriptEnvironment(\n            jsEnvironment,\n            webView: webView\n        )\n\n        return await jsEnvironment.build()\n    }\n\n    @MainActor\n    private func handleAirshipCommand(\n        command: JavaScriptCommand,\n        webView: WKWebView\n    ) async {\n        switch command.name {\n        case NativeBridge.closeCommand:\n            self.nativeBridgeDelegate?.close()\n\n        case NativeBridge.dismissCommand:\n            self.nativeBridgeDelegate?.close()\n\n        case NativeBridge.setNamedUserCommand:\n            let idArgs = command.options[\"id\"]\n            let argument = idArgs?.first\n\n            let contact: any AirshipContact = Airship.contact\n            if let identifier = argument, !identifier.isEmpty {\n                contact.identify(identifier)\n            } else {\n                contact.reset()\n            }\n\n        case NativeBridge.multiCommand:\n            let commands = command.url.query?.components(separatedBy: \"&\")\n                .compactMap {\n                    URL(string: $0.removingPercentEncoding ?? \"\")\n                }\n                .filter {\n                    $0.isAirshipCommand\n                }.compactMap { url in\n                    JavaScriptCommand(url: url)\n                } ?? []\n\n            for command in commands {\n                await self.handleAirshipCommand(\n                    command: command,\n                    webView: webView\n                )\n            }\n        default:\n            if NativeBridgeActionHandler.isActionCommand(command: command) {\n                let metadata = self.nativeBridgeExtensionDelegate?\n                    .actionsMetadata(\n                        for: command,\n                        webView: webView\n                    )\n\n                let script = await self.actionHandler.runActionsForCommand(\n                    command: command,\n                    metadata: metadata,\n                    webView: webView\n                )\n\n                do {\n                    if let script = script {\n                        try await webView.evaluateJavaScriptAsync(script)\n                    }\n                } catch {\n                    AirshipLogger.error(\"JavaScript error: \\(error) command: \\(command)\")\n                }\n            } else if !forwardAirshipCommand(command, webView: webView) {\n                AirshipLogger.debug(\"Unhandled JavaScript command: \\(command)\")\n            }\n        }\n    }\n\n    @MainActor\n    private func forwardAirshipCommand(\n        _ command: JavaScriptCommand,\n        webView: WKWebView\n    ) -> Bool {\n        /// Local JavaScript command delegate\n        if self.javaScriptCommandDelegate?\n            .performCommand(command, webView: webView)\n            == true\n        {\n            return true\n        }\n\n        if Airship.javaScriptCommandDelegate?\n            .performCommand(\n                command,\n                webView: webView\n            ) == true\n        {\n            return true\n        }\n\n        return false\n    }\n\n    @MainActor\n    private func cancelJSRequest(webView: WKWebView) {\n        jsRequests.removeAll { request in\n            if request.webView == nil || request.webView == webView {\n                request.cancel()\n                return true\n            }\n            return false\n        }\n    }\n    /**\n     * Handles a link click.\n     *\n     * - Parameters:\n     *   - url The link's URL.\n     *   - completionHandler  The completion handler to execute when openURL processing is complete.\n     * -\n     */\n    @available(iOSApplicationExtension, unavailable)\n    @MainActor\n    private func handle(\n        _ url: URL?,\n        _ completionHandler: @Sendable @escaping @MainActor (Bool) -> Void\n    ) {\n        guard let url = url, shouldForwardURL(url) else {\n            completionHandler(false)\n            return\n        }\n\n        DefaultURLOpener.shared.openURL(url, completionHandler: completionHandler)\n    }\n\n    private func shouldForwardURL(_ url: URL) -> Bool {\n        let scheme = url.scheme?.lowercased() ?? \"\"\n        let host = url.host?.lowercased() ?? \"\"\n        return NativeBridge.forwardSchemes.contains(scheme)\n        || NativeBridge.forwardHosts.contains(host)\n    }\n\n    private func closeWindow(_ animated: Bool) {\n        self.forwardNavigationDelegate?.closeWindow?(animated)\n    }\n}\n\n@objc\npublic protocol AirshipWKNavigationDelegate: WKNavigationDelegate {\n    @objc optional func closeWindow(_ animated: Bool)\n}\n\nextension URL {\n    @MainActor\n    fileprivate var isAirshipCommand: Bool {\n        return self.scheme == NativeBridge.airshipScheme\n    }\n\n    @MainActor\n    fileprivate func isAllowed(scope: URLAllowListScope) -> Bool {\n        return Airship.urlAllowList.isAllowed(self, scope: scope)\n    }\n}\n\n\n@MainActor\nfileprivate class JSBridgeLoadRequest: Sendable {\n    private(set) weak var webView: WKWebView?\n    private let jsFactoryBlock: () async throws -> String?\n    private var task: Task<Void, Never>?\n\n    init(webView: WKWebView? = nil, jsFactoryBlock: @escaping () async throws -> String?) {\n        self.webView = webView\n        self.jsFactoryBlock = jsFactoryBlock\n    }\n\n    func start() {\n        task?.cancel()\n        self.task = Task { @MainActor in\n            do {\n                try Task.checkCancellation()\n                let js = try await jsFactoryBlock()\n                try Task.checkCancellation()\n                if let webView = webView, let js = js {\n                    try await webView.evaluateJavaScript(js)\n                    AirshipLogger.trace(\"Native bridge injected\")\n                }\n            } catch {\n\n            }\n        }\n    }\n\n    func cancel() {\n        self.task?.cancel()\n    }\n}\n\nfileprivate extension WKWebView {\n\n    //The async/await version of `evaluateJavaScript` function exposed by apple is crashing when the JavaScript is a void method. We created this func to avoid the crash and we can update once the crash is fixed.\n    @discardableResult\n    func evaluateJavaScriptAsync(_ str: String) async throws -> Any? {\n        return try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Any?, Error>) in\n            DispatchQueue.main.async {\n                self.evaluateJavaScript(str) { data, error in\n                    if let error = error {\n                        continuation.resume(throwing: error)\n                    } else {\n                        continuation.resume(returning: AnySendable(value: data))\n                    }\n                }\n            }\n        }\n    }\n}\n\n// WORKAROUND: WKWebView's evaluateJavaScript async API requires Sendable closures,\n// but returns non-Sendable Any? values. This wrapper uses @unchecked Sendable to\n// bridge the JavaScript evaluation result safely across async boundaries.\nfileprivate struct AnySendable: @unchecked Sendable {\n    let value: Any?\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NativeBridgeActionHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\nimport WebKit\n\nprotocol NativeBridgeActionHandlerProtocol: Sendable {\n    \n    /**\n     * Runs actions for a command.\n     *  - Parameters:\n     *    - command The action command.\n     *    - metadata The action metadata.\n     *  - Returns: Returns the optional script to evaluate in the web view..\n     */\n    @MainActor\n    func runActionsForCommand(\n        command: JavaScriptCommand,\n        metadata: [String: any Sendable]?,\n        webView: WKWebView\n    ) async -> String?\n}\n\nprivate struct DefaultNativeBridgeActionRunner: NativeBridgeActionRunner {\n    func runAction(actionName: String, arguments: ActionArguments, webView: WKWebView) async -> ActionResult {\n        return await ActionRunner.run(actionName: actionName, arguments: arguments)\n    }\n}\n\nfinal class NativeBridgeActionHandler: NativeBridgeActionHandlerProtocol {\n\n\n    private let actionRunner: any NativeBridgeActionRunner\n\n    init(actionRunner: any NativeBridgeActionRunner = DefaultNativeBridgeActionRunner()) {\n        self.actionRunner = actionRunner\n    }\n\n    /**\n     * Runs actions for a command.\n     *  - Parameters:\n     *   - command The action command.\n     *   - metadata The action metadata.\n     *   - webView The web view\n     */\n    @MainActor\n    public func runActionsForCommand(\n        command: JavaScriptCommand,\n        metadata: [String: any Sendable]?,\n        webView: WKWebView\n    ) async -> String? {\n        AirshipLogger.debug(\"Running actions for command: \\(command)\")\n        \n        /*\n         * run-action-cb performs a single action and calls the completion handler with\n         * the result of the action. The action's value is JSON encoded.\n         *\n         * Expected format:\n         * run-action-cb/<actionName>/<actionValue>/<callbackID>\n         */\n        if command.name == \"run-action-cb\" {\n            if command.arguments.count != 3 {\n                AirshipLogger.debug(\n                    String(\n                        format:\n                            \"Unable to run-action-cb, wrong number of arguments. %@\",\n                        command.arguments\n                    )\n                )\n                AirshipLogger.error(\"Unable to run-action-cb, wrong number of arguments\")\n            }\n            \n            let actionName = command.arguments[0]\n            let actionValue = NativeBridgeActionHandler.parse(\n                command.arguments[1]\n            )\n            let callbackID = command.arguments[2]\n\n            /// Run the action\n            return await self.run(\n                actionName,\n                actionValue,\n                metadata ?? [:],\n                callbackID,\n                webView: webView\n            )\n        }\n        \n        /*\n         * run-actions performs several actions with the values JSON encoded.\n         *\n         * Expected format:\n         * run-actions?<actionName>=<actionValue>&<anotherActionName>=<anotherActionValue>...\n         */\n        if command.name == \"run-actions\" {\n            await self.run(\n                self.decodeActionValues(command, false),\n                metadata: metadata,\n                webView: webView\n            )\n            return nil\n        }\n        \n        /*\n         * run-basic-actions performs several actions with basic encoded action values.\n         *\n         * Expected format:\n         * run-basic-actions?<actionName>=<actionValue>&<anotherActionName>=<anotherActionValue>...\n         */\n        if command.name == \"run-basic-actions\" {\n                await self.run(\n                    self.decodeActionValues(command, true),\n                    metadata: metadata,\n                    webView: webView\n                )\n            return nil\n        }\n        \n        return nil\n    }\n\n    /**\n     * Runs a dictionary of action names to an array of action values.\n     *\n     * - Parameters:\n     *   - actionValues A map of action name to an array of action values.\n     *   - metadata Optional metadata to pass to the action arguments.\n     */\n    @MainActor\n    private func run(\n        _ actionValues: [String: [AirshipJSON]],\n        metadata: [String: any Sendable]?,\n        webView: WKWebView\n    ) async {\n        for (actionName, values) in actionValues {\n            for value in values {\n                _ = await self.actionRunner.runAction(\n                    actionName: actionName,\n                    arguments: ActionArguments(\n                        value: value,\n                        situation: .webViewInvocation,\n                        metadata: metadata ?? [:]\n                    ),\n                    webView: webView\n                )\n            }\n        }\n    }\n    \n    /**\n     * Runs an action with a given value.\n     *\n     * - Parameters:\n     *   - actionName The name of the action to perform\n     *   - actionValue The action argument's value\n     *   - metadata Optional metadata to pass to the action arguments.\n     *   - callbackID A callback identifier generated in the JS layer. This can be `nil`.\n     */\n    @MainActor\n    private func run(\n        _ actionName: String,\n        _ actionValue: AirshipJSON,\n        _ metadata: [String: any Sendable],\n        _ callbackID: String,\n        webView: WKWebView\n    ) async -> String? {\n        \n        let callbackID = try? AirshipJSONUtils.string(\n            callbackID,\n            options: .fragmentsAllowed\n        )\n        \n        let result = await self.actionRunner.runAction(\n            actionName: actionName, \n            arguments:  ActionArguments(\n                value: actionValue,\n                situation: .webViewInvocation,\n                metadata: metadata\n            ),\n            webView: webView\n        )\n        guard let callbackID = callbackID else {\n            return nil\n        }\n\n        switch result {\n        case .completed(let value):\n            return \"UAirship.finishAction(null, \\((try? value.toString()) ?? \"null\"), \\(callbackID));\"\n        case .actionNotFound:\n            return errorResponse(\n                errorMessage: \"No action found with name \\(actionName), skipping action.\",\n                callbackID: callbackID\n            )\n        case .error(let error):\n            return errorResponse(\n                errorMessage: error.localizedDescription,\n                callbackID: callbackID\n            )\n        case .argumentsRejected:\n            return errorResponse(\n                errorMessage: \"Action \\(actionName) rejected arguments.\",\n                callbackID: callbackID\n            )\n        }\n    }\n    \n    private class func parse(_ json: String) -> AirshipJSON {\n        do {\n            return try AirshipJSON.from(json: json)\n        } catch {\n            AirshipLogger.warn(\"Unable to json decode action args \\(error), \\(json)\")\n            return AirshipJSON.null\n        }\n    }\n\n    private func errorResponse(errorMessage: String, callbackID: String) -> String {\n        let json = (try? AirshipJSONUtils.string(errorMessage, options: .fragmentsAllowed)) ?? \"\"\n\n        return \"var error = new Error(); error.message = \\(json); UAirship.finishAction(error, null, \\(callbackID));\"\n    }\n    \n    /**\n     * Checks if a command defines an action.\n     * - Parameters:\n     *  - command The command.\n     * - Returns: `YES` if the command is either `run-actions`, `run-action`, or `run-action-cb`, otherwise `NO`.\n     */\n    public class func isActionCommand(command: JavaScriptCommand) -> Bool {\n        let name = command.name\n        return\n            (name == \"run-actions\" || name == \"run-basic-actions\"\n            || name == \"run-action-cb\")\n    }\n    \n    /**\n     * Decodes options with basic URL or URL+json encoding\n     *\n     * - Parameters:\n     *   - command The JavaScript command.\n     *   - basicEncoding Boolean to select for basic encoding\n     * - Returns: A dictionary of action name to an array of action values.\n     */\n    private func decodeActionValues(\n        _ command: JavaScriptCommand,\n        _ basicEncoding: Bool\n    ) -> [String: [AirshipJSON]] {\n        var actionValues: [String: [AirshipJSON]] = [:]\n\n        do {\n            try command.options.forEach { (actionName, optionValues) in\n                actionValues[actionName] = try optionValues.compactMap { actionArg in\n                    if (actionArg.isEmpty) {\n                        return AirshipJSON.null\n                    }\n\n                    if basicEncoding{\n                        return AirshipJSON.string(actionArg)\n                    }\n                    \n                    return try AirshipJSON.from(json: actionArg)\n                }\n            }\n        } catch {\n            AirshipLogger.warn(\"Unable to json decode action args \\(error) for command \\(command)\")\n            return [:]\n        }\n\n        return actionValues\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NativeBridgeActionRunner.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\npublic import WebKit\n\n/// Action runner used in the `NativeBridge`.\npublic protocol NativeBridgeActionRunner: Sendable {\n    /// Called to run an action when triggered from the web view.\n    ///  - Parameters:\n    ///     - actionName: The action name.\n    ///     - arguments: The action arguments.\n    ///     - webView: The web view.\n    /// - Returns: The action result.\n    @MainActor\n    func runAction(actionName: String, arguments: ActionArguments, webView: WKWebView) async -> ActionResult\n}\n\n\n/// Action runner used in the `NativeBridge` that calls through to a block.\npublic struct BlockNativeBridgeActionRunner: NativeBridgeActionRunner {\n    private let onRun: @MainActor (String, ActionArguments, WKWebView) async -> ActionResult\n\n\n    /// Default initialiizer.\n    ///  - Parameters:\n    ///     - onRun: The action block.\n    public init(onRun: @escaping @MainActor (String, ActionArguments, WKWebView) async -> ActionResult) {\n        self.onRun = onRun\n    }\n\n    @MainActor\n    public func runAction(actionName: String, arguments: ActionArguments, webView: WKWebView) async -> ActionResult {\n        return await self.onRun(actionName, arguments, webView)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NativeBridgeDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if !os(tvOS) && !os(watchOS)\n\n/// Delegate for native bridge events from web views.\npublic protocol NativeBridgeDelegate: AnyObject {\n    /// Called when `UAirship.close()` is triggered from the JavaScript environment.\n    func close()\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NativeBridgeExtensionDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\npublic import WebKit\n\n/// Delegate to extend the native bridge.\npublic protocol NativeBridgeExtensionDelegate: AnyObject {\n\n    /// Called when an action is triggered from the JavaScript Environment. This method should return the metadata used in the `ActionArgument`.\n    /// - Parameter command The JavaScript command.\n    /// - Parameter webView The webview.\n    /// @return The action metadata.\n    @MainActor\n    func actionsMetadata(\n        for command: JavaScriptCommand,\n        webView: WKWebView\n    ) -> [String: String]\n\n    /// Called before the JavaScript environment is being injected into the web view.\n    /// - Parameter js The JavaScript environment.\n    /// - Parameter webView  The web view.\n    /// - Parameter completionHandler The completion handler when finished.\n    @MainActor\n    func extendJavaScriptEnvironment(\n        _ js: any JavaScriptEnvironmentProtocol,\n        webView: WKWebView\n    ) async\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NativeVideoPlayer.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(watchOS) && !os(macOS)\n\nimport Foundation\nimport SwiftUI\nimport AVKit\nimport AVFoundation\nimport UIKit\n\n@MainActor\nstruct NativeVideoPlayer: UIViewRepresentable {\n    typealias UIViewType = UIView\n\n    let info: ThomasViewInfo.Media\n    let videoIdentifier: String?\n    let onMediaReady: @MainActor () -> Void\n\n    @Binding var hasError: Bool\n    @Binding var player: AVPlayer?\n\n    @Environment(\\.isVisible) private var isVisible\n    @Environment(\\.layoutDirection) private var layoutDirection\n    @State private var isLoaded: Bool = false\n    @EnvironmentObject var pagerState: PagerState\n    @EnvironmentObject var videoState: VideoState\n\n    private var video: ThomasViewInfo.Media.Video? {\n        self.info.properties.video\n    }\n\n    private var url: String {\n        self.info.properties.url\n    }\n\n    @MainActor\n    func makeUIView(context: Context) -> UIView {\n        let playerContainer = VideoPlayerContainer(frame: .zero)\n        playerContainer.isAccessibilityElement = true\n        playerContainer.accessibilityLabel = self.info.accessible.contentDescription\n        playerContainer.info = self.info\n        playerContainer.isRTL = layoutDirection == .rightToLeft\n\n        guard let videoURL = URL(string: url) else {\n            Task { @MainActor in\n                self.hasError = true\n            }\n            return playerContainer\n        }\n\n        let playerItem = AVPlayerItem(url: videoURL)\n        let playerInstance = AVPlayer(playerItem: playerItem)\n\n        playerContainer.player = playerInstance\n        playerContainer.videoURL = videoURL\n        playerContainer.shouldLoop = video?.loop ?? false\n        playerContainer.isMuted = video?.muted ?? false\n        playerContainer.configurePlayerView()\n\n        Task { @MainActor [weak coordinator = context.coordinator] in\n            self.player = playerInstance\n            coordinator?.configure(\n                playerContainer: playerContainer,\n                onMediaReady: onMediaReady\n            )\n        }\n\n        return playerContainer\n    }\n\n    static func dismantleUIView(_ uiView: UIView, coordinator: Coordinator) {\n        coordinator.teardown()\n    }\n\n    @MainActor\n    func updateUIView(_ uiView: UIView, context: Context) {\n        let isVisible = isVisible\n        let isLoaded = isLoaded\n        let inProgress = pagerState.inProgress\n        Task { @MainActor [weak coordinator = context.coordinator] in\n            coordinator?.update(\n                isVisible: isVisible,\n                isLoaded: isLoaded,\n                inProgress: inProgress\n            )\n        }\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(\n            isLoaded: $isLoaded,\n            hasError: $hasError,\n            videoState: videoState,\n            videoIdentifier: videoIdentifier,\n            isAutoplay: video?.autoplay ?? false,\n            showControls: video?.showControls ?? true,\n            autoResetPosition: video?.autoResetPosition ?? ((video?.autoplay ?? false) && !(video?.showControls ?? true))\n        )\n    }\n\n    // MARK: - PlayerObservers\n\n    private final class PlayerObservers: @unchecked Sendable {\n        var endTimeObserver: (any NSObjectProtocol)?\n        var statusObserver: NSKeyValueObservation?\n        var rateObserver: NSKeyValueObservation?\n        var muteObserver: NSKeyValueObservation?\n        weak var player: AVPlayer?\n\n        func cleanup() {\n            if let observer = endTimeObserver {\n                NotificationCenter.default.removeObserver(observer)\n                endTimeObserver = nil\n            }\n            statusObserver?.invalidate()\n            statusObserver = nil\n            rateObserver?.invalidate()\n            rateObserver = nil\n            muteObserver?.invalidate()\n            muteObserver = nil\n        }\n    }\n\n    // MARK: - Coordinator\n\n    @MainActor\n    class Coordinator: NSObject {\n        private var isLoaded: Binding<Bool>\n        private var hasError: Binding<Bool>\n        private var videoState: VideoState\n        private var videoIdentifier: String?\n        private var isAutoplay: Bool\n        private var showControls: Bool\n        private var autoResetPosition: Bool\n\n        private weak var playerContainer: VideoPlayerContainer?\n        private var onMediaReady: (@MainActor () -> Void)?\n        private let observers = PlayerObservers()\n\n        private var lastIsVisible: Bool = false\n        private var lastIsLoaded: Bool = false\n        private var lastInProgress: Bool = true\n\n        /// Tracks whether the system (visibility change, pager, backgrounding) initiated a pause.\n        /// When true, incoming rate changes from AVPlayer won't clear `localIsPlaying`.\n        private var isSystemPausing: Bool = false\n\n        /// Tracks playing intent from AVPlayer rate changes. `nil` = initial (autoplay should trigger),\n        /// `true` = playing/was playing, `false` = user explicitly paused.\n        /// Guarded by `isSystemPausing` so system pauses don't clear user intent.\n        private var localIsPlaying: Bool? = nil\n\n        private var appStateTask: Task<Void, Never>?\n\n        init(\n            isLoaded: Binding<Bool>,\n            hasError: Binding<Bool>,\n            videoState: VideoState,\n            videoIdentifier: String?,\n            isAutoplay: Bool,\n            showControls: Bool,\n            autoResetPosition: Bool\n        ) {\n            self.isLoaded = isLoaded\n            self.hasError = hasError\n            self.videoState = videoState\n            self.videoIdentifier = videoIdentifier\n            self.isAutoplay = isAutoplay\n            self.showControls = showControls\n            self.autoResetPosition = autoResetPosition\n\n            super.init()\n\n            AirshipLogger.trace(\"NativeVideoPlayer Coordinator init\")\n\n\n            appStateTask = Task { @MainActor [weak self] in\n                for await state in AppStateTracker.shared.stateUpdates {\n                    guard !Task.isCancelled else { return }\n                    if state == .active {\n                        self?.handleForeground()\n                    } else {\n                        self?.systemPause()\n                    }\n                }\n            }\n        }\n\n        deinit {\n            appStateTask?.cancel()\n            AirshipLogger.trace(\"NativeVideoPlayer Coordinator deinit\")\n        }\n\n        @MainActor\n        func teardown() {\n            appStateTask?.cancel()\n            appStateTask = nil\n\n            observers.player?.pause()\n            observers.cleanup()\n\n            if let videoIdentifier {\n                videoState.unregister(videoIdentifier: videoIdentifier)\n            }\n\n            playerContainer = nil\n        }\n\n        // MARK: - Configuration\n\n        @MainActor\n        func configure(\n            playerContainer: VideoPlayerContainer,\n            onMediaReady: @MainActor @escaping () -> Void\n        ) {\n            cleanupObservers()\n            self.playerContainer = playerContainer\n            self.onMediaReady = onMediaReady\n            setupObservers()\n            registerWithVideoState()\n        }\n\n        // MARK: - State Management\n\n        @MainActor\n        func update(isVisible: Bool, isLoaded: Bool, inProgress: Bool) {\n            let didChange = lastIsVisible != isVisible\n                || lastIsLoaded != isLoaded\n                || lastInProgress != inProgress\n            lastIsVisible = isVisible\n            lastIsLoaded = isLoaded\n            lastInProgress = inProgress\n\n            playerContainer?.alpha = hasError.wrappedValue ? 0 : 1\n\n            guard didChange else { return }\n\n            if inProgress, isVisible, isLoaded {\n                handleResume()\n            } else {\n                if !isVisible {\n                    self.resetToBeginning()\n                }\n                systemPause()\n            }\n        }\n\n        @MainActor\n        private func handleResume() {\n            let shouldPlay: Bool\n            if videoState.shouldControl(videoIdentifier: videoIdentifier) {\n                shouldPlay = videoState.isPlaying\n            } else if isAutoplay {\n                shouldPlay = localIsPlaying != false\n            } else {\n                shouldPlay = localIsPlaying == true\n            }\n            isSystemPausing = false\n            if shouldPlay {\n                localIsPlaying = true\n                playerContainer?.player?.play()\n            }\n        }\n\n        @MainActor\n        private func systemPause() {\n            isSystemPausing = true\n            playerContainer?.player?.pause()\n        }\n\n        @MainActor\n        private func resetToBeginning() {\n            guard autoResetPosition else { return }\n            playerContainer?.player?.seek(to: .zero)\n        }\n\n        @MainActor\n        private func handleForeground() {\n            guard lastIsVisible, lastIsLoaded, lastInProgress else { return }\n            handleResume()\n        }\n\n        // MARK: - Video State Registration\n\n        @MainActor\n        private func registerWithVideoState() {\n            guard let videoId = videoIdentifier,\n                  videoState.shouldControl(videoIdentifier: videoId),\n                  let player = playerContainer?.player else {\n                return\n            }\n\n            videoState.register(\n                videoIdentifier: videoId,\n                play: { [weak player] in player?.play() },\n                pause: { [weak player] in player?.pause() },\n                mute: { [weak player] in player?.isMuted = true },\n                unmute: { [weak player] in player?.isMuted = false }\n            )\n\n            videoState.muteGroup.initializeMuted(player.isMuted)\n            videoState.playGroup.initializePlaying(isAutoplay || player.rate > 0)\n            player.isMuted = videoState.isMuted\n        }\n\n        // MARK: - Observers\n\n        @MainActor\n        private func setupObservers() {\n            guard let playerContainer = playerContainer,\n                  let player = playerContainer.player else { return }\n\n            let shouldLoop = playerContainer.shouldLoop\n            observers.player = player\n\n            observers.endTimeObserver = NotificationCenter.default.addObserver(\n                forName: .AVPlayerItemDidPlayToEndTime,\n                object: player.currentItem,\n                queue: .main\n            ) { [weak player] _ in\n                Task { @MainActor in\n                    if shouldLoop {\n                        player?.seek(to: .zero)\n                        player?.play()\n                    }\n                }\n            }\n\n            if let playerItem = player.currentItem {\n                let isLoadedBinding = isLoaded\n                let hasErrorBinding = hasError\n                let onReady = onMediaReady\n\n                observers.statusObserver = playerItem.observe(\\.status, options: [.new]) { item, _ in\n                    Task { @MainActor in\n                        switch item.status {\n                        case .readyToPlay:\n                            isLoadedBinding.wrappedValue = true\n                            hasErrorBinding.wrappedValue = false\n                            onReady?()\n                        case .failed:\n                            hasErrorBinding.wrappedValue = true\n                        case .unknown:\n                            break\n                        @unknown default:\n                            break\n                        }\n                    }\n                }\n            }\n\n            observers.muteObserver = player.observe(\\.isMuted, options: [.new]) { [weak self] player, _ in\n                Task { @MainActor in\n                    guard let self else { return }\n                    let canControlVideo = self.lastIsVisible\n                        && self.videoState.shouldControl(videoIdentifier: self.videoIdentifier)\n                        && self.showControls\n                    if canControlVideo {\n                        self.videoState.updateMutedState(player.isMuted)\n                    }\n                }\n            }\n\n            observers.rateObserver = player.observe(\\.timeControlStatus, options: [.new]) { [weak self] player, _ in\n                Task { @MainActor in\n                    guard let self else { return }\n                    let isPlaying = player.timeControlStatus == .playing\n                    let isPaused = player.timeControlStatus == .paused\n\n                    let canControlVideo = self.lastIsVisible\n                        && self.videoState.shouldControl(videoIdentifier: self.videoIdentifier)\n\n                    if canControlVideo {\n                        if isPlaying {\n                            self.videoState.updatePlayingState(true)\n                        } else if isPaused && !self.isSystemPausing {\n                            self.videoState.updatePlayingState(false)\n                        }\n                    } else {\n                        if isPlaying {\n                            self.localIsPlaying = true\n                        } else if isPaused && !self.isSystemPausing {\n                            self.localIsPlaying = false\n                        }\n                    }\n                }\n            }\n        }\n\n        @MainActor\n        private func cleanupObservers() {\n            observers.cleanup()\n        }\n    }\n\n    // MARK: - VideoPlayerContainer\n\n    class VideoPlayerContainer: UIView {\n        var player: AVPlayer?\n        var videoURL: URL?\n        var shouldLoop: Bool = false\n        var isMuted: Bool = false\n        var info: ThomasViewInfo.Media?\n        var isRTL: Bool = false\n\n        override init(frame: CGRect) {\n            super.init(frame: frame)\n            backgroundColor = .clear\n        }\n\n        required init?(coder: NSCoder) {\n            fatalError(\"init(coder:) has not been implemented\")\n        }\n\n        func configurePlayerView() {\n            layer.sublayers?.forEach {\n                if $0 is AVPlayerLayer {\n                    $0.removeFromSuperlayer()\n                }\n            }\n\n            setupPlayer()\n\n            player?.isMuted = isMuted\n        }\n\n        func setupPlayer() {\n            guard let player = self.player else { return }\n\n            let playerLayer = AVPlayerLayer(player: player)\n            playerLayer.frame = bounds\n\n            if let mediaInfo = self.info {\n                playerLayer.videoGravity = videoGravityForMediaFit(mediaInfo.properties.mediaFit)\n            } else {\n                playerLayer.videoGravity = videoGravityForMediaFit(.centerInside)\n            }\n\n            layer.addSublayer(playerLayer)\n        }\n\n        func videoGravityForMediaFit(_ mediaFit: ThomasMediaFit) -> AVLayerVideoGravity {\n            switch mediaFit {\n            case .centerInside:\n                return .resizeAspect\n            case .center:\n                return .resize\n            case .fitCrop, .centerCrop:\n                return .resizeAspectFill\n            }\n        }\n\n        override func layoutSubviews() {\n            super.layoutSubviews()\n\n            if let playerLayer = layer.sublayers?.first(where: { $0 is AVPlayerLayer }) as? AVPlayerLayer {\n                playerLayer.frame = bounds\n            }\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NotificationCategories.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n\n#if !os(tvOS)\n\npublic import UserNotifications\n\n/// Utility methods to create categories from plist files or dictionaries.\npublic final class NotificationCategories {\n    // MARK: - Notification Categories Factories\n\n    /**\n     * Factory method to create the default set of user notification categories.\n     * Background user notification actions will default to requiring authorization.\n     * - Returns: A set of user notification categories\n     */\n    public class func defaultCategories() -> Set<UNNotificationCategory> {\n        return self.defaultCategories(withRequireAuth: true)\n    }\n\n    /**\n     * Factory method to create the default set of user notification categories.\n     *\n     * - Parameter requireAuth: If background actions should default to requiring authorization or not.\n     * - Returns: A set of user notification categories.\n     */\n    public class func defaultCategories(withRequireAuth requireAuth: Bool)\n        -> Set<UNNotificationCategory>\n    {\n        guard\n            let path = AirshipCoreResources.bundle.path(\n                forResource: \"UANotificationCategories\",\n                ofType: \"plist\"\n            )\n        else {\n            return []\n        }\n\n        return self.createCategories(\n            fromFile: path,\n            requireAuth: requireAuth\n        )\n    }\n\n    /**\n     * Creates a set of categories from the specified `.plist` file.\n     *\n     * Categories are defined in a plist dictionary with the category ID\n     * followed by an array of user notification action definitions. The\n     * action definitions use the same keys as the properties on the action,\n     * with the exception of \"foreground\" mapping to either UIUserNotificationActivationModeForeground\n     * or UIUserNotificationActivationModeBackground. The required action definition\n     * title can be defined with either the \"title\" or \"title_resource\" key, where\n     * the latter takes precedence. If \"title_resource\" does not exist, the action\n     * definition title will fall back to the value of \"title\". If the required action\n     * definition title is not defined, the category will not be created.\n     *\n     * Example:\n     *\n     *  {\n     *      \"category_id\" : [\n     *          {\n     *              \"identifier\" : \"action ID\",\n     *              \"title_resource\" : \"action title resource\",\n     *              \"title\" : \"action title\",\n     *              \"foreground\" : true,\n     *              \"authenticationRequired\" : false,\n     *              \"destructive\" : false\n     *          }]\n     *  }\n     *\n     * - Parameter path: The path of the `plist` file\n     * - Returns: A set of categories\n     */\n    public class func createCategories(fromFile path: String) -> Set<\n        UNNotificationCategory\n    > {\n        return self.createCategories(\n            fromFile: path,\n            actionDefinitionModBlock: { _ in }\n        )\n    }\n\n    /**\n     * Creates a user notification category with the specified ID and action definitions.\n     *\n     * - Parameter categoryId: The category identifier\n     * - Parameter actionDefinitions: An array of user notification action dictionaries used to construct UNNotificationAction for the category.\n     * - Returns: The user notification category created, or `nil` if an error occurred.\n     */\n    public class func createCategory(\n        _ categoryId: String,\n        actions actionDefinitions: [[AnyHashable: Any]]\n    ) -> UNNotificationCategory? {\n        guard\n            let actions = self.getActionsFromActionDefinitions(\n                actionDefinitions\n            )\n        else {\n            return nil\n        }\n\n        return UNNotificationCategory(\n            identifier: categoryId,\n            actions: actions,\n            intentIdentifiers: [],\n            options: []\n        )\n    }\n\n    /**\n     * Creates a user notification category with the specified ID, action definitions, and\n     * hiddenPreviewsBodyPlaceholder.\n     *\n     * - Parameter categoryId: The category identifier\n     * - Parameter actionDefinitions: An array of user notification action dictionaries used to construct UNNotificationAction for the category.\n     * - Parameter hiddenPreviewsBodyPlaceholder: A placeholder string to display when the user has disabled notification previews for the app.\n     * - Returns: The user notification category created or `nil` if an error occurred.\n     */\n    public class func createCategory(\n        _ categoryId: String,\n        actions actionDefinitions: [[AnyHashable: Any]],\n        hiddenPreviewsBodyPlaceholder: String\n    ) -> UNNotificationCategory? {\n\n        guard\n            let actions = self.getActionsFromActionDefinitions(\n                actionDefinitions\n            )\n        else {\n            return nil\n        }\n\n        #if !os(watchOS)\n        return UNNotificationCategory(\n            identifier: categoryId,\n            actions: actions,\n            intentIdentifiers: [],\n            hiddenPreviewsBodyPlaceholder:\n                hiddenPreviewsBodyPlaceholder,\n            options: []\n        )\n        #else\n        return UNNotificationCategory(\n            identifier: categoryId,\n            actions: actions,\n            intentIdentifiers: [],\n            options: []\n        )\n        #endif\n    }\n\n    private class func createCategories(\n        fromFile path: String,\n        requireAuth: Bool\n    ) -> Set<UNNotificationCategory> {\n        return self.createCategories(\n            fromFile: path,\n            actionDefinitionModBlock: { actionDefinition in\n                if actionDefinition[\"foreground\"] as? Bool == false {\n                    actionDefinition[\"authenticationRequired\"] = requireAuth\n                }\n            }\n        )\n    }\n\n    private class func createCategories(\n        fromFile path: String,\n        actionDefinitionModBlock: @escaping (inout [AnyHashable: Any]) ->\n            Void\n    ) -> Set<UNNotificationCategory> {\n\n        let categoriesDictionary = NSDictionary(contentsOfFile: path) as? [AnyHashable: Any] ?? [:]\n        var categories: Set<UNNotificationCategory> = []\n\n        for key in categoriesDictionary.keys {\n            guard let categoryId = key as? String else {\n                continue\n            }\n\n            guard\n                var actions = categoriesDictionary[categoryId]\n                    as? [[AnyHashable: Any]]\n            else {\n                continue\n            }\n\n            if actions.count == 0 {\n                continue\n            }\n\n            var mutableActions: [[AnyHashable: Any]] = []\n\n            for actionDef in actions {\n                var mutableActionDef: [AnyHashable: Any] =\n                    actionDef as [AnyHashable: Any]\n                actionDefinitionModBlock(&mutableActionDef)\n                mutableActions.append(mutableActionDef)\n            }\n\n            actions = mutableActions\n\n            if let category = self.createCategory(\n                categoryId,\n                actions: actions\n            ) {\n                categories.insert(category)\n            }\n        }\n\n        return categories\n    }\n\n    private class func getTitle(_ actionDefinition: [AnyHashable: Any])\n        -> String?\n    {\n        guard let title = actionDefinition[\"title\"] as? String else {\n            return nil\n        }\n        if let titleResource = actionDefinition[\"title_resource\"] as? String {\n            return AirshipLocalizationUtils.localizedString(\n                titleResource,\n                withTable: \"UrbanAirship\",\n                moduleBundle: AirshipCoreResources.bundle,\n            ) ?? title\n        }\n\n        return title\n    }\n\n    private class func getActionsFromActionDefinitions(\n        _ actionDefinitions: [[AnyHashable: Any]]\n    ) -> [UNNotificationAction]? {\n        var actions: [UNNotificationAction] = []\n\n        for actionDefinition in actionDefinitions {\n            guard let actionId = actionDefinition[\"identifier\"] as? String\n            else {\n                AirshipLogger.error(\n                    \"Error creating action from definition: \\(actionDefinition) due to missing identifier.\"\n                )\n                return nil\n            }\n\n            guard let title = getTitle(actionDefinition) else {\n                AirshipLogger.error(\n                    \"Error creating action: \\(actionId) due to missing title.\"\n                )\n                return nil\n            }\n\n            var options: UNNotificationActionOptions = []\n\n            if actionDefinition[\"destructive\"] as? Bool == true {\n                options.insert(.destructive)\n            }\n\n            if actionDefinition[\"foreground\"] as? Bool == true {\n                options.insert(.foreground)\n            }\n\n            if actionDefinition[\"authenticationRequired\"] as? Bool == true {\n                options.insert(.authenticationRequired)\n            }\n\n            if actionDefinition[\"action_type\"] as? String == \"text_input\" {\n                guard\n                    let textInputButtonTitle =\n                        actionDefinition[\"text_input_button_title\"]\n                        as? String\n                else {\n                    AirshipLogger.error(\n                        \"Error creating action: \\(actionId) due to missing text input button title.\"\n                    )\n                    return nil\n                }\n                guard\n                    let textInputPlaceholder =\n                        actionDefinition[\"text_input_placeholder\"]\n                        as? String\n                else {\n                    AirshipLogger.error(\n                        \"Error creating action: \\(actionId) due to missing text input placeholder.\"\n                    )\n                    return nil\n                }\n\n                actions.append(\n                    UNTextInputNotificationAction(\n                        identifier: actionId,\n                        title: title,\n                        options: options,\n                        textInputButtonTitle: textInputButtonTitle,\n                        textInputPlaceholder: textInputPlaceholder\n                    )\n                )\n            } else {\n                actions.append(\n                    UNNotificationAction(\n                        identifier: actionId,\n                        title: title,\n                        options: options\n                    )\n                )\n            }\n        }\n\n        return actions\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NotificationPermissionDelegate.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\nimport UserNotifications\n\nfinal class NotificationPermissionDelegate: AirshipPermissionDelegate {\n\n    struct Config: Sendable {\n        let options: UNAuthorizationOptions\n        let skipIfEphemeral: Bool\n    }\n\n    let registrar: any NotificationRegistrar\n    let config: @Sendable () -> Config\n\n    init(registrar: any NotificationRegistrar, config: @Sendable @escaping () -> Config) {\n        self.registrar = registrar\n        self.config = config\n    }\n\n    func checkPermissionStatus() async -> AirshipPermissionStatus {\n        return await registrar.checkStatus().0.permissionStatus\n    }\n\n    func requestPermission() async -> AirshipPermissionStatus {\n        let config = self.config()\n        await self.registrar.updateRegistration(\n            options: config.options,\n            skipIfEphemeral: config.skipIfEphemeral\n        )\n        return await self.checkPermissionStatus()\n    }\n}\n\nextension UNAuthorizationStatus {\n    var permissionStatus: AirshipPermissionStatus {\n        switch self {\n        case .authorized: return .granted\n        case .provisional: return .granted\n        case .ephemeral: return .granted\n        case .notDetermined: return .notDetermined\n        case .denied: return .denied\n        @unknown default: return .notDetermined\n        }\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NotificationRegistrar.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\nimport UserNotifications\n\nprotocol NotificationRegistrar: Sendable {\n\n    #if !os(tvOS)\n    @MainActor\n    func setCategories(_ categories: Set<UNNotificationCategory>)\n    #endif\n\n    @MainActor\n    func checkStatus() async -> (UNAuthorizationStatus, AirshipAuthorizedNotificationSettings)\n\n    @MainActor\n    func updateRegistration(\n        options: UNAuthorizationOptions,\n        skipIfEphemeral: Bool\n    ) async -> Void\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/NotificationRegistrationResult.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n@preconcurrency public import UserNotifications\n\n/// The result of the initial notification registration prompt.\npublic struct NotificationRegistrationResult: Sendable {\n    /// The settings that were authorized at the time of registration.\n    public let authorizedSettings: AirshipAuthorizedNotificationSettings\n\n    /// The authorization status.\n    public let status: UNAuthorizationStatus\n\n    #if !os(tvOS)\n    /// Set of the categories that were most recently registered.\n    private let _categories: AirshipUnsafeSendableWrapper<Set<UNNotificationCategory>>\n    public var categories: Set<UNNotificationCategory> {\n        return _categories.value\n    }\n\n    init(authorizedSettings: AirshipAuthorizedNotificationSettings, status: UNAuthorizationStatus, categories: Set<UNNotificationCategory>) {\n        self.authorizedSettings = authorizedSettings\n        self.status = status\n        self._categories = .init(categories)\n    }\n    #endif\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/OpenExternalURLAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Opens a URL, either in safari or using custom URL schemes.\n///\n/// Expected argument values: A valid URL String.\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.manualInvocation`, and `ActionSituation.automation`\n///\n/// Result value: The input value.\npublic final class OpenExternalURLAction: AirshipAction {\n\n    /// Default names - \"open_external_url_action\", \"^u\", \"^w\", \"wallet_action\"\n    public static let defaultNames: [String] = [\"open_external_url_action\", \"^u\", \"^w\", \"wallet_action\"]\n\n    /// Default predicate - rejects `ActionSituation.foregroundPush`\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.situation != .foregroundPush\n    }\n\n    private let urlOpener: any URLOpenerProtocol\n\n    init(urlOpener: any URLOpenerProtocol) {\n        self.urlOpener = urlOpener\n    }\n\n    public convenience init() {\n        self.init(urlOpener: DefaultURLOpener())\n    }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .backgroundPush:\n            return false\n        case .backgroundInteractiveButton:\n            return false\n        default:\n            return true\n        }\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let url = try parseURL(arguments.value)\n\n        guard Airship.urlAllowList.isAllowed(url, scope: .openURL) else {\n            throw AirshipErrors.error(\"URL \\(url) not allowed\")\n        }\n\n        guard await urlOpener.openURL(url) else {\n            throw AirshipErrors.error(\"Unable to open url \\(arguments.value).\")\n        }\n\n        return arguments.value\n    }\n    \n    private func parseURL(_ value: AirshipJSON) throws -> URL {\n        if let value = value.unWrap() as? String {\n            if let url = AirshipUtils.parseURL(value) {\n                return url\n            }\n        }\n\n        throw AirshipErrors.error(\"Invalid URL: \\(value)\")\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/OpenRegistrationOptions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Open registration options\npublic struct OpenRegistrationOptions: Codable, Sendable, Equatable, Hashable {\n\n    /**\n     * Platform name\n     */\n    let platformName: String\n\n    /**\n     * Identifiers\n     */\n    let identifiers: [String: String]?\n\n    private init(platformName: String, identifiers: [String: String]?) {\n        self.platformName = platformName\n        self.identifiers = identifiers\n    }\n\n    /// Returns an open registration options with opt-in status\n    /// - Parameter platformName: The platform name\n    /// - Parameter identifiers: The identifiers\n    /// - Returns: An open registration options.\n    public static func optIn(\n        platformName: String,\n        identifiers: [String: String]?\n    )\n        -> OpenRegistrationOptions\n    {\n        return OpenRegistrationOptions(\n            platformName: platformName,\n            identifiers: identifiers\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Pager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n@MainActor\nstruct Pager: View {\n\n    private enum PagerEvent {\n        case gesture(identifier: String, reportingMetadata: AirshipJSON?)\n        case automated(identifier: String, reportingMetadata: AirshipJSON?)\n        case accessibilityAction(ThomasAccessibilityAction)\n        case defaultSwipe(PagerState.NavigationResult)\n    }\n\n    // For debugging, set to true to force legacy pager behavior on iOS 17+\n    private static let forceLegacyPager: Bool = false\n\n    private static let timerTransition: CGFloat = 0.01\n    private static let minDragDistance: CGFloat = 60.0\n    static let animationSpeed: TimeInterval = 0.75\n\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var pagerState: PagerState\n    @EnvironmentObject private var thomasState: ThomasState\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n\n    @Environment(\\.isVisible) private var isVisible\n    @Environment(\\.layoutState) private var layoutState\n    @Environment(\\.layoutDirection) private var layoutDirection\n    @Environment(\\.isVoiceOverRunning) private var isVoiceOverRunning\n\n    private let info: ThomasViewInfo.Pager\n    private let constraints: ViewConstraints\n\n    @State private var lastReportedPageID: String?\n    @State private var hasReportedCompleted: Bool = false\n    @GestureState private var translation: CGFloat = 0\n    @State private var size: CGSize?\n    @State private var scrollPosition: String?\n    private let timer: Publishers.Autoconnect<Timer.TimerPublisher>\n\n    private var isLegacyPageSwipeEnabled: Bool {\n        if #available(iOS 17.0, *) {\n            return if Self.forceLegacyPager {\n                self.info.isDefaultSwipeEnabled\n            } else {\n                false\n            }\n        }\n\n        return self.info.isDefaultSwipeEnabled\n    }\n\n    private var shouldAddSwipeGesture: Bool {\n        if isLegacyPageSwipeEnabled { return true }\n        if self.info.containsGestures([.swipe]) { return true }\n        return false\n    }\n\n    private var shouldAddA11ySwipeActions: Bool {\n        if isVoiceOverRunning {\n                    return false\n        }\n        if self.info.isDefaultSwipeEnabled { return true }\n        if self.info.containsGestures([.swipe]) { return true }\n        return false\n    }\n\n    init(\n        info: ThomasViewInfo.Pager,\n        constraints: ViewConstraints\n    ) {\n        self.info = info\n        self.constraints = constraints\n        self.timer = Timer.publish(\n            every: Pager.timerTransition,\n            on: .main,\n            in: .default\n        )\n        .autoconnect()\n    }\n\n    @ViewBuilder\n    func makePager() -> some View {\n        if (pagerState.pageItems.count == 1) {\n            self.makeSinglePagePager()\n        } else {\n            GeometryReader { metrics in\n                let childConstraints = ViewConstraints(\n                    width: metrics.size.width.safeValue,\n                    height: metrics.size.height.safeValue,\n                    isHorizontalFixedSize: self.constraints.isHorizontalFixedSize,\n                    isVerticalFixedSize: self.constraints.isVerticalFixedSize,\n                    safeAreaInsets: self.constraints.safeAreaInsets\n                )\n\n                if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {\n                    if (Self.forceLegacyPager) {\n                        makeLegacyPager(childConstraints: childConstraints, metrics: metrics)\n                    } else {\n                        makeScrollViewPager(childConstraints: childConstraints, metrics: metrics)\n                    }\n                } else {\n                    makeLegacyPager(childConstraints: childConstraints, metrics: metrics)\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    func makeSinglePagePager() -> some View {\n        ViewFactory.createView(\n            pagerState.pageItems[0].view,\n            constraints: constraints\n        )\n        .environment(\\.isVisible, self.isVisible)\n        .environment(\n            \\.pageIdentifier,\n             pagerState.pageItems[0].identifier\n        )\n        .constraints(constraints)\n        .airshipMeasureView(self.$size)\n    }\n\n    @ViewBuilder\n    func makeLegacyPager(childConstraints: ViewConstraints, metrics: GeometryProxy) -> some View {\n        VStack {\n            HStack(spacing: 0) {\n                makePageViews(\n                    childConstraints: childConstraints,\n                    metrics: metrics,\n                    isLegacyPager: true\n                )\n            }\n            .offset(x: -((metrics.size.width.safeValue ?? 0) * CGFloat(pagerState.pageIndex)))\n            .offset(x: calcDragOffset(index: pagerState.pageIndex))\n            .animation(.interactiveSpring(duration: Pager.animationSpeed), value: pagerState.pageIndex)\n        }\n        .frame(\n            width: metrics.size.width.safeValue,\n            height: metrics.size.height.safeValue,\n            alignment: .leading\n        )\n        .clipped()\n        .onAppear {\n            size = metrics.size\n        }\n        .airshipOnChangeOf(metrics.size) { newSize in\n            size = newSize\n        }\n    }\n\n    @available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *)\n    @ViewBuilder\n    func makeScrollViewPager(childConstraints: ViewConstraints, metrics: GeometryProxy) -> some View {\n        ScrollView(.horizontal) {\n            LazyHStack(spacing: 0) {\n                makePageViews(\n                    childConstraints: childConstraints,\n                    metrics: metrics,\n                    isLegacyPager: false\n                )\n            }\n            .scrollTargetLayout()\n        }\n        .scrollDisabled(self.info.properties.disableSwipe == true || self.pagerState.isScrollingDisabled)\n        .allowsHitTesting(!pagerState.isNavigationInProgress)\n        .scrollTargetBehavior(.paging)\n        .scrollPosition(id: $scrollPosition)\n        .scrollIndicators(.never)\n        .accessibilityElement(children: .contain)\n        .airshipOnChangeOf(scrollPosition ?? \"\", initial: false) { value in\n            guard !value.isEmpty, value != self.pagerState.currentPageId else {\n                return\n            }\n\n            let result = self.pagerState.navigateToPage(id: value)\n            if let result {\n                handleEvents(.defaultSwipe(result))\n            }\n        }\n        .frame(\n            width: metrics.size.width.safeValue,\n            height: metrics.size.height.safeValue,\n            alignment: .leading\n        )\n        .onAppear {\n            size = metrics.size\n        }\n        .airshipOnChangeOf(metrics.size) { newSize in\n            size = newSize\n        }\n    }\n\n    @ViewBuilder\n    private func makePageView(\n        for index: Int,\n        childConstraints: ViewConstraints,\n        metrics: GeometryProxy,\n        isLegacyPager: Bool\n    ) -> some View {\n        let pageItem = pagerState.pageItems[index]\n        let isCurrentPage = self.isVisible && pageItem.identifier == pagerState.currentPageId\n\n        VStack {\n            ViewFactory.createView(\n                pageItem.view,\n                constraints: childConstraints\n            )\n            .airshipApplyIf(isLegacyPager) { view in\n                view.allowsHitTesting(isCurrentPage)\n            }\n            .environment(\\.isVisible, isCurrentPage)\n            .environment(\\.pageIdentifier, pageItem.identifier)\n            .accessibilityActions {\n                makeAccessibilityActions(pageItem: pageItem)\n            }\n            .accessibilityHidden(!self.isVisible)\n        }\n        .frame(\n            width: metrics.size.width.safeValue,\n            height: metrics.size.height.safeValue\n        )\n        .environment(\n            \\.isButtonActionsEnabled,\n             (!self.isLegacyPageSwipeEnabled || self.translation == 0)\n        )\n        .accessibilityElement(children: .contain)\n        .id(pageItem.identifier)\n    }\n\n    @ViewBuilder\n    private func makePageViews(\n        childConstraints: ViewConstraints,\n        metrics: GeometryProxy,\n        isLegacyPager: Bool\n    ) -> some View {\n        ForEach(0..<pagerState.pageItems.count, id: \\.self) { index in\n            makePageView(\n                for: index,\n                childConstraints: childConstraints,\n                metrics: metrics,\n                isLegacyPager: isLegacyPager\n            )\n        }\n    }\n\n    @ViewBuilder\n    private func makeAccessibilityActions(pageItem: ThomasViewInfo.Pager.Item) -> some View {\n        if let actions = pageItem.accessibilityActions {\n            ForEach(0..<actions.count, id: \\.self) { i in\n                let action = actions[i]\n                Button {\n                    handleEvents(.accessibilityAction(action))\n                    self.process(\n                        behaviors: action.properties.behaviors,\n                        actions: action.properties.actions\n                    )\n                } label: {\n                    Text(\n                        action.accessible.resolveContentDescription ?? \"unknown\"\n                    )\n                }\n                .accessibilityRemoveTraits(.isButton)\n            }\n        }\n    }\n\n    @ViewBuilder\n    var body: some View {\n        makePager()\n            .onAppear(perform: attachToPagerState)\n            .airshipOnChangeOf(pagerState.completed) { completed in\n                guard completed else { return }\n                reportCompleted()\n            }\n            .airshipOnChangeOf(pagerState.currentPageId, initial: true) { pageID in\n                guard let pageID else { return }\n\n                reportPage(pageID: pageID)\n\n                guard pageID != scrollPosition else { return }\n\n                if scrollPosition != nil {\n                    pagerState.disableTouchDuringNavigation()\n                }\n                withAnimation {\n                    scrollPosition = pageID\n                }\n            }\n            .airshipOnChangeOf(isVisible) { visible in\n                if visible, let pageID = pagerState.currentPageId {\n                    reportPage(pageID: pageID)\n                }\n                if visible, pagerState.completed {\n                    reportCompleted()\n                }\n            }\n            .onReceive(self.timer) { _ in\n                onTimer()\n            }\n\n#if !os(tvOS)\n            .airshipApplyIf(self.shouldAddSwipeGesture) { view in\n                view.simultaneousGesture(\n                    makeSwipeGesture()\n                )\n            }\n            .airshipApplyIf(self.shouldAddA11ySwipeActions) { view in\n                view.accessibilityScrollAction  { edge in\n                    let swipeDirection = PagerSwipeDirection.from(\n                        edge: edge,\n                        layoutDirection: self.layoutDirection\n                    )\n                    handleSwipe(direction: swipeDirection, isAccessibilityScrollAction: true)\n                }\n            }\n            .airshipApplyIf(self.info.containsGestures([.hold, .tap])) { view in\n                view.addPagerTapGesture(\n                    onTouch: { isPressed in\n                        handleTouch(isPressed: isPressed)\n                    },\n                    onTap: { location in\n                        handleTap(tapLocation: location)\n                    }\n                )\n            }\n#endif\n            .constraints(constraints)\n            .thomasCommon(self.info)\n            .airshipGeometryGroupCompat()\n            .accessibilityElement(children: .contain)\n\n    }\n\n    // MARK: Handle Gesture\n\n\n#if !os(tvOS)\n    private func makeSwipeGesture() -> some Gesture {\n        return DragGesture(minimumDistance: Self.minDragDistance)\n            .updating(self.$translation) { value, state, _ in\n                guard self.isLegacyPageSwipeEnabled else {\n                    return\n                }\n\n                if (abs(value.translation.width) > Self.minDragDistance) {\n                    state = if (value.translation.width > 0) {\n                        value.translation.width - Self.minDragDistance\n                    } else {\n                        value.translation.width + Self.minDragDistance\n                    }\n                } else {\n                    state = 0\n                }\n            }\n            .onEnded { value in\n                guard\n                    let size = self.size,\n                    let swipeDirection = PagerSwipeDirection.from(\n                        dragValue: value,\n                        size: size,\n                        layoutDirection: layoutDirection\n                    )\n                else {\n                    return\n                }\n\n                handleSwipe(direction: swipeDirection)\n            }\n    }\n\n    private func handleTap(tapLocation: CGPoint)  {\n        guard let size = size else {\n            return\n        }\n\n        let pagerGestureExplorer = PagerGestureMapExplorer(\n            CGRect(\n                x: 0,\n                y: 0,\n                width: size.width,\n                height: size.height\n            )\n        )\n\n        let locations = pagerGestureExplorer.location(\n            layoutDirection: layoutDirection,\n            forPoint: tapLocation\n        )\n\n        locations.forEach { location in\n            self.info.retrieveGestures(type: ThomasViewInfo.Pager.Gesture.Tap.self)\n                .filter { $0.location == location }\n                .forEach { gesture in\n                    handleEvents(\n                        .gesture(\n                            identifier: gesture.identifier,\n                            reportingMetadata: gesture.reportingMetadata\n                        )\n                    )\n\n                    self.process(\n                        behaviors: gesture.behavior.behaviors,\n                        actions: gesture.behavior.actions\n                    )\n                }\n        }\n    }\n\n#endif\n\n    // MARK: Utils methods\n\n    private func attachToPagerState() {\n        pagerState.setPagesAndListenForUpdates(\n            pages: info.properties.items,\n            thomasState: thomasState,\n            swipeDisableSelectors: info.properties.disableSwipePredicate\n        )\n    }\n\n    private func handleSwipe(\n        direction: PagerSwipeDirection,\n        isAccessibilityScrollAction: Bool = false\n    ) {\n        switch(direction) {\n        case .up: fallthrough\n        case .down:\n            self.info.retrieveGestures(type: ThomasViewInfo.Pager.Gesture.Swipe.self)\n                .filter {\n                    if ($0.direction == .up && direction == .up) {\n                        return true\n                    }\n\n                    if ($0.direction == .down && direction == .down) {\n                        return true\n                    }\n\n                    return false\n                }\n                .forEach { gesture in\n                    handleEvents(\n                        .gesture(\n                            identifier: gesture.identifier,\n                            reportingMetadata: gesture.reportingMetadata\n                        )\n                    )\n                    self.process(\n                        behaviors: gesture.behavior.behaviors,\n                        actions: gesture.behavior.actions\n                    )\n                }\n        case .start:\n            guard\n                !pagerState.isFirstPage, self.pagerState.canGoBack,\n                isAccessibilityScrollAction || self.isLegacyPageSwipeEnabled\n            else {\n                return\n            }\n\n            // Treat a11y swipes as page requests so they animate\n            if let result = pagerState.process(request: .back) {\n                self.handleEvents(.defaultSwipe(result))\n            }\n        case .end:\n            guard\n                !pagerState.isLastPage,\n                isAccessibilityScrollAction || self.isLegacyPageSwipeEnabled\n            else {\n                return\n            }\n\n            // Treat a11y swipes as page requests so they animate\n            if let result = pagerState.process(request: .next) {\n                self.handleEvents(.defaultSwipe(result))\n            }\n        }\n    }\n\n    private func handleTouch(isPressed: Bool) {\n        self.info.retrieveGestures(type: ThomasViewInfo.Pager.Gesture.Hold.self).forEach { gesture in\n            let behavior = isPressed ? gesture.pressBehavior : gesture.releaseBehavior\n            if !isPressed {\n                handleEvents(\n                    .gesture(\n                        identifier: gesture.identifier,\n                        reportingMetadata: gesture.reportingMetadata\n                    )\n                )\n            }\n\n            self.process(\n                behaviors: behavior.behaviors,\n                actions: behavior.actions\n            )\n        }\n    }\n\n    private func onTimer() {\n        guard !isVoiceOverRunning,\n              let automatedActions = self.pagerState.pageItems[self.pagerState.pageIndex].automatedActions\n        else {\n            return\n        }\n\n        let duration = self.pagerState.pageStates[pagerState.pageIndex].delay\n        let safeDuration = (duration > 0 && duration.isFinite) ? duration : 1.0\n\n        if self.pagerState.inProgress && (self.pagerState.pageIndex < pagerState.pageItems.count) {\n            if (self.pagerState.progress < 1) {\n                self.pagerState.progress += Pager.timerTransition / safeDuration\n            }\n\n            // Check for any automated action past the current duration that have not been executed yet\n            automatedActions.filter {\n                let isExecuted = (self.pagerState.currentPageState?.automatedActionStatus[$0.identifier] == true)\n                let isOlder = (self.pagerState.progress * duration) >= ($0.delay ?? 0.0)\n                return !isExecuted && isOlder\n            }.forEach { action in\n                self.processAutomatedAction(action)\n            }\n        }\n    }\n\n    private func processAutomatedAction(_ automatedAction: ThomasAutomatedAction) {\n        self.handleEvents(\n            .automated(\n                identifier: automatedAction.identifier,\n                reportingMetadata: automatedAction.reportingMetadata\n            )\n        )\n\n        self.process(\n            behaviors: automatedAction.behaviors,\n            actions: automatedAction.actions\n        )\n\n        self.pagerState.markAutomatedActionExecuted(automatedAction.identifier)\n    }\n\n    private func process(\n        stateActions: [ThomasStateAction]? = nil,\n        behaviors: [ThomasButtonClickBehavior]? = nil,\n        actions: [ThomasActionsPayload]? = nil\n    ) {\n        Task { @MainActor in\n            // Handle state first\n            if let stateActions {\n                thomasState.processStateActions(stateActions)\n\n                // WORKAROUND: SwiftUI state updates are not immediately available to child views.\n                // Yielding allows the state changes to propagate through the view hierarchy\n                // before executing behaviors that may depend on the updated state.\n                await Task.yield()\n            }\n\n            // Behaviors\n            behaviors?.sortedBehaviors.forEach { behavior in\n                switch(behavior) {\n                case .dismiss:\n                    self.thomasEnvironment.dismiss(layoutState: layoutState)\n\n                case .cancel:\n                    self.thomasEnvironment.dismiss(cancel: true, layoutState: layoutState)\n\n                case .pagerNext:\n                    self.pagerState.process(request: .next)\n\n                case .pagerPrevious:\n                    self.pagerState.process(request: .back)\n\n                case .pagerNextOrDismiss:\n                    if pagerState.isLastPage {\n                        self.thomasEnvironment.dismiss()\n                    } else {\n                        self.pagerState.process(request: .next)\n                    }\n\n                case .pagerNextOrFirst:\n                    if self.pagerState.isLastPage {\n                        self.pagerState.process(request: .first)\n                    } else {\n                        self.pagerState.process(request: .next)\n                    }\n\n                case .pagerPause:\n                    self.pagerState.pause()\n\n                case .pagerResume:\n                    self.pagerState.resume()\n\n                case .pagerPauseToggle:\n                    pagerState.togglePause()\n\n                case .formSubmit, .formValidate:\n                    // not supported\n                    break\n\n                case .videoPlay, .videoPause, .videoTogglePlay,\n                     .videoMute, .videoUnmute, .videoToggleMute:\n                    // Video behaviors handled by VideoController, not Pager\n                    break\n                }\n            }\n\n            // Actions\n            if let actions = actions {\n                actions.forEach { action in\n                    self.thomasEnvironment.runActions(action, layoutState: layoutState)\n                }\n            }\n        }\n    }\n\n    private func handleEvents(_ event: PagerEvent) {\n        AirshipLogger.debug(\"Processing pager event: \\(event)\")\n\n        switch event {\n        case .defaultSwipe(let navigationResult):\n            if let from = navigationResult.fromPage {\n                thomasEnvironment.pageSwiped(\n                    pagerState: self.pagerState,\n                    from: from,\n                    to: navigationResult.toPage,\n                    layoutState: layoutState\n                )\n            }\n\n        case .gesture(let identifier, let reportingMetadata):\n            thomasEnvironment.pageGesture(\n                identifier: identifier,\n                reportingMetadata: reportingMetadata,\n                layoutState: layoutState\n            )\n        case .automated(let identifier, let reportingMetadata):\n            thomasEnvironment.pageAutomated(\n                identifier: identifier,\n                reportingMetadata: reportingMetadata,\n                layoutState: layoutState\n            )\n        case .accessibilityAction(_):\n            /// TODO add accessibility action analytics event\n            break\n        }\n    }\n\n    private func reportCompleted() {\n        guard isVisible, !hasReportedCompleted else { return }\n        self.hasReportedCompleted = true\n        self.thomasEnvironment.pagerCompleted(\n            pagerState: pagerState,\n            layoutState: layoutState\n        )\n    }\n\n    private func reportPage(pageID: String) {\n        guard\n            isVisible,\n            self.lastReportedPageID != pageID,\n            let page = pagerState.pageItems.first(where: { $0.identifier == pageID })\n        else {\n            return\n        }\n\n        self.thomasEnvironment.pageViewed(\n            pagerState: self.pagerState,\n            pageInfo: self.pagerState.pageInfo(pageIdentifier: pageID),\n            layoutState: layoutState\n        )\n        self.lastReportedPageID = pageID\n\n        if isVoiceOverRunning {\n            // Small delay to allow the UI to settle after navigation\n            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {\n#if os(watchOS)\n                // watchOS handles accessibility focus via the system pager automatically\n#elseif os(macOS)\n                // For macOS, notify that the layout has changed within the app\n                NSAccessibility.post(\n                    element: (NSApp.mainWindow ?? NSApp) as Any,\n                    notification: .layoutChanged\n                )\n#else\n                // For iOS, tvOS, and visionOS\n                UIAccessibility.post(notification: .layoutChanged, argument: nil)\n#endif\n            }\n        }\n\n        // Run any actions set on the current page\n        let displayActions: [ThomasActionsPayload]? = if let actions = page.displayActions {\n            [actions]\n        } else {\n            nil\n        }\n\n        self.process(\n            stateActions: page.stateActions,\n            actions: displayActions\n        )\n\n        // Process any automated navigation actions\n        onTimer()\n    }\n\n    private func calcDragOffset(index: Int) -> CGFloat {\n        var dragOffSet = self.translation\n        if index <= 0 {\n            dragOffSet = min(dragOffSet, 0)\n        } else if index >= pagerState.pageItems.count - 1 {\n            dragOffSet = max(dragOffSet, 0)\n        }\n\n        return dragOffSet\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PagerController.swift",
    "content": "import Foundation\nimport SwiftUI\n\n@MainActor\nstruct PagerController: View {\n    private let info: ThomasViewInfo.PagerController\n    private let constraints: ViewConstraints\n\n    @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n    @EnvironmentObject private var environment: ThomasEnvironment\n    @EnvironmentObject private var state: ThomasState\n\n    init(\n        info: ThomasViewInfo.PagerController,\n        constraints: ViewConstraints\n    ) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: constraints,\n            environment: environment,\n            formDataCollector: formDataCollector,\n            parentState: state\n        )\n    }\n\n    @MainActor\n    struct Content: View {\n        private let info: ThomasViewInfo.PagerController\n        private let constraints: ViewConstraints\n\n        @Environment(\\.layoutState) private var layoutState\n\n        @ObservedObject private var pagerState: PagerState\n        @StateObject private var formDataCollector: ThomasFormDataCollector\n\n        @Environment(\\.isVoiceOverRunning) private var isVoiceOverRunning\n        @StateObject private var state: ThomasState\n\n\n        init(\n            info: ThomasViewInfo.PagerController,\n            constraints: ViewConstraints,\n            environment: ThomasEnvironment,\n            formDataCollector: ThomasFormDataCollector,\n            parentState: ThomasState\n        ) {\n            self.info = info\n            self.constraints = constraints\n\n            // Use the environment to create or retrieve the state in case the view\n            // stack changes and we lose our state.\n            let pagerState = environment.retrieveState(identifier: info.properties.identifier) {\n                PagerState(\n                    identifier: info.properties.identifier,\n                    branching: info.properties.branching\n                )\n            }\n\n            self._pagerState = ObservedObject(wrappedValue: pagerState)\n\n            self._formDataCollector = StateObject(\n                wrappedValue: formDataCollector.with(pagerState: pagerState)\n            )\n\n            self._state = StateObject(\n                wrappedValue: parentState.with(pagerState: pagerState)\n            )\n        }\n\n        var body: some View {\n            ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                .constraints(constraints)\n                .airshipOnChangeOf(self.isVoiceOverRunning, initial: true) { value in\n                    pagerState.isVoiceOverRunning = value\n                }\n                .onAppear {\n                    pagerState.isVoiceOverRunning = isVoiceOverRunning\n                }\n                .thomasCommon(self.info)\n                .environmentObject(self.pagerState)\n                .environmentObject(self.formDataCollector)\n                .environmentObject(self.state)\n                .environment(\\.layoutState, layoutState.override(pagerState: pagerState))\n                .accessibilityElement(children: .contain)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PagerGestureMap.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nfileprivate struct TopTrapezoid: Shape {\n    func path(in rect: CGRect) -> Path {\n        let thirdWidth = rect.width * 0.3\n        let thirdHeight = rect.height * 0.3\n\n        var path = Path()\n        path.move(to: CGPoint(x: rect.minX, y: rect.minY))\n        path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY))\n        path.addLine(to: CGPoint(x: rect.maxX - thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX + thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))\n        return path\n    }\n}\n\nfileprivate struct BottomTrapezoid: Shape {\n    func path(in rect: CGRect) -> Path {\n        let thirdWidth = rect.width * 0.3\n        let thirdHeight = rect.height * 0.3\n\n        var path = Path()\n        path.move(to: CGPoint(x: rect.minX, y: rect.maxY))\n        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))\n        path.addLine(to: CGPoint(x: rect.maxX - thirdWidth, y: rect.maxY - thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX + thirdWidth, y: rect.maxY - thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))\n        return path\n    }\n}\n\nfileprivate struct RightTrapezoid: Shape {\n    func path(in rect: CGRect) -> Path {\n        let thirdWidth = rect.width * 0.3\n        let thirdHeight = rect.height * 0.3\n\n        var path = Path()\n        path.move(to: CGPoint(x: rect.maxX, y: rect.minY))\n        path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY))\n        path.addLine(to: CGPoint(x: rect.maxX - thirdWidth, y: rect.maxY - thirdHeight))\n        path.addLine(to: CGPoint(x: rect.maxX - thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))\n        return path\n    }\n}\n\nfileprivate struct LeftTrapezoid: Shape {\n    func path(in rect: CGRect) -> Path {\n        let thirdWidth = rect.width * 0.3\n        let thirdHeight = rect.height * 0.3\n\n        var path = Path()\n        path.move(to: CGPoint(x: rect.minX, y: rect.minY))\n        path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY))\n        path.addLine(to: CGPoint(x: rect.minX + thirdWidth, y: rect.maxY - thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX + thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX, y: rect.minY))\n        return path\n    }\n}\n\nfileprivate struct CenterRectangle: Shape {\n    func path(in rect: CGRect) -> Path {\n        let thirdWidth = rect.width * 0.3\n        let thirdHeight = rect.height * 0.3\n\n        var path = Path()\n        path.move(to: CGPoint(x: rect.minX + thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX + thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.maxX - thirdWidth, y: rect.minY + thirdHeight))\n        path.addLine(to: CGPoint(x: rect.maxX - thirdWidth, y: rect.maxY - thirdHeight))\n        path.addLine(to: CGPoint(x: rect.minX + thirdWidth, y: rect.maxY - thirdHeight))\n        return path\n    }\n}\n\n\nstruct PagerGestureMapExplorer {\n    \n    let topTrapezoidPath: Path\n    let bottomTrapezoidPath: Path\n    let leftTrapezoidPath: Path\n    let rightTrapezoidPath: Path\n    let centerSquarePath: Path\n\n\n    init(_ rect: CGRect) {\n        topTrapezoidPath = TopTrapezoid().path(in: rect)\n        bottomTrapezoidPath = BottomTrapezoid().path(in: rect)\n        leftTrapezoidPath = LeftTrapezoid().path(in: rect)\n        rightTrapezoidPath = RightTrapezoid().path(in: rect)\n        centerSquarePath = CenterRectangle().path(in: rect)\n    }\n    \n    func location(\n        layoutDirection: LayoutDirection,\n        forPoint point: CGPoint\n    ) -> [ThomasViewInfo.Pager.Gesture.GestureLocation] {\n        if topTrapezoidPath.contains(point) {\n            return [.top, .any]\n        }\n\n        if bottomTrapezoidPath.contains(point) {\n            return [.bottom, .any]\n        }\n\n        if leftTrapezoidPath.contains(point) {\n            if (layoutDirection == .leftToRight) {\n                return [.left, .start, .any]\n            } else {\n                return [.left, .end, .any]\n            }\n        }\n\n        if rightTrapezoidPath.contains(point) {\n            if (layoutDirection == .leftToRight) {\n                return [.right, .end, .any]\n            } else {\n                return [.right, .start, .any]\n            }\n        }\n\n        if centerSquarePath.contains(point) {\n            return [.any]\n        }\n\n        return []\n    }\n}\n\nstruct PagerGestureMap: View {\n    var body: some View {\n        Rectangle()\n            .overlayView {\n                TopTrapezoid()\n                    .fill(.blue)\n                BottomTrapezoid()\n                    .fill(.red)\n                RightTrapezoid()\n                    .fill(.yellow)\n                LeftTrapezoid()\n                    .fill(.purple)\n                CenterRectangle()\n                    .fill(.green)\n\n            }\n            .border(.red, width: 1)\n    }\n}\n\nstruct PagerGestureMap_Previews: PreviewProvider {\n    static var previews: some View {\n        PagerGestureMap()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PagerIndicator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct PagerIndicator: View {\n\n    let info: ThomasViewInfo.PagerIndicator\n    let constraints: ViewConstraints\n\n    @EnvironmentObject var pagerState: PagerState\n    @Environment(\\.colorScheme) var colorScheme\n\n    @ViewBuilder\n    private func createChild(\n        binding: ThomasViewInfo.PagerIndicator.Properties.Binding,\n        constraints: ViewConstraints\n    ) -> some View {\n        ZStack {\n            if let shapes = binding.shapes {\n                ForEach(0..<shapes.count, id: \\.self) { index in\n                    Shapes.shape(\n                        info: shapes[index],\n                        constraints: constraints,\n                        colorScheme: colorScheme\n                    )\n                }\n            }\n\n            if let iconModel = binding.icon {\n                Icons.icon(\n                    info: iconModel,\n                    colorScheme: colorScheme\n                )\n            }\n        }\n    }\n\n    func announcePage(info: ThomasViewInfo.PagerIndicator) -> Bool {\n        return info.properties.automatedAccessibilityActions?.contains{ $0.type == .announce} ?? false\n    }\n\n    var body: some View {\n        let size: Double = if let height = constraints.height {\n            height - (self.info.commonProperties.border?.strokeWidth ?? 0)\n        } else {\n            32.0\n        }\n        \n        let childConstraints = ViewConstraints(\n            width: size,\n            height: size\n        )\n\n        HStack(spacing: self.info.properties.spacing) {\n            ForEach(0..<self.pagerState.pageStates.count, id: \\.self) { index in\n                if self.pagerState.pageIndex == index {\n                    createChild(\n                        binding: self.info.properties.bindings.selected,\n                        constraints: childConstraints\n                    )\n                } else {\n                    createChild(\n                        binding: self.info.properties.bindings.unselected,\n                        constraints: childConstraints\n                    )\n                }\n            }\n        }\n        .padding(.horizontal, self.info.properties.spacing)\n        .animation(.interactiveSpring(duration: Pager.animationSpeed), value: self.info)\n        .constraints(constraints)\n        .thomasCommon(self.info)\n        .airshipApplyIf(announcePage(info: self.info), transform: { view in\n            view.accessibilityLabel(String(format: \"ua_pager_progress\".airshipLocalizedString(\n                fallback: \"Page %@ of %@\"\n            ), (self.pagerState.pageIndex + 1).airshipLocalizedForVoiceOver(), self.pagerState.pageStates.count.airshipLocalizedForVoiceOver()))\n        })\n        .accessibilityHidden(true)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PagerState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\nstruct PageState: ThomasSerializable {\n    var identifier: String\n    var delay: Double\n\n    // represent the automated action identifier and it's status (true if it's executed and false if not)\n    var automatedActionStatus: [String: Bool] = [:]\n    \n    init(\n        identifier: String,\n        delay: Double,\n        automatedActions: [String]?\n    ) {\n        self.identifier = identifier\n        self.delay = delay\n        \n        if let automatedActions = automatedActions {\n            automatedActions.forEach { automatedAction in\n                self.automatedActionStatus[automatedAction] = false\n            }\n        }\n    }\n    \n    mutating func markAutomatedActionExecuted(\n        _ identifier: String\n    ) {\n        self.automatedActionStatus[identifier] = true\n    }\n    \n    mutating func resetExecutedActions() {\n        automatedActionStatus.keys.forEach { key in\n            automatedActionStatus[key] = false\n        }\n    }\n}\n\nenum PageRequest {\n    case next\n    case back\n    case first\n}\n\nstruct ThomasPageInfo: Sendable {\n    var identifier: String\n    var index: Int\n    var viewCount: Int\n}\n\n\n\n\n@MainActor\nclass PagerState: ObservableObject {\n\n    struct NavigationResult: Sendable {\n        var fromPage: ThomasPageInfo?\n        var toPage: ThomasPageInfo\n    }\n\n    var pageIndex: Int {\n        pageItems.firstIndex(where: { $0.identifier == currentPageId }) ?? 0\n    }\n\n    @Published private(set) var currentPageId: String? {\n        didSet {\n            guard\n                let page = currentPageId,\n                page != oldValue\n            else {\n                return\n            }\n\n            self.pageViewCounts[page] = (self.pageViewCounts[page] ?? 0) + 1\n            updateInProgress(pageId: page)\n            resetExecutedActions(for: oldValue)\n            branchControl?.addToHistoryPage(id: page)\n            updateCompleted()\n        }\n    }\n    \n    @Published private(set) var pageStates: [PageState] = []\n    @Published private(set) var pageItems: [ThomasViewInfo.Pager.Item] = []\n    @Published var progress: Double = 0.0\n    @Published private(set) var completed: Bool = false\n    @Published private(set) var isScrollingDisabled = false\n    @Published private(set) var isNavigationInProgress = false\n\n    /// Used to pause/resume a story\n    @Published var inProgress: Bool = true\n    \n    private var isManuallyPaused: Bool = false\n    private var navigationCooldownTask: Task<Void, Never>?\n    private var pageViewCounts: [String: Int] = [:]\n\n    @Published\n    var isVoiceOverRunning = false\n\n    private var mediaReadyState: [MediaKey: Bool] = [:]\n\n    var currentPageState: PageState? {\n        get { pageStates.isEmpty ? nil : pageStates[pageIndex] }\n        set {\n            guard let newValue, !pageStates.isEmpty else { return }\n            pageStates[pageIndex] = newValue\n        }\n    }\n    \n    private static let navigationCooldownInterval: TimeInterval = 0.3\n\n    let identifier: String\n    private let branchControl: BranchControl?\n    private var thomasStateSubscription: AnyCancellable? = nil\n    private let taskSleeper: any AirshipTaskSleeper\n\n    // Used for reporting\n    var reportingPageCount: Int {\n        get { branchControl == nil ? pageItems.count : -1 }\n    }\n\n    init(\n        identifier: String,\n        branching: ThomasPagerControllerBranching?,\n        taskSleeper: any AirshipTaskSleeper = DefaultAirshipTaskSleeper.shared\n    ) {\n        self.identifier = identifier\n        self.taskSleeper = taskSleeper\n        \n        if let branching {\n            branchControl = BranchControl(completionChecker: branching)\n        } else {\n            branchControl = nil\n        }\n        \n        if let branchControl {\n            branchControl.$pages\n                .map { pages in\n                    pages.map { $0.toPageState() }\n                }\n                .assign(to: &$pageStates)\n\n            branchControl.$pages.assign(to: &$pageItems)\n\n            branchControl.$isComplete.assign(to: &$completed)\n        }\n    }\n    \n    func setPagesAndListenForUpdates(\n        pages: [ThomasViewInfo.Pager.Item],\n        thomasState: ThomasState,\n        swipeDisableSelectors: [ThomasViewInfo.Pager.DisableSwipeSelector]?\n    ) {\n        let pagesChanged = pages != self.pageItems\n        \n        if let branchControl {\n            branchControl.configureAndAttachTo(\n                pages: pages,\n                thomasState: thomasState\n            )\n        } else {\n            self.pageStates = pages.map({ $0.toPageState() })\n            self.pageItems = pages\n        }\n        \n        thomasStateSubscription?.cancel()\n        if let selectors = swipeDisableSelectors {\n            thomasStateSubscription = thomasState.$state\n                .receive(on: DispatchQueue.main)\n                .sink { @MainActor [weak self] newState in\n                    self?.reEvaluateScrollability(state: newState, selectors: selectors)\n                }\n        }\n\n        if self.currentPageId == nil || pagesChanged {\n            self.currentPageId = pageItems.first?.identifier\n        }\n    }\n\n    func pause() {\n        self.isManuallyPaused = true\n        if let currentPageId {\n            updateInProgress(pageId: currentPageId)\n        }\n    }\n\n    func togglePause() {\n        if self.isManuallyPaused {\n            resume()\n        } else {\n            pause()\n        }\n    }\n\n\n    func resume() {\n        self.isManuallyPaused = false\n        if let currentPageId {\n            updateInProgress(pageId: currentPageId)\n        }\n    }\n    \n    var isFirstPage: Bool {\n        return pageIndex == 0\n    }\n    \n    var isLastPage: Bool {\n        return pageIndex == pageItems.count - 1\n    }\n    \n    var canGoBack: Bool {\n        return pageIndex > 0\n    }\n\n    var canGoForward: Bool {\n        return pageIndex < pageItems.count - 1\n    }\n\n    @discardableResult\n    func navigateToPage(id: String) -> NavigationResult?  {\n        guard\n            self.pageItems.contains(where: { $0.identifier == id }),\n            id != self.currentPageId\n        else {\n            return nil\n        }\n\n        let fromPage: ThomasPageInfo? = if let currentPageId {\n            self.pageInfo(pageIdentifier: currentPageId)\n        } else {\n            nil\n        }\n\n        let toPage = self.pageInfo(pageIdentifier: id)\n\n        branchControl?.clearHistoryAfter(id: id)\n        self.progress = 0.0\n        self.currentPageId = id\n        return NavigationResult(fromPage: fromPage, toPage: toPage)\n    }\n\n    func pageInfo(pageIdentifier: String) -> ThomasPageInfo {\n        return ThomasPageInfo(\n            identifier: pageIdentifier,\n            index: self.pageItems.firstIndex(where: { item in\n                item.identifier == pageIdentifier\n            }) ?? -1,\n            viewCount: self.pageViewCounts[pageIdentifier] ?? 0\n        )\n    }\n\n    func pageInfo(index: Int) -> ThomasPageInfo {\n        let pageIdentifier = self.pageItems[index].identifier\n        return ThomasPageInfo(\n            identifier: pageIdentifier,\n            index: index,\n            viewCount: self.pageViewCounts[pageIdentifier] ?? 0\n        )\n    }\n\n    @discardableResult\n    func process(request: PageRequest) -> NavigationResult? {\n        let id = pageItems[nextIndexNoBranching(request: request)].identifier\n        guard\n            let result = self.navigateToPage(id: id)\n        else {\n            return nil\n        }\n\n        branchControl?.onPageRequest(request)\n        return result\n    }\n    \n    private func reEvaluateScrollability(\n        state: AirshipJSON,\n        selectors: [ThomasViewInfo.Pager.DisableSwipeSelector]\n    ) {\n        let selector = selectors.first(where: { $0.predicate?.evaluate(json: state) ?? true })\n        \n        switch(selector?.direction) {\n        case .horizontal:\n            isScrollingDisabled = true\n        case .none:\n            isScrollingDisabled = false\n        }         \n    }\n    \n    private func resetExecutedActions(for pageId: String?) {\n        guard\n            let pageId,\n            let index = pageStates.firstIndex(where: { $0.identifier == pageId })\n        else {\n            return\n        }\n        \n        pageStates[index].resetExecutedActions()\n    }\n    \n    func disableTouchDuringNavigation() {\n        // WORKAROUND: SwiftUI's scrollPosition(id:) has a race condition where rapid touch\n        // during scroll animation causes scrollPosition state to desync from actual position.\n        self.isNavigationInProgress = true\n        self.navigationCooldownTask?.cancel()\n        self.navigationCooldownTask = Task { @MainActor [weak self] in\n            guard let self = self else { return }\n            try? await taskSleeper.sleep(timeInterval: Self.navigationCooldownInterval)\n            guard !Task.isCancelled else { return }\n            self.navigationCooldownTask = nil\n            self.isNavigationInProgress = false\n        }\n    }\n    \n    private func nextIndexNoBranching(request: PageRequest) -> Int {\n        return switch request {\n        case .next:\n            min(pageIndex + 1, pageItems.count - 1)\n        case .back:\n            max(pageIndex - 1, 0)\n        case .first:\n            0\n        }\n    }\n    \n    private func updateCompleted() {\n        if branchControl != nil || completed {\n            return\n        }\n        \n        completed = pageIndex == (pageItems.count - 1)\n    }\n\n    func registerMedia(pageId: String, id: UUID) {\n        let key = MediaKey(pageId: pageId, id: id)\n        guard mediaReadyState[key] == nil else { return }\n        mediaReadyState[key] = false\n        updateInProgress(pageId: pageId)\n    }\n\n    func setMediaReady(pageId: String, id: UUID, isReady: Bool) {\n        let key = MediaKey(pageId: pageId, id: id)\n        mediaReadyState[key] = isReady\n        updateInProgress(pageId: pageId)\n    }\n\n    func markAutomatedActionExecuted(_ identifier: String) {\n        self.currentPageState?.markAutomatedActionExecuted(identifier)\n    }\n\n    private func updateInProgress(pageId: String) {\n        let isMediaReady = !mediaReadyState.contains(where: { key, isReady in\n            key.pageId == pageId && isReady == false\n        })\n\n        let update = isMediaReady && !isManuallyPaused && !isVoiceOverRunning\n        if self.inProgress != update {\n            self.inProgress = update\n        }\n    }\n\n    struct MediaKey: Hashable, Equatable {\n        let pageId: String\n        let id: UUID\n    }\n}\n\n@MainActor\nprivate class BranchControl: Sendable {\n    let completionChecker: ThomasPagerControllerBranching\n    \n    private var allPages: [ThomasViewInfo.Pager.Item] = []\n    \n    @Published private(set) var pages: [ThomasViewInfo.Pager.Item] = []\n    @Published private(set) var isComplete: Bool = false\n\n    private var thomasState: ThomasState?\n    private var history: [ThomasViewInfo.Pager.Item] = []\n    private var subscriptions: Set<AnyCancellable> = []\n    \n    init(completionChecker: ThomasPagerControllerBranching) {\n        self.completionChecker = completionChecker\n    }\n\n    var payload: AirshipJSON {\n        return self.thomasState?.state ?? .null\n    }\n\n    func configureAndAttachTo(\n        pages: [ThomasViewInfo.Pager.Item],\n        thomasState: ThomasState\n    ) {\n        detach()\n\n        self.thomasState = thomasState\n\n        allPages = pages\n\n        thomasState.$state\n            .receive(on: RunLoop.main)\n            .sink { [weak self] _ in\n                self?.updateState()\n            }\n            .store(in: &subscriptions)\n\n        updateState()\n    }\n    \n    func detach() {\n        subscriptions.forEach({ $0.cancel() })\n        subscriptions.removeAll()\n    }\n\n    private func updateState() {\n        self.reEvaluatePath()\n        self.evaluateCompletion()\n    }\n    \n    \n    func onPageRequest(_ request: PageRequest) {\n        self.updateState()\n        \n        switch request {\n        case .next, .back: break\n        case .first: history.removeAll()\n        }\n    }\n    \n    func clearHistoryAfter(id: String) {\n        guard let index = history.firstIndex(where: { $0.identifier == id }) else {\n            return\n        }\n        \n        history.removeSubrange((index + 1)...)\n    }\n    \n    func addToHistoryPage(id: String) {\n        guard\n            let page = allPages.first(where: { $0.identifier == id }),\n            !history.contains(page)\n        else {\n            return\n        }\n        \n        history.append(page)\n    }\n\n    private func reEvaluatePath() {\n        if history.isEmpty, !allPages.isEmpty {\n            history = [allPages[0]]\n        }\n        \n        var historyCopy = history\n        guard let current = historyCopy.popLast() else {\n            return\n        }\n        \n        pages = historyCopy + buildPathFrom(page: current, payload: payload)\n    }\n    \n    private func buildPathFrom(\n        page: ThomasViewInfo.Pager.Item,\n        payload: AirshipJSON\n    ) -> [ThomasViewInfo.Pager.Item] {\n        \n        guard var pageIndex = allPages.firstIndex(of: page) else {\n            return []\n        }\n        \n        var result: [ThomasViewInfo.Pager.Item] = []\n        \n        while(pageIndex >= 0 && pageIndex < allPages.count) {\n            let current = allPages[pageIndex]\n            \n            if result.contains(current) {\n                AirshipLogger.warn(\"Trying to add a duplicate \\(current)\")\n                break\n            }\n            \n            result.append(current)\n            \n            guard\n                let branching = current.branching,\n                let nextPage = branching.nextPageId(json: payload),\n                let nextPageIndex = allPages.firstIndex(where: { $0.identifier == nextPage })\n            else {\n                break\n            }\n            \n            pageIndex = nextPageIndex\n        }\n        \n        return result\n    }\n    \n    private func evaluateCompletion() {\n        guard !isComplete else { return }\n        \n        var result = false\n        for indicator in completionChecker.completions {\n            if indicator.predicate?.evaluate(json: payload) != false {\n                result = true\n                break\n            }\n        }\n        \n        if result, result != isComplete {\n            performCompletionStateActions()\n        }\n        \n        self.isComplete = result\n    }\n    \n    private func performCompletionStateActions() {\n        guard let thomasState else { return }\n        let actions = completionChecker.completions\n            .filter { $0.predicate?.evaluate(json: payload) != false }\n            .compactMap { $0.stateActions }\n            .flatMap { $0 }\n\n        thomasState.processStateActions(actions)\n    }\n}\n\nfileprivate extension ThomasPageBranching {\n    func nextPageId(json: AirshipJSON) -> String? {\n        return nextPage?\n            .first(where: { selector in\n                selector.predicate?.evaluate(json: json) != false\n            })?\n            .pageId\n    }\n}\n\nfileprivate extension ThomasViewInfo.Pager.Item {\n    func toPageState() -> PageState {\n        return PageState(\n            identifier: identifier,\n            delay: automatedActions?.earliestNavigationAction?.delay ?? 0.0,\n            automatedActions: automatedActions?.compactMap { automatedAction in\n                automatedAction.identifier\n            }\n        )\n    }\n}\n\n//MARK: - ThomasStateProvider\nextension PagerState: ThomasStateProvider {\n    typealias SnapshotType = Snapshot\n    \n    struct Snapshot: Codable, Equatable {\n        let pageStates: [PageState]\n        let currentPageId: String?\n        let progress: Double\n    }\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return Publishers\n            .CombineLatest3($pageStates, $currentPageId, $progress)\n            .map(Snapshot.init)\n            .removeDuplicates()\n            .map(\\.self)\n            .eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> SnapshotType {\n        Snapshot(\n            pageStates: self.pageStates,\n            currentPageId: self.currentPageId,\n            progress: self.progress\n        )\n    }\n    \n    func restorePersistentState(_ state: Snapshot) {\n        DispatchQueue.main.async {\n            self.pageStates = state.pageStates\n            self.currentPageId = state.currentPageId\n            self.progress = state.progress\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PagerSwipeDirection.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nenum PagerSwipeDirection: Sendable {\n    case up\n    case down\n    case start\n    case end\n}\n\nextension PagerSwipeDirection {\n    private static let flingSpeed: CGFloat = 150.0\n    private static let offsetPercent: CGFloat = 0.50\n\n    static func from(\n        edge: Edge,\n        layoutDirection: LayoutDirection\n    ) -> PagerSwipeDirection {\n        switch (edge) {\n        case .top:\n            return .down\n        case .leading:\n            return if (layoutDirection == .leftToRight) {\n                .end\n            } else {\n                .start\n            }\n        case .bottom:\n            return .up\n        case .trailing:\n            return if (layoutDirection == .leftToRight) {\n                .start\n            } else {\n                .end\n            }\n        }\n    }\n\n#if !os(tvOS)\n    static func from(\n        dragValue: DragGesture.Value,\n        size: CGSize,\n        layoutDirection: LayoutDirection\n    ) -> PagerSwipeDirection? {\n        let xVelocity = dragValue.predictedEndLocation.x - dragValue.location.x\n        let yVelocity = dragValue.predictedEndLocation.y - dragValue.location.y\n        let widthOffset = dragValue.translation.width / size.width\n        let heightOffset = dragValue.translation.height / size.height\n\n        var swipeDirection: PagerSwipeDirection? = nil\n        if (abs(xVelocity) > abs(yVelocity)) {\n            if abs(xVelocity) >= Self.flingSpeed {\n                if (xVelocity > 0) {\n                    swipeDirection = (layoutDirection == .leftToRight) ? .start : .end\n                } else {\n                    swipeDirection = (layoutDirection == .leftToRight) ? .end : .start\n                }\n            } else if abs(widthOffset) >= Self.offsetPercent {\n                if (widthOffset > 0) {\n                    swipeDirection = (layoutDirection == .leftToRight) ? .start : .end\n                } else {\n                    swipeDirection = (layoutDirection == .leftToRight) ? .end : .start\n                }\n            }\n        } else {\n            if abs(yVelocity) >= Self.flingSpeed {\n                swipeDirection = (yVelocity > 0) ? .down : .up\n             } else if abs(heightOffset) >= Self.offsetPercent {\n                 swipeDirection = (heightOffset > 0) ? .down : .up\n             }\n        }\n        return swipeDirection\n    }\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PagerUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nextension ThomasViewInfo.Pager {\n    var isDefaultSwipeEnabled: Bool {\n        return self.properties.disableSwipe != true && self.properties.items.count > 1\n    }\n\n    func retrieveGestures<T: ThomasViewInfo.Pager.Gesture.Info>(type: T.Type) -> [T] {\n        guard let gestures = self.properties.gestures else {\n            return []\n        }\n\n        return gestures.compactMap { gesture in\n            switch gesture {\n            case .tapGesture(let model):\n                return model as? T\n            case .swipeGesture(let model):\n                return model as? T\n            case .holdGesture(let model):\n                return model as? T\n            }\n        }\n    }\n\n    func containsGestures(_ types: [ThomasViewInfo.Pager.Gesture.GestureType]) -> Bool {\n        guard let gestures = self.properties.gestures else {\n            return false\n        }\n\n        return gestures.contains(where: { gesture in\n            switch(gesture) {\n            case .swipeGesture(let gesture): return types.contains(gesture.type)\n            case .tapGesture(let gesture): return types.contains(gesture.type)\n            case .holdGesture(let gesture): return types.contains(gesture.type)\n            }\n        })\n    }\n}\n\nextension Array where Element == ThomasAutomatedAction {\n    var earliestNavigationAction: ThomasAutomatedAction? {\n        return self.first {\n            return $0.behaviors?.filter {\n                return switch($0) {\n                case .dismiss: true\n                case .cancel: true\n                case .pagerNext: true\n                case .pagerPrevious: true\n                case .pagerNextOrDismiss: true\n                case .pagerNextOrFirst: true\n                case .formValidate: false\n                case .formSubmit: false\n                case .pagerPause: false\n                case .pagerResume: false\n                case .pagerPauseToggle: false\n                case .videoPlay: false\n                case .videoPause: false\n                case .videoTogglePlay: false\n                case .videoMute: false\n                case .videoUnmute: false\n                case .videoToggleMute: false\n                }\n            }.isEmpty == false\n        }\n    }\n}\n\nextension View {\n#if !os(tvOS)\n    @ViewBuilder\n    func addPagerTapGesture(onTouch: @escaping (Bool) -> Void, onTap: @escaping (CGPoint) -> Void) -> some View {\n        self.onTouch { isPressed in\n            onTouch(isPressed)\n        }\n        .simultaneousGesture(\n            SpatialTapGesture()\n                .onEnded { event in\n                    onTap(event.location)\n                }\n            )\n    }\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PasteboardAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(watchOS)\n\nimport Foundation\n\n/// Sets the pasteboard's string.\n///\n/// Expected argument values: String or an Object with the pasteboard's string\n/// under the 'text' key.\n///\n/// Valid situations: `ActionSituation.launchedFromPush`,\n/// `ActionSituation.webViewInvocation`, `ActionSituation.manualInvocation`,\n/// `ActionSituation.foregroundInteractiveButton`, `ActionSituation.backgroundInteractiveButton`,\n/// and `ActionSituation.automation`\n///\n/// Result value: The arguments value.\n@available(tvOS, unavailable)\npublic final class PasteboardAction: AirshipAction {\n\n    /// Default names - \"clipboard_action\", \"^c\"\n    public static let defaultNames: [String] = [\"clipboard_action\", \"^c\"]\n\n    private let pasteboard: any AirshipPasteboardProtocol\n\n    init(pasteboard: any AirshipPasteboardProtocol = DefaultAirshipPasteboard()) {\n        self.pasteboard = pasteboard\n    }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .manualInvocation, .webViewInvocation, .launchedFromPush,\n             .backgroundInteractiveButton, .foregroundInteractiveButton,\n             .automation:\n            return pasteboardString(arguments) != nil\n        case .backgroundPush, .foregroundPush:\n            return false\n        }\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        if let string = pasteboardString(arguments) {\n            self.pasteboard.copy(value: string)\n        }\n\n        return arguments.value\n    }\n\n    func pasteboardString(_ arguments: ActionArguments) -> String? {\n        if let value = arguments.value.unWrap() as? String {\n            return value\n        }\n\n        if let dict = arguments.value.unWrap() as? [AnyHashable: Any] {\n            return dict[\"text\"] as? String\n        }\n\n        return nil\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Permission.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Airship permissions. Used with `PermissionsManager`\npublic enum AirshipPermission: String, Sendable, Codable {\n    /// Post notifications\n    case displayNotifications = \"display_notifications\"\n\n    /// Location\n    case location\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PermissionDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Permissions manager delegate. Allows for extending permission gathering.\npublic protocol AirshipPermissionDelegate: Sendable {\n\n    /// Called when a permission needs to be checked.\n    /// - Returns: the permission status.\n    @MainActor\n    func checkPermissionStatus() async -> AirshipPermissionStatus\n\n    /// Called when a permission should be requested.\n    ///\n    /// - Note: A permission might be already granted when this method is called.\n    ///\n    /// - Returns: the permission status.\n    @MainActor\n    func requestPermission() async -> AirshipPermissionStatus\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PermissionPrompter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\ntypealias PermissionResultReceiver = @Sendable (\n    AirshipPermission, AirshipPermissionStatus, AirshipPermissionStatus\n) async -> Void\n\nprotocol PermissionPrompter: Sendable {\n\n    func prompt(\n        permission: AirshipPermission,\n        enableAirshipUsage: Bool,\n        fallbackSystemSettings: Bool\n    ) async ->  AirshipPermissionResult\n}\n\nstruct AirshipPermissionPrompter: PermissionPrompter {\n\n    private let permissionsManager: any AirshipPermissionsManager\n\n    init(\n        permissionsManager: any AirshipPermissionsManager\n    ) {\n        self.permissionsManager = permissionsManager\n    }\n\n    @MainActor\n    func prompt(\n        permission: AirshipPermission,\n        enableAirshipUsage: Bool,\n        fallbackSystemSettings: Bool\n    ) async -> AirshipPermissionResult {\n        return await self.permissionsManager.requestPermission(\n            permission,\n            enableAirshipUsageOnGrant: enableAirshipUsage,\n            fallback: fallbackSystemSettings ? .systemSettings : .none\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PermissionStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Permission status\npublic enum AirshipPermissionStatus: String, Sendable, Codable {\n    /// Could not determine the permission status.\n    case notDetermined = \"not_determined\"\n\n    /// Permission is granted.\n    case granted\n\n    /// Permission is denied.\n    case denied\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PermissionsManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n/// Airship permissions manager.\n///\n/// Airship will provide the default handling for `Permission.postNotifications`. All other permissions will need\n/// to be configured by the app by providing a `PermissionDelegate` for the given permissions.\npublic protocol AirshipPermissionsManager: Sendable {\n    \n    /// The set of permissions that have a configured delegate.\n    var configuredPermissions: Set<AirshipPermission> { get }\n    \n    /// Returns an async stream with status updates for the given permission\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    func statusUpdate(\n        for permission: AirshipPermission\n    ) -> AsyncStream<AirshipPermissionStatus>\n    \n    /// - Note: For internal use only. :nodoc:\n    func permissionStatusMap() async -> [String: String]\n    \n    /// Sets a permission delegate.\n    ///\n    /// - Note: The delegate will be strongly retained.\n    ///\n    /// - Parameters:\n    ///     - delegate: The delegate.\n    ///     - permission: The permission.\n    func setDelegate(\n        _ delegate: (any AirshipPermissionDelegate)?,\n        permission: AirshipPermission\n    )\n    \n    /// Checks a permission status.\n    ///\n    /// - Note: If no delegate is set for the given permission this will always return `.notDetermined`.\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    @MainActor\n    func checkPermissionStatus(\n        _ permission: AirshipPermission\n    ) async -> AirshipPermissionStatus\n    \n    /// Requests a permission.\n    ///\n    /// - Note: If no permission delegate is set for the given permission this will always return `.notDetermined`\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    @MainActor\n    func requestPermission(\n        _ permission: AirshipPermission\n    ) async -> AirshipPermissionStatus\n    \n    /// Requests a permission.\n    ///\n    /// - Note: If no permission delegate is set for the given permission this will always return `.notDetermined`\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    ///     - enableAirshipUsageOnGrant: `true` to allow any Airship features that need the permission to be enabled as well, e.g., enabling push privacy manager feature and user notifications if `.displayNotifications` is granted.\n    @MainActor\n    func requestPermission(\n        _ permission: AirshipPermission,\n        enableAirshipUsageOnGrant: Bool\n    ) async -> AirshipPermissionStatus\n    \n    /// Requests a permission.\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    ///     - enableAirshipUsageOnGrant: `true` to allow any Airship features that need the permission to be enabled as well, e.g., enabling push privacy manager feature and user notifications if `.displayNotifications` is granted.\n    ///     - fallback: The fallback behavior if the permission is alreay denied.\n    /// - Returns: A `AirshipPermissionResult` with the starting and ending status If no permission delegate is\n    /// set for the given permission the status will be `.notDetermined`\n    @MainActor\n    func requestPermission(\n        _ permission: AirshipPermission,\n        enableAirshipUsageOnGrant: Bool,\n        fallback: PromptPermissionFallback\n    ) async -> AirshipPermissionResult\n    \n    /// - Note: for internal use only.  :nodoc:\n    func addRequestExtender(\n        permission: AirshipPermission,\n        extender: @escaping @Sendable (AirshipPermissionStatus) async -> Void\n    )\n    \n    /// - Note: for internal use only.  :nodoc:\n    func addAirshipEnabler(\n        permission: AirshipPermission,\n        onEnable: @escaping @Sendable () async -> Void\n    )\n}\n\nfinal class DefaultAirshipPermissionsManager: AirshipPermissionsManager {\n    private let delegateMap: AirshipAtomicValue<[AirshipPermission: any AirshipPermissionDelegate]> = AirshipAtomicValue([AirshipPermission: any AirshipPermissionDelegate]())\n    \n    private let airshipEnablers: AirshipAtomicValue<[AirshipPermission: [@Sendable () async -> Void]]> = AirshipAtomicValue([AirshipPermission: [@Sendable () async -> Void]]())\n\n    private let queue: AirshipSerialQueue = AirshipSerialQueue()\n\n    private let extenders: AirshipAtomicValue<[AirshipPermission: [@Sendable (AirshipPermissionStatus) async -> Void]]> = AirshipAtomicValue([AirshipPermission: [@Sendable (AirshipPermissionStatus) async -> Void]]())\n    \n    private let statusUpdates: AirshipAsyncChannel<(AirshipPermission, AirshipPermissionStatus)> = AirshipAsyncChannel()\n    private let appStateTracker: any AppStateTrackerProtocol\n    private let systemSettingsNavigator: any SystemSettingsNavigatorProtocol\n\n    @MainActor\n    init(\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        systemSettingsNavigator: any SystemSettingsNavigatorProtocol = SystemSettingsNavigator()\n    ) {\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n        self.systemSettingsNavigator = systemSettingsNavigator\n\n        Task { @MainActor [weak self] in\n            guard let updates = self?.appStateTracker.stateUpdates else { return }\n            for await update in updates {\n                if (update == .active) {\n                    await self?.refreshPermissionStatuses()\n                }\n            }\n        }\n    }\n\n    public var configuredPermissions: Set<AirshipPermission> {\n        return Set(delegateMap.value.keys)\n    }\n    \n    public func statusUpdate(for permission: AirshipPermission) -> AsyncStream<AirshipPermissionStatus> {\n\n        return AsyncStream<AirshipPermissionStatus> { [weak self, statusUpdates] continuation in\n            let task = Task { [weak self, statusUpdates] in\n                if let startingStatus = await self?.checkPermissionStatus(permission) {\n                    continuation.yield(startingStatus)\n                }\n\n                let updates = await statusUpdates.makeStream()\n                    .filter({ $0.0 == permission })\n                    .map({ $0.1 })\n\n                for await item in updates {\n                    continuation.yield(item)\n                }\n                continuation.finish()\n            }\n            \n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n    \n    /// - Note: For internal use only. :nodoc:\n    public func permissionStatusMap() async -> [String: String] {\n        var map: [String: String] = [:]\n        for permission in configuredPermissions {\n            let status = await checkPermissionStatus(permission)\n            map[permission.rawValue] = status.rawValue\n        }\n        return map\n    }\n    \n    public func setDelegate(\n        _ delegate: (any AirshipPermissionDelegate)?,\n        permission: AirshipPermission\n    ) {\n        delegateMap.update { input in\n            var mutable = input\n            mutable[permission] = delegate\n            return mutable\n        }\n    }\n    \n    @MainActor\n    public func checkPermissionStatus(\n        _ permission: AirshipPermission\n    ) async -> AirshipPermissionStatus {\n        guard let delegate = self.permissionDelegate(permission) else {\n            return .notDetermined\n        }\n\n        return await delegate.checkPermissionStatus()\n    }\n\n    @MainActor\n    public func requestPermission(\n        _ permission: AirshipPermission\n    ) async -> AirshipPermissionStatus {\n        return await requestPermission(\n            permission,\n            enableAirshipUsageOnGrant: false\n        )\n    }\n    \n    @MainActor\n    public func requestPermission(\n        _ permission: AirshipPermission,\n        enableAirshipUsageOnGrant: Bool\n    ) async -> AirshipPermissionStatus {\n        return await requestPermission(\n            permission,\n            enableAirshipUsageOnGrant: enableAirshipUsageOnGrant,\n            fallback: .none\n        ).endStatus\n    }\n\n    @MainActor\n    public func requestPermission(\n        _ permission: AirshipPermission,\n        enableAirshipUsageOnGrant: Bool,\n        fallback: PromptPermissionFallback\n    ) async -> AirshipPermissionResult {\n        let status: AirshipPermissionResult? = try? await self.queue.run { @MainActor in\n            guard let delegate = self.permissionDelegate(permission) else {\n                return AirshipPermissionResult.notDetermined\n            }\n\n            let startingStatus = await delegate.checkPermissionStatus()\n            var endStatus: AirshipPermissionStatus = await delegate.requestPermission()\n\n            if startingStatus == .denied, endStatus == .denied {\n                switch fallback {\n                case .none:\n                    endStatus = .denied\n                case .systemSettings:\n                    if await self.systemSettingsNavigator.open(for: permission) {\n                        await self.appStateTracker.waitForActive()\n                        endStatus = await delegate.checkPermissionStatus()\n                    } else {\n                        endStatus = .denied\n                    }\n                case .callback(let callback):\n                    await callback()\n                    endStatus = await delegate.checkPermissionStatus()\n                }\n            }\n\n            if endStatus == .granted {\n                await self.onPermissionEnabled(\n                    permission,\n                    enableAirshipUsage: enableAirshipUsageOnGrant\n                )\n            }\n\n            await self.onExtend(permission: permission, status: endStatus)\n\n            return AirshipPermissionResult(startStatus: startingStatus, endStatus: endStatus)\n        }\n\n        let result = status ?? AirshipPermissionResult.notDetermined\n\n        await statusUpdates.send((permission, result.endStatus))\n\n        return result\n    }\n\n    public func addRequestExtender(\n        permission: AirshipPermission,\n        extender: @escaping @Sendable (AirshipPermissionStatus) async -> Void\n    ) {\n        extenders.update { current in\n            var mutable = current\n            if mutable[permission] == nil {\n                mutable[permission] = [extender]\n            } else {\n                mutable[permission]?.append(extender)\n            }\n            return mutable\n        }\n    }\n    \n    public func addAirshipEnabler(\n        permission: AirshipPermission,\n        onEnable: @escaping @Sendable () async -> Void\n    ) {\n        airshipEnablers.update { current in\n            var mutable = current\n            if mutable[permission] == nil {\n                mutable[permission] = [onEnable]\n            } else {\n                mutable[permission]?.append(onEnable)\n            }\n            return mutable\n        }\n    }\n    \n    private func onPermissionEnabled(\n        _ permission: AirshipPermission,\n        enableAirshipUsage: Bool\n    ) async {\n        guard enableAirshipUsage else  { return }\n\n        let enablers = airshipEnablers.value[permission] ?? []\n\n        for enabler in enablers {\n            await enabler()\n        }\n    }\n\n    private func permissionDelegate(\n        _ permission: AirshipPermission\n    ) -> (any AirshipPermissionDelegate)? {\n        return delegateMap.value[permission]\n    }\n    \n    @MainActor\n    private func refreshPermissionStatuses() async {\n        for permission in configuredPermissions {\n            let status = await checkPermissionStatus(permission)\n            await statusUpdates.send((permission, status))\n        }\n    }\n\n    @MainActor\n    func onExtend(\n        permission: AirshipPermission,\n        status: AirshipPermissionStatus\n    ) async {\n        let extenders = self.extenders.value[permission] ?? []\n        \n        for extender in extenders {\n            await extender(status)\n        }\n    }\n}\n\n/// Permission request result.\npublic struct AirshipPermissionResult: Sendable {\n    /// Starting status\n    public var startStatus: AirshipPermissionStatus\n\n    /// Ending status\n    public var endStatus: AirshipPermissionStatus\n\n    public init(startStatus: AirshipPermissionStatus, endStatus: AirshipPermissionStatus) {\n        self.startStatus = startStatus\n        self.endStatus = endStatus\n    }\n\n    static var notDetermined: AirshipPermissionResult {\n        AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n    }\n}\n\n\n\n/// Prompt permission fallback to be used if the requested permission is already denied.\npublic enum PromptPermissionFallback: Sendable {\n    /// No fallback\n    case none\n    /// Navigate to system settings\n    case systemSettings\n    // Custom callback\n    case callback(@MainActor @Sendable () async -> Void)\n}\n\nextension PromptPermissionFallback {\n    var isNone: Bool {\n        switch self {\n        case .none: return true\n        default: return false\n        }\n    }\n}\n\nprotocol SystemSettingsNavigatorProtocol: Sendable {\n    @MainActor\n    func open(for: AirshipPermission) async -> Bool\n}\n\nstruct SystemSettingsNavigator: SystemSettingsNavigatorProtocol {\n    @MainActor\n    func open(for permission: AirshipPermission) async -> Bool {\n#if os(watchOS)\n        return false\n#else\n        guard let url = systemSettingURLForPermission(permission) else {\n            return false\n        }\n        \n        // Use our unified cross-platform opener\n        return await DefaultURLOpener.shared.openURL(url)\n#endif\n    }\n    \n    @MainActor\n    private func systemSettingURLForPermission(_ permission: AirshipPermission) -> URL? {\n#if os(macOS)\n        let path = switch(permission) {\n        case .displayNotifications:\n            \"x-apple.systempreferences:com.apple.Notifications-Settings.extension\"\n        case .location:\n            \"x-apple.systempreferences:com.apple.preference.security?Privacy_LocationServices\"\n        }\n        return URL(string: path)\n#elseif !os(watchOS)\n        let string = switch(permission) {\n        case .displayNotifications:\n            UIApplication.openNotificationSettingsURLString\n        case .location:\n            UIApplication.openSettingsURLString\n        }\n        return URL(string: string)\n#else\n        return nil\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PreferenceDataStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Preference data store.\n/// - Note: For internal use only. :nodoc:\npublic final class PreferenceDataStore: @unchecked Sendable {\n    private let defaults: UserDefaults\n    private let appKey: String\n    static let deviceIDKey: String = \"deviceID\"\n    \n    private var pending: [String: [Any?]] = [:]\n    private var cache: [String: Cached] = [:]\n    private let lock: AirshipLock = AirshipLock()\n    private let dispatcher: any UADispatcher\n    private var deviceID: any AirshipDeviceIDProtocol\n\n    var isAppRestore: Bool {\n        get async {\n            let deviceIDValue = await deviceID.value\n\n            var restored: Bool = false\n            lock.sync {\n                let previousDeviceID = self.string(forKey: PreferenceDataStore.deviceIDKey)\n                if (deviceIDValue != previousDeviceID) {\n                    restored = previousDeviceID != nil\n                    self.setObject(deviceIDValue, forKey: PreferenceDataStore.deviceIDKey)\n                }\n            }\n\n            if (restored) {\n                AirshipLogger.info(\"App restored\")\n            }\n            return restored\n        }\n    }\n\n    public convenience init(appKey: String) {\n        self.init(\n            appKey: appKey,\n            dispatcher: DefaultDispatcher.serial(),\n            deviceID: AirshipDeviceID(appKey: appKey)\n        )\n    }\n\n    init(appKey: String, dispatcher: any UADispatcher, deviceID: any AirshipDeviceIDProtocol) {\n        self.defaults = PreferenceDataStore.createDefaults(appKey: appKey)\n        self.appKey = appKey\n        self.dispatcher = dispatcher\n        self.deviceID = deviceID\n        mergeKeys()\n    }\n\n    class func createDefaults(appKey: String) -> UserDefaults {\n        let suiteName = \"\\(Bundle.main.bundleIdentifier ?? \"\").airship.settings\"\n        guard let defaults = UserDefaults(suiteName: suiteName) else {\n            AirshipLogger.error(\"Failed to create defaults \\(suiteName)\")\n            return UserDefaults.standard\n        }\n\n        let legacyPrefix = legacyKeyPrefix(appKey: appKey)\n        for (key, value) in UserDefaults.standard.dictionaryRepresentation() {\n            if key.hasPrefix(appKey) || key.hasPrefix(legacyPrefix) {\n                defaults.set(value, forKey: key)\n                UserDefaults.standard.removeObject(forKey: key)\n            }\n        }\n\n        return defaults\n    }\n\n    public func value(forKey key: String) -> Any? {\n        return read(key)\n    }\n\n    public func setValue(_ value: Any?, forKey key: String) {\n        write(key, value: value)\n    }\n\n    func storeValue(_ value: Any?, forKey key: String) {\n        write(key, value: value)\n    }\n\n    @objc\n    public func removeObject(forKey key: String) {\n        write(key, value: nil)\n    }\n\n    public func keyExists(_ key: String) -> Bool {\n        return object(forKey: key) != nil\n    }\n\n    @objc\n    public func object(forKey key: String) -> Any? {\n        return read(key)\n    }\n\n    public func string(forKey key: String) -> String? {\n        return read(key)\n    }\n\n    public func array(forKey key: String) -> [AnyHashable]? {\n        return read(key)\n    }\n\n    public func dictionary(forKey key: String) -> [AnyHashable: Any]? {\n        return read(key)\n    }\n\n    public func data(forKey key: String) -> Data? {\n        return read(key)\n    }\n\n    public func stringArray(forKey key: String) -> [AnyHashable]? {\n        return read(key)\n    }\n\n    public func integer(forKey key: String) -> Int {\n        return read(key) ?? 0\n    }\n\n    public func unsignedInteger(forKey key: String) -> UInt? {\n        return read(key)\n    }\n\n    public func float(forKey key: String) -> Float {\n        return read(key) ?? 0.0\n    }\n\n    public func double(forKey key: String) -> Double {\n        return read(key) ?? 0.0\n    }\n\n    public func double(forKey key: String, defaultValue: Double) -> Double {\n        return read(key, defaultValue: defaultValue)\n    }\n\n    @objc\n    public func bool(forKey key: String) -> Bool {\n        return read(key) ?? false\n    }\n\n    public func bool(forKey key: String, defaultValue: Bool) -> Bool {\n        return read(key, defaultValue: defaultValue)\n    }\n\n    public func setInteger(_ int: Int, forKey key: String) {\n        write(key, value: int)\n    }\n\n    public func setUnsignedInteger(_ value: UInt, forKey key: String) {\n        write(key, value: value)\n    }\n\n    public func setFloat(_ float: Float, forKey key: String) {\n        write(key, value: float)\n    }\n\n    public func setDouble(_ double: Double, forKey key: String) {\n        write(key, value: double)\n    }\n\n    public func setBool(_ bool: Bool, forKey key: String) {\n        write(key, value: bool)\n    }\n\n    @objc\n    public func setObject(_ object: Any?, forKey key: String) {\n        write(key, value: object)\n    }\n\n    public func codable<T: Codable>(forKey key: String) throws -> T? {\n        guard let data: Data = read(key) else {\n            return nil\n        }\n\n        return try JSONDecoder().decode(T.self, from: data)\n    }\n\n    public func safeCodable<T: Codable>(forKey key: String) -> T? {\n        do {\n            return try codable(forKey: key)\n        } catch {\n            AirshipLogger.error(\"Failed to read codable for key \\(key)\")\n            return nil\n        }\n    }\n\n    public func setSafeCodable<T: Codable>(\n        _ codable: T?,\n        forKey key: String\n    ) {\n        do {\n            try setCodable(codable, forKey: key)\n        } catch {\n            AirshipLogger.error(\"Failed to write codable for key \\(key)\")\n        }\n    }\n\n    public func setCodable<T: Codable>(\n        _ codable: T?,\n        forKey key: String\n    ) throws {\n        guard let codable = codable else {\n            write(key, value: nil)\n            return\n        }\n\n        let data = try JSONEncoder().encode(codable)\n        write(key, value: data)\n    }\n\n    /// Merges old key formats `com.urbanairship.<APP_KEY>.<PREFERENCE>` to\n    /// the new key formats `<APP_KEY><PREFERENCE>`. Fixes a bug in SDK 15.x-16.0.1\n    /// where the key changed but we didn't migrate the data.\n    private func mergeKeys() {\n        let legacyKeyPrefix = PreferenceDataStore.legacyKeyPrefix(\n            appKey: self.appKey\n        )\n\n        for (key, value) in self.defaults.dictionaryRepresentation() {\n\n            // Check for old key\n            if key.hasPrefix(legacyKeyPrefix) {\n\n                let preference = String(key.dropFirst(legacyKeyPrefix.count))\n                let newValue = object(forKey: preference)\n\n                if newValue == nil {\n                    // Value not updated on new key, restore value\n                    setObject(value, forKey: preference)\n                } else if preference == \"com.urbanairship.channel.tags\" {\n\n                    // Both old and new tag keys have data, merge\n                    if let old = value as? [String],\n                        let new = newValue as? [String]\n                    {\n                        let combined = AudienceUtils.normalizeTags(old + new)\n                        setObject(combined, forKey: preference)\n                    }\n                }\n\n                // Delete the old key\n                self.defaults.removeObject(forKey: key)\n            }\n        }\n    }\n\n    private class func legacyKeyPrefix(appKey: String) -> String {\n        return \"com.urbanairship.\\(appKey).\"\n    }\n\n    private func read<T>(_ key: String, defaultValue: T) -> T {\n        return read(key) ?? defaultValue\n    }\n\n    private func read<T>(_ key: String) -> T? {\n        let key = prefixKey(key)\n        let defaults = self.defaults\n        var result: Any?\n\n        lock.sync {\n            if let cached = self.cache[key] {\n                result = cached.value\n            } else {\n                result = defaults.object(forKey: key)\n            }\n        }\n\n        guard let result = result else {\n            return nil\n        }\n\n        return result as? T\n    }\n\n    func write(_ key: String, value: Any?) {\n        let key = prefixKey(key)\n        let value = value\n\n        lock.sync {\n            self.cache[key] = Cached(value: value)\n        }\n\n        self.dispatcher.dispatchAsync {\n            self.lock.sync {\n                if let value = self.cache[key]?.value {\n                    self.defaults.set(value, forKey: key)\n                } else {\n                    self.defaults.removeObject(forKey: key)\n                }\n            }\n        }\n    }\n\n    private func prefixKey(_ key: String) -> String {\n        return (appKey) + key\n    }\n}\n\n\nprivate struct Cached {\n    let value: Any?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PromptPermissionAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Action that prompts for permission using `PermissionsManager`\n///\n/// Expected arguments, dictionary with keys:\n/// -`enable_airship_usage`: Bool?. If related airship features should be enabled if the permission is granted.\n/// -`fallback_system_settings`: Bool?. If denied, fallback to system settings.\n/// -`permission`: String. The name of the permission. `post_notifications`, `bluetooth`, `mic`, `location`, `contacts`, `camera`, etc...\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`,\n/// `ActionSituation.webViewInvocation`, `ActionSituation.manualInvocation`,\n/// `ActionSituation.foregroundInteractiveButton`, and `ActionSituation.automation`\npublic final class PromptPermissionAction: AirshipAction {\n\n    /// Default names - \"prompt_permission_action\", \"^pp\"\n    public static let defaultNames: [String] = [\"prompt_permission_action\", \"^pp\"]\n    \n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n\n    /// Metadata key for the result receiver. Must be (Permission, PermissionStatus, PermissionStatus) -> Void\n    /// - Note: For internal use only. :nodoc:\n    public static let resultReceiverMetadataKey: String = \"permission_result\"\n\n    private let permissionPrompter: @Sendable () -> any PermissionPrompter\n\n    public convenience init() {\n           self.init {\n               return AirshipPermissionPrompter(\n                   permissionsManager: Airship.permissionsManager\n               )\n           }\n       }\n\n    \n    required init(permissionPrompter: @escaping @Sendable () -> any PermissionPrompter) {\n        self.permissionPrompter = permissionPrompter\n    }\n    \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .automation, .manualInvocation, .launchedFromPush,\n            .webViewInvocation,\n            .foregroundInteractiveButton, .foregroundPush:\n            return true\n        case .backgroundPush: fallthrough\n        case .backgroundInteractiveButton: fallthrough\n        @unknown default:\n            return false\n        }\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n            \n        let unwrapped = arguments.value.unWrap()\n        guard let arg = unwrapped else {\n            return nil\n        }\n                       \n        let data = try JSONSerialization.data(\n            withJSONObject: arg,\n            options: []\n        )\n        let args = try JSONDecoder().decode(Args.self, from: data)\n                \n        let result = await self.permissionPrompter().prompt(\n            permission: args.permission,\n            enableAirshipUsage: args.enableAirshipUsage ?? false,\n            fallbackSystemSettings: args.fallbackSystemSettings ?? false\n        )\n\n        let resultReceiver = arguments.metadata[\n            PromptPermissionAction.resultReceiverMetadataKey\n        ] as? PermissionResultReceiver\n\n        await resultReceiver?(args.permission, result.startStatus, result.endStatus)\n\n        return nil\n    }\n\n    internal struct Args: Decodable {\n        let enableAirshipUsage: Bool?\n        let fallbackSystemSettings: Bool?\n        let permission: AirshipPermission\n\n        enum CodingKeys: String, CodingKey {\n            case enableAirshipUsage = \"enable_airship_usage\"\n            case fallbackSystemSettings = \"fallback_system_settings\"\n            case permission = \"permission\"\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ProximityRegion.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// A proximity region defines an identifier, major and minor.\npublic class ProximityRegion {\n\n    let latitude: Double?\n    let longitude: Double?\n    let rssi: Double?\n    let proximityID: String\n    let major: Double\n    let minor: Double\n\n    /**\n     * Default constructor.\n     *\n     * - Parameter proximityID: The ID of the proximity region.\n     * - Parameter major: The major.\n     * - Parameter minor: The minor.\n     * - Parameter rssi: The rssi.\n     * - Parameter latitude: The latitude of the circular region's center point in degrees.\n     * - Parameter longitude: The longitude of the circular region's center point in degrees.\n     *\n     * - Returns: Proximity region object or `nil` if error occurs.\n     */\n    public init?(\n        proximityID: String,\n        major: Double,\n        minor: Double,\n        rssi: Double? = nil,\n        latitude: Double? = nil,\n        longitude: Double? = nil\n    ) {\n\n        if (latitude != nil || longitude != nil)\n            && (latitude == nil || longitude == nil)\n        {\n            AirshipLogger.error(\n                \"Invalid proximity region. Both lat and long must both be defined if one is provided.\"\n            )\n            return nil\n        }\n\n        if let latitude = latitude {\n            guard EventUtils.isValid(latitude: latitude) else {\n                return nil\n            }\n        }\n\n        if let longitude = longitude {\n            guard EventUtils.isValid(longitude: longitude) else {\n                return nil\n            }\n        }\n\n        if let rssi = rssi {\n            guard ProximityRegion.isValid(rssi: rssi) else {\n                return nil\n            }\n        }\n\n        guard ProximityRegion.isValid(proximityID: proximityID) else {\n            return nil\n        }\n\n        guard ProximityRegion.isValid(major: major) else {\n            return nil\n        }\n\n        guard ProximityRegion.isValid(minor: minor) else {\n            return nil\n        }\n\n        self.proximityID = proximityID\n        self.major = major\n        self.minor = minor\n        self.rssi = rssi\n        self.latitude = latitude\n        self.longitude = longitude\n    }\n\n    /**\n     * Factory method for creating a proximity region.\n     *\n     * - Parameter proximityID: The ID of the proximity region.\n     * - Parameter major: The major.\n     * - Parameter minor: The minor.\n     *\n     * - Returns: Proximity region object or `nil` if error occurs.\n     */\n    public class func proximityRegion(\n        proximityID: String,\n        major: Double,\n        minor: Double\n    ) -> ProximityRegion? {\n        return ProximityRegion(\n            proximityID: proximityID,\n            major: major,\n            minor: minor\n        )\n    }\n\n    /**\n     * Factory method for creating a proximity region.\n     *\n     * - Parameter proximityID: The ID of the proximity region.\n     * - Parameter major: The major.\n     * - Parameter minor: The minor.\n     * - Parameter rssi: The rssi.\n     *\n     * - Returns: Proximity region object or `nil` if error occurs.\n     */\n    public class func proximityRegion(\n        proximityID: String,\n        major: Double,\n        minor: Double,\n        rssi: Double\n    ) -> ProximityRegion? {\n        return ProximityRegion(\n            proximityID: proximityID,\n            major: major,\n            minor: minor,\n            rssi: rssi\n        )\n    }\n\n    /**\n     * Factory method for creating a proximity region.\n     *\n     * - Parameter proximityID: The ID of the proximity region.\n     * - Parameter major: The major.\n     * - Parameter minor: The minor.\n     * - Parameter latitude: The latitude of the circular region's center point in degrees.\n     * - Parameter longitude: The longitude of the circular region's center point in degrees.\n     *\n     * - Returns: Proximity region object or `nil` if error occurs.\n     */\n    public class func proximityRegion(\n        proximityID: String,\n        major: Double,\n        minor: Double,\n        latitude: Double,\n        longitude: Double\n    ) -> ProximityRegion? {\n        return ProximityRegion(\n            proximityID: proximityID,\n            major: major,\n            minor: minor,\n            latitude: latitude,\n            longitude: longitude\n        )\n    }\n\n    /**\n     * Factory method for creating a proximity region.\n     *\n     * - Parameter proximityID: The ID of the proximity region.\n     * - Parameter major: The major.\n     * - Parameter minor: The minor.\n     * - Parameter rssi: The rssi.\n     * - Parameter latitude: The latitude of the circular region's center point in degrees.\n     * - Parameter longitude: The longitude of the circular region's center point in degrees.\n     *\n     * - Returns: Proximity region object or `nil` if error occurs.\n     */\n    public class func proximityRegion(\n        proximityID: String,\n        major: Double,\n        minor: Double,\n        rssi: Double,\n        latitude: Double,\n        longitude: Double\n    ) -> ProximityRegion? {\n        return ProximityRegion(\n            proximityID: proximityID,\n            major: major,\n            minor: minor,\n            rssi: rssi,\n            latitude: latitude,\n            longitude: longitude\n        )\n    }\n\n    private class func isValid(proximityID: String) -> Bool {\n        guard proximityID.count > 0 && proximityID.count <= 255 else {\n            AirshipLogger.error(\n                \"Invalid proximityID \\(proximityID). Must be between 1 and 255 characters\"\n            )\n            return false\n        }\n        return true\n    }\n\n    private class func isValid(rssi: Double) -> Bool {\n        guard rssi >= -100 && rssi <= 100 else {\n            AirshipLogger.error(\n                \"Invalid RSSI \\(rssi). Must be between -100 and 100\"\n            )\n            return false\n        }\n        return true\n    }\n\n    private class func isValid(major: Double) -> Bool {\n        guard major >= 0 && major <= 65535 else {\n            AirshipLogger.error(\n                \"Invalid major \\(major). Must be between 0 and 65535\"\n            )\n            return false\n        }\n        return true\n    }\n\n    private class func isValid(minor: Double) -> Bool {\n        guard minor >= 0 && minor <= 65535 else {\n            AirshipLogger.error(\n                \"Invalid minor \\(minor). Must be between 0 and 65535\"\n            )\n            return false\n        }\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/PushNotificationDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(WatchKit)\npublic import WatchKit\n#endif\n\n#if canImport(UIKit) && !os(watchOS)\npublic import UIKit\n#endif\n\npublic import UserNotifications\n\n/// Protocol to be implemented by push notification clients. All methods are optional.\npublic protocol PushNotificationDelegate: AnyObject, Sendable {\n    /// Called when a notification is received in the foreground.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    @MainActor\n    func receivedForegroundNotification(_ userInfo: [AnyHashable: Any]) async\n#if os(watchOS)\n    /// Called when a notification is received in the background.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    @MainActor\n    func receivedBackgroundNotification(_ userInfo: [AnyHashable: Any]) async -> WKBackgroundFetchResult\n#elseif os(macOS)\n    /// Called when a notification is received in the background.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    @MainActor\n    func receivedBackgroundNotification(_ userInfo: [AnyHashable: Any]) async -> Void\n#else\n    /// Called when a notification is received in the background.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    @MainActor\n    func receivedBackgroundNotification(_ userInfo: [AnyHashable: Any]) async -> UIBackgroundFetchResult\n#endif\n#if !os(tvOS)\n    /// Called when a notification is received in the background or foreground and results in a user interaction.\n    /// User interactions can include launching the application from the push, or using an interactive control on the notification interface\n    /// such as a button or text field.\n    ///\n    /// - Parameters:\n    ///   - notificationResponse: UNNotificationResponse object representing the user's response\n    /// to the notification and the associated notification contents.\n    @MainActor\n    func receivedNotificationResponse(_ notificationResponse: UNNotificationResponse) async\n#endif\n    \n    /// Called when a notification has arrived in the foreground and is available for display.\n    ///\n    /// - Parameters:\n    ///   - options: The notification presentation options.\n    ///   - notification: The notification.\n    @MainActor\n    func extendPresentationOptions(\n        _ options: UNNotificationPresentationOptions,\n        notification: UNNotification\n    ) async -> UNNotificationPresentationOptions\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RadioInput.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct RadioInput: View {\n    private let info: ThomasViewInfo.RadioInput\n    private let constraints: ViewConstraints\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var radioInputState: RadioInputState\n    @EnvironmentObject private var thomasState: ThomasState\n\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    init(info: ThomasViewInfo.RadioInput, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .radioInput,\n            thomasState: thomasState\n        )\n    }\n    private var isOnBinding: Binding<Bool> {\n        return radioInputState.makeBinding(\n            identifier: nil,\n            reportingValue: info.properties.reportingValue,\n            attributeValue: info.properties.attributeValue\n        )\n    }\n\n    @ViewBuilder\n    var body: some View {\n        Toggle(isOn: self.isOnBinding.animation()) {}\n            .thomasToggleStyle(\n                self.info.properties.style,\n                constraints: self.constraints\n            )\n            .constraints(constraints)\n            .thomasCommon(self.info)\n            .accessible(\n                self.info.accessible,\n                associatedLabel: associatedLabel,\n                hideIfDescriptionIsMissing: false\n            )\n            .formElement()\n            .accessibilityRemoveTraits(.isSelected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RadioInputController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct RadioInputController: View {\n    private let info: ThomasViewInfo.RadioInputController\n    private let constraints: ViewConstraints\n\n    @EnvironmentObject private var environment: ThomasEnvironment\n\n    init(info: ThomasViewInfo.RadioInputController, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: constraints,\n            environment: environment\n        )\n        .id(info.properties.identifier)\n        .accessibilityElement(children: .contain)\n    }\n\n    @MainActor\n    struct Content: View {\n        private let info: ThomasViewInfo.RadioInputController\n        private let constraints: ViewConstraints\n\n        @Environment(\\.pageIdentifier) private var pageID\n        @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n        @EnvironmentObject private var formState: ThomasFormState\n        @EnvironmentObject private var thomasState: ThomasState\n        @ObservedObject private var radioInputState: RadioInputState\n        @EnvironmentObject private var validatableHelper: ValidatableHelper\n\n        @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n        private var associatedLabel: String? {\n            associatedLabelResolver?.labelFor(\n                identifier: info.properties.identifier,\n                viewType: .radioInputController,\n                thomasState: thomasState\n            )\n        }\n\n        init(\n            info: ThomasViewInfo.RadioInputController,\n            constraints: ViewConstraints,\n            environment: ThomasEnvironment\n        ) {\n            self.info = info\n            self.constraints = constraints\n\n            // Use the environment to create or retrieve the state in case the view\n            // stack changes and we lose our state.\n            let radioInputState = environment.retrieveState(identifier: info.properties.identifier) {\n                RadioInputState()\n            }\n\n            self._radioInputState = ObservedObject(wrappedValue: radioInputState)\n        }\n\n        var body: some View {\n            ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                .constraints(constraints)\n                .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n                .accessible(\n                    self.info.accessible,\n                    associatedLabel: associatedLabel,\n                    hideIfDescriptionIsMissing: false\n                )\n                .formElement()\n                .environmentObject(radioInputState)\n                .airshipOnChangeOf(self.radioInputState.selected) { incoming in\n                    updateFormState(selected: incoming)\n                }\n                .onAppear {\n                    updateFormState(selected: self.radioInputState.selected)\n                    if self.formState.validationMode == .onDemand {\n                        validatableHelper.subscribe(\n                            forIdentifier: info.properties.identifier,\n                            formState: formState,\n                            initialValue: radioInputState.selected,\n                            valueUpdates: radioInputState.$selected,\n                            validatables: info.validation\n                        ) { [weak thomasState, weak radioInputState] actions in\n                            guard let thomasState, let radioInputState else { return }\n                            thomasState.processStateActions(\n                                actions,\n                                formFieldValue: .radio(\n                                    radioInputState.selected?.reportingValue\n                                )\n                            )\n                        }\n                    }\n                }\n        }\n\n        private func checkValid(value: AirshipJSON?) -> Bool {\n            return value != nil || info.validation.isRequired != true\n        }\n\n        private func makeAttribute(\n            selected: RadioInputState.Selected?\n        ) -> [ThomasFormField.Attribute]? {\n            guard\n                let name = info.properties.attributeName,\n                let value = selected?.attributeValue\n            else {\n                return nil\n            }\n\n            return  [\n                ThomasFormField.Attribute(\n                    attributeName: name,\n                    attributeValue: value\n                )\n            ]\n        }\n\n        private func updateFormState(selected: RadioInputState.Selected?) {\n            let field: ThomasFormField = if checkValid(value: selected?.reportingValue) {\n                ThomasFormField.validField(\n                    identifier: self.info.properties.identifier,\n                    input: .radio(selected?.reportingValue),\n                    result: .init(\n                        value: .radio(selected?.reportingValue),\n                        attributes: makeAttribute(selected: selected)\n                    )\n                )\n            } else {\n                ThomasFormField.invalidField(\n                    identifier: self.info.properties.identifier,\n                    input: .radio(selected?.reportingValue)\n                )\n            }\n\n            self.formDataCollector.updateField(field, pageID: pageID)\n        }\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RadioInputState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n@MainActor\nclass RadioInputState: ObservableObject {\n\n    @Published\n    private(set) var selected: Selected?\n\n    func setSelected(\n        identifier: String?,\n        reportingValue: AirshipJSON,\n        attributeValue: ThomasAttributeValue?\n    ) {\n        let incoming = Selected(\n            identifier: identifier,\n            reportingValue: reportingValue,\n            attributeValue: attributeValue\n        )\n        if (incoming != self.selected) {\n            self.selected = incoming\n        }\n    }\n\n    struct Selected: ThomasSerializable, Hashable {\n        var identifier: String?\n        var reportingValue: AirshipJSON\n        var attributeValue: ThomasAttributeValue?\n    }\n}\n\nextension RadioInputState {\n    func makeBinding(\n        identifier: String?,\n        reportingValue: AirshipJSON,\n        attributeValue: ThomasAttributeValue?\n    ) -> Binding<Bool> {\n        return Binding<Bool>(\n            get: {\n                if let identifier {\n                    self.selected?.identifier == identifier\n                } else {\n                    self.selected?.reportingValue == reportingValue\n                }\n            },\n            set: {\n                if $0 {\n                    self.setSelected(\n                        identifier: identifier,\n                        reportingValue: reportingValue,\n                        attributeValue: attributeValue\n                    )\n                }\n            }\n        )\n    }\n}\n\n// MARK: - ThomasStateProvider\nextension RadioInputState: ThomasStateProvider {\n    typealias SnapshotType = Selected?\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return $selected\n            .removeDuplicates()\n            .map(\\.self)\n            .eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> SnapshotType {\n        return selected\n    }\n    \n    func restorePersistentState(_ state: SnapshotType) {\n        self.selected = state\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RadioInputToggleLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct RadioInputToggleLayout: View {\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var radioInputState: RadioInputState\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .radioInputToggleLayout,\n            thomasState: thomasState\n        )\n    }\n\n    private let info: ThomasViewInfo.RadioInputToggleLayout\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.RadioInputToggleLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var isOnBinding: Binding<Bool> {\n        return radioInputState.makeBinding(\n            identifier: info.properties.identifier,\n            reportingValue: info.properties.reportingValue,\n            attributeValue: info.properties.attributeValue\n        )\n    }\n\n    var body: some View {\n        ToggleLayout(\n            isOn: self.isOnBinding,\n            onToggleOn: self.info.properties.onToggleOn,\n            onToggleOff: self.info.properties.onToggleOff\n        ) {\n            ViewFactory.createView(\n                self.info.properties.view,\n                constraints: constraints\n            )\n        }\n        .constraints(self.constraints)\n        .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n        .accessible(\n            self.info.accessible,\n            associatedLabel: associatedLabel,\n            hideIfDescriptionIsMissing: false\n        )\n        .formElement()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RateAppAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS) && !os(macOS)\n\nimport Foundation\nimport StoreKit\n\n/// Links directly to app store review page or opens an app rating prompt.\n///\n/// This action is registered under the names rate_app_action and ^ra.\n///\n/// The rate app action requires your application to provide an itunes ID as an argument value, or have it\n/// set on the Airship Config `Config.itunesID` instance used for takeoff.\n///\n/// Expected argument values:\n/// - ``show_link_prompt``: Optional Boolean, true to show prompt, false to link to the app store.\n/// - ``itunes_id``: Optional String, the iTunes ID for the application to be rated.\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`, `ActionSituation.webViewInvocation`\n/// `ActionSituation.manualInvocation`, `ActionSituation.foregroundInteractiveButton`, and `ActionSituation.automation`\n///\n/// Result value: nil\npublic final class RateAppAction: AirshipAction, Sendable {\n    public static let defaultNames: [String] = [\"rate_app_action\", \"^ra\"]\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.situation != .foregroundPush\n    }\n\n    let itunesID: @Sendable () -> String?\n    let appRater: any AppRaterProtocol\n\n    init(\n        appRater: any AppRaterProtocol,\n        itunesID: @escaping @Sendable () -> String?\n    ) {\n        self.appRater = appRater\n        self.itunesID = itunesID\n    }\n\n    public convenience init() {\n        self.init(appRater: DefaultAppRater()) {\n            return Airship.config.airshipConfig.itunesID\n        }\n    }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .manualInvocation, .launchedFromPush, .foregroundPush,\n            .webViewInvocation, .foregroundInteractiveButton, .automation:\n            return true\n        case .backgroundPush: fallthrough\n        case .backgroundInteractiveButton: fallthrough\n        @unknown default: return false\n        }\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        var args: Args? = nil\n        if !arguments.value.isNull {\n            args = try arguments.value.decode()\n        }\n\n        if args?.showPrompt == true {\n            try await appRater.showPrompt()\n        } else {\n            guard let itunesID = args?.itunesID ?? self.itunesID() else {\n                throw AirshipErrors.error(\"Missing itunes ID\")\n            }\n            try await appRater.openStore(itunesID: itunesID)\n        }\n        return nil\n    }\n\n    private struct Args: Decodable {\n        let itunesID: String?\n        let showPrompt: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case itunesID = \"itunes_id\"\n            case showPrompt = \"show_link_prompt\"\n        }\n    }\n}\n\nprotocol AppRaterProtocol: Sendable {\n    func openStore(itunesID: String) async throws\n    func showPrompt() async throws\n}\n\nprivate struct DefaultAppRater: AppRaterProtocol {\n    @MainActor\n    func openStore(itunesID: String) async throws {\n        let urlString =\n        \"itms-apps://itunes.apple.com/app/id\\(itunesID)?action=write-review\"\n        \n        guard let url = URL(string: urlString) else {\n            throw AirshipErrors.error(\"Unable to generate URL\")\n        }\n        \n        guard await UIApplication.shared.open(url) else {\n            throw AirshipErrors.error(\"Failed to open url \\(url)\")\n        }\n    }\n    \n    @MainActor\n    func showPrompt() async throws {\n        guard let scene = self.findScene() else {\n            throw AirshipErrors.error(\n                \"Unable to find scene for rate app prompt\"\n            )\n        }\n        \n        AppStore.requestReview(in: scene)\n    }\n    \n    @MainActor\n    private func findScene() -> UIWindowScene? {\n        return try? AirshipSceneManager.shared.lastActiveScene\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RegionEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents the boundary crossing event type.\npublic enum AirshipBoundaryEvent: Int, Sendable {\n    /**\n     * Enter event\n     */\n\n    case enter = 1\n\n    /**\n     * Exit event\n     */\n    case exit = 2\n}\n\n/// A region event captures information regarding a region event for analytics.\npublic class RegionEvent {\n\n    public static let eventType: String = \"region_event\"\n\n    public static let regionIDKey: String = \"region_id\"\n    static let sourceKey: String = \"source\"\n    static let boundaryEventKey: String = \"action\"\n    static let boundaryEventEnterValue: String = \"enter\"\n    static let boundaryEventExitValue: String = \"exit\"\n    static let latitudeKey: String = \"latitude\"\n    static let longitudeKey: String = \"longitude\"\n    static let proximityRegionKey: String = \"proximity\"\n    static let proximityRegionIDKey: String = \"proximity_id\"\n    static let proximityRegionMajorKey: String = \"major\"\n    static let proximityRegionMinorKey: String = \"minor\"\n    static let proximityRegionRSSIKey: String = \"rssi\"\n    static let circularRegionKey: String = \"circular_region\"\n    static let circularRegionRadiusKey: String = \"radius\"\n\n    /**\n     * The region's identifier.\n     */\n    public let regionID: String\n\n    /**\n     * The source of the event.\n     */\n    public let source: String\n\n    /**\n     * The type of boundary event.\n     */\n    public let boundaryEvent: AirshipBoundaryEvent\n\n    /**\n     * A circular region with a radius, and latitude/longitude from its center.\n     */\n    public let circularRegion: CircularRegion?\n\n    /**\n     * A proximity region with an identifier, major and minor.\n     */\n    public let proximityRegion: ProximityRegion?\n\n    /**\n     * Default constructor.\n     *\n     * - Parameter regionID: The ID of the region.\n     * - Parameter source: The source of the event.\n     * - Parameter boundaryEvent: The type of boundary crossing event.\n     * - Parameter circularRegion: The circular region info.\n     * - Parameter proximityRegion: The proximity region info.\n     *\n     * - Returns: Region event object or `nil` if error occurs.\n     */\n    public init?(\n        regionID: String,\n        source: String,\n        boundaryEvent: AirshipBoundaryEvent,\n        circularRegion: CircularRegion? = nil,\n        proximityRegion: ProximityRegion? = nil\n    ) {\n\n        guard RegionEvent.isValid(regionID: regionID) else {\n            return nil\n        }\n\n        guard RegionEvent.isValid(source: source) else {\n            return nil\n        }\n\n        self.regionID = regionID\n        self.source = source\n        self.boundaryEvent = boundaryEvent\n        self.circularRegion = circularRegion\n        self.proximityRegion = proximityRegion\n    }\n\n    /**\n     * Factory method for creating a region event.\n     *\n     * - Parameter regionID: The ID of the region.\n     * - Parameter source: The source of the event.\n     * - Parameter boundaryEvent: The type of boundary crossing event.\n     *\n     * - Returns: Region event object or `nil` if error occurs.\n     */\n    public class func regionEvent(\n        regionID: String,\n        source: String,\n        boundaryEvent: AirshipBoundaryEvent\n    ) -> RegionEvent? {\n        return RegionEvent(\n            regionID: regionID,\n            source: source,\n            boundaryEvent: boundaryEvent\n        )\n    }\n\n    /**\n     * Factory method for creating a region event.\n     *\n     * - Parameter regionID: The ID of the region.\n     * - Parameter source: The source of the event.\n     * - Parameter boundaryEvent: The type of boundary crossing event.\n     * - Parameter circularRegion: The circular region info.\n     * - Parameter proximityRegion: The proximity region info.\n     *\n     * - Returns: Region event object or `nil` if error occurs.\n     */\n    public class func regionEvent(\n        regionID: String,\n        source: String,\n        boundaryEvent: AirshipBoundaryEvent,\n        circularRegion: CircularRegion?,\n        proximityRegion: ProximityRegion?\n    ) -> RegionEvent? {\n        return RegionEvent(\n            regionID: regionID,\n            source: source,\n            boundaryEvent: boundaryEvent,\n            circularRegion: circularRegion,\n            proximityRegion: proximityRegion\n        )\n    }\n\n    private class func isValid(regionID: String) -> Bool {\n        guard regionID.count >= 1 && regionID.count <= 255 else {\n            AirshipLogger.error(\n                \"Invalid region ID \\(regionID). Must be between 1 and 255 characters\"\n            )\n            return false\n        }\n        return true\n    }\n\n    private class func isValid(source: String) -> Bool {\n        guard source.count >= 1 && source.count <= 255 else {\n            AirshipLogger.error(\n                \"Invalid source ID \\(source). Must be between 1 and 255 characters\"\n            )\n            return false\n        }\n        return true\n    }\n\n    func eventBody(stringifyFields: Bool) throws -> AirshipJSON {\n        var dictionary: [String: Any] = [:]\n        dictionary[RegionEvent.sourceKey] = self.source\n        dictionary[RegionEvent.regionIDKey] = self.regionID\n\n        switch self.boundaryEvent {\n        case .enter:\n            dictionary[RegionEvent.boundaryEventKey] =\n                RegionEvent.boundaryEventEnterValue\n        case .exit:\n            dictionary[RegionEvent.boundaryEventKey] =\n                RegionEvent.boundaryEventExitValue\n        }\n\n        if let proximityRegion = self.proximityRegion {\n            var proximityData: [String: Any] = [:]\n            proximityData[RegionEvent.proximityRegionIDKey] =\n                proximityRegion.proximityID\n            proximityData[RegionEvent.proximityRegionMajorKey] =\n                proximityRegion.major\n            proximityData[RegionEvent.proximityRegionMinorKey] =\n                proximityRegion.minor\n            proximityData[RegionEvent.proximityRegionRSSIKey] =\n                proximityRegion.rssi\n\n            if proximityRegion.latitude != nil\n                && proximityRegion.longitude != nil\n            {\n                if stringifyFields {\n                    proximityData[RegionEvent.latitudeKey] = String(\n                        format: \"%.7f\",\n                        proximityRegion.latitude!\n                    )\n                    proximityData[RegionEvent.longitudeKey] = String(\n                        format: \"%.7f\",\n                        proximityRegion.longitude!\n                    )\n                } else {\n                    proximityData[RegionEvent.latitudeKey] =\n                        proximityRegion.latitude\n                    proximityData[RegionEvent.longitudeKey] =\n                        proximityRegion.longitude\n                }\n            }\n\n            dictionary[RegionEvent.proximityRegionKey] = proximityData\n        }\n\n        if let circularRegion = self.circularRegion {\n            var circularData: [String: Any] = [:]\n            if stringifyFields {\n                circularData[RegionEvent.circularRegionRadiusKey] = String(\n                    format: \"%.1f\",\n                    circularRegion.radius\n                )\n                circularData[RegionEvent.latitudeKey] = String(\n                    format: \"%.7f\",\n                    circularRegion.latitude\n                )\n                circularData[RegionEvent.longitudeKey] = String(\n                    format: \"%.7f\",\n                    circularRegion.longitude\n                )\n            } else {\n                circularData[RegionEvent.circularRegionRadiusKey] =\n                    circularRegion.radius\n                circularData[RegionEvent.latitudeKey] = circularRegion.latitude\n                circularData[RegionEvent.longitudeKey] =\n                    circularRegion.longitude\n            }\n            dictionary[RegionEvent.circularRegionKey] = circularData\n        }\n\n        return try AirshipJSON.wrap(dictionary)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RegistrationDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\npublic import UserNotifications\n\n/// Implement this protocol and add as a Push.registrationDelegate to receive\n/// registration success and failure callbacks.\n///\npublic protocol RegistrationDelegate: AnyObject {\n    #if !os(tvOS)\n    /// Called when APNS registration completes.\n    ///\n    /// - Parameters:\n    ///   - authorizedSettings: The settings that were authorized at the time of registration.\n    ///   - categories: Set of the categories that were most recently registered.\n    ///   - status: The authorization status.\n    @MainActor\n    func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            AirshipAuthorizedNotificationSettings,\n        categories: Set<UNNotificationCategory>,\n        status: UNAuthorizationStatus\n    )\n    #endif\n\n    /// Called when APNS registration completes.\n    ///\n    /// - Parameters:\n    ///   - authorizedSettings: The settings that were authorized at the time of registration.\n    ///   - status: The authorization status.\n    @MainActor\n    func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            AirshipAuthorizedNotificationSettings,\n        status: UNAuthorizationStatus\n    )\n\n    /// Called when notification authentication changes with the new authorized settings.\n    ///\n    /// - Parameter authorizedSettings: AirshipAuthorizedNotificationSettings The newly changed authorized settings.\n    @MainActor\n    func notificationAuthorizedSettingsDidChange(\n        _ authorizedSettings: AirshipAuthorizedNotificationSettings\n    )\n\n    /// Called when the UIApplicationDelegate's application:didRegisterForRemoteNotificationsWithDeviceToken:\n    /// delegate method is called.\n    ///\n    /// - Parameter deviceToken: The APNS device token.\n    @MainActor\n    func apnsRegistrationSucceeded(\n        withDeviceToken deviceToken: Data\n    )\n\n    /// Called when the UIApplicationDelegate's application:didFailToRegisterForRemoteNotificationsWithError:\n    /// delegate method is called.\n    ///\n    /// - Parameter error: An NSError object that encapsulates information why registration did not succeed.\n    @MainActor\n    func apnsRegistrationFailedWithError(_ error: any Error)\n}\n\npublic extension RegistrationDelegate {\n       \n    #if !os(tvOS)\n    func notificationRegistrationFinished(withAuthorizedSettings authorizedSettings: AirshipAuthorizedNotificationSettings, categories: Set<UNNotificationCategory>, status: UNAuthorizationStatus) {\n    }\n    #endif\n    \n    func notificationRegistrationFinished(withAuthorizedSettings authorizedSettings: AirshipAuthorizedNotificationSettings, status: UNAuthorizationStatus) {}\n    \n    func notificationAuthorizedSettingsDidChange(_ authorizedSettings: AirshipAuthorizedNotificationSettings) {}\n    \n    func apnsRegistrationSucceeded(withDeviceToken deviceToken: Data) {}\n    \n    func apnsRegistrationFailedWithError(_ error: any Error) {}\n    \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct RemoteConfig: Codable, Equatable, Sendable {\n\n    let airshipConfig: AirshipConfig?\n    let meteredUsageConfig: MeteredUsageConfig?\n    let fetchContactRemoteData: Bool?\n    let contactConfig: ContactConfig?\n    let disabledFeatures: AirshipFeature?\n    public let iaaConfig: IAAConfig?\n\n    var remoteDataRefreshInterval: TimeInterval? {\n        return remoteDataRefreshIntervalMilliseconds?.timeInterval\n    }\n\n    let remoteDataRefreshIntervalMilliseconds: Int64?\n\n    init(\n        airshipConfig: AirshipConfig? = nil,\n        meteredUsageConfig: MeteredUsageConfig? = nil,\n        fetchContactRemoteData: Bool? = nil,\n        contactConfig: ContactConfig? = nil,\n        disabledFeatures: AirshipFeature? = nil,\n        remoteDataRefreshIntervalMilliseconds: Int64? = nil,\n        iaaConfig: IAAConfig? = nil\n    ) {\n        self.airshipConfig = airshipConfig\n        self.meteredUsageConfig = meteredUsageConfig\n        self.fetchContactRemoteData = fetchContactRemoteData\n        self.contactConfig = contactConfig\n        self.disabledFeatures = disabledFeatures\n        self.remoteDataRefreshIntervalMilliseconds = remoteDataRefreshIntervalMilliseconds\n        self.iaaConfig = iaaConfig\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case airshipConfig = \"airship_config\"\n        case meteredUsageConfig = \"metered_usage\"\n        case fetchContactRemoteData = \"fetch_contact_remote_data\"\n        case contactConfig = \"contact_config\"\n        case disabledFeatures = \"disabled_features\"\n        case remoteDataRefreshIntervalMilliseconds = \"remote_data_refresh_interval\"\n        case iaaConfig = \"in_app_config\"\n    }\n\n    struct ContactConfig: Codable, Equatable, Sendable {\n        let foregroundIntervalMilliseconds: Int64?\n        let channelRegistrationMaxResolveAgeMilliseconds: Int64?\n\n        var foregroundInterval: TimeInterval? {\n            return foregroundIntervalMilliseconds?.timeInterval\n        }\n\n        var channelRegistrationMaxResolveAge: TimeInterval? {\n            return channelRegistrationMaxResolveAgeMilliseconds?.timeInterval\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case foregroundIntervalMilliseconds = \"foreground_resolve_interval_ms\"\n            case channelRegistrationMaxResolveAgeMilliseconds = \"max_cra_resolve_age_ms\"\n        }\n    }\n\n    struct MeteredUsageConfig: Codable, Equatable, Sendable {\n        let isEnabled: Bool?\n        let initialDelayMilliseconds: Int64?\n        let intervalMilliseconds: Int64?\n\n        var intialDelay: TimeInterval? {\n            return initialDelayMilliseconds?.timeInterval\n        }\n\n        var interval: TimeInterval? {\n            return intervalMilliseconds?.timeInterval\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case isEnabled = \"enabled\"\n            case initialDelayMilliseconds = \"initial_delay_ms\"\n            case intervalMilliseconds = \"interval_ms\"\n        }\n    }\n\n    struct AirshipConfig: Codable, Equatable, Sendable {\n        public let remoteDataURL: String?\n        public let deviceAPIURL: String?\n        public let analyticsURL: String?\n        public let meteredUsageURL: String?\n\n        enum CodingKeys: String, CodingKey {\n            case remoteDataURL = \"remote_data_url\"\n            case deviceAPIURL = \"device_api_url\"\n            case analyticsURL = \"analytics_url\"\n            case meteredUsageURL = \"metered_usage_url\"\n        }\n    }\n    \n    public struct IAAConfig: Codable, Equatable, Sendable {\n        public let retryingQueue: RetryingQueueConfig?\n        public let additionalAudienceConfig: AdditionalAudienceCheckConfig?\n\n        enum CodingKeys: String, CodingKey {\n            case retryingQueue = \"queue\"\n            case additionalAudienceConfig = \"additional_audience_check\"\n        }\n    }\n    \n    public struct RetryingQueueConfig: Codable, Equatable, Sendable {\n        public let maxConcurrentOperations: UInt?\n        public let maxPendingResults: UInt?\n        public let initialBackoff: TimeInterval?\n        public let maxBackOff: TimeInterval?\n\n        enum CodingKeys: String, CodingKey {\n            case maxConcurrentOperations = \"max_concurrent_operations\"\n            case maxPendingResults = \"max_pending_results\"\n            case initialBackoff = \"initial_back_off_seconds\"\n            case maxBackOff = \"max_back_off_seconds\"\n        }\n    }\n    \n    public struct AdditionalAudienceCheckConfig: Codable, Equatable, Sendable {\n        public let isEnabled: Bool\n        public let context: AirshipJSON?\n        public let url: String?\n        \n        enum CodingKeys: String, CodingKey {\n            case isEnabled = \"enabled\"\n            case context\n            case url\n        }\n        \n        public init(isEnabled: Bool, context: AirshipJSON?, url: String?) {\n            self.isEnabled = isEnabled\n            self.context = context\n            self.url = url\n        }\n        \n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            isEnabled = try container.decodeIfPresent(Bool.self, forKey: .isEnabled) ?? false\n            context = try container.decodeIfPresent(AirshipJSON.self, forKey: .context)\n            url = try container.decodeIfPresent(String.self, forKey: .url)\n        }\n    }\n}\n\nfileprivate extension Int64 {\n    var timeInterval: TimeInterval {\n        Double(self)/1000\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteConfigCache.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// NOTE: For internal use only. :nodoc:\nfinal class RemoteConfigCache: Sendable {\n    private static let dataStoreKey: String = \"com.urbanairship.config.remote_config_cache\"\n    private let dataStore: PreferenceDataStore\n    private let _remoteConfig: AirshipAtomicValue<RemoteConfig>\n\n    var remoteConfig: RemoteConfig {\n        get {\n            return _remoteConfig.value\n        }\n        set {\n            _remoteConfig.value = newValue\n            do {\n                try self.dataStore.setCodable(\n                    newValue,\n                    forKey: RemoteConfigCache.dataStoreKey\n                )\n            } catch {\n                AirshipLogger.error(\"Failed to store remote config cache \\(error)\")\n            }\n        }\n    }\n\n    init(dataStore: PreferenceDataStore) {\n        self.dataStore = dataStore\n        \n        var fromStore: RemoteConfig? = nil\n        do {\n            fromStore = try dataStore.codable(\n                forKey: RemoteConfigCache.dataStoreKey\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to read remote config cache \\(error)\")\n        }\n        \n        self._remoteConfig = AirshipAtomicValue(fromStore ?? RemoteConfig())\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteConfigManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nfinal class RemoteConfigManager: @unchecked Sendable {\n\n    private var subscription: AnyCancellable?\n    private let remoteData: any RemoteDataProtocol\n    private let privacyManager: any AirshipPrivacyManager\n    private let notificationCenter: AirshipNotificationCenter\n\n    private let appVersion: String\n    private let lock: AirshipLock = AirshipLock()\n    private let config: RuntimeConfig\n\n    init(\n        config: RuntimeConfig,\n        remoteData: any RemoteDataProtocol,\n        privacyManager: any AirshipPrivacyManager,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        appVersion: String = AirshipUtils.bundleShortVersionString() ?? \"\"\n    ) {\n        self.config = config\n        self.remoteData = remoteData\n        self.privacyManager = privacyManager\n        self.notificationCenter = notificationCenter\n        self.appVersion = appVersion\n    }\n\n    func airshipReady() {\n        self.notificationCenter.addObserver(\n            self,\n            selector: #selector(updateRemoteConfigSubscription),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n\n        self.updateRemoteConfigSubscription()\n    }\n\n    func processRemoteConfig(_ payloads: [RemoteDataPayload]?) {\n        var combinedData: [String: Any] = [:]\n\n        // Combine the data, overriding the common config (first) with\n        // the platform config (second).\n        payloads?.forEach { payload in\n            combinedData.merge((payload.data.object ?? [:])) { (_, new) in new }\n        }\n\n        //Remote config\n        applyRemoteConfig(combinedData)\n    }\n\n    private func applyRemoteConfig(_ data: [String: Any]) {\n        do {\n            let remoteConfig: RemoteConfig = try AirshipJSON.wrap(data).decode()\n            Task { @MainActor [config] in\n                config.updateRemoteConfig(remoteConfig)\n            }\n        } catch {\n            AirshipLogger.error(\"Invalid remote config \\(error)\")\n            return\n        }\n    }\n\n    @objc\n    private func updateRemoteConfigSubscription() {\n        lock.sync {\n            if self.privacyManager.isAnyFeatureEnabled() {\n                if self.subscription == nil {\n                    self.subscription = self.remoteData.publisher(\n                        types: [\"app_config\", \"app_config:ios\"]\n                    )\n                    .removeDuplicates()\n                    .sink { [weak self] remoteConfig in\n                        self?.processRemoteConfig(remoteConfig)\n                    }\n                }\n            } else {\n                self.subscription?.cancel()\n                self.subscription = nil\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteData.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@preconcurrency\nimport Combine\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\nimport UserNotifications\n\n/// NOTE: For internal use only. :nodoc:\nfinal class RemoteData: AirshipComponent, RemoteDataProtocol {\n    fileprivate enum RefreshStatus: Sendable {\n        case none\n        case success\n        case failed\n    }\n\n    static let refreshTaskID: String = \"RemoteData.refresh\"\n    static let defaultRefreshInterval: TimeInterval = 10\n    static let refreshRemoteDataPushPayloadKey: String = \"com.urbanairship.remote-data.update\"\n\n    // Datastore keys\n    private static let randomValueKey: String = \"remotedata.randomValue\"\n    private static let changeTokenKey: String = \"remotedata.CHANGE_TOKEN\"\n\n    private let config: RuntimeConfig\n    private let providers: [any RemoteDataProviderProtocol]\n    private let dataStore: PreferenceDataStore\n    private let date: any AirshipDateProtocol\n    private let localeManager: any AirshipLocaleManager\n    private let workManager: any AirshipWorkManagerProtocol\n    private let privacyManager: any InternalAirshipPrivacyManager\n    private let appVersion: String\n    private let statusUpdates: AirshipAsyncChannel<[RemoteDataSource: RemoteDataSourceStatus]> = AirshipAsyncChannel()\n    private let currentSourceStatus: AirshipAtomicValue<[RemoteDataSource: RemoteDataSourceStatus]> = .init([:])\n\n    private let refreshResultSubject: PassthroughSubject<(source: RemoteDataSource, result: RemoteDataRefreshResult), Never> = PassthroughSubject<(source: RemoteDataSource, result: RemoteDataRefreshResult), Never>()\n    private let refreshStatusSubjectMap: [RemoteDataSource: CurrentValueSubject<RefreshStatus, Never>]\n\n    private let lastActiveRefreshDate: AirshipMainActorValue<Date> = AirshipMainActorValue(Date.distantPast)\n    private let changeTokenLock: AirshipLock = AirshipLock()\n    private let contactSubscription: AirshipUnsafeSendableWrapper<AnyCancellable?> = AirshipUnsafeSendableWrapper(nil)\n    let serialQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n\n    private var randomValue: Int {\n        if let value = self.dataStore.object(forKey: RemoteData.randomValueKey) as? Int {\n            return value\n        }\n\n        let randomValue = Int.random(in: 0...9999)\n        self.dataStore.setObject(randomValue, forKey: RemoteData.randomValueKey)\n        return randomValue\n    }\n\n    @MainActor\n    convenience init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        localeManager: any AirshipLocaleManager,\n        privacyManager: any InternalAirshipPrivacyManager,\n        contact: any InternalAirshipContact\n    ) {\n        let client = RemoteDataAPIClient(config: config)\n        self.init(\n            config: config,\n            dataStore: dataStore,\n            localeManager: localeManager,\n            privacyManager: privacyManager,\n            contact: contact,\n            providers: [\n\n                // App\n                RemoteDataProvider(\n                    dataStore: dataStore,\n                    delegate: AppRemoteDataProviderDelegate(\n                        config: config,\n                        apiClient: client\n                    )\n                ),\n\n                // Contact\n                RemoteDataProvider(\n                    dataStore: dataStore,\n                    delegate: ContactRemoteDataProviderDelegate(\n                        config: config,\n                        apiClient: client,\n                        contact: contact\n                    ),\n                    defaultEnabled: false\n                )\n            ]\n        )\n    }\n\n    @MainActor\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        localeManager: any AirshipLocaleManager,\n        privacyManager: any InternalAirshipPrivacyManager,\n        contact: any InternalAirshipContact,\n        providers: [any RemoteDataProviderProtocol],\n        workManager: any AirshipWorkManagerProtocol = AirshipWorkManager.shared,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        appVersion: String = AirshipUtils.bundleShortVersionString() ?? \"\"\n\n    ) {\n        self.config = config\n        self.dataStore = dataStore\n        self.localeManager = localeManager\n        self.privacyManager = privacyManager\n        self.providers = providers\n        self.workManager = workManager\n        self.date = date\n        self.appVersion = appVersion\n\n        self.refreshStatusSubjectMap = self.providers.reduce(\n            into: [RemoteDataSource: CurrentValueSubject<RefreshStatus, Never>]()\n        ) {\n            $0[$1.source] = CurrentValueSubject(.none)\n        }\n\n        self.contactSubscription.value = contact.contactIDUpdates\n            .map { $0.contactID }\n            .removeDuplicates()\n            .sink { [weak self] _ in\n                self?.enqueueRefreshTask()\n            }\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(enqueueRefreshTask),\n            name: AirshipNotifications.LocaleUpdated.name,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(applicationDidForeground),\n            name: AppStateTracker.didTransitionToForeground,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(enqueueRefreshTask),\n            name: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil\n        )\n        \n        self.workManager.registerWorker(\n            RemoteData.refreshTaskID\n        ) { [weak self] _ in\n            return try await self?.handleRefreshTask() ?? .success\n        }\n\n        onConfigUpdated(config.remoteConfig, isUpdate: false)\n        config.addRemoteConfigListener(notifyCurrent: false) { [weak self] _, new in\n            self?.onConfigUpdated(new, isUpdate: true)\n        }\n        updateChangeToken()\n    }\n\n    private func onConfigUpdated(_ remoteConfig: RemoteConfig?, isUpdate: Bool) {\n        self.serialQueue.enqueue { [providers] in\n            let provider = providers.first { $0.source == .contact }\n            let updated = await provider?.setEnabled(remoteConfig?.fetchContactRemoteData ?? false)\n            if (isUpdate || updated == true) {\n                await self.enqueueRefreshTask()\n            }\n        }\n    }\n\n    public func status(\n        source: RemoteDataSource\n    ) async -> RemoteDataSourceStatus {\n        let result = await sourceStatus(source: source)\n        await recordStatusFor([source])\n        return result\n    }\n    \n    private func sourceStatus(\n        source: RemoteDataSource\n    ) async -> RemoteDataSourceStatus {\n        return if let provider = providers.first(where: { $0.source == source }) {\n            await provider.status(\n                changeToken: self.changeToken,\n                locale: self.localeManager.currentLocale,\n                randomeValue: self.randomValue\n            )\n        } else {\n            .outOfDate\n        }\n    }\n    \n    private func recordStatusFor(_ sources: [RemoteDataSource]) async {\n        var updates: [RemoteDataSource: RemoteDataSourceStatus] = self.currentSourceStatus.value\n        \n        for source in sources {\n            updates[source] = await sourceStatus(source: source)\n        }\n        \n        guard updates != self.currentSourceStatus.value else {\n            return\n        }\n        \n        self.currentSourceStatus.update(onModify: { _ in updates })\n        await statusUpdates.send(updates)\n    }\n\n    public func isCurrent(\n        remoteDataInfo: RemoteDataInfo\n    ) async -> Bool {\n        let locale = localeManager.currentLocale\n        for provider in self.providers {\n            if (provider.source == remoteDataInfo.source) {\n                return await provider.isCurrent(\n                    locale: locale,\n                    randomeValue: randomValue,\n                    remoteDataInfo: remoteDataInfo\n                )\n            }\n        }\n\n        AirshipLogger.error(\"No remote data handler for \\(remoteDataInfo.source)\")\n        return false\n    }\n\n    public func notifyOutdated(remoteDataInfo: RemoteDataInfo) async {\n        for provider in self.providers {\n            if (provider.source == remoteDataInfo.source) {\n                if (await provider.notifyOutdated(remoteDataInfo: remoteDataInfo)) {\n                    await enqueueRefreshTask()\n                }\n                return\n            }\n        }\n    }\n    \n    @MainActor\n    public func statusUpdates<T:Sendable>(\n        sources: [RemoteDataSource],\n        map: @escaping (@Sendable (_ statuses: [RemoteDataSource: RemoteDataSourceStatus]) -> T)\n    ) async -> AsyncStream<T> {\n        \n        return AsyncStream { [weak self] continuation in\n            let task = Task { [weak self] in\n                \n                await self?.recordStatusFor(sources)\n                \n                let isInSource: ((RemoteDataSource, RemoteDataSourceStatus)) -> Bool = {\n                    sources.contains($0.0)\n                }\n                \n                let current = self?.currentSourceStatus.value.filter(isInSource) ?? [:]\n                let mappedStatuses = map(current)\n                continuation.yield(mappedStatuses)\n                \n                if let updates = await self?.statusUpdates.makeStream() {\n                    for await item in updates {\n                        let filtered = item.filter(isInSource)\n                        continuation.yield(map(filtered))\n                    }\n                }\n                \n                continuation.finish()\n            }\n            \n            continuation.onTermination = { _ in\n                task.cancel()\n            }\n        }\n    }\n\n    @MainActor\n    public func airshipReady() {\n        self.enqueueRefreshTask()\n    }\n\n    var refreshInterval: TimeInterval {\n        return self.config.remoteConfig.remoteDataRefreshInterval ?? Self.defaultRefreshInterval\n    }\n\n    @objc\n    @MainActor\n    private func applicationDidForeground() {\n        let now = self.date.now\n\n        let nextUpdate = self.lastActiveRefreshDate.value.advanced(by: \n            self.refreshInterval\n        )\n\n        if now >= nextUpdate {\n            updateChangeToken()\n            self.enqueueRefreshTask()\n            self.lastActiveRefreshDate.set(now)\n        }\n    }\n\n    @objc\n    @MainActor\n    private func enqueueRefreshTask() {\n        self.refreshStatusSubjectMap.values.forEach { subject in\n            subject.sendMainActor(.none)\n        }\n        self.workManager.dispatchWorkRequest(\n            AirshipWorkRequest(\n                workID: RemoteData.refreshTaskID,\n                initialDelay: 0,\n                requiresNetwork: true,\n                conflictPolicy: .replace\n            )\n        )\n    }\n\n    private func updateChangeToken() {\n        self.changeTokenLock.sync {\n            self.dataStore.setObject(UUID().uuidString, forKey: RemoteData.changeTokenKey)\n        }\n    }\n\n    /// The change token is just an easy way to know when we need to require an actual update vs checking the remote-data info if it has\n    /// changed. We will create a new token on foreground (if its passed the refresh interval) or background push.\n    private var changeToken: String {\n        var token: String!\n        self.changeTokenLock.sync {\n            let fromStore = self.dataStore.string(forKey: RemoteData.changeTokenKey)\n            if let fromStore = fromStore {\n                token = fromStore\n            } else {\n                token = UUID().uuidString\n                self.dataStore.setObject(token, forKey: RemoteData.changeTokenKey)\n            }\n        }\n\n        return token + self.appVersion\n    }\n    \n    private func handleRefreshTask() async throws -> AirshipWorkResult {\n        guard self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: true) else {\n            for provider in providers {\n                await refreshResultSubject.sendMainActor((provider.source, .skipped))\n                await refreshStatusSubjectMap[provider.source]?.sendMainActor(.success)\n            }\n            return .success\n        }\n\n        let changeToken = self.changeToken\n        let locale = self.localeManager.currentLocale\n        let randomValue = self.randomValue\n\n        let success = await withTaskGroup(\n            of: (RemoteDataSource, RemoteDataRefreshResult).self\n        ) { [providers, refreshResultSubject, refreshStatusSubjectMap] group in\n            for provider in providers {\n                group.addTask{\n                    let result = await provider.refresh(\n                        changeToken: changeToken,\n                        locale: locale,\n                        randomeValue: randomValue\n                    )\n                    return (provider.source, result)\n                }\n            }\n\n            var success: Bool = true\n            for await (source, result) in group {\n                await refreshResultSubject.sendMainActor((source, result))\n                if (result == .failed) {\n                    success = false\n                    await refreshStatusSubjectMap[source]?.sendMainActor(.failed)\n                } else {\n                    await refreshStatusSubjectMap[source]?.sendMainActor(.success)\n                }\n            }\n\n            return success\n        }\n        \n        await recordStatusFor(providers.map({ $0.source }))\n\n        return success ? .success : .failure\n    }\n\n    public func forceRefresh() async {\n        self.updateChangeToken()\n        await enqueueRefreshTask()\n        let sources = self.providers.map { $0.source }\n        for source in sources {\n            await self.waitRefreshAttempt(source: source)\n        }\n    }\n\n    public func waitRefresh(source: RemoteDataSource) async {\n        await waitRefresh(source: source, maxTime: nil)\n    }\n\n    public func waitRefresh(\n        source: RemoteDataSource,\n        maxTime: TimeInterval?\n    ) async {\n        AirshipLogger.trace(\"Waiting for remote data to refresh succesfully \\(source)\")\n        await waitRefreshStatus(source: source, maxTime: maxTime) { status in\n            status == .success\n        }\n    }\n\n    public func waitRefreshAttempt(source: RemoteDataSource) async {\n        await waitRefreshAttempt(source: source, maxTime: nil)\n    }\n\n    public func waitRefreshAttempt(\n        source: RemoteDataSource,\n        maxTime: TimeInterval?\n    ) async {\n        AirshipLogger.trace(\"Waiting for remote refresh attempt \\(source)\")\n        await waitRefreshStatus(source: source, maxTime: maxTime) { status in\n            status != .none\n        }\n    }\n\n    private func waitRefreshStatus(\n        source: RemoteDataSource,\n        maxTime: TimeInterval?,\n        statusPredicate: @escaping @Sendable (RefreshStatus) -> Bool\n    ) async {\n        guard let subject = self.refreshStatusSubjectMap[source] else {\n            return\n        }\n\n        let result: RefreshStatus = await withUnsafeContinuation { continuation in\n            var cancellable: AnyCancellable?\n\n            var publisher: AnyPublisher<RefreshStatus, Never> = subject.first(where: statusPredicate).eraseToAnyPublisher()\n\n            if let maxTime = maxTime, maxTime > 0.0 {\n                publisher = Publishers.Merge(\n                    Just(.none).delay(\n                        for: .seconds(maxTime),\n                        scheduler: RunLoop.main\n                    ),\n                    publisher\n                ).eraseToAnyPublisher()\n            }\n\n            cancellable = publisher.first()\n                .sink { result in\n                    continuation.resume(returning: result)\n                    cancellable?.cancel()\n                }\n        }\n\n        AirshipLogger.trace(\"Remote data refresh: \\(source), status: \\(result)\")\n    }\n    \n    public func payloads(types: [String]) async -> [RemoteDataPayload] {\n        var payloads: [RemoteDataPayload] = []\n        for provider in self.providers {\n            payloads += await provider.payloads(types: types)\n        }\n        return payloads.sortedByType(types)\n    }\n\n    private func payloadsFuture(types: [String]) -> Future<[RemoteDataPayload], Never> {\n        return Future { promise in\n            let wrapped = SendablePromise(promise: promise)\n            Task { @MainActor in\n                let payloads = await self.payloads(types: types)\n                wrapped.promise(.success(payloads))\n            }\n        }\n    }\n\n    public func publisher(\n        types: [String]\n    ) -> AnyPublisher<[RemoteDataPayload], Never> {\n        // We use the refresh subject to know when to update\n        // the current values by listening for a `newData` result\n        return self.refreshResultSubject\n            .filter { result in\n                result.result == .newData\n            }\n            .flatMap { _ in\n                // Fetch data\n                self.payloadsFuture(types: types)\n            }\n            .prepend(\n                // Prepend current data\n                self.payloadsFuture(types: types)\n            )\n            .eraseToAnyPublisher()\n    }\n}\n\nextension RemoteData: AirshipPushableComponent {\n    public func receivedRemoteNotification(\n        _ notification: AirshipJSON\n    ) async -> UABackgroundFetchResult {\n\n        guard\n            let userInfo = notification.unWrap() as? [AnyHashable: Any],\n            userInfo[RemoteData.refreshRemoteDataPushPayloadKey] != nil\n        else {\n            return .noData\n        }\n        \n        self.updateChangeToken()\n        self.enqueueRefreshTask()\n        return .newData\n    }\n\n\n#if !os(tvOS)\n    public func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        // no-op\n    }\n#endif\n}\n\n\n\n\nextension Sequence where Iterator.Element == RemoteDataPayload {\n    func sortedByType(_ types: [String]) -> [Iterator.Element] {\n        return self.sorted { first, second in\n            let firstIndex = types.firstIndex(of: first.type) ?? 0\n            let secondIndex = types.firstIndex(of: second.type) ?? 0\n            return firstIndex < secondIndex\n        }\n    }\n}\n\nfileprivate struct SendablePromise<O, E>: @unchecked Sendable where E : Error {\n    let promise: Future<O,E>.Promise\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol RemoteDataAPIClientProtocol: Sendable {\n    func fetchRemoteData(\n        url: URL,\n        auth: AirshipRequestAuth,\n        lastModified: String?,\n        remoteDataInfoBlock: @Sendable @escaping (String?) throws -> RemoteDataInfo\n    ) async throws -> AirshipHTTPResponse<RemoteDataResult>\n}\n\nfinal class RemoteDataAPIClient: RemoteDataAPIClientProtocol {\n    private let session: any AirshipRequestSession\n    private let config: RuntimeConfig\n\n    private var decoder: JSONDecoder {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .custom({ (decoder) -> Date in\n            let container = try decoder.singleValueContainer()\n            let dateStr = try container.decode(String.self)\n\n            guard let date = AirshipDateFormatter.date(fromISOString: dateStr) else {\n                throw AirshipErrors.error(\"Invalid date \\(dateStr)\")\n            }\n            return date\n        })\n        return decoder\n    }\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(config: config, session: config.requestSession)\n    }\n\n    func fetchRemoteData(\n        url: URL,\n        auth: AirshipRequestAuth,\n        lastModified: String?,\n        remoteDataInfoBlock: @Sendable @escaping (String?) throws -> RemoteDataInfo\n    ) async throws -> AirshipHTTPResponse<RemoteDataResult> {\n        var headers: [String: String] = [\n            \"X-UA-Appkey\": self.config.appCredentials.appKey,\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ]\n\n        if let lastModified = lastModified {\n            headers[\"If-Modified-Since\"] = lastModified\n        }\n\n        let request = AirshipRequest(\n            url: url,\n            headers: headers,\n            method: \"GET\",\n            auth: auth\n        )\n\n        AirshipLogger.debug(\"Request to update remote data: \\(request)\")\n\n        return try await self.session.performHTTPRequest(request) { data, response in\n            \n            AirshipLogger.debug(\"Fetching remote data finished with response: \\(response)\")\n            \n            guard response.statusCode == 200, let data = data else {\n                return nil\n            }\n\n            let remoteDataResponse = try self.decoder.decode(RemoteDataResponse.self, from: data)\n            let remoteDataInfo = try remoteDataInfoBlock(response.value(forHTTPHeaderField: \"Last-Modified\"))\n\n            let payloads = (remoteDataResponse.payloads ?? [])\n                .map { payload in\n                    RemoteDataPayload(\n                        type: payload.type,\n                        timestamp: payload.timestamp,\n                        data: payload.data,\n                        remoteDataInfo: remoteDataInfo\n                    )\n                }\n            return RemoteDataResult(payloads: payloads, remoteDataInfo: remoteDataInfo)\n        }\n    }\n}\n\nstruct RemoteDataResult: Equatable {\n    let payloads: [RemoteDataPayload]\n    let remoteDataInfo: RemoteDataInfo\n}\n\nfileprivate struct RemoteDataResponse: Codable {\n    let payloads: [Payload]?\n    \n    struct Payload: Codable {\n        let type: String\n        let timestamp: Date\n        let data: AirshipJSON\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataInfo.swift",
    "content": "public import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct RemoteDataInfo: Sendable, Codable, Equatable, Hashable {\n    public let url: URL\n    public let lastModifiedTime: String?\n    public let source: RemoteDataSource\n    public let contactID: String?\n\n    public init(url: URL, lastModifiedTime: String?, source: RemoteDataSource, contactID: String? = nil) {\n        self.url = url\n        self.lastModifiedTime = lastModifiedTime\n        self.source = source\n        self.contactID = contactID\n    }\n\n    static func fromJSON(data: Data) throws -> RemoteDataInfo {\n        try JSONDecoder().decode(RemoteDataInfo.self, from: data)\n    }\n\n    func toEncodedJSONData() throws -> Data {\n        try JSONEncoder().encode(self)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataPayload.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct RemoteDataPayload: Sendable, Equatable, Hashable {\n\n    /// The payload type\n    public let type: String\n\n    /// The timestamp of the most recent change to this data payload\n    public let timestamp: Date\n\n    /// The actual data associated with this payload\n    public let data: AirshipJSON\n\n    public let remoteDataInfo: RemoteDataInfo?\n\n    public init(\n        type: String,\n        timestamp: Date,\n        data: AirshipJSON,\n        remoteDataInfo: RemoteDataInfo?\n    ) {\n        self.type = type\n        self.timestamp = timestamp\n        self.data = data\n        self.remoteDataInfo = remoteDataInfo\n    }\n}\n\npublic extension RemoteDataPayload {\n    func data(key: String) -> AnyHashable? {\n        guard case .object(let map) = self.data else { return nil }\n        return map[key]?.unWrap()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataProtocol.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol RemoteDataProtocol: AnyObject, Sendable {\n    /// Gets the update status for the given source\n    /// - Parameter source: The source.\n    /// - Returns The status of the source.\n    func status(source: RemoteDataSource) async -> RemoteDataSourceStatus\n\n    /// Checks if the remote data info is current or not.\n    /// - Parameter remoteDataInfo: The remote data info.\n    /// - Returns `true` if current, otherwise `false`.\n    func isCurrent(remoteDataInfo: RemoteDataInfo) async -> Bool\n\n    func notifyOutdated(remoteDataInfo: RemoteDataInfo) async\n    func publisher(types: [String]) -> AnyPublisher<[RemoteDataPayload], Never>\n    func payloads(types: [String]) async -> [RemoteDataPayload]\n\n    /// Waits for a successful refresh\n    /// - Parameters:\n    ///     - source: The remote data source.\n    ///     - maxTime: The max time to wait\n    func waitRefresh(source: RemoteDataSource, maxTime: TimeInterval?) async\n\n    /// Waits for a successful refresh\n    /// - Parameters:\n    ///     - source: The remote data source.\n    func waitRefresh(source: RemoteDataSource) async\n\n    /// Waits for a refresh attempt for the session.\n    /// - Parameters:\n    ///     - source: The remote data source.\n    ///     - maxTime: The max time to wait\n    func waitRefreshAttempt(source: RemoteDataSource, maxTime: TimeInterval?) async\n\n    /// Waits for a refresh attempt for the session.\n    /// - Parameters:\n    ///     - source: The remote data source.\n    func waitRefreshAttempt(source: RemoteDataSource) async\n\n    /// Forces a refresh attempt. This should generally never be called externally. Currently being exposed for\n    /// test apps.\n    func forceRefresh() async\n    \n    /// Gets the status updates using the given mapping.\n    /// - Returns:a stream of status updates.\n    func statusUpdates<T:Sendable>(sources: [RemoteDataSource], map: @escaping (@Sendable (_ statuses: [RemoteDataSource: RemoteDataSourceStatus]) -> T)) async -> AsyncStream<T> \n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nactor RemoteDataProvider: RemoteDataProviderProtocol {\n\n\n    // Old\n    private static let lastRefreshMetadataKey: String = \"remotedata.LAST_REFRESH_METADATA\"\n    private static let lastRefreshTimeKey: String = \"remotedata.LAST_REFRESH_TIME\"\n    private static let lastRefreshAppVersionKey: String = \"remotedata.LAST_REFRESH_APP_VERSION\"\n\n    private static let maxStaleTime: TimeInterval = 3 * 24 * 60.0 // 3 days\n\n    private let dataStore: PreferenceDataStore\n    private let delegate: any RemoteDataProviderDelegate\n    private let remoteDataStore: RemoteDataStore\n    private let date: AirshipDate\n\n    private let sourceName: String\n    private let defaultEnabled: Bool\n\n    private var requiresRefresh: Bool = false\n\n    nonisolated var source: RemoteDataSource {\n        return self.delegate.source\n    }\n    \n    init(\n        dataStore: PreferenceDataStore,\n        delegate: any RemoteDataProviderDelegate,\n        defaultEnabled: Bool = true,\n        inMemory: Bool = false,\n        date: AirshipDate = AirshipDate.shared\n    ) {\n        self.dataStore = dataStore\n        self.delegate = delegate\n        self.defaultEnabled = defaultEnabled\n        self.date = date\n        self.remoteDataStore = RemoteDataStore(\n            storeName: delegate.storeName,\n            inMemory: inMemory\n        )\n        self.sourceName = delegate.source.name\n\n        if (delegate.source == .app) {\n            // If we have an old key\n            if (self.dataStore.value(forKey: RemoteDataProvider.lastRefreshMetadataKey) != nil) {\n                // Remove the old metadata to force an update if the SDK is downgraded\n                self.dataStore.removeObject(forKey: RemoteDataProvider.lastRefreshMetadataKey)\n                self.dataStore.removeObject(forKey: RemoteDataProvider.lastRefreshTimeKey)\n                self.dataStore.removeObject(forKey: RemoteDataProvider.lastRefreshAppVersionKey)\n\n                // This prevents an issue going from 17 -> 16 -> 17 where remote-data is not refreshed\n                self.dataStore.removeObject(forKey: \"remotedata.\\(self.sourceName)_state\")\n            }\n\n        }\n    }\n\n    func payloads(types: [String]) async -> [RemoteDataPayload] {\n        guard self.isEnabled else {\n            return []\n        }\n        \n        do {\n            return try await self.remoteDataStore.fetchRemoteDataFromCache(\n                types: types\n            )\n            .sortedByType(types)\n        } catch {\n            AirshipLogger.error(\"Failed to get contact remote data \\(error)\")\n            return []\n        }\n    }\n\n    func setEnabled(_ enabled: Bool) -> Bool {\n        guard enabled != self.isEnabled else {\n            return false\n        }\n\n        if (!enabled) {\n            self.refreshState = nil\n        }\n        \n        self.isEnabled = enabled\n        return true\n    }\n\n    private var refreshState: LastRefreshState? {\n        get {\n            self.dataStore.safeCodable(\n                forKey: \"remotedata.\\(self.sourceName)_state\"\n            )\n        }\n        set {\n            self.dataStore.setSafeCodable(\n                newValue,\n                forKey: \"remotedata.\\(self.sourceName)_state\"\n            )\n        }\n    }\n\n    private var isEnabled: Bool {\n        get {\n            self.dataStore.bool(\n                forKey: \"remotedata.\\(self.sourceName)_enabled\",\n                defaultValue: defaultEnabled\n            )\n        }\n        set {\n            self.dataStore.setBool(\n                newValue,\n                forKey: \"remotedata.\\(self.sourceName)_enabled\"\n            )\n        }\n    }\n\n    func notifyOutdated(remoteDataInfo: RemoteDataInfo) -> Bool {\n        guard self.refreshState?.remoteDataInfo == remoteDataInfo else {\n            return false\n        }\n\n        self.refreshState = nil\n        return true\n    }\n\n    func isCurrent(locale: Locale, randomeValue: Int, remoteDataInfo: RemoteDataInfo) async -> Bool {\n        guard self.isEnabled else {\n            return false\n        }\n\n        guard let refreshState = self.refreshState else {\n             return false\n        }\n\n        guard remoteDataInfo == refreshState.remoteDataInfo else {\n            return false\n        }\n\n        return await self.delegate.isRemoteDataInfoUpToDate(\n            refreshState.remoteDataInfo,\n            locale: locale,\n            randomValue: randomeValue\n        )\n    }\n\n    /// Assumes no reentry\n    func refresh(\n        changeToken: String,\n        locale: Locale,\n        randomeValue: Int\n    ) async -> RemoteDataRefreshResult {\n        AirshipLogger.trace(\"Checking \\(self.sourceName) remote data\")\n\n        guard self.isEnabled else {\n            do {\n                if (try await self.remoteDataStore.hasData()) {\n                    try await self.remoteDataStore.clear()\n                    return .newData\n                }\n            } catch {\n                AirshipLogger.trace(\"Failed to clear \\(self.sourceName) remote data: \\(error)\")\n                return .failed\n            }\n\n            return .skipped\n        }\n\n        let refreshState = self.refreshState\n\n        let shouldRefresh = await self.status(\n            refreshState: refreshState,\n            changeToken: changeToken,\n            locale: locale,\n            randomeValue: randomeValue\n        ) != .upToDate\n\n        guard shouldRefresh else {\n            AirshipLogger.trace(\"Skipping update, \\(self.sourceName) remote data already up to date\")\n            return .skipped\n        }\n\n        AirshipLogger.trace(\"Requesting \\(self.sourceName) remote data\")\n\n        do {\n            let response = try await self.delegate.fetchRemoteData(\n                locale: locale,\n                randomValue: randomeValue,\n                lastRemoteDataInfo: refreshState?.remoteDataInfo\n            )\n\n            AirshipLogger.trace(\"Refresh result for \\(self.sourceName) remote data: \\(response)\")\n\n            guard response.isSuccess || response.statusCode == 304 else {\n                return .failed\n            }\n\n            if response.isSuccess, let remoteData = response.result {\n                try await self.remoteDataStore.overwriteCachedRemoteData(remoteData.payloads)\n\n                self.refreshState = LastRefreshState(\n                    changeToken: changeToken,\n                    remoteDataInfo: remoteData.remoteDataInfo,\n                    date: date.now\n                )\n\n                return .newData\n            } else {\n                guard let refreshState = refreshState else {\n                    throw AirshipErrors.error(\"Received 304 without a last modified time.\")\n                }\n\n                self.refreshState = LastRefreshState(\n                    changeToken: changeToken,\n                    remoteDataInfo: refreshState.remoteDataInfo,\n                    date: date.now\n                )\n\n                return .skipped\n            }\n            \n        } catch {\n            AirshipLogger.trace(\"Refresh failed for \\(self.sourceName) remote data: \\(error)\")\n            return .failed\n        }\n    }\n\n    func status(changeToken: String, locale: Locale, randomeValue: Int) async -> RemoteDataSourceStatus {\n        return await status(\n            refreshState: self.refreshState,\n            changeToken: changeToken,\n            locale: locale,\n            randomeValue: randomeValue\n        )\n    }\n\n    private func status(\n        refreshState: LastRefreshState?,\n        changeToken: String,\n        locale: Locale,\n        randomeValue: Int\n    ) async ->RemoteDataSourceStatus {\n        guard \n            self.isEnabled,\n            let refreshState = refreshState,\n            let refreshDate = refreshState.date,\n            self.date.now.timeIntervalSince(refreshDate) <= RemoteDataProvider.maxStaleTime\n        else {\n            return .outOfDate\n        }\n\n        let isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            refreshState.remoteDataInfo,\n            locale: locale,\n            randomValue: randomeValue\n        )\n\n        guard isUpToDate else {\n            return .outOfDate\n        }\n\n        guard changeToken == refreshState.changeToken else {\n            return .stale\n        }\n\n        return .upToDate\n    }\n}\n\nfileprivate struct LastRefreshState: Codable {\n    let changeToken: String\n    let remoteDataInfo: RemoteDataInfo\n    let date: Date?\n}\n\n\nfileprivate extension RemoteDataSource {\n    var name: String {\n        switch(self) {\n        case .app: return \"app\"\n        case .contact: return \"contact\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataProviderDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n\nprotocol RemoteDataProviderDelegate: Sendable {\n    var source: RemoteDataSource { get }\n    var storeName: String { get }\n\n    func isRemoteDataInfoUpToDate(\n        _  remoteDataInfo: RemoteDataInfo,\n        locale: Locale,\n        randomValue: Int\n    ) async -> Bool\n\n    func fetchRemoteData(\n        locale: Locale,\n        randomValue: Int,\n        lastRemoteDataInfo: RemoteDataInfo?\n    ) async throws -> AirshipHTTPResponse<RemoteDataResult>\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataProviderProtocol.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Remote data provider protocol\nprotocol RemoteDataProviderProtocol: Actor {\n\n    /// The remote-data source.\n    nonisolated var source: RemoteDataSource { get }\n\n    /// Gets the payloads from remote-data.\n    /// - Parameter types: The payload types.\n    /// - Returns An array of payloads.\n    func payloads(types: [String]) async -> [RemoteDataPayload]\n\n    /// Notifies that the remote-data info is outdated. This will cause the next refresh to\n    /// to fetch data.\n    /// - Parameter remoteDataInfo: The remote data info.\n    /// - Returns true if cleared, otherwise false.\n    func notifyOutdated(remoteDataInfo: RemoteDataInfo) -> Bool\n\n    /// Checks if the source is current.\n    /// - Parameter locale: The current locale.\n    /// - Parameter randomeValue: The remote-data random value.\n    func isCurrent(locale: Locale, randomeValue: Int, remoteDataInfo: RemoteDataInfo) async -> Bool\n\n\n    /// Checks if the source update status.\n    /// - Parameter changeToken: The change token.\n    /// - Parameter locale: The current locale.\n    /// - Parameter randomeValue: The remote-data random value.\n    /// - Returns The update status.\n    func status(changeToken: String, locale: Locale, randomeValue: Int) async -> RemoteDataSourceStatus\n\n\n    /// Refreshes remote data\n    /// - Parameter changeToken: The change token. Used to control checking for a refresh\n    /// even if the remote data info is up to date.\n    /// - Parameter locale: The current locale.\n    /// - Parameter randomeValue: The remote-data random value.\n    /// - Returns true if the value changed, false if not.\n    func refresh(\n        changeToken: String,\n        locale: Locale,\n        randomeValue: Int\n    ) async -> RemoteDataRefreshResult\n\n    /// Enables/Disables the provider.\n    /// - Parameter enabled: true to enable, false to disable\n    /// - Returns true if the value changed, false if not.\n    func setEnabled(_ enabled: Bool) -> Bool\n}\n\n/// Refresh result\nenum RemoteDataRefreshResult: Equatable, Sendable {\n    /// Refresh was skipped either because it was disabled or data is up to date.\n    case skipped\n\n    /// Source was refreshed with new data.\n    case newData\n\n    // Refresh failed.\n    case failed\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataSource.swift",
    "content": "\nimport Foundation\n\n\n/// NOTE: For internal use only. :nodoc:\npublic enum RemoteDataSource: Int, Sendable, Codable, Equatable, Hashable, CaseIterable {\n    case app\n    case contact\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataSourceStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic enum RemoteDataSourceStatus: Sendable {\n    case upToDate\n    case stale\n    case outOfDate\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\n\nfinal class RemoteDataStore: Sendable {\n    \n    static let remoteDataEntity: String = \"UARemoteDataStorePayload\"\n    \n    private let coreData: UACoreData\n    private let inMemory: Bool\n    \n    public init(storeName: String, inMemory: Bool) {\n        self.inMemory = inMemory\n        let modelURL = AirshipCoreResources.bundle.url(\n            forResource: \"UARemoteData\",\n            withExtension: \"momd\"\n        )\n        self.coreData = UACoreData(\n            name:  \"UARemoteData\",\n            modelURL: modelURL!,\n            inMemory: inMemory,\n            stores: [storeName]\n        )\n    }\n    \n    convenience init(storeName: String) {\n        self.init(storeName: storeName, inMemory: false)\n    }\n\n    func hasData() async throws -> Bool {\n        return try await self.coreData.performWithResult { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: RemoteDataStore.remoteDataEntity\n            )\n            return try context.count(for: request) > 0\n        }\n    }\n\n    public func fetchRemoteDataFromCache(\n        types: [String]? = nil\n    ) async throws -> [RemoteDataPayload] {\n        AirshipLogger.trace(\n            \"Fetching remote data from cache with types: \\(String(describing: types))\"\n        )\n\n        return try await self.coreData.performWithResult { context in\n            let request = NSFetchRequest<any NSFetchRequestResult>(\n                entityName: RemoteDataStore.remoteDataEntity\n            )\n\n            if let types = types {\n                let predicate = AirshipCoreDataPredicate(format: \"(type IN %@)\", args: [types])\n                request.predicate = predicate.toNSPredicate()\n            }\n\n            let result = try context.fetch(request) as? [RemoteDataStorePayload] ?? []\n            return result.compactMap {\n\n                var remoteDataInfo: RemoteDataInfo? = nil\n                do {\n                    if let data = $0.remoteDataInfo {\n                        remoteDataInfo = try RemoteDataInfo.fromJSON(data: data)\n                    }\n                } catch {\n                    AirshipLogger.error(\"Unable to parse remote-data info from data \\(error.localizedDescription)\")\n                }\n\n                var data: AirshipJSON = AirshipJSON.null\n\n                do {\n                    data = try AirshipJSON.from(data: $0.data)\n                } catch {\n                    AirshipLogger.error(\"Unable to parse remote-data data \\(error.localizedDescription)\")\n                }\n\n\n                return RemoteDataPayload(\n                    type: $0.type,\n                    timestamp: $0.timestamp,\n                    data: data,\n                    remoteDataInfo: remoteDataInfo\n                )\n            }\n        }\n    }\n\n\n    public func clear() async throws {\n        try await self.coreData.perform({ context in\n            try self.deleteAll(context: context)\n        })\n    }\n\n    public func overwriteCachedRemoteData(\n        _ payloads: [RemoteDataPayload]\n    ) async throws {\n        \n        try await self.coreData.perform({ context in\n            try self.deleteAll(context: context)\n            payloads.forEach {\n                self.addPayload($0, context: context)\n            }\n        })\n        \n    }\n    \n    private func deleteAll(\n        context: NSManagedObjectContext\n    ) throws {\n        \n        let fetchRequest = NSFetchRequest<any NSFetchRequestResult>(\n            entityName: RemoteDataStore.remoteDataEntity\n        )\n        \n        if self.inMemory {\n            fetchRequest.includesPropertyValues = false\n            let payloads = try context.fetch(fetchRequest) as? [NSManagedObject]\n            payloads?.forEach {\n                context.delete($0)\n            }\n        } else {\n            let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)\n            try context.execute(deleteRequest)\n        }\n    }\n    \n    nonisolated private func addPayload(\n        _ payload: RemoteDataPayload,\n        context: NSManagedObjectContext\n    ) {\n        // create the NSManagedObject\n        guard\n            let remoteDataStorePayload = NSEntityDescription.insertNewObject(\n                forEntityName: RemoteDataStore.remoteDataEntity,\n                into: context\n            ) as? RemoteDataStorePayload\n        else {\n            return\n        }\n        \n        // set the properties\n        remoteDataStorePayload.type = payload.type\n        remoteDataStorePayload.timestamp = payload.timestamp\n        remoteDataStorePayload.data = AirshipJSONUtils.toData(payload.data.unWrap() as? [AnyHashable : Any]) ?? Data()\n        do {\n            remoteDataStorePayload.remoteDataInfo = try payload.remoteDataInfo?.toEncodedJSONData()\n        } catch {\n            AirshipLogger.error(\"Unable to transform remote-data info to data \\(error)\")\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataStorePayload.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\n@objc(UARemoteDataStorePayload)\nclass RemoteDataStorePayload: NSManagedObject {\n\n    /// The payload type\n    @objc\n    @NSManaged public var type: String\n\n    /// The timestamp of the most recent change to this data payload\n    @objc\n    @NSManaged public var timestamp: Date\n\n    /// The actual data associated with this payload\n    @objc\n    @NSManaged public var data: Data\n\n    /// The remote data info as json encoded data.\n    @objc\n    @NSManaged public var remoteDataInfo: Data?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoteDataURLFactory.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct RemoteDataURLFactory: Sendable {\n    static func makeURL(config: RuntimeConfig, path: String, locale: Locale, randomValue: Int) throws -> URL {\n        guard var components = URLComponents(string: config.remoteDataAPIURL) else {\n            throw AirshipErrors.error(\"URL is null\")\n        }\n\n        components.path = path\n\n        var queryItems: [URLQueryItem] = []\n\n        let languageItem = URLQueryItem(\n            name: \"language\",\n            value: locale.getLanguageCode()\n        )\n\n        if languageItem.value?.isEmpty == false {\n            queryItems.append(languageItem)\n        }\n\n        let countryItem = URLQueryItem(\n            name: \"country\",\n            value: locale.getRegionCode()\n        )\n\n        if countryItem.value?.isEmpty == false {\n            queryItems.append(countryItem)\n        }\n\n        let versionItem = URLQueryItem(\n            name: \"sdk_version\",\n            value: AirshipVersion.version\n        )\n\n        if versionItem.value?.isEmpty == false {\n            queryItems.append(versionItem)\n        }\n\n        let randomValueItem = URLQueryItem(\n            name: \"random_value\",\n            value: String(randomValue)\n        )\n\n        if randomValueItem.value?.isEmpty == false {\n            queryItems.append(randomValueItem)\n        }\n\n        components.queryItems = queryItems\n\n        guard let url = components.url else {\n            throw AirshipErrors.error(\"URL is null\")\n        }\n\n        return url\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RemoveTagsAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/// Removes tags.\n///\n/// Expected argument values: `String` (single tag), `[String]` (single or multiple tags), or an object.\n/// An example tag group JSON payload:\n/// {\n///     \"channel\": {\n///         \"channel_tag_group\": [\"channel_tag_1\", \"channel_tag_2\"],\n///         \"other_channel_tag_group\": [\"other_channel_tag_1\"]\n///     },\n///     \"named_user\": {\n///         \"named_user_tag_group\": [\"named_user_tag_1\", \"named_user_tag_2\"],\n///         \"other_named_user_tag_group\": [\"other_named_user_tag_1\"]\n///     },\n///     \"device\": [ \"tag\", \"another_tag\"]\n/// }\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.backgroundInteractiveButton`, `ActionSituation.manualInvocation` and\n/// `ActionSituation.automation`\npublic final class RemoveTagsAction: AirshipAction {\n\n    /// Default names - \"remove_tags_action\", \"^-t\"\n    public static let defaultNames: [String] = [\"remove_tags_action\", \"^-t\"]\n\n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n\n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n    \n    private let tagMutationsChannel: AirshipAsyncChannel<TagActionMutation> = AirshipAsyncChannel<TagActionMutation>()\n    \n    public var tagMutations: AsyncStream<TagActionMutation> {\n        get async {\n            return await tagMutationsChannel.makeStream()\n        }\n    }\n\n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier()\n        )\n    }\n\n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact\n    ) {\n        self.channel = channel\n        self.contact = contact\n    }\n\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        guard arguments.situation != .backgroundPush else {\n            return false\n        }\n        return true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        let unwrapped = arguments.value.unWrap()\n        if let tag = unwrapped as? String {\n            channel().editTags { editor in\n                editor.remove(tag)\n            }\n            \n            sendTagMutation(.channelTags([tag]))\n        } else if let tags = arguments.value.unWrap() as? [String] {\n            channel().editTags { editor in\n                editor.remove(tags)\n            }\n            \n            sendTagMutation(.channelTags(tags))\n        } else if let args: TagsActionsArgs = try arguments.value.decode() {\n            if let channelTagGroups = args.channel {\n                channel().editTagGroups { editor in\n                    channelTagGroups.forEach { group, tags in\n                        editor.remove(tags, group: group)\n                    }\n                }\n                sendTagMutation(.channelTagGroups(channelTagGroups))\n            }\n\n            if let contactTagGroups = args.namedUser {\n                contact().editTagGroups { editor in\n                    contactTagGroups.forEach { group, tags in\n                        editor.remove(tags, group: group)\n                    }\n                }\n                sendTagMutation(.contactTagGroups(contactTagGroups))\n            }\n\n            if let deviceTags = args.device {\n                channel().editTags() { editor in\n                    editor.remove(deviceTags)\n                }\n                sendTagMutation(.channelTags(deviceTags))\n            }\n        }\n        return nil\n    }\n    \n    private func sendTagMutation(_ mutation: TagActionMutation) {\n        Task { @MainActor in\n            await tagMutationsChannel.send(mutation)\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RetailEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\npublic extension CustomEvent {\n\n    /// Retail templates\n    enum RetailTemplate: Sendable {\n        /// Browsed\n        case browsed\n\n        /// Added to cart\n        case addedToCart\n\n        /// Starred\n        case starred\n\n        /// Purchased\n        case purchased\n\n        /// Shared\n        /// - Parameters:\n        ///     - source: Optional source.\n        ///     - medium: Optional medium.\n        case shared(source: String? = nil, medium: String? = nil)\n\n        /// Added to wishlist\n        /// - Parameters:\n        ///     - id: Optional id.\n        ///     - name: Optional name.\n        case wishlist(id: String? = nil, name: String? = nil)\n\n        fileprivate static let templateName: String = \"retail\"\n\n        fileprivate var eventName: String {\n            return switch self {\n            case .browsed: \"browsed\"\n            case .addedToCart: \"added_to_cart\"\n            case .starred: \"starred_product\"\n            case .purchased: \"purchased\"\n            case .shared: \"shared_product\"\n            case .wishlist: \"wishlist\"\n            }\n        }\n    }\n\n    /// Additional retail template properties\n    struct RetailProperties: Encodable, Sendable {\n        /// The event's ID.\n        public var id: String?\n\n        /// The event's category.\n        public var category: String?\n\n        /// The event's type.\n        public var type: String?\n\n        /// The event's description.\n        public var eventDescription: String?\n\n        /// The brand.\n        public var brand: String?\n\n        /// If its a new item or not.\n        public var isNewItem: Bool?\n\n        /// The currency.\n        public var currency: String?\n\n        /// If the value is a lifetime value or not.\n        public var isLTV: Bool\n\n        // Set from templates\n        fileprivate var source: String? = nil\n        fileprivate var medium: String? = nil\n        fileprivate var wishlistName: String? = nil\n        fileprivate var wishlistID: String? = nil\n\n        public init(\n            id: String? = nil,\n            category: String? = nil,\n            type: String? = nil,\n            eventDescription: String? = nil,\n            isLTV: Bool = false,\n            brand: String? = nil,\n            isNewItem: Bool? = nil,\n            currency: String? = nil\n        ) {\n            self.id = id\n            self.category = category\n            self.type = type\n            self.eventDescription = eventDescription\n            self.brand = brand\n            self.isNewItem = isNewItem\n            self.currency = currency\n            self.isLTV = isLTV\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case id\n            case category\n            case type\n            case eventDescription = \"description\"\n            case brand\n            case isNewItem = \"new_item\"\n            case currency\n            case isLTV = \"ltv\"\n            case source\n            case medium\n            case wishlistName = \"wishlist_name\"\n            case wishlistID = \"wishlist_id\"\n        }\n    }\n\n    /// Constructs a custom event using the retail template.\n    /// - Parameters:\n    ///     - accountTemplate: The retail template.\n    ///     - properties: Optional additional properties\n    ///     - encoder: Encoder used to encode the additional properties. Defaults to `CustomEvent.defaultEncoder`.\n    init(\n        retailTemplate: RetailTemplate,\n        properties: RetailProperties = RetailProperties(),\n        encoder: @autoclosure () -> JSONEncoder = CustomEvent.defaultEncoder()\n    ) {\n        self = .init(name: retailTemplate.eventName)\n        self.templateType = RetailTemplate.templateName\n\n        var mutableProperties = properties\n\n        switch retailTemplate {\n        case .browsed: break\n        case .addedToCart: break\n        case .starred: break\n        case .purchased: break\n        case .shared(source: let source, medium: let medium):\n            mutableProperties.source = source\n            mutableProperties.medium = medium\n        case .wishlist(id: let id, name: let name):\n            mutableProperties.wishlistID = id\n            mutableProperties.wishlistName = name\n        }\n\n        do {\n            try self.setProperties(mutableProperties, encoder: encoder())\n        } catch {\n            /// Should never happen so we are just catching the exception and logging\n            AirshipLogger.error(\"Failed to generate event \\(error)\")\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RootView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct RootView<Content: View>: View {\n\n#if !os(tvOS) && !os(watchOS)\n    @Environment(\\.verticalSizeClass) private var verticalSizeClass\n    @Environment(\\.horizontalSizeClass) private var horizontalSizeClass\n#endif\n\n    @State private var currentOrientation: ThomasOrientation = RootView.resolveOrientation()\n\n    @State private var isForeground: Bool = true\n    @State private var isVisible: Bool = false\n    @State private var isVoiceOverRunning: Bool = Self.resolveIsVoiceOverRunning()\n\n    @ObservedObject var thomasEnvironment: ThomasEnvironment\n    @StateObject var thomasState: ThomasState\n    @StateObject var validatableHelper: ValidatableHelper = ValidatableHelper()\n    @StateObject var formInputCollector: ThomasFormDataCollector = ThomasFormDataCollector()\n\n    // Default form state so @EnvironmentObject does not crash\n    @StateObject\n    private var defaultFormState: ThomasFormState = ThomasFormState(\n        identifier: \"\",\n        formType: .form,\n        formResponseType: nil,\n        validationMode: .onDemand\n    )\n\n    // Default pager state so @EnvironmentObject does not crash\n    @StateObject\n    private var defaultPagerState: PagerState = PagerState(\n        identifier: \"\",\n        branching: nil\n    )\n\n    // Default video state so @EnvironmentObject does not crash\n    @StateObject\n    private var defaultVideoState: VideoState = VideoState(\n        identifier: \"\"\n    )\n\n    let layout: AirshipLayout\n    let content: (ThomasOrientation, ThomasWindowSize) -> Content\n\n    let associatedLabelResolver: ThomasAssociatedLabelResolver\n\n    init(\n        thomasEnvironment: ThomasEnvironment,\n        layout: AirshipLayout,\n        @ViewBuilder content: @escaping (ThomasOrientation, ThomasWindowSize) -> Content\n    ) {\n        self.thomasEnvironment = thomasEnvironment\n        self.layout = layout\n        self.content = content\n        self.isForeground = AppStateTracker.shared.isForegrounded\n        self._thomasState = StateObject(\n            wrappedValue: ThomasState() { [weak thomasEnvironment] state in\n                thomasEnvironment?.onStateChange(state)\n            }\n        )\n        self.associatedLabelResolver = ThomasAssociatedLabelResolver(layout: layout)\n    }\n\n    @ViewBuilder\n    var body: some View {\n        content(currentOrientation, resolveWindowSize())\n            .environmentObject(self.thomasEnvironment)\n            .environmentObject(self.thomasState)\n            .environmentObject(self.formInputCollector)\n            .environmentObject(self.validatableHelper)\n            .environmentObject(self.defaultPagerState)\n            .environmentObject(self.defaultFormState)\n            .environmentObject(self.defaultVideoState)\n            .environment(\\.orientation, currentOrientation)\n            .environment(\\.windowSize, resolveWindowSize())\n            .environment(\\.isVisible, isVisible)\n            .environment(\\.isVoiceOverRunning, isVoiceOverRunning)\n            .environment(\\.thomasAssociatedLabelResolver, associatedLabelResolver)\n            .onReceive(NotificationCenter.default.publisher(for: AppStateTracker.didTransitionToForeground)) { (_) in\n                self.isForeground = true\n                self.thomasEnvironment.onVisibilityChanged(isVisible: self.isVisible, isForegrounded: self.isForeground)\n            }\n            .onReceive(NotificationCenter.default.publisher(for: AppStateTracker.didTransitionToBackground)) { (_) in\n                self.isForeground = false\n                self.thomasEnvironment.onVisibilityChanged(isVisible: self.isVisible, isForegrounded: self.isForeground)\n            }\n#if os(macOS)\n            .onReceive(NSWorkspace.shared.notificationCenter.publisher(for: NSWorkspace.accessibilityDisplayOptionsDidChangeNotification)) { _ in\n                updateVoiceoverRunningState()\n            }\n#elseif !os(watchOS)\n        // iOS, tvOS, visionOS\n            .onReceive(NotificationCenter.default.publisher(for: UIAccessibility.voiceOverStatusDidChangeNotification)) { _ in\n                updateVoiceoverRunningState()\n            }\n#endif\n            .onAppear {\n                updateVoiceoverRunningState()\n                self.currentOrientation = RootView.resolveOrientation()\n                self.isVisible = true\n                self.thomasEnvironment.onVisibilityChanged(isVisible: self.isVisible, isForegrounded: self.isForeground)\n            }\n            .onDisappear {\n                self.isVisible = false\n                self.thomasEnvironment.onVisibilityChanged(isVisible: self.isVisible, isForegrounded: self.isForeground)\n            }\n#if os(iOS)\n            .onReceive(\n                NotificationCenter.default.publisher(\n                    for: UIDevice.orientationDidChangeNotification\n                )\n            ) { _ in\n                self.currentOrientation = RootView.resolveOrientation()\n            }\n#endif\n    }\n\n    /// Uses the vertical and horizontal class size to determine small, medium, large window size:\n    /// - large: regular x regular = large\n    /// - medium: regular x compact or compact x regular\n    /// - small: compact x compact\n    func resolveWindowSize() -> ThomasWindowSize {\n#if os(watchOS)\n        return .small\n#elseif os(tvOS)\n        return .large\n#else\n        switch (verticalSizeClass, horizontalSizeClass) {\n        case (.regular, .regular):\n            return .large\n        case (.compact, .compact):\n            return .small\n        default:\n            return .medium\n        }\n#endif\n    }\n\n    static func resolveOrientation() -> ThomasOrientation {\n#if os(tvOS) || os(watchOS) || os(macOS)\n        return .landscape\n#else\n        let scene = try? AirshipSceneManager.shared.lastActiveScene\n\n        if let scene = scene {\n            if scene.interfaceOrientation.isLandscape {\n                return .landscape\n            } else if scene.interfaceOrientation.isPortrait {\n                return .portrait\n            }\n        }\n        return .portrait\n#endif\n    }\n\n    private func updateVoiceoverRunningState() {\n        isVoiceOverRunning = Self.resolveIsVoiceOverRunning()\n    }\n\n    private static func resolveIsVoiceOverRunning() -> Bool {\n#if os(watchOS)\n        // watchOS does not expose a public property to check VoiceOver status\n        return false\n#elseif os(macOS)\n        // macOS equivalent\n        return NSWorkspace.shared.isVoiceOverEnabled\n#else\n        // iOS, tvOS, visionOS\n        return UIAccessibility.isVoiceOverRunning\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/RuntimeConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\nimport Combine\n\n/// Airship config needed for runtime. Generated from `AirshipConfig` during takeOff.\npublic final class RuntimeConfig: Sendable {\n\n    /// - NOTE: This option is reserved for internal debugging. :nodoc:\n    public static let configUpdatedEvent: Notification.Name = Notification.Name(\n        \"com.urbanairship.runtime_config_updated\"\n    )\n\n    struct DefaultURLs {\n        let deviceURL: String\n        let analyticsURL: String\n        let remoteDataURL: String\n\n        static let us: DefaultURLs = DefaultURLs(\n            deviceURL: \"https://device-api.urbanairship.com\",\n            analyticsURL: \"https://combine.urbanairship.com\",\n            remoteDataURL: \"https://remote-data.urbanairship.com\"\n        )\n\n        static let eu: DefaultURLs = DefaultURLs(\n            deviceURL: \"https://device-api.asnapieu.com\",\n            analyticsURL: \"https://combine.asnapieu.com\",\n            remoteDataURL: \"https://remote-data.asnapieu.com\"\n        )\n    }\n\n    /// The resolved app credentials.\n    public let appCredentials: AirshipAppCredentials\n\n    /// The request session used to perform authenticated interactions with the API\n    public let requestSession: any AirshipRequestSession\n\n    /// The airship config\n    public let airshipConfig: AirshipConfig\n\n    private let remoteConfigCache: RemoteConfigCache\n    private let notificationCenter: NotificationCenter\n    private let defaultURLs: DefaultURLs\n\n    /// - NOTE: For internal use only. :nodoc:\n    public var remoteConfig: RemoteConfig {\n        return self.remoteConfigCache.remoteConfig\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    public var deviceAPIURL: String? {\n        if let url = remoteConfig.airshipConfig?.deviceAPIURL {\n            return url\n        }\n\n        guard !self.airshipConfig.requireInitialRemoteConfigEnabled else {\n            return nil\n        }\n\n        return defaultURLs.deviceURL\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    var remoteDataAPIURL: String {\n        if let url = remoteConfig.airshipConfig?.remoteDataURL {\n            return url\n        }\n\n        if\n            let initialConfigURL = airshipConfig.initialConfigURL?.normalizeURLString(),\n            !initialConfigURL.isEmpty\n        {\n            return initialConfigURL\n        }\n\n        return defaultURLs.remoteDataURL\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    var analyticsURL: String? {\n        if let url = remoteConfig.airshipConfig?.analyticsURL {\n            return url\n        }\n\n        guard !self.airshipConfig.requireInitialRemoteConfigEnabled else {\n            return nil\n        }\n\n        return defaultURLs.analyticsURL\n    }\n\n    /// - NOTE: For internal use only. :nodoc:\n    var meteredUsageURL: String? {\n        return remoteConfigCache.remoteConfig.airshipConfig?.meteredUsageURL\n    }\n\n    public convenience init(\n        airshipConfig: AirshipConfig,\n        appCredentials: AirshipAppCredentials,\n        dataStore: PreferenceDataStore,\n        notificationCenter: NotificationCenter = NotificationCenter.default\n    ) {\n        self.init(\n            airshipConfig: airshipConfig,\n            appCredentials: appCredentials,\n            dataStore: dataStore,\n            requestSession: DefaultAirshipRequestSession(\n                appKey: appCredentials.appKey,\n                appSecret: appCredentials.appSecret\n            ),\n            notificationCenter: notificationCenter\n        )\n    }\n\n    init(\n        airshipConfig: AirshipConfig,\n        appCredentials: AirshipAppCredentials,\n        dataStore: PreferenceDataStore,\n        requestSession: any AirshipRequestSession,\n        notificationCenter: NotificationCenter = NotificationCenter.default\n    ) {\n        self.airshipConfig = airshipConfig\n        self.appCredentials = appCredentials\n        self.requestSession = requestSession\n        self.remoteConfigCache = RemoteConfigCache(dataStore: dataStore)\n        self.notificationCenter = notificationCenter\n        self.defaultURLs = switch(airshipConfig.site) {\n        case .eu: DefaultURLs.eu\n        case .us: DefaultURLs.us\n        }\n    }\n\n    @MainActor\n    func updateRemoteConfig(_ config: RemoteConfig) {\n        let old = self.remoteConfig\n        if config != old {\n            self.remoteConfigCache.remoteConfig = config\n            self.notificationCenter.post(\n                name: RuntimeConfig.configUpdatedEvent,\n                object: nil\n            )\n\n            self.remoteConfigListeners.value.forEach { listener in\n                listener(old, config)\n            }\n        }\n    }\n\n    @MainActor\n    func addRemoteConfigListener(\n        notifyCurrent: Bool = true,\n        listener: @MainActor @Sendable @escaping (RemoteConfig?, RemoteConfig) -> Void\n    ) {\n        if (notifyCurrent) {\n            listener(nil, self.remoteConfig)\n        }\n\n        self.remoteConfigListeners.update { $0.append(listener) }\n    }\n\n    let remoteConfigListeners: AirshipMainActorValue<[@MainActor @Sendable (RemoteConfig?, RemoteConfig) -> Void]> = AirshipMainActorValue([])\n}\n\nextension String {\n    fileprivate func normalizeURLString() -> String {\n        guard hasSuffix(\"/\") else {\n            return self\n        }\n        var copy = self\n        copy.removeLast()\n        return copy\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SMSRegistrationOptions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// SMS registration options\npublic struct SMSRegistrationOptions: Codable, Sendable, Equatable, Hashable {\n\n    /**\n     * Sender ID\n     */\n    let senderID: String\n\n    private init(senderID: String) {\n        self.senderID = senderID\n    }\n\n    /// Returns a SMS registration options with opt-in status\n    /// - Parameter senderID: The sender ID\n    /// - Returns: A SMS registration options.\n    public static func optIn(senderID: String) -> SMSRegistrationOptions {\n        return SMSRegistrationOptions(senderID: senderID)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SMSValidatorAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum SMSValidatorAPIClientResult: Decodable, Equatable, Sendable {\n    case valid(String)\n    case invalid\n\n    enum CodingKeys: CodingKey {\n        case valid\n        case msisdn\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n\n        if try container.decode(Bool.self, forKey: .valid) {\n            let msisdn = try container.decode(String.self, forKey: .msisdn)\n            self = .valid(msisdn)\n        } else {\n            self = .invalid\n        }\n    }\n}\n\nprotocol SMSValidatorAPIClientProtocol: Sendable {\n    func validateSMS(\n        msisdn: String,\n        sender: String\n    ) async throws ->  AirshipHTTPResponse<SMSValidatorAPIClientResult>\n\n    func validateSMS(\n        msisdn: String,\n        prefix: String\n    ) async throws ->  AirshipHTTPResponse<SMSValidatorAPIClientResult>\n}\n\nfinal class SMSValidatorAPIClient: SMSValidatorAPIClientProtocol {\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(config: config, session: config.requestSession)\n    }\n\n    func validateSMS(\n        msisdn: String,\n        sender: String\n    ) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        return try await performSMSValidation(\n            requestBody: RequestBody(\n                msisdn: msisdn,\n                sender: sender,\n                prefix: nil\n            )\n        )\n    }\n\n    func validateSMS(\n        msisdn: String,\n        prefix: String\n    ) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        return try await performSMSValidation(\n            requestBody: RequestBody(\n                msisdn: msisdn,\n                sender: nil,\n                prefix: prefix\n            )\n        )\n    }\n\n    fileprivate func performSMSValidation<T: Encodable>(\n        requestBody: T\n    ) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        let request = AirshipRequest(\n            url: try makeURL(path: \"/api/channels/sms/format\"),\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: try JSONEncoder().encode(requestBody)\n        )\n\n        return try await self.session.performHTTPRequest(\n            request\n        ) { (data, response) in\n            AirshipLogger.debug(\n                \"SMS validation finished with response: \\(response)\"\n            )\n\n            guard let data = data, response.statusCode >= 200, response.statusCode < 300 else {\n                throw AirshipErrors.error(\"Invalid request made in performSMSValidation\")\n            }\n\n            return try JSONDecoder().decode(SMSValidatorAPIClientResult.self, from: data)\n        }\n    }\n\n    private func makeURL(path: String) throws -> URL {\n        guard let deviceAPIURL = self.config.deviceAPIURL else {\n            throw AirshipErrors.error(\"Initial config not resolved.\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(path)\"\n\n        guard let url = URL(string: \"\\(deviceAPIURL)\\(path)\") else {\n            throw AirshipErrors.error(\"Invalid ContactAPIClient URL: \\(String(describing: urlString))\")\n        }\n\n        return url\n    }\n\n    fileprivate struct RequestBody: Encodable {\n        let msisdn: String\n        let sender: String?\n        let prefix: String?\n\n        enum CodingKeys: String, CodingKey {\n            case msisdn\n            case sender\n            case prefix\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScopedSubscriptionListEdit.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents an edit made to a scoped subscription list through the SDK.\npublic enum ScopedSubscriptionListEdit: Equatable {\n\n    /// Subscribed\n    case subscribe(String, ChannelScope)\n\n    /// Unsubscribed\n    case unsubscribe(String, ChannelScope)\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScopedSubscriptionListEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Scoped subscription list editor.\npublic class ScopedSubscriptionListEditor {\n\n    private var subscriptionListUpdates: [ScopedSubscriptionListUpdate] = []\n    private let date: any AirshipDateProtocol\n    private let completionHandler: ([ScopedSubscriptionListUpdate]) -> Void\n\n    init(\n        date: any AirshipDateProtocol,\n        completionHandler: @escaping ([ScopedSubscriptionListUpdate]) -> Void\n    ) {\n        self.date = date\n        self.completionHandler = completionHandler\n    }\n\n    /**\n     * Subscribes to a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     *   - scope: Defines the channel types that the change applies to.\n     */\n    public func subscribe(_ subscriptionListID: String, scope: ChannelScope) {\n        let subscriptionListUpdate = ScopedSubscriptionListUpdate(\n            listId: subscriptionListID,\n            type: .subscribe,\n            scope: scope,\n            date: self.date.now\n        )\n        subscriptionListUpdates.append(subscriptionListUpdate)\n    }\n\n    /**\n     * Unsubscribes from a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     *   - scope: Defines the channel types that the change applies to.\n     */\n    public func unsubscribe(_ subscriptionListID: String, scope: ChannelScope) {\n        let subscriptionListUpdate = ScopedSubscriptionListUpdate(\n            listId: subscriptionListID,\n            type: .unsubscribe,\n            scope: scope,\n            date: date.now\n        )\n        subscriptionListUpdates.append(subscriptionListUpdate)\n    }\n\n    /**\n     * Internal helper that uses a boolean flag to indicate whether to subscribe or unsubscribe.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     *   - scopes: The scopes.\n     *   - subscribe:`true` to subscribe, `false`to unsubscribe\n     */\n    public func mutate(\n        _ subscriptionListID: String,\n        scopes: [ChannelScope],\n        subscribe: Bool\n    ) {\n        if subscribe {\n            scopes.forEach { self.subscribe(subscriptionListID, scope: $0) }\n        } else {\n            scopes.forEach { self.unsubscribe(subscriptionListID, scope: $0) }\n        }\n    }\n\n    /**\n     * Applies subscription list changes.\n     */\n    public func apply() {\n        completionHandler(subscriptionListUpdates)\n        subscriptionListUpdates.removeAll()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScopedSubscriptionListUpdate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nstruct ScopedSubscriptionListUpdate: Codable, Equatable, Sendable {\n    let listId: String\n    let type: SubscriptionListUpdateType\n    let scope: ChannelScope\n    let date: Date\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Score.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\nstruct Score: View {\n    private let info: ThomasViewInfo.Score\n    private let constraints: ViewConstraints\n\n    @MainActor\n    fileprivate class ViewModel: ObservableObject {\n\n        let style: ThomasViewInfo.Score.ScoreStyle\n\n        init(style: ThomasViewInfo.Score.ScoreStyle) {\n            self.style = style\n        }\n\n        @Published\n        var score: AirshipJSON?\n\n        @Published\n        var index: Int?\n\n        var accessibilityValue: String? {\n            guard let index else { return nil }\n            switch(style) {\n            case .numberRange(let rangeStyle):\n                let totalItems = rangeStyle.end - rangeStyle.start + 1\n                return String(format: \"ua_x_of_y\".airshipLocalizedString(fallback: \"%@ of %@\"), index.airshipLocalizedForVoiceOver(), totalItems.airshipLocalizedForVoiceOver())\n            }\n        }\n\n        func incrementScore() {\n            switch(style) {\n            case .numberRange(let rangeStyle):\n                guard var index else {\n                    self.index = rangeStyle.start\n                    self.score = .number(Double(rangeStyle.start))\n                    return\n                }\n\n                index = min(rangeStyle.end, index + 1)\n                if self.index != index {\n                    self.index = index\n                    self.score = .number(Double(index))\n                }\n            }\n        }\n\n        func decrementScore() {\n            switch(style) {\n            case .numberRange(let rangeStyle):\n                guard var index else {\n                    self.index = rangeStyle.start\n                    self.score = .number(Double(rangeStyle.start))\n                    return\n                }\n\n                index = max(rangeStyle.start, index - 1)\n                if self.index != index {\n                    self.index = index\n                    self.score = .number(Double(index))\n                }\n            }\n        }\n    }\n\n    @Environment(\\.pageIdentifier)\n    private var pageID\n\n    @EnvironmentObject\n    private var formDataCollector: ThomasFormDataCollector\n\n    @EnvironmentObject\n    private var formState: ThomasFormState\n\n    @EnvironmentObject\n    private var thomasState: ThomasState\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @EnvironmentObject\n    private var validatableHelper: ValidatableHelper\n\n    @Environment(\\.thomasAssociatedLabelResolver)\n    private var associatedLabelResolver\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .score,\n            thomasState: thomasState\n        )\n    }\n\n    @StateObject\n    private var viewModel: ViewModel\n\n    init(info: ThomasViewInfo.Score, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n        _viewModel = .init(wrappedValue: ViewModel(style: info.properties.style))\n    }\n\n    @ViewBuilder\n    private func makeNumberRangeScoreItems(style: ThomasViewInfo.Score.ScoreStyle.NumberRange, constraints: ViewConstraints) -> some View {\n        ForEach((style.start...style.end), id: \\.self) { index in\n            let isOn = Binding<Bool>(\n                get: {\n                    self.viewModel.index == index\n                },\n                set: {\n                    if $0 {\n                        self.viewModel.index = index\n                        self.viewModel.score = .number(Double(index))\n                    }\n                }\n            )\n            Toggle(isOn: isOn.animation()) {}\n                .toggleStyle(\n                    AirshipNumberRangeToggleStyle(\n                        style: style,\n                        viewConstraints: constraints,\n                        value: index,\n                        colorScheme: colorScheme,\n                        disabled: !formState.isFormInputEnabled\n                    )\n                )\n                .airshipGeometryGroupCompat()\n        }\n    }\n\n    @ViewBuilder\n    private func createScore(_ constraints: ViewConstraints) -> some View {\n        switch self.info.properties.style {\n        case .numberRange(let style):\n            if style.wrapping != nil {\n                let itemSpacing = CGFloat(style.spacing ?? 0)\n                let lineSpacing = CGFloat(style.wrapping?.lineSpacing ?? 0)\n                let maxItemsPerLine = style.wrapping?.maxItemsPerLine\n                WrappingLayout(\n                    viewConstraints: constraints,\n                    itemSpacing: itemSpacing,\n                    lineSpacing: lineSpacing,\n                    maxItemsPerLine: maxItemsPerLine\n                ) {\n                    makeNumberRangeScoreItems(style: style, constraints: constraints)\n                }\n            } else {\n                HStack(spacing: style.spacing ?? 0) {\n                    makeNumberRangeScoreItems(style: style, constraints: constraints)\n                }\n                .constraints(constraints)\n            }\n        }\n    }\n\n    var body: some View {\n        let constraints = modifiedConstraints()\n        createScore(constraints)\n            .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n            .accessibilityElement(children: .ignore)\n            .accessible(\n                self.info.accessible,\n                associatedLabel: associatedLabel,\n                hideIfDescriptionIsMissing: false\n            )\n            .accessibilityAdjustableAction { direction in\n                switch(direction) {\n                case .increment:\n                    viewModel.incrementScore()\n                case .decrement:\n                    viewModel.decrementScore()\n                @unknown default:\n                    break\n                }\n            }\n            .accessibilityValue(self.viewModel.accessibilityValue ?? \"\")\n            .formElement()\n            .airshipOnChangeOf(self.viewModel.score) { score in\n                self.updateScore(score)\n            }\n            .onAppear {\n                self.restoreFormState()\n                if self.formState.validationMode == .onDemand {\n                    validatableHelper.subscribe(\n                        forIdentifier: info.properties.identifier,\n                        formState: formState,\n                        initialValue: self.viewModel.score,\n                        valueUpdates: self.viewModel.$score,\n                        validatables: info.validation\n                    ) { [weak thomasState, weak viewModel] actions in\n                        guard let thomasState, let viewModel else { return }\n                        thomasState.processStateActions(\n                            actions,\n                            formFieldValue: .score(viewModel.score)\n                        )\n                    }\n                }\n            }\n    }\n\n    private func modifiedConstraints() -> ViewConstraints {\n        var constraints = self.constraints\n        if self.constraints.width == nil && self.constraints.height == nil {\n            constraints.height = 32\n        } else {\n            switch self.info.properties.style {\n            case .numberRange(let style):\n                constraints.height = self.calculateHeight(\n                    style: style,\n                    width: constraints.width\n                )\n            }\n        }\n        return constraints\n    }\n\n    func calculateHeight(\n        style: ThomasViewInfo.Score.ScoreStyle.NumberRange,\n        width: CGFloat?\n    ) -> CGFloat? {\n        guard let width = width else {\n            return nil\n        }\n        let count = Double((style.start...style.end).count)\n        let spacing = (count - 1.0) * (style.spacing ?? 0.0)\n        let remainingSpace = width - spacing\n        if remainingSpace <= 0 {\n            return nil\n        }\n        return min(remainingSpace / count, 66.0)\n    }\n\n    private func attributes(value: AirshipJSON?) -> [ThomasFormField.Attribute]? {\n        guard\n            let value,\n            let name = info.properties.attributeName\n        else {\n            return nil\n        }\n\n        let attributeValue: ThomasAttributeValue? = if let string = value.string {\n            .string(string)\n        } else if let number = value.number {\n            .number(number)\n        } else {\n            nil\n        }\n\n        guard let attributeValue else { return nil }\n\n        return [\n            ThomasFormField.Attribute(\n                attributeName: name,\n                attributeValue: attributeValue\n            )\n        ]\n    }\n\n    private func checkValid(_ value: AirshipJSON?) -> Bool {\n        return value != nil || self.info.validation.isRequired != true\n    }\n\n    private func updateScore(_ value: AirshipJSON?) {\n        let field: ThomasFormField = if checkValid(value) {\n            ThomasFormField.validField(\n                identifier: self.info.properties.identifier,\n                input: .score(value),\n                result: .init(\n                    value: .score(value),\n                    attributes: self.attributes(value: value)\n                )\n           )\n        } else {\n            ThomasFormField.invalidField(\n                identifier: self.info.properties.identifier,\n                input: .score(value)\n            )\n        }\n\n        self.formDataCollector.updateField(field, pageID: pageID)\n    }\n\n    private func restoreFormState() {\n        guard\n            case .score(let value) = self.formState.fieldValue(\n                identifier: self.info.properties.identifier\n            ),\n            let value,\n            let index = value.number\n        else {\n            self.updateScore(self.viewModel.score)\n            return\n        }\n\n        self.viewModel.score = value\n        self.viewModel.index = Int(index)\n    }\n}\n\nprivate struct AirshipNumberRangeToggleStyle: ToggleStyle {\n    let style: ThomasViewInfo.Score.ScoreStyle.NumberRange\n    let viewConstraints: ViewConstraints\n    let value: Int\n    let colorScheme: ColorScheme\n    let disabled: Bool\n\n    func makeBody(configuration: Self.Configuration) -> some View {\n        let isOn = configuration.isOn\n\n        // Pick which text appearance we should use\n        let selectedAppearance = style.bindings.selected.textAppearance\n        let unselectedAppearance = style.bindings.unselected.textAppearance\n\n        let maxDimension = max(measureForAppearance(selectedAppearance), measureForAppearance(unselectedAppearance))\n\n        /// Inject new constraints\n        let viewConstraints = ViewConstraints(\n            width: maxDimension,\n            height: maxDimension,\n            maxWidth: viewConstraints.maxWidth,\n            maxHeight: viewConstraints.maxHeight,\n            isHorizontalFixedSize: viewConstraints.isHorizontalFixedSize,\n            isVerticalFixedSize: viewConstraints.isVerticalFixedSize,\n            safeAreaInsets: viewConstraints.safeAreaInsets\n        )\n\n        return Button(action: { configuration.isOn.toggle() }) {\n            ZStack {\n                // Drawing both with 1 hidden in case the content size changes between the two\n                // it will prevent the parent from resizing on toggle\n                Group {\n                    if let shapes = style.bindings.selected.shapes {\n                        ForEach(0..<shapes.count, id: \\.self) { index in\n                            Shapes.shape(\n                                info: shapes[index],\n                                constraints: viewConstraints,\n                                colorScheme: colorScheme\n                            )\n                        }\n                        .opacity(isOn ? 1 : 0)\n                    }\n                    Text(String(self.value))\n                        .textAppearance(style.bindings.selected.textAppearance, colorScheme: colorScheme)\n                }\n                .opacity(isOn ? 1 : 0)\n                .airshipApplyIf(disabled) { view in\n                    view.colorMultiply(ThomasConstants.disabledColor)\n                }\n\n                Group {\n                    if let shapes = style.bindings.unselected.shapes {\n                        ForEach(0..<shapes.count, id: \\.self) { index in\n                            Shapes.shape(\n                                info: shapes[index],\n                                constraints: viewConstraints,\n                                colorScheme: colorScheme\n                            )\n                        }\n                    }\n                    Text(String(self.value))\n                        .textAppearance(style.bindings.unselected.textAppearance, colorScheme: colorScheme)\n                }\n                .opacity(isOn ? 0 : 1)\n            }\n            .aspectRatio(1, contentMode: .fit)\n        }\n        .animation(Animation.easeInOut(duration: 0.05), value: configuration.isOn)\n#if os(tvOS)\n        .buttonStyle(TVButtonStyle())\n#endif\n    }\n    \n    private func measureForAppearance(_ appearance: ThomasTextAppearance?) -> CGFloat {\n        let measuredSize = measureTextSize(\"\\(style.end)\", with: appearance)\n\n        let minTappableDimension: CGFloat = 44.0\n\n        let scaledWidthSpacing = AirshipFont.scaledSize(measuredSize.width)\n        let scaledHeightSpacing = AirshipFont.scaledSize(measuredSize.height)\n\n        let minWidth = max(minTappableDimension, measuredSize.width + scaledWidthSpacing)\n        let minHeight = max(minTappableDimension, measuredSize.height + scaledHeightSpacing)\n\n        return max(minWidth, minHeight)\n    }\n\n    private func measureTextSize(_ text: String, with appearance: ThomasTextAppearance?) -> CGSize {\n        guard let appearance = appearance else {\n            return CGSizeZero\n        }\n\n        let font = appearance.nativeFont\n        return (text as String).size(withAttributes: [.font: font])\n    }\n\n    private func measureTextHeight(_ text: String, with appearance: ThomasTextAppearance?) -> CGFloat {\n        guard let appearance = appearance else {\n            return 0\n        }\n\n        let font = appearance.nativeFont\n        return (text as String).size(withAttributes: [.font: font]).height\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScoreController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct ScoreController: View {\n    private let info: ThomasViewInfo.ScoreController\n    private let constraints: ViewConstraints\n    @EnvironmentObject private var environment: ThomasEnvironment\n\n    init(info: ThomasViewInfo.ScoreController, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: constraints,\n            environment: environment\n        )\n        .id(info.properties.identifier)\n    }\n\n    @MainActor\n    struct Content: View {\n        private let info: ThomasViewInfo.ScoreController\n        private let constraints: ViewConstraints\n\n        @Environment(\\.pageIdentifier) private var pageID\n        @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n        @EnvironmentObject private var formState: ThomasFormState\n        @EnvironmentObject private var thomasState: ThomasState\n        @ObservedObject private var scoreState: ScoreState\n        @EnvironmentObject private var validatableHelper: ValidatableHelper\n        @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n        private var associatedLabel: String? {\n            associatedLabelResolver?.labelFor(\n                identifier: info.properties.identifier,\n                viewType: .scoreController,\n                thomasState: thomasState\n            )\n        }\n        \n        init(\n            info: ThomasViewInfo.ScoreController,\n            constraints: ViewConstraints,\n            environment: ThomasEnvironment\n        ) {\n            self.info = info\n            self.constraints = constraints\n\n            // Use the environment to create or retrieve the state in case the view\n            // stack changes and we lose our state.\n            let scoreState = environment.retrieveState(identifier: info.properties.identifier) {\n                ScoreState(info: info)\n            }\n\n            self._scoreState = ObservedObject(wrappedValue: scoreState)\n        }\n\n\n        var body: some View {\n            ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                .constraints(constraints)\n                .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n                .accessible(\n                    self.info.accessible,\n                    associatedLabel: associatedLabel,\n                    hideIfDescriptionIsMissing: true\n                )\n                .formElement()\n                .accessibilityElement(children: .ignore)\n                .accessible(\n                    self.info.accessible,\n                    associatedLabel: associatedLabel,\n                    hideIfDescriptionIsMissing: false\n                )\n                .accessibilityAdjustableAction { direction in\n                    switch(direction) {\n                    case .increment:\n                        self.scoreState.incrementScore()\n                    case .decrement:\n                        self.scoreState.decrementScore()\n                    @unknown default:\n                        break\n                    }\n                }\n                .accessibilityValue(self.scoreState.accessibilityValue ?? \"\")\n                .environmentObject(scoreState)\n                .airshipOnChangeOf(self.scoreState.selected) { incoming in\n                    updateFormState(selected: incoming)\n                }\n                .onAppear {\n                    updateFormState(selected: self.scoreState.selected)\n                    if self.formState.validationMode == .onDemand {\n                        validatableHelper.subscribe(\n                            forIdentifier: info.properties.identifier,\n                            formState: formState,\n                            initialValue: scoreState.selected,\n                            valueUpdates: scoreState.$selected,\n                            validatables: info.validation\n                        ) { [weak thomasState, weak scoreState] actions in\n                            guard let thomasState, let scoreState else { return }\n                            thomasState.processStateActions(\n                                actions,\n                                formFieldValue: .score(scoreState.selected?.reportingValue)\n                            )\n                        }\n                    }\n                }\n        }\n\n        private func checkValid(value: AirshipJSON?) -> Bool {\n            return value != nil || info.validation.isRequired != true\n        }\n\n        private func makeAttribute(\n            selected: ScoreState.Selected?\n        ) -> [ThomasFormField.Attribute]? {\n            guard\n                let name = info.properties.attributeName,\n                let value = selected?.attributeValue\n            else {\n                return nil\n            }\n\n            return  [\n                ThomasFormField.Attribute(\n                    attributeName: name,\n                    attributeValue: value\n                )\n            ]\n        }\n\n        private func updateFormState(selected: ScoreState.Selected?) {\n            let field: ThomasFormField = if checkValid(value: selected?.reportingValue) {\n                ThomasFormField.validField(\n                    identifier: self.info.properties.identifier,\n                    input: .score(selected?.reportingValue),\n                    result: .init(\n                        value: .score(selected?.reportingValue),\n                        attributes: makeAttribute(selected: selected)\n                    )\n                )\n            } else {\n                ThomasFormField.invalidField(\n                    identifier: self.info.properties.identifier,\n                    input: .score(selected?.reportingValue)\n                )\n            }\n\n            self.formDataCollector.updateField(field, pageID: pageID)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScoreState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@MainActor\nfinal class ScoreState: ObservableObject {\n    \n    @Published\n    private(set) var selected: Selected?\n\n    let entries: [ThomasViewInfo.ScoreToggleLayout]\n\n    init(info: ThomasViewInfo.ScoreController) {\n        self.entries = info.properties.view.extractDescendants { info in\n            return if case let .scoreToggleLayout(score) = info {\n                score\n            } else {\n                nil\n            }\n        }\n    }\n\n    func setSelected(\n        identifier: String,\n        reportingValue: AirshipJSON,\n        attributeValue: ThomasAttributeValue?\n    ) {\n        let incoming = Selected(\n            identifier: identifier,\n            reportingValue: reportingValue,\n            attributeValue: attributeValue\n        )\n        if (incoming != self.selected) {\n            self.selected = incoming\n        }\n    }\n\n    private var currentIndex: Int? {\n        guard let selected else { return nil }\n        return entries.firstIndex { layout in\n            layout.properties.identifier == selected.identifier\n        }\n    }\n\n    var accessibilityValue: String? {\n        guard\n            let currentIndex,\n            entries.isEmpty == false\n        else {\n            return nil\n        }\n\n        return entries[currentIndex].accessible.resolveContentDescription\n    }\n\n    func incrementScore() {\n        guard entries.isEmpty == false else { return }\n        guard let currentIndex else {\n            updateSelected(entry: entries[0])\n            return\n        }\n\n        let nextEntry = min(currentIndex + 1, entries.count - 1)\n        updateSelected(entry: entries[nextEntry])\n    }\n\n    func decrementScore() {\n        guard entries.isEmpty == false else { return }\n        guard let currentIndex else {\n            updateSelected(entry: entries[0])\n            return\n        }\n\n        let nextEntry = max(currentIndex - 1, 0)\n        updateSelected(entry: entries[nextEntry])\n    }\n\n    private func updateSelected(entry: ThomasViewInfo.ScoreToggleLayout) {\n        self.selected = .init(\n            identifier: entry.properties.identifier,\n            reportingValue: entry.properties.reportingValue,\n            attributeValue: entry.properties.attributeValue\n        )\n    }\n\n    struct Selected: ThomasSerializable, Hashable {\n        var identifier: String\n        var reportingValue: AirshipJSON\n        var attributeValue: ThomasAttributeValue?\n    }\n}\n\n//MARK: - State provider\nextension ScoreState: ThomasStateProvider {\n    typealias SnapshotType = Selected?\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return $selected\n            .removeDuplicates()\n            .map(\\.self)\n            .eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> SnapshotType {\n        return selected\n    }\n    \n    func restorePersistentState(_ state: SnapshotType) {\n        self.selected = state\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScoreToggleLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct ScoreToggleLayout: View {\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var scoreState: ScoreState\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .scoreToggleLayout,\n            thomasState: thomasState\n        )\n    }\n\n    private let info: ThomasViewInfo.ScoreToggleLayout\n    private let constraints: ViewConstraints\n\n    init(info: ThomasViewInfo.ScoreToggleLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var isOnBinding: Binding<Bool> {\n        return Binding<Bool>(\n            get: {\n                self.scoreState.selected?.identifier == self.info.properties.identifier\n            },\n            set: {\n                if $0 {\n                    self.scoreState.setSelected(\n                        identifier: self.info.properties.identifier,\n                        reportingValue: self.info.properties.reportingValue,\n                        attributeValue: self.info.properties.attributeValue\n                    )\n                }\n            }\n        )\n    }\n\n    var body: some View {\n        ToggleLayout(\n            isOn: self.isOnBinding,\n            onToggleOn: self.info.properties.onToggleOn,\n            onToggleOff: self.info.properties.onToggleOff\n        ) {\n            ViewFactory.createView(\n                self.info.properties.view,\n                constraints: constraints\n            )\n        }\n        .constraints(self.constraints)\n        .thomasCommon(self.info, formInputID: self.info.properties.identifier)\n        .accessible(\n            self.info.accessible,\n            associatedLabel: associatedLabel,\n            hideIfDescriptionIsMissing: false\n        )\n        .formElement()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ScrollLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Scroll view layout\n\nstruct ScrollLayout: View {\n    \n    /// ScrollLayout model.\n    private let info: ThomasViewInfo.ScrollLayout\n    \n    /// View constraints.\n    private let constraints: ViewConstraints\n    \n    @State private var contentSize: CGSize? = nil\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n    @State private var scrollTask: (String, Task<Void, Never>)?\n    \n    private static let scrollInterval: TimeInterval = 0.01\n    \n    \n    init(info: ThomasViewInfo.ScrollLayout, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n    \n    @ViewBuilder\n    private func makeScrollView(axis: Axis.Set) -> some View {\n        ScrollView(axis) {\n            makeContent()\n                .background(\n                    GeometryReader(content: { contentMetrics -> Color in\n                        let size = contentMetrics.size\n                        DispatchQueue.main.async {\n                            if (self.contentSize != size) {\n                                self.contentSize = size\n                            }\n                        }\n                        return Color.clear\n                    })\n                )\n        }\n#if os(iOS)\n        .scrollDismissesKeyboard(\n            self.thomasEnvironment.focusedID != nil ? .immediately : .never\n        )\n#endif\n    }\n    \n    @ViewBuilder\n    private func makeScrollView() -> some View {\n        let isVertical = self.info.properties.direction == .vertical\n        let axis = isVertical ? Axis.Set.vertical : Axis.Set.horizontal\n        \n        ScrollViewReader { proxy in\n            makeScrollView(axis: axis)\n                .clipped()\n                .airshipOnChangeOf(self.thomasEnvironment.keyboardState) { [weak thomasEnvironment] newValue in\n                    if let focusedID = thomasEnvironment?.focusedID {\n                        switch newValue {\n                        case .hidden:\n                            scrollTask?.1.cancel()\n                        case .displaying(let duration):\n                            let task = Task {\n                                await self.startScrolling(\n                                    scrollID: focusedID,\n                                    proxy: proxy,\n                                    duration: duration\n                                )\n                            }\n                            self.scrollTask = (focusedID, task)\n                        case .visible:\n                            scrollTask?.1.cancel()\n                            proxy.scrollTo(focusedID)\n                        }\n                    } else {\n                        scrollTask?.1.cancel()\n                    }\n                }\n        }\n        .airshipApplyIf(self.shouldApplyFrameSize) {\n            switch (info.properties.direction) {\n            case .vertical:\n                $0.frame(maxHeight: self.contentSize?.height ?? 0)\n            case .horizontal:\n                $0.frame(maxWidth: self.contentSize?.width ?? 0)\n            }\n        }\n    }\n    \n    private var shouldApplyFrameSize: Bool {\n        switch (info.properties.direction) {\n        case .vertical:\n            self.constraints.height == nil\n        case .horizontal:\n            self.constraints.width == nil\n        }\n    }\n    \n    \n    @ViewBuilder\n    func makeContent() -> some View {\n        ZStack {\n            ViewFactory.createView(\n                self.info.properties.view,\n                constraints: self.childConstraints()\n            )\n            .fixedSize(\n                horizontal: self.info.properties.direction == .horizontal,\n                vertical: self.info.properties.direction == .vertical\n            )\n        }\n        .frame(alignment: .topLeading)\n    }\n    \n    @ViewBuilder\n    var body: some View {\n        makeScrollView()\n            .constraints(self.constraints)\n            .thomasCommon(self.info)\n#if os(tvOS)\n            .focusSection()\n#endif\n    }\n    \n    private func childConstraints() -> ViewConstraints {\n        var childConstraints = constraints\n        if self.info.properties.direction == .vertical {\n            childConstraints.height = nil\n            childConstraints.maxHeight = nil\n            childConstraints.isVerticalFixedSize = false\n        } else {\n            childConstraints.width = nil\n            childConstraints.maxWidth = nil\n            childConstraints.isHorizontalFixedSize = false\n        }\n        \n        return childConstraints\n    }\n    \n    @MainActor\n    private func startScrolling(\n        scrollID: String,\n        proxy: ScrollViewProxy,\n        duration: TimeInterval\n    ) async {\n        var remaining = duration\n        repeat {\n            proxy.scrollTo(scrollID, anchor: .center)\n            remaining = remaining - ScrollLayout.scrollInterval\n            try? await Task.sleep(\n                nanoseconds: UInt64(ScrollLayout.scrollInterval * 1_000_000_000)\n            )\n        } while remaining > 0 && !Task.isCancelled\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SearchEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\npublic extension CustomEvent {\n\n    /// Search template\n    enum SearchTemplate: Sendable {\n        /// Search\n        case search\n\n        fileprivate static let templateName: String = \"search\"\n\n        fileprivate var eventName: String {\n            return switch self {\n            case .search: \"search\"\n            }\n        }\n    }\n\n    /// Additional search template properties\n    struct SearchProperties: Encodable, Sendable {\n        /// The event's ID.\n        public var id: String?\n\n        /// The search query.\n        public var query: String?\n\n        /// The total search results\n        public var totalResults: Int?\n\n        /// The event's category.\n        public var category: String?\n\n        /// The event's type.\n        public var type: String?\n\n        /// If the value is a lifetime value or not.\n        public var isLTV: Bool\n\n        public init(\n            id: String? = nil,\n            category: String? = nil,\n            type: String? = nil,\n            isLTV: Bool = false,\n            query: String? = nil,\n            totalResults: Int? = nil\n        ) {\n            self.id = id\n            self.query = query\n            self.totalResults = totalResults\n            self.category = category\n            self.type = type\n            self.isLTV = isLTV\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case id\n            case query\n            case totalResults = \"total_results\"\n            case category\n            case type\n            case isLTV = \"ltv\"\n        }\n    }\n\n    /// Constructs a custom event using the search template.\n    /// - Parameters:\n    ///     - accountTemplate: The search template.\n    ///     - properties: Optional additional properties\n    ///     - encoder: Encoder used to encode the additional properties. Defaults to `CustomEvent.defaultEncoder`.\n    init(\n        searchTemplate: SearchTemplate,\n        properties: SearchProperties = SearchProperties(),\n        encoder: @autoclosure () -> JSONEncoder = CustomEvent.defaultEncoder()\n    ) {\n        self = .init(name: searchTemplate.eventName)\n        self.templateType = SearchTemplate.templateName\n\n        do {\n            try self.setProperties(properties, encoder: encoder())\n        } catch {\n            /// Should never happen so we are just catching the exception and logging\n            AirshipLogger.error(\"Failed to generate event \\(error)\")\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SerialQueue.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n// An actor that will run a task with a result in order.\n/// NOTE: For internal use only. :nodoc:\npublic actor AirshipSerialQueue {\n    private var nextTaskNumber: Int = 0\n    private var currentTaskNumber: Int = 0\n    private var currentTask: Task<Void, Never>?\n\n    public init() {}\n    \n    public func run<T: Sendable>(work: @escaping @Sendable () async throws -> T) async throws -> T {\n        let myTaskNumber = nextTaskNumber\n        nextTaskNumber = nextTaskNumber + 1\n\n        while myTaskNumber != currentTaskNumber {\n            await self.currentTask?.value\n            if myTaskNumber != currentTaskNumber {\n                await Task.yield()\n            }\n        }\n\n        let task: Task<T, any Error> = Task {\n            return try await work()\n        }\n\n        currentTask = Task {\n            let _ = try? await task.value\n            currentTaskNumber += 1\n            self.currentTask = nil\n        }\n\n        return try await task.value\n    }\n\n    public func runSafe<T: Sendable>(work: @escaping @Sendable () async -> T) async -> T {\n        let myTaskNumber = nextTaskNumber\n        nextTaskNumber = nextTaskNumber + 1\n\n        while myTaskNumber != currentTaskNumber {\n            await self.currentTask?.value\n            if myTaskNumber != currentTaskNumber {\n                await Task.yield()\n            }\n        }\n\n        let task: Task<T, Never> = Task {\n            return await work()\n        }\n\n        currentTask = Task {\n            let _ = await task.value\n            currentTaskNumber += 1\n            self.currentTask = nil\n        }\n\n        return await task.value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SessionEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct SessionEvent: Sendable, Equatable {\n    let type: SessionEventType\n    let date: Date\n    let sessionState: SessionState\n\n    enum SessionEventType: Sendable, Equatable {\n        case foregroundInit\n        case backgroundInit\n        case foreground\n        case background\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SessionEventFactory.swift",
    "content": "import Foundation\n\n\nprotocol SessionEventFactoryProtocol: Sendable {\n    @MainActor\n    func make(event: SessionEvent) -> AirshipEvent\n}\n\nstruct SessionEventFactory: SessionEventFactoryProtocol {\n\n    let push: @Sendable () -> any AirshipPush\n\n    init(push: @escaping @Sendable () -> any AirshipPush = Airship.componentSupplier()) {\n        self.push = push\n    }\n\n    @MainActor\n    func make(event: SessionEvent) -> AirshipEvent {\n        AirshipEvents.sessionEvent(sessionEvent: event, push: self.push())\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SessionState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct SessionState: Equatable, Sendable {\n    var sessionID: String\n    var conversionSendID: String?\n    var conversionMetadata: String?\n\n    init(\n        sessionID: String = UUID().uuidString,\n        conversionSendID: String? = nil,\n        conversionMetadata: String? = nil\n    ) {\n        self.sessionID = sessionID\n        self.conversionSendID = conversionSendID\n        self.conversionMetadata = conversionMetadata\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SessionTracker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol SessionTrackerProtocol: Sendable {\n    var sessionState: SessionState { get }\n    var events: AsyncStream<SessionEvent> { get }\n\n    @MainActor\n    func airshipReady()\n\n    @MainActor\n    func launchedFromPush(sendID: String?, metadata: String?)\n}\n\n\nfinal class SessionTracker: SessionTrackerProtocol {\n\n    // Time to wait for the initial app init event when we start tracking\n    // the session. We wait to generate an app init event for either a foreground,\n    // background, or notification response so we can capture the push info for\n    // conversion tracking\n    private static let appInitWaitTime: TimeInterval = 1.0\n\n    private let eventsContinuation: AsyncStream<SessionEvent>.Continuation\n    public let events: AsyncStream<SessionEvent>\n\n    private let date: any AirshipDateProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n    private let isForeground: AirshipMainActorValue<Bool?> = AirshipMainActorValue(nil)\n    private let initialized: AirshipMainActorValue<Bool> = AirshipMainActorValue(false)\n    private let _sessionState: AirshipAtomicValue<SessionState>\n    private let appStateTracker: any AppStateTrackerProtocol\n    private let sessionStateFactory: @Sendable () -> SessionState\n\n    nonisolated var sessionState: SessionState {\n        return _sessionState.value\n    }\n\n    @MainActor\n    init(\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared,\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter.shared,\n        appStateTracker: (any AppStateTrackerProtocol)? = nil,\n        sessionStateFactory: @Sendable @escaping () -> SessionState = { SessionState() }\n    ) {\n        self.date = date\n        self.taskSleeper = taskSleeper\n        self.appStateTracker = appStateTracker ?? AppStateTracker.shared\n        (self.events, self.eventsContinuation) = AsyncStream<SessionEvent>.airshipMakeStreamWithContinuation()\n        self.sessionStateFactory = sessionStateFactory\n        self._sessionState = AirshipAtomicValue(sessionStateFactory())\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(didBecomeActiveNotification),\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n\n        notificationCenter.addObserver(\n            self,\n            selector: #selector(didEnterBackgroundNotification),\n            name: AppStateTracker.didEnterBackgroundNotification,\n            object: nil\n        )\n    }\n\n    @MainActor\n    func launchedFromPush(sendID: String?, metadata: String?) {\n        AirshipLogger.debug(\"Launched from push\")\n\n        self._sessionState.update { state in\n            var state = state\n            state.conversionMetadata = metadata\n            state.conversionSendID = sendID\n            return state\n        }\n        self.ensureInit(isForeground: true) {\n            AirshipLogger.debug(\"App init - launched from push\")\n        }\n    }\n\n    @MainActor\n    func airshipReady() {\n        let date = self.date.now\n        Task { @MainActor in\n            try await self.taskSleeper.sleep(timeInterval: SessionTracker.appInitWaitTime)\n            let isForeground = self.appStateTracker.state != .background\n            self.ensureInit(isForeground: isForeground, date: date) {\n                AirshipLogger.debug(\"App init - AirshipReady\")\n            }\n        }\n    }\n\n    @MainActor\n    private func ensureInit(isForeground: Bool, date: Date? = nil, onInit: () -> Void) {\n        guard self.initialized.value else {\n            self.initialized.set(true)\n            self.isForeground.set(isForeground)\n            self.addEvent(isForeground ? .foregroundInit : .backgroundInit, date: date)\n            onInit()\n            return\n        }\n    }\n\n    @MainActor\n    private func addEvent(_ type: SessionEvent.SessionEventType, date: Date? = nil) {\n        AirshipLogger.debug(\"Added session event \\(type) state: \\(self.sessionState)\")\n\n        self.eventsContinuation.yield(\n            SessionEvent(type: type, date: date ?? self.date.now, sessionState: self.sessionState)\n        )\n    }\n\n    @objc\n    @MainActor\n    private func didBecomeActiveNotification() {\n        AirshipLogger.debug(\"Application did become active.\")\n\n        // Ensure the app init event\n        ensureInit(isForeground: true) {\n            AirshipLogger.debug(\"App init - foreground\")\n        }\n\n        // Background -> foreground\n        if isForeground.value == false {\n            isForeground.set(true)\n            self._sessionState.update { [sessionStateFactory] old in\n                var session = sessionStateFactory()\n                session.conversionMetadata = old.conversionMetadata\n                session.conversionSendID = old.conversionSendID\n                return session\n            }\n            addEvent(.foreground)\n        }\n    }\n\n    @objc\n    @MainActor\n    private func didEnterBackgroundNotification() {\n        AirshipLogger.debug(\"Application entered background\")\n\n        // Ensure the app init event\n        ensureInit(isForeground: false) {\n            AirshipLogger.debug(\"App init - background\")\n        }\n\n        // Foreground -> background\n        if isForeground.value == true {\n            isForeground.set(false)\n            addEvent(.background)\n\n            self._sessionState.value = self.sessionStateFactory()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Shapes.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n\nstruct Shapes {\n\n    @ViewBuilder\n    @MainActor\n    static func shape(\n        info: ThomasShapeInfo,\n        constraints: ViewConstraints,\n        colorScheme: ColorScheme\n    ) -> some View {\n        switch info {\n        case .ellipse(let info):\n            ellipse(\n                info: info,\n                constraints: constraints,\n                colorScheme: colorScheme\n            )\n        case .rectangle(let info):\n            rectangle(\n                info: info,\n                constraints: constraints,\n                colorScheme: colorScheme\n            )\n        }\n    }\n\n    @ViewBuilder\n    private static func rectangle(\n        colorScheme: ColorScheme,\n        border: ThomasBorder?\n    ) -> some View {\n        let strokeColor = border?.strokeColor?.toColor(colorScheme) ?? Color.clear\n        let strokeWidth = border?.strokeWidth ?? 0\n        let cornerRadius = border?.maxRadius ?? 0\n\n        if cornerRadius > 0 {\n            let cornerRadii = CustomCornerRadii(outerRadiiFor: border)\n            let shape = CustomRoundedRectangle(cornerRadii: cornerRadii, style: .continuous)\n            if strokeWidth > 0 {\n                shape.strokeBorder(strokeColor, lineWidth: strokeWidth)\n            } else {\n                shape.fill(Color.clear)\n            }\n        } else {\n            if strokeWidth > 0 {\n                Rectangle()\n                    .strokeBorder(strokeColor, lineWidth: strokeWidth)\n            } else {\n                Rectangle().fill(Color.clear)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private static func rectangleBackground(\n        border: ThomasBorder?,\n        color: Color\n    ) -> some View {\n        let cornerRadius = border?.maxRadius ?? 0\n        if cornerRadius > 0 {\n            let cornerRadii = CustomCornerRadii(outerRadiiFor: border)\n            CustomRoundedRectangle(cornerRadii: cornerRadii, style: .continuous)\n                .fill(color)\n        } else {\n            Rectangle()\n                .fill(color)\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    private static func rectangle(\n        info: ThomasShapeInfo.Rectangle,\n        constraints: ViewConstraints,\n        colorScheme: ColorScheme\n    ) -> some View {\n        let resolvedColor = info.color?.toColor(colorScheme) ?? Color.clear\n        if let border = info.border {\n            rectangle(colorScheme: colorScheme, border: border)\n                .background(\n                    rectangleBackground(border: border, color: resolvedColor)\n                )\n                .aspectRatio(info.aspectRatio ?? 1, contentMode: .fit)\n                .airshipApplyIf(info.scale != nil) { view in\n                    view.constraints(\n                        scaledConstraints(constraints, scale: info.scale)\n                    )\n                }\n                .constraints(constraints)\n        } else {\n            Rectangle()\n                .fill(resolvedColor)\n                .aspectRatio(info.aspectRatio ?? 1, contentMode: .fit)\n                .airshipApplyIf(info.scale != nil) { view in\n                    view.constraints(\n                        scaledConstraints(constraints, scale: info.scale)\n                    )\n                }\n                .constraints(constraints)\n        }\n    }\n\n    private static func scaledConstraints(\n        _ constraints: ViewConstraints,\n        scale: Double?\n    ) -> ViewConstraints {\n        guard let scale = scale else {\n            return constraints\n        }\n\n        var scaled = constraints\n        if let width = scaled.width {\n            scaled.width = width * scale\n        }\n        if let height = scaled.height {\n            scaled.height = height * scale\n        }\n        return scaled\n    }\n\n    @ViewBuilder\n    private static func ellipse(colorScheme: ColorScheme, border: ThomasBorder?)\n        -> some View\n    {\n        let strokeColor = border?.strokeColor?.toColor(colorScheme)\n        let strokeWidth = border?.strokeWidth ?? 0\n\n        if let strokeColor = strokeColor, strokeWidth > 0 {\n            Ellipse().strokeBorder(strokeColor, lineWidth: strokeWidth)\n        } else {\n            Ellipse()\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    private static func ellipse(\n        info: ThomasShapeInfo.Ellipse,\n        constraints: ViewConstraints,\n        colorScheme: ColorScheme\n    ) -> some View {\n        let scaled = scaledConstraints(constraints, scale: info.scale)\n        let color = info.color?.toColor(colorScheme) ?? Color.clear\n        if let border = info.border {\n            ellipse(colorScheme: colorScheme, border: border)\n                .aspectRatio(info.aspectRatio ?? 1, contentMode: .fit)\n                .background(Ellipse().fill(color))\n                .airshipApplyIf(info.scale != nil) { view in\n                    view.constraints(scaled)\n                }\n                .constraints(constraints)\n        } else {\n            Ellipse()\n                .fill(color)\n                .aspectRatio(info.aspectRatio ?? 1, contentMode: .fit)\n                .airshipApplyIf(info.scale != nil) { view in\n                    view.constraints(scaled)\n                }\n                .constraints(constraints)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ShareAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\n/**\n * Shares text using ActivityViewController.\n * *\n * Expected argument value is a `String`.\n *\n * Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`,\n * `ActionSituation.webViewInvocation`, `ActionSituation.manualInvocation`,\n * `ActionSituation.foregroundInteractiveButton`, and `ActionSituation.automation`\n */\n#if os(iOS)\n\nimport UIKit\n\npublic final class ShareAction: AirshipAction {\n    /// Default names - \"share_action\", \"^s\"\n    public static let defaultNames: [String] = [\"share_action\", \"^s\"]\n\n    /// Default predicate - rejects `ActionSituation.foregroundPush`\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.situation != .foregroundPush\n    }\n    \n    public static let name: String = \"share_action\"\n\n    public static let shortName: String = \"^s\"\n    \n    public func accepts(arguments: ActionArguments) async -> Bool {\n        guard arguments.situation != .backgroundPush,\n            arguments.situation != .backgroundInteractiveButton\n        else {\n            return false\n        }\n        return true\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n\n        AirshipLogger.debug(\"Running share action: \\(arguments)\")\n\n        let activityViewController = ActivityViewController(\n            activityItems: [arguments.value.unWrap() as Any],\n            applicationActivities: nil\n        )\n\n        activityViewController.excludedActivityTypes = [\n            .assignToContact,\n            .print,\n            .saveToCameraRoll,\n            .airDrop,\n            .postToFacebook,\n        ]\n\n        let viewController = UIViewController()\n        var window: UIWindow? = Self.presentInNewWindow(\n            viewController,\n            windowLevel: .alert\n        )\n\n        activityViewController.dismissalBlock = {\n            window?.windowLevel = .normal\n            window?.isHidden = true\n            window = nil\n        }\n\n        if let popoverPresentationController = activityViewController.popoverPresentationController {\n            popoverPresentationController.permittedArrowDirections = []\n            // Set the delegate, center the popover on the screen\n            popoverPresentationController.delegate = activityViewController\n            popoverPresentationController.sourceRect = activityViewController.sourceRect()\n            popoverPresentationController.sourceView = viewController.view\n        }\n\n        viewController.present(\n            activityViewController,\n            animated: true\n        )\n\n        return nil\n    }\n\n    @MainActor\n    class func presentInNewWindow(\n        _ rootViewController: UIViewController,\n        windowLevel: UIWindow.Level = .normal\n    ) -> UIWindow? {\n        do {\n            let scene = try AirshipSceneManager.shared.lastActiveScene\n            let window = AirshipWindowFactory.shared.makeWindow(windowScene: scene)\n            window.rootViewController = rootViewController\n            window.windowLevel = windowLevel\n            window.makeKeyAndVisible()\n            return window\n        } catch {\n            AirshipLogger.error(\"\\(error)\")\n            return nil\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SmsLocalePicker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if !os(watchOS)\nstruct SmsLocalePicker: View {\n    \n    @Binding private var selectedLocale: ThomasSMSLocale?\n    private let availableLocales: [ThomasSMSLocale]\n    private let fontSize: Double\n\n    init(\n        selectedLocale: Binding<ThomasSMSLocale?>,\n        availableLocales: [ThomasSMSLocale],\n        fontSize: Double\n    ) {\n        self._selectedLocale = selectedLocale\n        self.availableLocales = availableLocales\n        self.fontSize = fontSize\n    }\n\n    var body: some View {\n        Menu {\n            ForEach(availableLocales, id: \\.countryCode) { locale in\n                Button([\n                    locale.countryCode.toFlagEmoji(),\n                    locale.countryCode,\n                    locale.prefix\n                ].joined(separator: \" \")) {\n                    selectedLocale = locale\n                }\n            }\n        } label: {\n            HStack(spacing: 0) {\n                if let selectedLocale {\n                    Text(selectedLocale.countryCode.toFlagEmoji())\n                        .font(.system(size: fontSize))\n                        .minimumScaleFactor(0.1)\n                        .scaledToFit()\n                        .padding(.trailing, 5)\n                }\n                    \n                Image(systemName: \"chevron.down\")\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: fontSize * 0.75, height: fontSize * 0.75)\n                    .foregroundStyle(.gray)\n            }\n        }\n    }\n}\n\nprivate extension String {\n    private static let base = UnicodeScalar(\"🇦\").value - UnicodeScalar(\"A\").value\n    \n    func toFlagEmoji() -> String {\n        guard self.count == 2 else { return self }\n        \n        return self\n            .uppercased()\n            .unicodeScalars\n            .compactMap({ UnicodeScalar(Self.base + $0.value)?.description })\n            .joined()\n    }\n}\n\n#Preview {\n    let locale: ThomasSMSLocale? = .init(countryCode: \"US\", prefix: \"+1\", registration: nil)\n    SmsLocalePicker(\n        selectedLocale: .constant(locale),\n        availableLocales: [\n            .init(\n                countryCode: \"US\",\n                prefix: \"+1\",\n                registration: nil\n            ),\n            .init(\n                countryCode: \"FR\",\n                prefix: \"+33\",\n                registration: nil\n            ),\n            .init(\n                countryCode: \"MO\",\n                prefix: \"+853\",\n                registration: nil\n            ),\n        ],\n        fontSize: 34\n    )\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/StackImageButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\nstruct StackImageButton : View {\n\n    /// Image Button model.\n    private let info: ThomasViewInfo.StackImageButton\n\n    /// View constraints.\n    private let constraints: ViewConstraints\n\n    @Environment(\\.colorScheme) private var colorScheme\n    @Environment(\\.layoutState) private var layoutState\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n    @EnvironmentObject private var thomasState: ThomasState\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    init(info: ThomasViewInfo.StackImageButton, constraints: ViewConstraints) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    private var resolveItems: [ThomasViewInfo.StackImageButton.Item] {\n        ThomasPropertyOverride.resolveRequired(\n            state: thomasState,\n            overrides: info.overrides?.items,\n            defaultValue: info.properties.items\n        )\n    }\n\n    private var resolvedLocalizedContentDescription: ThomasAccessibleInfo.Localized? {\n        ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: info.overrides?.localizedContentDescription,\n            defaultValue: info.accessible.localizedContentDescription\n        )\n    }\n\n    private var resolvedContentDescription: String? {\n        if let contentDescription = ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: info.overrides?.contentDescription,\n            defaultValue: info.accessible.contentDescription\n        ) {\n            return contentDescription\n        }\n\n        guard let localized = resolvedLocalizedContentDescription else {\n            return nil\n        }\n\n        if let refs = localized.refs {\n            for ref in refs {\n                if let string = AirshipResources.localizedString(key: ref) {\n                    return string\n                }\n            }\n        } else if let ref = localized.ref {\n            if let string = AirshipResources.localizedString(key: ref) {\n                return string\n            }\n        }\n\n        return localized.fallback\n    }\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .imageButton,\n            thomasState: thomasState\n        )\n    }\n\n    @ViewBuilder\n    var body: some View {\n        AirshipButton(\n            identifier: self.info.properties.identifier,\n            reportingMetadata: self.info.properties.reportingMetadata,\n            description: self.resolvedContentDescription,\n            clickBehaviors: self.info.properties.clickBehaviors,\n            eventHandlers: self.info.commonProperties.eventHandlers,\n            actions: self.info.properties.actions,\n            tapEffect: self.info.properties.tapEffect\n        ) {\n            makeInnerButton()\n                .constraints(constraints, fixedSize: true)\n                .thomasCommon(self.info, scope: [.background])\n                .accessible(\n                    self.info.accessible,\n                    associatedLabel: self.associatedLabel,\n                    hideIfDescriptionIsMissing: false\n                )\n                .background(Color.airshipTappableClear)\n        }\n        .thomasCommon(self.info, scope: [.enableBehaviors, .visibility])\n        .environment(\n            \\.layoutState,\n             layoutState.override(\n                buttonState: ButtonState(identifier: self.info.properties.identifier)\n             )\n        )\n        .accessibilityHidden(info.accessible.accessibilityHidden ?? false)\n    }\n\n    @ViewBuilder\n    private func makeInnerButton() -> some View {\n        let items = resolveItems\n        ZStack {\n            ForEach(0..<items.count, id: \\.self) { index in\n                let item = items[index]\n                switch(item) {\n                case .icon(let item):\n                    Icons.icon(info: item.icon, colorScheme: colorScheme)\n                case .imageURL(let info):\n                    ThomasAsyncImage(\n                        url: info.url,\n                        imageLoader: thomasEnvironment.imageLoader,\n                        image: { image, imageSize in\n                            image.fitMedia(\n                                mediaFit: info.mediaFit,\n                                cropPosition: info.cropPosition,\n                                constraints: constraints,\n                                imageSize: imageSize\n                            )\n                        },\n                        placeholder: {\n                            AirshipProgressView()\n                        }\n                    )\n                case .shape(let info):\n                    Shapes.shape(\n                        info: info.shape, constraints: constraints, colorScheme: colorScheme\n                    )\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/StateController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n@MainActor\nstruct StateController: View {\n    private let info: ThomasViewInfo.StateController\n    private let constraints: ViewConstraints\n\n    @EnvironmentObject\n    private var thomasEnvironment: ThomasEnvironment\n    \n    init(\n        info: ThomasViewInfo.StateController,\n        constraints: ViewConstraints\n    ) {\n        self.info = info\n        self.constraints = constraints\n    }\n    \n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: self.constraints,\n            thomasEnvironment: self.thomasEnvironment\n        )\n    }\n    \n    private struct Content: View {\n        let info: ThomasViewInfo.StateController\n        let constraints: ViewConstraints\n\n        @EnvironmentObject\n        private var state: ThomasState\n        \n        @StateObject\n        private var scopedStateCache: ScopedStateCache\n        \n        @StateObject\n        private var mutableState: ThomasState.MutableState\n        \n        init(\n            info: ThomasViewInfo.StateController,\n            constraints: ViewConstraints,\n            thomasEnvironment: ThomasEnvironment\n        ) {\n            self.info = info\n            self.constraints = constraints\n            \n            let scopedStateCache = StateObject(\n                wrappedValue: thomasEnvironment\n                    .retrieveState(\n                        identifier: info.identifier,\n                        create: ScopedStateCache.init\n                    )\n            )\n            \n            self._scopedStateCache = scopedStateCache\n            \n            self._mutableState = StateObject(\n                wrappedValue: ThomasState.MutableState(initialState: info.properties.initialState)\n            )\n            \n        }\n        \n        var body: some View {\n            ViewFactory\n                .createView(self.info.properties.view, constraints: constraints)\n                .constraints(constraints)\n                .thomasCommon(self.info)\n                .environmentObject(\n                    scopedStateCache.getOrCreate {\n                        state.with(mutableState: mutableState)\n                    }\n                )\n                .accessibilityElement(children: .contain)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/StateSubscriptionsModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\ninternal struct StateTriggerModifier: ViewModifier {\n    let triggers: [ThomasStateTriggers]\n    @EnvironmentObject var thomasState: ThomasState\n\n    @State private var triggered: Set<String> = Set()\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        content.airshipOnChangeOf(thomasState.state, initial: true) { state in\n            triggers.forEach { trigger in\n                if triggered.contains(trigger.id), trigger.resetWhenStateMatches?.evaluate(json: state) == true {\n                    triggered.remove(trigger.id)\n                }\n\n                if !triggered.contains(trigger.id), trigger.triggerWhenStateMatches.evaluate(json: state) {\n                    triggered.insert(trigger.id)\n                    if let stateActions = trigger.onTrigger.stateActions {\n                        thomasState.processStateActions(stateActions)\n                    }\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/StoryIndicator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct StoryIndicator: View {\n\n    private static let defaultSpacing = 10.0\n    private static let defaultHeight = 6.0\n    private static let defaultInactiveSegmentScaler = 0.5\n    \n    let info: ThomasViewInfo.StoryIndicator\n    let constraints: ViewConstraints\n\n    @EnvironmentObject var pagerState: PagerState\n    @Environment(\\.colorScheme) var colorScheme\n    \n    func announcePage(info: ThomasViewInfo.StoryIndicator) -> Bool {\n        return info.properties.automatedAccessibilityActions?.contains{ $0.type == .announce} ?? false\n    }\n\n    var style: ThomasViewInfo.StoryIndicator.Style.LinearProgress {\n        switch (self.info.properties.style) {\n        case .linearProgress(let style): return style\n        }\n    }\n\n    @ViewBuilder\n    private func createStoryIndicatorView(\n        progressDelay: Binding<Double>,\n        childConstraints: ViewConstraints\n    ) -> some View {\n        if (self.info.properties.source.type == .pager) {\n\n            let totalDelay = pagerState.pageStates.compactMap{ $0.delay }.reduce(0, +)\n            GeometryReader { metrics in\n                HStack(spacing: style.spacing ?? StoryIndicator.defaultSpacing) {\n                    ForEach(0..<self.pagerState.pageStates.count, id: \\.self) { index in\n                        \n                        let isCurrentPage = self.pagerState.pageIndex == index\n                        let isCurrentPageProgressing = progressDelay.wrappedValue < 1\n                        let delay = (isCurrentPage && isCurrentPageProgressing) ? progressDelay : nil\n                        let currentDelay = pagerState.pageStates[index].delay\n                        \n                        createChild(\n                            index: index,\n                            progressDelay: delay,\n                            constraints: childConstraints\n                        )\n                        .airshipApplyIf(self.style.sizing == .pageDuration && totalDelay > 0) { view in\n                            view.frame(\n                                width: (metrics.size.width * (currentDelay / totalDelay)).safeValue\n                            )\n                        }\n                    }\n                }.airshipApplyIf(announcePage(info: info), transform: { view in\n                    view.accessibilityLabel(String(format: \"ua_pager_progress\".airshipLocalizedString(\n                        fallback: \"Page %@ of %@\"\n                    ), (self.pagerState.pageIndex + 1).airshipLocalizedForVoiceOver(), self.pagerState.pageStates.count.airshipLocalizedForVoiceOver()))\n                })\n            }\n            \n        } else if (self.info.properties.source.type == .currentPage) {\n            createChild(\n                progressDelay: progressDelay,\n                constraints: childConstraints\n            )\n        }\n    }\n    \n    @ViewBuilder\n    private func createChild(\n        index: Int? = nil,\n        progressDelay: Binding<Double>? = nil,\n        constraints: ViewConstraints\n    ) -> some View {\n        let scaler =\n            style.inactiveSegmentScaler ?? StoryIndicator.defaultInactiveSegmentScaler\n        Rectangle()\n            .fill(indicatorColor(index))\n            .airshipApplyIf(progressDelay == nil) { view in\n                view.frame(\n                    height: (constraints.height ?? StoryIndicator.defaultHeight)\n                        * scaler\n                )\n            }\n            .overlayView {\n                if let progressDelay = progressDelay {\n                    GeometryReader { metrics in\n                        Rectangle()\n                            .frame(width: (metrics.size.width * progressDelay.wrappedValue).safeValue)\n                            .foregroundColor(self.style.progressColor.toColor(colorScheme))\n                            .animation(.linear(duration: Pager.animationSpeed), value: self.info)\n                    }\n                }\n            }\n    }\n    \n    var body: some View {\n        let childConstraints = ViewConstraints(\n            height: constraints.height ?? StoryIndicator.defaultHeight\n        )\n\n        let progress = Binding<Double> (\n            get: { self.pagerState.progress },\n            set: { self.pagerState.progress = $0 }\n        )\n        \n        createStoryIndicatorView(\n            progressDelay: progress,\n            childConstraints: childConstraints)\n        .animation(nil, value: self.info)\n        .frame(height: constraints.height ?? StoryIndicator.defaultHeight)\n        .constraints(constraints)\n        .thomasCommon(self.info)\n    }\n    \n    private func indicatorColor(_ index: Int?) -> Color {\n        guard let index = index else {\n            return self.style.progressColor.toColor(colorScheme)\n        }\n        \n        if pagerState.completed && pagerState.progress >= 1 {\n            return self.style.progressColor.toColor(colorScheme)\n        }\n        \n        return index < pagerState.pageIndex ? self.style.progressColor.toColor(colorScheme) : self.style.trackColor.toColor(colorScheme)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubjectExtension.swift",
    "content": "/* Copyright Airship and Contributors */\nimport Combine\nimport Foundation\n\nextension Subject {\n    @MainActor\n    func sendMainActor(_ value: Self.Output) {\n        self.send(value)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubscriptionListAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\nprotocol SubscriptionListAPIClientProtocol: Sendable {\n    func get(\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<[String]>\n}\n\n/// NOTE: For internal use only. :nodoc:\nfinal class SubscriptionListAPIClient: SubscriptionListAPIClientProtocol {\n\n    private static let getPath: String = \"/api/subscription_lists/channels/\"\n\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    convenience init(config: RuntimeConfig) {\n        self.init(\n            config: config,\n            session: config.requestSession\n        )\n    }\n\n    func get(channelID: String) async throws -> AirshipHTTPResponse<[String]> {\n        AirshipLogger.debug(\"Retrieving subscription lists\")\n\n        guard let deviceAPIURL = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"App config not available.\")\n        }\n\n        let url = URL(\n            string:\n                \"\\(deviceAPIURL)\\(SubscriptionListAPIClient.getPath)\\(channelID)\"\n        )\n\n        let request = AirshipRequest(\n            url: url,\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\"\n            ],\n            method: \"GET\",\n            auth: .channelAuthToken(identifier: channelID)\n        )\n\n        return try await session.performHTTPRequest(request) { data, response in\n            \n            AirshipLogger.debug(\"Fetching subscription list finished with response: \\(response)\")\n            \n            guard response.statusCode == 200 else {\n                return nil\n            }\n\n            guard let data = data,\n                  let jsonResponse = try JSONSerialization.jsonObject(\n                    with: data,\n                    options: .allowFragments\n                  ) as? [AnyHashable: Any]\n            else {\n                throw AirshipErrors.error(\"Invalid response body \\(String(describing: data))\")\n            }\n\n            return jsonResponse[\"list_ids\"] as? [String] ?? []\n        }\n\n    }\n\n\n    private func map(subscriptionListsUpdates: [SubscriptionListUpdate])\n        -> [[AnyHashable: Any]]\n    {\n        return subscriptionListsUpdates.map { (list) -> ([AnyHashable: Any]) in\n            switch list.type {\n            case .subscribe:\n                return [\n                    \"action\": \"subscribe\",\n                    \"list_id\": list.listId,\n                ]\n            case .unsubscribe:\n                return [\n                    \"action\": \"unsubscribe\",\n                    \"list_id\": list.listId,\n                ]\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubscriptionListAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// Subscribes to/unsubscribes from a subscription list.\n///\n/// Valid situations: `ActionSituation.foregroundPush`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.backgroundInteractiveButton`, `ActionSituation.manualInvocation`, and\n/// `ActionSituation.automation`\npublic final class SubscriptionListAction: AirshipAction {\n\n    /// Default names - \"subscription_list_action\", \"^sl\", \"edit_subscription_list_action\", \"^sla\"\n    public static let defaultNames: [String] = [\n        \"subscription_list_action\", \"^sl\", \"edit_subscription_list_action\", \"^sla\"\n    ]\n    \n    /// Default predicate - rejects foreground pushes with visible display options\n    public static let defaultPredicate: @Sendable (ActionArguments) -> Bool = { args in\n        return args.metadata[ActionArguments.isForegroundPresentationMetadataKey] as? Bool != true\n    }\n\n    private let channel: @Sendable () -> any AirshipChannel\n    private let contact: @Sendable () -> any AirshipContact\n  \n    private var decoder: JSONDecoder {\n        let decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return decoder\n    }\n    \n    public var _decoder: JSONDecoder {\n        return decoder\n    }\n\n    public convenience init() {\n        self.init(\n            channel: Airship.componentSupplier(),\n            contact: Airship.componentSupplier()\n        )\n    }\n\n\n    init(\n        channel: @escaping @Sendable () -> any AirshipChannel,\n        contact: @escaping @Sendable () -> any AirshipContact\n    ) {\n        self.channel = channel\n        self.contact = contact\n    }\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        guard arguments.situation != .backgroundPush\n        else {\n            return false\n        }\n\n        return true\n    }\n\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        \n        let edits = try parse(args: arguments)\n        applyChannelEdits(edits)\n        applyContactEdits(edits)\n        return arguments.value\n        \n    }\n\n    private func parse(args: ActionArguments) throws -> [Edit] {\n        var edits: Any? = args.value.unWrap()\n\n        let unwrapped = args.value.unWrap()\n        if let value = unwrapped as? [String: Any] {\n            edits = value[\"edits\"]\n        }\n\n        guard let edits = edits else {\n            throw AirshipErrors.error(\n                \"Invalid argument \\(String(describing: args.value))\"\n            )\n        }\n\n        let data = try JSONSerialization.data(\n            withJSONObject: edits,\n            options: []\n        )\n        return try self.decoder.decode([Edit].self, from: data)\n    }\n\n    private func applyContactEdits(_ edits: [Edit]) {\n        let contactEdits = edits.compactMap { (edit: Edit) -> ContactEdit? in\n            if case .contact(let contactEdit) = edit {\n                return contactEdit\n            }\n            return nil\n        }\n\n        if !contactEdits.isEmpty {\n            self.contact()\n                .editSubscriptionLists { editor in\n                    contactEdits.forEach { edit in\n                        switch edit.action {\n                        case .subscribe:\n                            editor.subscribe(edit.list, scope: edit.scope)\n                        case .unsubscribe:\n                            editor.unsubscribe(edit.list, scope: edit.scope)\n                        }\n                    }\n                }\n        }\n    }\n\n    private func applyChannelEdits(_ edits: [Edit]) {\n        let channelEdits = edits.compactMap { (edit: Edit) -> ChannelEdit? in\n            if case .channel(let channelEdit) = edit {\n                return channelEdit\n            }\n            return nil\n        }\n\n        if !channelEdits.isEmpty {\n            self.channel()\n                .editSubscriptionLists { editor in\n                    channelEdits.forEach { edit in\n                        switch edit.action {\n                        case .subscribe: editor.subscribe(edit.list)\n                        case .unsubscribe: editor.unsubscribe(edit.list)\n                        }\n                    }\n                }\n        }\n    }\n\n    internal enum SubscriptionAction: String, Decodable {\n        case subscribe\n        case unsubscribe\n    }\n\n    internal enum SubscriptionType: String, Decodable {\n        case channel\n        case contact\n    }\n\n    internal struct ChannelEdit: Decodable {\n        let list: String\n        let action: SubscriptionAction\n\n        enum CodingKeys: String, CodingKey {\n            case list = \"list\"\n            case action = \"action\"\n        }\n    }\n\n    internal struct ContactEdit: Decodable {\n        let list: String\n        let action: SubscriptionAction\n        let scope: ChannelScope\n\n        enum CodingKeys: String, CodingKey {\n            case list = \"list\"\n            case action = \"action\"\n            case scope = \"scope\"\n        }\n    }\n\n    enum Edit: Decodable {\n        case channel(ChannelEdit)\n        case contact(ContactEdit)\n\n        enum CodingKeys: String, CodingKey {\n            case type = \"type\"\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let type = try container.decode(\n                SubscriptionType.self,\n                forKey: .type\n            )\n            let singleValueContainer = try decoder.singleValueContainer()\n\n            switch type {\n            case .channel:\n                self = .channel(\n                    (try singleValueContainer.decode(ChannelEdit.self))\n                )\n            case .contact:\n                self = .contact(\n                    (try singleValueContainer.decode(ContactEdit.self))\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubscriptionListEdit.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents an edit made to a subscription list through the SDK.\npublic enum SubscriptionListEdit: Equatable {\n    /// Subscribed\n    case subscribe(String)\n\n    /// Unsubscribed\n    case unsubscribe(String)\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubscriptionListEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Subscription list editor.\npublic class SubscriptionListEditor {\n\n    private var subscriptionListUpdates: [SubscriptionListUpdate] = []\n    private let completionHandler: ([SubscriptionListUpdate]) -> Void\n\n    init(completionHandler: @escaping ([SubscriptionListUpdate]) -> Void) {\n        self.completionHandler = completionHandler\n    }\n\n    /**\n     * Subscribes to a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     */\n    public func subscribe(_ subscriptionListID: String) {\n        let subscriptionListUpdate = SubscriptionListUpdate(\n            listId: subscriptionListID,\n            type: .subscribe\n        )\n        subscriptionListUpdates.append(subscriptionListUpdate)\n    }\n\n    /**\n     * Unsubscribes from a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     */\n    public func unsubscribe(_ subscriptionListID: String) {\n        let subscriptionListUpdate = SubscriptionListUpdate(\n            listId: subscriptionListID,\n            type: .unsubscribe\n        )\n        subscriptionListUpdates.append(subscriptionListUpdate)\n    }\n\n    /**\n     * Applies subscription list changes.\n     */\n    public func apply() {\n        completionHandler(AudienceUtils.collapse(subscriptionListUpdates))\n        subscriptionListUpdates.removeAll()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubscriptionListProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n/**\n * Subscription list provider protocol for receiving contact updates.\n * @note For internal use only. :nodoc:\n */\nprotocol SubscriptionListProviderProtocol: Sendable {\n    func subscriptionList(stableContactIDUpdates: AsyncStream<String>) -> AsyncStream<SubscriptionListResult>\n    func fetch(contactID: String) async throws -> [String: [ChannelScope]]\n    func refresh() async\n}\n\nfinal class SubscriptionListProvider: SubscriptionListProviderProtocol {\n\n    private let actor: BaseCachingRemoteDataProvider<SubscriptionListResult, ContactAudienceOverrides>\n    \n    init(\n        audienceOverrides: any AudienceOverridesProvider,\n        apiClient: any ContactSubscriptionListAPIClientProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared,\n        maxChannelListCacheAgeSeconds: TimeInterval = 600,\n        privacyManager: any AirshipPrivacyManager\n    ) {\n        \n        self.actor = BaseCachingRemoteDataProvider(\n            remoteFetcher: { contactID in\n                return try await apiClient\n                    .fetchSubscriptionLists(contactID: contactID)\n                    .map(onMap: { response in\n                        guard let result = response.result else {\n                            return nil\n                        }\n                        \n                        return .success(result)\n                    })\n            },\n            overridesProvider: { identifier in\n                return await audienceOverrides.contactOverrideUpdates(contactID: identifier)\n            },\n            overridesApplier: { result, overrides in\n                guard\n                    case .success(let list) = result\n                else {\n                    return result\n                }\n                \n                let updated = AudienceUtils.applySubscriptionListsUpdates(list, updates: overrides.subscriptionLists)\n                return .success(updated)\n            },\n            isEnabled: { privacyManager.isEnabled(.contacts) },\n            date: date,\n            taskSleeper: taskSleeper,\n            cacheTtl: maxChannelListCacheAgeSeconds\n        )\n    }\n    \n    func subscriptionList(stableContactIDUpdates: AsyncStream<String>) -> AsyncStream<SubscriptionListResult> {\n        return actor.updates(identifierUpdates: stableContactIDUpdates)\n    }\n    \n    func refresh() async {\n        await actor.refresh()\n    }\n    \n    func fetch(contactID: String) async throws -> [String: [ChannelScope]] {\n        var stream = actor.updates(identifierUpdates: AsyncStream { continuation in\n            continuation.yield(contactID)\n            continuation.finish()\n        })\n            .makeAsyncIterator()\n        \n        guard let result = await stream.next() else {\n            throw AirshipErrors.error(\"Failed to get subscription list\")\n        }\n        \n        switch result {\n        case .fail(let error): throw error\n        case .success(let list): return list\n        }\n    }\n}\n\nenum SubscriptionListResult: Equatable, Sendable, Hashable, CachingRemoteDataProviderResult {\n    static func error(_ error: CachingRemoteDataError) -> any CachingRemoteDataProviderResult {\n        return SubscriptionListResult.fail(error)\n    }\n    \n    case success([String: [ChannelScope]])\n    case fail(CachingRemoteDataError)\n\n    public var subscriptionList: [String: [ChannelScope]] {\n        get throws {\n            switch(self) {\n            case .fail(let error): throw error\n            case .success(let list): return list\n            }\n        }\n    }\n\n    public var isSuccess: Bool {\n        switch(self) {\n        case .fail(_): return false\n        case .success(_): return true\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/SubscriptionListUpdate.swift",
    "content": "import Foundation\n\n/// NOTE: For internal use only. :nodoc:\nenum SubscriptionListUpdateType: Int, Codable, Equatable, Sendable {\n    case subscribe\n    case unsubscribe\n}\n\n/// NOTE: For internal use only. :nodoc:\nstruct SubscriptionListUpdate: Codable, Equatable, Sendable {\n    let listId: String\n    let type: SubscriptionListUpdateType\n}\n\n\nextension SubscriptionListUpdate {\n    var operation: SubscriptionListOperation {\n        switch self.type {\n        case .subscribe:\n            return SubscriptionListOperation(\n                action: .subscribe,\n                listID: self.listId\n            )\n        case .unsubscribe:\n            return SubscriptionListOperation(\n                action: .unsubscribe,\n                listID: self.listId\n            )\n        }\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\n// Used by ChannelBulkAPIClient and DeferredAPIClient\nstruct SubscriptionListOperation: Encodable {\n    enum SubscriptionAction: String, Encodable {\n        case subscribe\n        case unsubscribe\n    }\n\n    var action: SubscriptionAction\n    var listID: String\n\n    enum CodingKeys: String, CodingKey {\n        case action = \"action\"\n        case listID = \"list_id\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TagActionMutation.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Tag mutations from `AddTagsAction` and `RemoveTagsAction`\npublic enum TagActionMutation: Sendable, Equatable {\n    \n    /// Represents a mutation for applying a set of tags to a channel.\n    /// Associated value: A set of unique strings representing the tags to be applied to the channel\n    case channelTags([String])\n    \n    /// Represents a mutation for applying tag group changes to the channel.\n    /// Associated value: A map of tag group to tags.\n    case channelTagGroups([String: [String]])\n    \n    /// Represents a mutation for applying tag group changes to the contact.\n    /// Associated value: A map of tag group to tags.\n    case contactTagGroups([String: [String]])\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TagEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Tag editor.\npublic class TagEditor {\n\n    typealias TagApplicator = ([String]) -> [String]\n\n    private var tagOperations: [([String]) -> [String]] = []\n    private let onApply: (TagApplicator) -> Void\n\n    init(onApply: @escaping (TagApplicator) -> Void) {\n        self.onApply = onApply\n    }\n\n    /**\n     * Adds tags.\n     * - Parameters:\n     *   - tags: The tags.\n     */\n    public func add(_ tags: [String]) {\n        let normalizedTags = AudienceUtils.normalizeTags(tags)\n        self.tagOperations.append({ incoming in\n            var mutable = incoming\n            mutable.append(contentsOf: normalizedTags)\n            return mutable\n        })\n    }\n\n    /**\n     * Adds a single tag.\n     * - Parameters:\n     *   - tag: The tag.\n     */\n    public func add(_ tag: String) {\n        self.add([tag])\n    }\n\n    /**\n     * Removes tags from the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     */\n    public func remove(_ tags: [String]) {\n        let normalizedTags = AudienceUtils.normalizeTags(tags)\n        self.tagOperations.append({ incoming in\n            var mutable = incoming\n            mutable.removeAll(where: { normalizedTags.contains($0) })\n            return mutable\n        })\n    }\n\n    /**\n     * Removes a single tag.\n     * - Parameters:\n     *   - tag: The tag.\n     */\n    public func remove(_ tag: String) {\n        self.remove([tag])\n    }\n\n    /**\n     * Sets tags on the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     */\n    public func set(_ tags: [String]) {\n        let normalizedTags = AudienceUtils.normalizeTags(tags)\n        self.tagOperations.append({ incoming in\n            return normalizedTags\n        })\n    }\n\n    /**\n     * Clears tags.\n     */\n    public func clear() {\n        self.tagOperations.append({ _ in\n            return []\n        })\n    }\n\n    /**\n     * Applies tag changes.\n     */\n    public func apply() {\n        let operations = tagOperations\n        tagOperations.removeAll()\n        self.onApply({ tags in\n            return operations.reduce(tags) { result, operation in\n                return operation(result)\n            }\n        })\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TagGroupMutations.swift",
    "content": "import Foundation\n\n/// Used to migrate data to TagGroupUpdate in contact and channels.\n@objc(UATagGroupsMutation)\nclass TagGroupsMutation: NSObject, NSSecureCoding {\n    static let codableAddKey: String = \"add\"\n    static let codableRemoveKey: String = \"remove\"\n    static let codableSetKey: String = \"set\"\n\n    public static let supportsSecureCoding: Bool = true\n\n    private let adds: [String: Set<String>]?\n    private let removes: [String: Set<String>]?\n    private let sets: [String: Set<String>]?\n\n    public init(\n        adds: [String: Set<String>]?,\n        removes: [String: Set<String>]?,\n        sets: [String: Set<String>]?\n    ) {\n        self.adds = adds\n        self.removes = removes\n        self.sets = sets\n        super.init()\n    }\n\n    public var tagGroupUpdates: [TagGroupUpdate] {\n        var updates: [TagGroupUpdate] = []\n\n        self.adds?\n            .forEach {\n                updates.append(\n                    TagGroupUpdate(\n                        group: $0.key,\n                        tags: Array($0.value),\n                        type: .add\n                    )\n                )\n            }\n\n        self.removes?\n            .forEach {\n                updates.append(\n                    TagGroupUpdate(\n                        group: $0.key,\n                        tags: Array($0.value),\n                        type: .remove\n                    )\n                )\n            }\n\n        self.sets?\n            .forEach {\n                updates.append(\n                    TagGroupUpdate(\n                        group: $0.key,\n                        tags: Array($0.value),\n                        type: .set\n                    )\n                )\n            }\n\n        return updates\n    }\n\n    func encode(with coder: NSCoder) {\n        coder.encode(self.adds, forKey: TagGroupsMutation.codableAddKey)\n        coder.encode(self.removes, forKey: TagGroupsMutation.codableRemoveKey)\n        coder.encode(self.sets, forKey: TagGroupsMutation.codableSetKey)\n    }\n\n    required init?(coder: NSCoder) {\n        self.adds =\n            coder.decodeObject(\n                of: [NSDictionary.self, NSString.self, NSSet.self],\n                forKey: TagGroupsMutation.codableAddKey\n            ) as? [String: Set<String>]\n        self.removes =\n            coder.decodeObject(\n                of: [NSDictionary.self, NSString.self, NSSet.self],\n                forKey: TagGroupsMutation.codableRemoveKey\n            ) as? [String: Set<String>]\n        self.sets =\n            coder.decodeObject(\n                of: [NSDictionary.self, NSString.self, NSSet.self],\n                forKey: TagGroupsMutation.codableSetKey\n            ) as? [String: Set<String>]\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TagGroupUpdate.swift",
    "content": "import Foundation\n\n/// NOTE: For internal use only. :nodoc:\nenum TagGroupUpdateType: Int, Codable, Equatable, Hashable, Sendable {\n    case add\n    case remove\n    case set\n}\n\n/// NOTE: For internal use only. :nodoc:\nstruct TagGroupUpdate: Codable, Sendable, Equatable, Hashable {\n    let group: String\n    let tags: [String]\n    let type: TagGroupUpdateType\n}\n\n\n/// NOTE: For internal use only. :nodoc:\n// Used by ChannelBulkAPIClient and DeferredAPIClient\nstruct TagGroupOverrides: Encodable, Sendable {\n    var add: [String: [String]]? = nil\n    var remove: [String: [String]]? = nil\n    var set: [String: [String]]? = nil\n\n    init(add: [String : [String]]? = nil, remove: [String : [String]]? = nil, set: [String : [String]]? = nil) {\n        self.add = add\n        self.remove = remove\n        self.set = set\n    }\n\n    static func from(updates: [TagGroupUpdate]?) -> TagGroupOverrides? {\n        guard let updates = updates, !updates.isEmpty else {\n            return nil\n        }\n        var overrides = TagGroupOverrides()\n        AudienceUtils.collapse(updates).forEach { tagUpdate in\n            switch tagUpdate.type {\n            case .set:\n                if overrides.set == nil {\n                    overrides.set = [:]\n                }\n                overrides.set?[tagUpdate.group] = tagUpdate.tags\n            case .remove:\n                if overrides.remove == nil {\n                    overrides.remove = [:]\n                }\n                overrides.remove?[tagUpdate.group] = tagUpdate.tags\n            case .add:\n                if overrides.add == nil {\n                    overrides.add = [:]\n                }\n                overrides.add?[tagUpdate.group] = tagUpdate.tags\n            }\n        }\n\n        return overrides\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TagGroupsEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Tag groups editor.\npublic class TagGroupsEditor {\n\n    private var tagUpdates: [TagGroupUpdate] = []\n    private var allowDeviceTagGroup: Bool = false\n    private let completionHandler: ([TagGroupUpdate]) -> Void\n\n    init(\n        allowDeviceTagGroup: Bool,\n        completionHandler: @escaping ([TagGroupUpdate]) -> Void\n    ) {\n        self.allowDeviceTagGroup = allowDeviceTagGroup\n        self.completionHandler = completionHandler\n    }\n\n    convenience init(completionHandler: @escaping ([TagGroupUpdate]) -> Void) {\n        self.init(\n            allowDeviceTagGroup: false,\n            completionHandler: completionHandler\n        )\n    }\n\n    /**\n     * Adds tags to the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - group: The tag group.\n     */\n    public func add(_ tags: [String], group: String) {\n        let group = AudienceUtils.normalizeTagGroup(group)\n        let tags = AudienceUtils.normalizeTags(tags)\n\n        guard isValid(group: group) else { return }\n        guard !tags.isEmpty else { return }\n\n        let update = TagGroupUpdate(group: group, tags: tags, type: .add)\n        tagUpdates.append(update)\n    }\n\n    /**\n     * Removes tags from the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - group: The tag group.\n     */\n    public func remove(_ tags: [String], group: String) {\n        let group = AudienceUtils.normalizeTagGroup(group)\n        let tags = AudienceUtils.normalizeTags(tags)\n\n        guard isValid(group: group) else { return }\n        guard !tags.isEmpty else { return }\n\n        let update = TagGroupUpdate(group: group, tags: tags, type: .remove)\n        tagUpdates.append(update)\n    }\n\n    /**\n     * Sets tags on the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - group: The tag group.\n     */\n    public func set(_ tags: [String], group: String) {\n        let group = AudienceUtils.normalizeTagGroup(group)\n        let tags = AudienceUtils.normalizeTags(tags)\n\n        guard isValid(group: group) else { return }\n\n        let update = TagGroupUpdate(group: group, tags: tags, type: .set)\n        tagUpdates.append(update)\n    }\n\n    /**\n     * Applies tag changes.\n     */\n    public func apply() {\n        self.completionHandler(tagUpdates)\n        tagUpdates.removeAll()\n    }\n\n    private func isValid(group: String) -> Bool {\n        guard !group.isEmpty else {\n            AirshipLogger.error(\"Invalid tag group \\(group)\")\n            return false\n        }\n\n        if group == \"ua_device\" && !allowDeviceTagGroup {\n            AirshipLogger.error(\"Unable to modify device tag group\")\n            return false\n        }\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TagsActionArgs.swift",
    "content": "import Foundation\n\nstruct TagsActionsArgs: Decodable {\n    let channel: [String: [String]]?\n    let namedUser: [String: [String]]?\n    let device: [String]?\n\n    enum CodingKeys: String, CodingKey {\n        case channel = \"channel\"\n        case namedUser = \"named_user\"\n        case device = \"device\"\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TextInput.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\nstruct TextInput: View {\n\n\n    private let info: ThomasViewInfo.TextInput\n    private let constraints: ViewConstraints\n\n    @Environment(\\.pageIdentifier) private var pageID\n    @Environment(\\.sizeCategory) private var sizeCategory\n    @Environment(\\.colorScheme) private var colorScheme\n\n    @EnvironmentObject private var formDataCollector: ThomasFormDataCollector\n    @EnvironmentObject private var formState: ThomasFormState\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n    @EnvironmentObject private var thomasState: ThomasState\n    @EnvironmentObject private var validatableHelper: ValidatableHelper\n    @Environment(\\.thomasAssociatedLabelResolver) private var associatedLabelResolver\n\n    @State private var isEditing: Bool = false\n    @StateObject private var viewModel: ViewModel\n\n    private var associatedLabel: String? {\n        associatedLabelResolver?.labelFor(\n            identifier: info.properties.identifier,\n            viewType: .textInput,\n            thomasState: thomasState\n        )\n    }\n\n\n    private var scaledFontSize: Double {\n        AirshipFont.scaledSize(self.info.properties.textAppearance.fontSize)\n    }\n\n    init(\n        info: ThomasViewInfo.TextInput,\n        constraints: ViewConstraints\n    ) {\n        self.info = info\n        self.constraints = constraints\n\n        self._viewModel = StateObject(\n            wrappedValue: ViewModel(\n                inputProperties: info.properties,\n                isRequired: info.validation.isRequired ?? false\n            )\n        )\n    }\n\n#if !os(watchOS) && !os(macOS)\n    private var keyboardType: UIKeyboardType {\n        switch self.info.properties.inputType {\n        case .email:\n            return .emailAddress\n        case .number:\n            return .decimalPad\n        case .text:\n            return .default\n        case .textMultiline:\n            return .default\n        case .sms:\n            return .phonePad\n        }\n    }\n#endif\n\n    @ViewBuilder\n    private func makeTextEditor() -> some View {\n        AirshipTextField(\n            info: self.info,\n            constraints: constraints,\n            alignment: self.textFieldAlignment,\n            binding: self.$viewModel.input,\n            isEditing: $isEditing\n        )\n    }\n\n    var showSMSPicker: Bool {\n        guard\n            self.info.properties.inputType == .sms,\n            self.viewModel.availableLocales != nil\n        else {\n            return false\n        }\n        return true\n    }\n\n    @ViewBuilder\n    private func smsPicker() -> some View {\n#if !os(watchOS)\n        SmsLocalePicker(\n            selectedLocale: $viewModel.selectedSMSLocale,\n            availableLocales: self.viewModel.availableLocales ?? [],\n            fontSize: scaledFontSize\n        )\n#else\n        EmptyView()\n#endif\n    }\n\n    var textFieldAlignment: Alignment {\n        return switch(self.info.properties.inputType) {\n        case .email, .text, .number, .sms: .center\n        case .textMultiline: .top\n        }\n    }\n\n    var placeHolderAlignment: Alignment {\n        let textAlignment = self.info.properties.textAppearance.alignment ?? .start\n\n        let horizontalAlignment: HorizontalAlignment = switch(textAlignment) {\n        case .start: .leading\n        case .end: .trailing\n        case .center: .center\n        }\n\n        return Alignment(\n            horizontal: horizontalAlignment,\n            vertical: self.textFieldAlignment.vertical\n        )\n    }\n\n    @ViewBuilder\n    private func textInputContent() -> some View {\n        ZStack {\n            if let hint = self.info.properties.placeholder ?? self.viewModel.selectedSMSLocale?.prefix {\n                Text(hint)\n                    .textAppearance(placeHolderTextAppearance(), colorScheme: colorScheme)\n                    .padding(5)\n                    .constraints(constraints, alignment: self.placeHolderAlignment)\n                    .opacity(self.viewModel.input.isEmpty && !isEditing ? 1 : 0)\n                    .animation(.linear(duration: 0.1), value: self.info.properties.placeholder)\n                    .accessibilityHidden(true)\n            }\n            HStack {\n                makeTextEditor()\n#if !os(watchOS) && !os(macOS)\n                    .airshipApplyIf(self.info.properties.inputType == .email) { view in\n                        view.textInputAutocapitalization(.never)\n                    }\n#endif\n                    .id(self.info.properties.identifier)\n\n                if let resolvedIconEndInfo = resolvedIconEndInfo?.icon {\n                    let size = scaledFontSize\n                    Icons.icon(info: resolvedIconEndInfo, colorScheme: colorScheme, resizable: false)\n                        .frame(maxWidth: size, maxHeight: size)\n                        .padding(5)\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    var body: some View {\n        HStack {\n            if showSMSPicker {\n                smsPicker()\n                    .padding(.vertical, 5)\n                    .padding(.leading, 5)\n            }\n            \n            textInputContent()\n        }\n#if !os(watchOS) && !os(macOS)\n        .keyboardType(keyboardType)\n        .airshipApplyIf(self.info.properties.inputType == .email) { view in\n            view.textContentType(.emailAddress)\n        }\n        .airshipApplyIf(self.info.properties.inputType == .sms) { view in\n            view.textContentType(.telephoneNumber)\n        }\n#endif\n        .thomasCommon(self.info)\n        .accessible(\n            self.info.accessible,\n            associatedLabel: associatedLabel,\n            hideIfDescriptionIsMissing: false\n        )\n        .formElement()\n        .onAppear {\n            let (value, locale) = restoredValue()\n            viewModel.setInitialValue(value, locale: locale)\n            validatableHelper.subscribe(\n                forIdentifier: info.properties.identifier,\n                formState: formState,\n                initialValue: self.viewModel.input,\n                valueUpdates: self.viewModel.$input,\n                validatables: info.validation\n            ) { [weak thomasState, weak viewModel] actions in\n                guard let thomasState, let viewModel else { return }\n                thomasState.processStateActions(\n                    actions,\n                    formFieldValue: viewModel.formField?.input\n                )\n            }\n        }\n        .onReceive(self.viewModel.$formField) { field in\n            guard let field else { return }\n            self.formDataCollector.updateField(field, pageID: pageID)\n        }\n    }\n\n    private var resolvedIconEndInfo: ThomasViewInfo.TextInput.IconEndInfo? {\n        return ThomasPropertyOverride.resolveOptional(\n            state: thomasState,\n            overrides: self.info.overrides?.iconEnd,\n            defaultValue: self.info.properties.iconEnd ?? nil\n        )\n    }\n\n    private func handleStateActions(_ stateActions: [ThomasStateAction]) {\n        thomasState.processStateActions(\n            stateActions,\n            formFieldValue: self.viewModel.formField?.input\n        )\n    }\n    \n    private func restoredValue() -> (String?, ThomasSMSLocale?) {\n        let identifier = self.info.properties.identifier\n        switch(self.info.properties.inputType, formState.fieldValue(identifier: identifier)) {\n        case(.email, .email(let value)),\n            (.number, .text(let value)),\n            (.text, .text(let value)),\n            (.textMultiline, .text(let value)):\n            return (value, nil)\n        case (.sms, .sms(let value, let locale)):\n            return (value, locale)\n        default:\n            return (nil, nil)\n        }\n    }\n\n    private func placeHolderTextAppearance() -> ThomasTextAppearance {\n        guard let color = self.info.properties.textAppearance.placeHolderColor else {\n            return self.info.properties.textAppearance\n        }\n\n        var appearance = self.info.properties.textAppearance\n        appearance.color = color\n        return appearance\n    }\n\n    @MainActor\n    fileprivate final class ViewModel: ObservableObject {\n        private let inputProperties: ThomasViewInfo.TextInput.Properties\n        private let isRequired: Bool\n\n        private var inputValidator: (any AirshipInputValidation.Validator)? {\n            guard Airship.isFlying else { return nil }\n            return Airship.inputValidator\n        }\n\n        @Published\n        var formField: ThomasFormField?\n        private var lastInput: String?\n        \n        @Published\n        var selectedSMSLocale: ThomasSMSLocale?\n        let availableLocales: [ThomasSMSLocale]?\n\n        @Published\n        var input: String = \"\" {\n            didSet {\n                if !self.input.isEmpty, !didEdit {\n                    didEdit = true\n                }\n                self.updateFormData()\n            }\n        }\n\n        @Published\n        var didEdit: Bool = false\n\n        init(\n            inputProperties: ThomasViewInfo.TextInput.Properties,\n            isRequired: Bool,\n        ) {\n            self.inputProperties = inputProperties\n            self.isRequired = isRequired\n            self.availableLocales = inputProperties.smsLocales\n            self.selectedSMSLocale = inputProperties.smsLocales?.first\n        }\n        \n        func setInitialValue(_ value: String?, locale: ThomasSMSLocale?) {\n            guard self.formField == nil else { return }\n            \n            if\n                let locale,\n                inputProperties.smsLocales?.contains(where: { $0 == locale }) == true {\n                self.selectedSMSLocale = locale\n            }\n            \n            self.formField = self.makeFormField(input: value ?? \"\")\n            self.input = value ?? \"\"\n        }\n\n        private func updateFormData() {\n            guard lastInput != self.input else {\n                return\n            }\n            self.lastInput = self.input\n            self.formField = self.makeFormField(input: input)\n        }\n\n        private func makeAttributes(value: String) -> [ThomasFormField.Attribute]? {\n            guard\n                !value.isEmpty,\n                let name = inputProperties.attributeName\n            else {\n                return nil\n            }\n\n            return [\n                ThomasFormField.Attribute(\n                    attributeName: name,\n                    attributeValue: .string(value)\n                )\n            ]\n        }\n\n        private func makeChannels(\n            value: String,\n            selectedSMSLocale: ThomasSMSLocale? = nil\n        ) -> [ThomasFormField.Channel]? {\n            guard !value.isEmpty else { return nil }\n\n            switch(self.inputProperties.inputType) {\n            case .email:\n                return if let options = self.inputProperties.emailRegistration {\n                    [.email(value, options)]\n                } else {\n                    nil\n                }\n            case .sms:\n                return if let options = selectedSMSLocale?.registration {\n                    [.sms(value, options)]\n                } else {\n                    nil\n                }\n            case .number, .text, .textMultiline: return nil\n            }\n        }\n\n        private func makeFormField(input: String) -> ThomasFormField {\n            let trimmed = input.trimmingCharacters(in: .whitespacesAndNewlines)\n\n            switch(self.inputProperties.inputType) {\n\n            case .email:\n                guard !trimmed.isEmpty else {\n                    return if isRequired {\n                        ThomasFormField.invalidField(\n                            identifier: inputProperties.identifier,\n                            input: .email(input)\n                        )\n                    } else {\n                        ThomasFormField.validField(\n                            identifier: inputProperties.identifier,\n                            input: .email(input),\n                            result: .init(value: .email(nil))\n                        )\n                    }\n                }\n\n                let request: AirshipInputValidation.Request = .email(\n                    AirshipInputValidation.Request.Email(\n                        rawInput: input\n                    )\n                )\n\n                return ThomasFormField.asyncField(\n                    identifier: inputProperties.identifier,\n                    input: .email(input),\n                    processDelay: 1.5\n                ) { [inputValidator, weak self] in\n\n                    guard let inputValidator else { return .invalid }\n\n                    let result = try await inputValidator.validateRequest(\n                        request\n                    )\n\n                    guard let self else { return .invalid }\n\n                    switch (result) {\n                    case .invalid:\n                        return .invalid\n                    case .valid(let address):\n                        return .valid(\n                            .init(\n                                value: .email(address),\n                                channels: self.makeChannels(value: address),\n                                attributes: self.makeAttributes(value: address)\n                            )\n                        )\n                    }\n                }\n            case .sms:\n\n                guard !trimmed.isEmpty, let selectedSMSLocale else {\n                    return if isRequired {\n                        ThomasFormField.invalidField(\n                            identifier: inputProperties.identifier,\n                            input: .sms(input, selectedSMSLocale)\n                        )\n                    } else {\n                        ThomasFormField.validField(\n                            identifier: inputProperties.identifier,\n                            input: .sms(input, selectedSMSLocale),\n                            result: .init(value: .sms(nil, nil))\n                        )\n                    }\n                }\n\n                let request: AirshipInputValidation.Request = .sms(\n                    AirshipInputValidation.Request.SMS(\n                        rawInput: input,\n                        validationOptions: .prefix(prefix: selectedSMSLocale.prefix),\n                        validationHints: .init(\n                            minDigits: selectedSMSLocale.validationHints?.minDigits,\n                            maxDigits: selectedSMSLocale.validationHints?.maxDigits\n                        )\n                    )\n                )\n\n                return ThomasFormField.asyncField(\n                    identifier: inputProperties.identifier,\n                    input: .sms(input, selectedSMSLocale)\n                ) { [weak self, inputValidator] in\n                    guard let inputValidator else { return .invalid }\n\n                    let result = try await inputValidator.validateRequest(request)\n                    guard let self else { return .invalid }\n\n                    switch (result) {\n                    case .invalid:\n                        return .invalid\n                    case .valid(let address):\n                        return .valid(\n                            .init(\n                                value: .sms(address, selectedSMSLocale),\n                                channels: self.makeChannels(\n                                    value: address,\n                                    selectedSMSLocale: selectedSMSLocale\n                                ),\n                                attributes: self.makeAttributes(value: address)\n                            )\n                        )\n                    }\n                }\n            case .number, .text, .textMultiline:\n                return if trimmed.isEmpty, isRequired {\n                    ThomasFormField.invalidField(\n                        identifier: inputProperties.identifier,\n                        input: .text(input)\n                    )\n                } else {\n                    ThomasFormField.validField(\n                        identifier: inputProperties.identifier,\n                        input: .text(input),\n                        result: .init(\n                            value: .text(trimmed),\n                            attributes: self.makeAttributes(value: trimmed)\n                        )\n                    )\n                }\n            }\n        }\n    }\n}\n\nstruct AirshipTextField: View {\n    @Environment(\\.sizeCategory) private var sizeCategory\n\n    private let info: ThomasViewInfo.TextInput\n    private let constraints: ViewConstraints\n    private let alignment: Alignment\n\n    @Binding private var binding: String\n    @Binding private var isEditing: Bool\n\n    @Environment(\\.colorScheme) private var colorScheme\n    @EnvironmentObject private var thomasEnvironment: ThomasEnvironment\n    @EnvironmentObject private var viewState: ThomasState\n\n    @FocusState private var focused: Bool\n\n    @State private var icon: ThomasViewInfo.TextInput.IconEndInfo?\n\n    init(\n        info: ThomasViewInfo.TextInput,\n        constraints: ViewConstraints,\n        alignment: Alignment,\n        binding: Binding<String>,\n        isEditing: Binding<Bool>\n    ) {\n        self.info = info\n        self.constraints = constraints\n        self.alignment = alignment\n        self._binding = binding\n        self._isEditing = isEditing\n    }\n\n    var body: some View {\n        let isMultiline = self.info.properties.inputType == .textMultiline\n        let axis: Axis = isMultiline ? .vertical : .horizontal\n\n        return TextField(\"\", text: $binding, axis: axis)\n            .padding(5)\n            .constraints(constraints, alignment: alignment)\n            .focused($focused)\n            .foregroundColor(self.info.properties.textAppearance.color.toColor(colorScheme))\n            .contentShape(Rectangle())\n            .textFieldStyle(.plain)\n            .onTapGesture {\n                self.focused = true\n            }\n            .applyViewAppearance(self.info.properties.textAppearance, colorScheme: colorScheme)\n            .airshipApplyIf(isUnderlined, transform: { content in\n                content.underline()\n            })\n            .airshipOnChangeOf(focused) { newValue in\n                if (newValue) {\n                    self.thomasEnvironment.focusedID = self.info.properties.identifier\n                } else if (self.thomasEnvironment.focusedID == self.info.properties.identifier) {\n                    self.thomasEnvironment.focusedID = nil\n                }\n\n                isEditing = newValue\n            }\n            .airshipApplyIf(isMultiline) { view in\n                view.airshipOnChangeOf(binding) { [binding] newValue in\n                    let oldCount = binding.filter { $0 == \"\\n\" }.count\n                    let newCount = newValue.filter { $0 == \"\\n\" }.count\n\n                    if (newCount == oldCount + 1) {\n                        // Only update if values are different\n                        if newValue != binding {\n                            self.binding = binding\n                        }\n                        self.focused = false\n                    }\n                }\n            }\n    }\n\n    private var isUnderlined : Bool {\n        if let styles = self.info.properties.textAppearance.styles {\n            if styles.contains(.underlined) {\n                return true\n            }\n        }\n        return false\n    }\n    \n}\n\n\nfileprivate extension String {\n    var nilIfEmpty: String? {\n        return isEmpty ? nil : self\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Thomas.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Airship rendering engine.\n/// - Note: for internal use only.  :nodoc:\npublic final class Thomas {\n\n    #if !os(watchOS)\n    @MainActor\n    @discardableResult\n    public class func display(\n        layout: AirshipLayout,\n        displayTarget: AirshipDisplayTarget,\n        extensions: ThomasExtensions? = nil,\n        delegate: any ThomasDelegate,\n        extras: AirshipJSON?,\n        priority: Int\n    ) throws -> any AirshipMainActorCancellable {\n        switch layout.presentation {\n        case .banner(let presentation):\n            return try displayBanner(\n                presentation,\n                displayTarget: displayTarget,\n                layout: layout,\n                extensions: extensions,\n                delegate: delegate\n            )\n        case .modal(let presentation):\n            return try displayModal(\n                presentation,\n                displayTarget: displayTarget,\n                layout: layout,\n                extensions: extensions,\n                delegate: delegate\n            )\n        case .embedded(let presentation):\n            return AirshipEmbeddedViewManager.shared.addPending(\n                presentation: presentation,\n                layout: layout,\n                extensions: extensions,\n                delegate: delegate,\n                extras: extras,\n                priority: priority\n            )\n        }\n    }\n\n    @MainActor\n    private class func displayBanner(\n        _ presentation: ThomasPresentationInfo.Banner,\n        displayTarget: AirshipDisplayTarget,\n        layout: AirshipLayout,\n        extensions: ThomasExtensions?,\n        delegate: any ThomasDelegate\n    ) throws -> any AirshipMainActorCancellable {\n        let displayable = displayTarget.prepareDisplay(for: .banner)\n\n        let options = ThomasViewControllerOptions()\n        let environment = ThomasEnvironment(\n            delegate: delegate,\n            extensions: extensions\n        )\n\n        try displayable.display { windowInfo in\n            let bannerConstraints = ThomasBannerConstraints(\n                windowSize: windowInfo.size\n            )\n\n            let rootView = BannerView(\n                viewControllerOptions: options,\n                presentation: presentation,\n                layout: layout,\n                thomasEnvironment: environment,\n                bannerConstraints: bannerConstraints,\n            ) {\n                displayable.dismiss()\n            }\n\n            return ThomasBannerViewController(\n                rootView: rootView,\n                position: presentation.defaultPlacement.position,\n                options: options,\n                constraints: bannerConstraints\n            )\n        }\n\n        return AirshipMainActorCancellableBlock { [weak environment] in\n            environment?.dismiss()\n        }\n    }\n\n    @MainActor\n    private class func displayModal(\n        _ presentation: ThomasPresentationInfo.Modal,\n        displayTarget: AirshipDisplayTarget,\n        layout: AirshipLayout,\n        extensions: ThomasExtensions?,\n        delegate: any ThomasDelegate\n    ) throws -> any AirshipMainActorCancellable {\n        let displayable = displayTarget.prepareDisplay(for: .modal)\n\n        let options = ThomasViewControllerOptions()\n        options.orientation = presentation.defaultPlacement.device?.orientationLock\n\n        let environment = ThomasEnvironment(\n            delegate: delegate,\n            extensions: extensions\n        ) {\n            displayable.dismiss()\n        }\n\n        let rootView = ModalView(\n            presentation: presentation,\n            layout: layout,\n            thomasEnvironment: environment,\n            viewControllerOptions: options\n        )\n\n        try displayable.display { window in\n            return ThomasModalViewController(\n                rootView: rootView,\n                options: options\n            )\n        }\n\n        return AirshipMainActorCancellableBlock { [weak environment] in\n            environment?.dismiss()\n        }\n    }\n\n    #endif\n}\n\n/// Airship rendering engine extensions.\n/// - Note: for internal use only.  :nodoc:\npublic struct ThomasExtensions {\n\n    #if !os(tvOS) && !os(watchOS)\n    var nativeBridgeExtension: (any NativeBridgeExtensionDelegate)?\n    #endif\n\n    var imageProvider: (any AirshipImageProvider)?\n\n    var actionRunner: (any ThomasActionRunner)?\n\n    #if os(tvOS) || os(watchOS)\n    public init(\n        imageProvider: (any AirshipImageProvider)? = nil,\n        actionRunner: (any ThomasActionRunner)? = nil\n    ) {\n        self.imageProvider = imageProvider\n    }\n    #else\n\n    public init(\n        nativeBridgeExtension: (any NativeBridgeExtensionDelegate)? = nil,\n        imageProvider: (any AirshipImageProvider)? = nil,\n        actionRunner: (any ThomasActionRunner)? = nil\n    ) {\n        self.nativeBridgeExtension = nativeBridgeExtension\n        self.imageProvider = imageProvider\n        self.actionRunner = actionRunner\n    }\n    #endif\n}\n\n/// Thomas action runner\n/// - Note: for internal use only.  :nodoc:\npublic protocol ThomasActionRunner: Sendable {\n    @MainActor\n    func runAsync(actions: AirshipJSON, layoutContext: ThomasLayoutContext)\n\n    @MainActor\n    func run(actionName: String, arguments: ActionArguments, layoutContext: ThomasLayoutContext) async -> ActionResult\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAccessibilityAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasAccessibilityAction: ThomasSerializable {\n\n    enum ActionType: String, ThomasSerializable {\n        case `default` = \"default\"\n        case escape = \"escape\"\n    }\n\n    struct Properties: ThomasSerializable {\n        var type: ActionType\n        var reportingMetadata: AirshipJSON?\n        var actions: [ThomasActionsPayload]?\n        var behaviors: [ThomasButtonClickBehavior]?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case reportingMetadata = \"reporting_metadata\"\n            case actions\n            case behaviors\n        }\n    }\n\n    var accessible: ThomasAccessibleInfo\n    var properties: Properties\n\n    func encode(to encoder: any Encoder) throws {\n        try accessible.encode(to: encoder)\n        try properties.encode(to: encoder)\n    }\n\n    init(from decoder: any Decoder) throws {\n        self.accessible = try ThomasAccessibleInfo(from: decoder)\n        self.properties = try Properties(from: decoder)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAccessibleInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nstruct ThomasAccessibleInfo: ThomasSerializable {\n    var contentDescription: String?\n    var localizedContentDescription: Localized?\n    var accessibilityHidden: Bool?\n\n    struct Localized: ThomasSerializable {\n        var ref: String?\n        var refs: [String]?\n        var fallback: String\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case contentDescription = \"content_description\"\n        case localizedContentDescription = \"localized_content_description\"\n        case accessibilityHidden = \"accessibility_hidden\"\n    }\n}\n\nextension ThomasAccessibleInfo {\n    var resolveContentDescription: String? {\n        if let contentDescription = self.contentDescription {\n            return contentDescription\n        }\n\n        guard let localizedContentDescription else {\n            return nil\n        }\n\n        if let refs = localizedContentDescription.refs {\n            for ref in refs {\n                if let string = AirshipResources.localizedString(key: ref) {\n                    return string\n                }\n            }\n        } else if let ref = localizedContentDescription.ref {\n            if let string = AirshipResources.localizedString(key: ref) {\n                return string\n            }\n        }\n\n        return localizedContentDescription.fallback\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasActionsPayload.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasActionsPayload: ThomasSerializable, Hashable {\n    static let keyActionOverride = \"platform_action_overrides\"\n\n    private let original: AirshipJSON\n    private let merged: AirshipJSON\n\n    var value: AirshipJSON {\n        return merged\n    }\n\n    init(value: AirshipJSON) {\n        self.original = value\n        self.merged = Self.overridingPlatformActions(value)\n    }\n\n    init(from decoder: any Decoder) throws {\n        let json = try AirshipJSON.init(from: decoder)\n\n        guard case .object = json else {\n            throw AirshipErrors.error(\"Invalid actions payload.\")\n        }\n\n        self.original = json\n        self.merged = Self.overridingPlatformActions(json)\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        try self.original.encode(to: encoder)\n    }\n\n    static func overridingPlatformActions(_ input: AirshipJSON) -> AirshipJSON {\n        guard\n            case .object(var actions) = input,\n            let override = actions.removeValue(forKey: Self.keyActionOverride),\n            case .object(let platforms) = override,\n            case .object(let overridenActions) = platforms[\"ios\"]\n        else {\n            return input\n        }\n\n        actions.merge(overridenActions) { _, overriden in\n            overriden\n        }\n\n        return .object(actions)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAssociatedLabelResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@MainActor\nstruct ThomasAssociatedLabelResolver: Sendable {\n    var labelMap: [String: (ThomasState) -> String?] = [:]\n\n    init(layout: AirshipLayout) {\n        var labelMap: [String: (ThomasState) -> String?] = [:]\n        let labels = layout.labels ?? []\n        labels.forEach { info in\n            if let labels = info.properties.labels, labels.type == .labels {\n                labelMap[Self.makeKey(for: labels.viewID, viewType: labels.viewType)] = { state in\n                    let resolvedString = info.resolveLabelString(thomasState: state)\n                    return if info.properties.markdown?.disabled == true {\n                        resolvedString\n                    } else {\n                        String(AttributedString(resolvedString).characters)\n                    }\n                }\n            }\n        }\n\n        self.labelMap = labelMap\n    }\n\n    func labelFor(\n        identifier: String?,\n        viewType: ThomasViewInfo.ViewType,\n        thomasState: ThomasState\n    ) -> String? {\n        guard let identifier else {\n            return nil\n        }\n        return labelMap[Self.makeKey(for: identifier, viewType: viewType)]?(thomasState)\n    }\n\n    private static func makeKey(for identifier: String, viewType: ThomasViewInfo.ViewType) -> String {\n        return \"\\(identifier):\\(viewType.rawValue)\"\n    }\n}\n\nfileprivate extension AirshipLayout {\n    var labels: [ThomasViewInfo.Label]? {\n        return extract { info in\n            return if case let .label(label) = info {\n                label\n            } else {\n                nil\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAsyncImage.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\npublic struct ThomasAsyncImage<Placeholder: View, ImageView: View>: View {\n\n    let url: String\n    let imageLoader: AirshipImageLoader\n    let image: (Image, CGSize) -> ImageView\n    let placeholder: () -> Placeholder\n\n    public init(\n        url: String,\n        imageLoader: AirshipImageLoader = AirshipImageLoader(),\n        image: @escaping (Image, CGSize) -> ImageView,\n        placeholder: @escaping () -> Placeholder\n    ) {\n        self.url = url\n        self.imageLoader = imageLoader\n        self.image = image\n        self.placeholder = placeholder\n    }\n\n    @State private var loadedURL: String?\n    @State private var loadedImage: AirshipImageData?\n    @State private var currentImage: AirshipNativeImage?\n    @State private var imageIndex: Int = 0\n    @State private var imageTask: Task<Void, Never>?\n    @State private var loopsCompleted: Int = 0\n\n    @Environment(\\.isVisible) var isVisible: Bool   // we use this value not for updating view tree, but for starting stopping animation,\n                                                    // that's why we need to store the actual value in a separate @State variable\n    @State private var isImageVisible: Bool = false\n\n    public var body: some View {\n        content\n            .task(id: url) {\n                self.isImageVisible = self.isVisible\n\n                guard loadedURL != url else {\n                    animateIfNeeded()\n                    return\n                }\n\n                self.loadedImage = nil\n                self.currentImage = nil\n\n                do {\n                    let image = try await imageLoader.load(url: url)\n                    self.loadedURL = url\n                    self.loadedImage = image\n                    animateIfNeeded()\n                } catch is CancellationError {\n                } catch {\n                    AirshipLogger.error(\"Unable to load image \\(error)\")\n                }\n            }\n            .airshipOnChangeOf(isVisible) { newValue in\n                self.isImageVisible = newValue\n                if newValue {\n                    self.loopsCompleted = 0 // Reset gif frame loop counter every time isVisible changes\n                }\n                animateIfNeeded()\n            }\n    }\n\n    private var content: some View {\n        Group {\n            if let image = currentImage {\n                self.image(Image(airshipNativeImage: image), image.size)\n                    .animation(nil, value: self.imageIndex)\n                    .onDisappear {\n                        imageTask?.cancel()\n                    }\n            } else {\n                self.placeholder()\n            }\n        }\n    }\n\n    private func animateIfNeeded() {\n        self.imageTask?.cancel()\n\n        if isImageVisible {\n            self.imageTask = Task { @MainActor in\n                await animateImage()\n            }\n        } else {\n            self.imageTask = Task { @MainActor in\n                await preloadFirstImage()\n            }\n        }\n    }\n\n    @MainActor\n    private func preloadFirstImage() async {\n        guard let loadedImage = self.loadedImage, self.currentImage == nil else { return }\n\n        guard loadedImage.isAnimated else {\n            self.currentImage = await loadedImage.loadFrames().first?.image\n            return\n        }\n\n        let image = await loadedImage.getActor().loadFrame(at: 0)?.image\n        if !Task.isCancelled {\n            self.currentImage = image\n        }\n    }\n\n    @MainActor\n    private func animateImage() async {\n        guard let loadedImage = self.loadedImage else { return }\n\n        guard loadedImage.isAnimated else {\n            self.currentImage = await loadedImage.loadFrames().first?.image\n            return\n        }\n\n        let frameActor = loadedImage.getActor()\n\n        imageIndex = 0\n        var frame = await frameActor.loadFrame(at: imageIndex)\n\n        self.currentImage = frame?.image\n\n        /// GIFs will sometimes have a 0 in their loop count metadata to denote infinite loops\n        let loopCount = loadedImage.loopCount ?? 0\n\n        /// Continue looping if loop count is nil (coalesces to zero), zero or nonzero and greater than the loops completed\n        while !Task.isCancelled && (loopCount <= 0 || loopCount > loopsCompleted) {\n            let duration = frame?.duration ?? AirshipImageData.minFrameDuration\n\n            async let delay: () = Task.sleep(nanoseconds: UInt64(duration * 1_000_000_000))\n\n            let nextIndex = (imageIndex + 1) % loadedImage.imageFramesCount\n\n            do {\n                let (_, nextFrame) = try await (delay, frameActor.loadFrame(at: nextIndex))\n                frame = nextFrame\n            } catch {} // most likely it's a task cancelled exception when animation is stopped\n\n            imageIndex = nextIndex\n\n            /// Consider a loop completed when we reach the last frame\n            if imageIndex == loadedImage.imageFramesCount - 1 {\n                /// Stops the GIF when loopsCompleted == loopCount when loopCount is specified\n                self.loopsCompleted += 1\n            }\n\n            if !Task.isCancelled {\n                self.currentImage = frame?.image\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAttributeName.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasAttributeName: ThomasSerializable, Hashable {\n    var channel: String?\n    var contact: String?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAttributeValue.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasAttributeValue: ThomasSerializable, Hashable {\n    case string(String)\n    case number(Double)\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n\n        if let string = try? container.decode(String.self) {\n            self = .string(string)\n        } else if let number = try? container.decode(Double.self) {\n            self = .number(number)\n        } else {\n            throw AirshipErrors.error(\"Invalid attribute value\")\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n\n        switch self {\n        case .string(let value):\n            try container.encode(value)\n        case .number(let value):\n            try container.encode(value)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAutomatedAccessibilityAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasAutomatedAccessibilityAction: ThomasSerializable {\n    let type: ActionType\n\n    enum ActionType: String, ThomasSerializable {\n        case announce = \"announce\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasAutomatedAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasAutomatedAction: Codable, Equatable, Sendable {\n    var identifier: String\n    var delay: Double?\n    var actions: [ThomasActionsPayload]?\n    var behaviors: [ThomasButtonClickBehavior]?\n    var reportingMetadata: AirshipJSON?\n\n    enum CodingKeys: String, CodingKey {\n        case identifier\n        case delay\n        case actions\n        case behaviors\n        case reportingMetadata = \"reporting_metadata\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasBorder.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasBorder: Codable, Equatable, Sendable {\n    struct CornerRadius: Codable, Equatable, Sendable {\n        var topLeft: Double?\n        var topRight: Double?\n        var bottomLeft: Double?\n        var bottomRight: Double?\n\n        enum CodingKeys: String, CodingKey {\n            case topLeft = \"top_left\"\n            case topRight = \"top_right\"\n            case bottomLeft = \"bottom_left\"\n            case bottomRight = \"bottom_right\"\n        }\n    }\n\n    var radius: Double?\n    var cornerRadius: CornerRadius?\n    var strokeWidth: Double?\n    var strokeColor: ThomasColor?\n\n    enum CodingKeys: String, CodingKey {\n        case radius\n        case cornerRadius = \"corner_radius\"\n        case strokeWidth = \"stroke_width\"\n        case strokeColor = \"stroke_color\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasButtonClickBehavior.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasButtonClickBehavior: String, ThomasSerializable {\n    case dismiss\n    case cancel\n    case pagerNext = \"pager_next\"\n    case pagerPrevious = \"pager_previous\"\n    case pagerNextOrDismiss = \"pager_next_or_dismiss\"\n    case pagerNextOrFirst = \"pager_next_or_first\"\n    case formSubmit = \"form_submit\"\n    case formValidate = \"form_validate\"\n    case pagerPause = \"pager_pause\"\n    case pagerResume = \"pager_resume\"\n    case pagerPauseToggle = \"pager_toggle_pause\"\n    case videoPlay = \"video_play\"\n    case videoPause = \"video_pause\"\n    case videoTogglePlay = \"video_toggle_play\"\n    case videoMute = \"video_mute\"\n    case videoUnmute = \"video_unmute\"\n    case videoToggleMute = \"video_toggle_mute\"\n}\n\nextension ThomasButtonClickBehavior {\n    fileprivate var sortOrder: Int {\n        switch self {\n        case .dismiss:\n            return 3\n        case .cancel:\n            return 3\n        case .pagerPause:\n            return 2\n        case .pagerResume:\n            return 2\n        case .pagerPauseToggle:\n            return 2\n        case .videoPlay:\n            return 2\n        case .videoPause:\n            return 2\n        case .videoTogglePlay:\n            return 2\n        case .videoMute:\n            return 2\n        case .videoUnmute:\n            return 2\n        case .videoToggleMute:\n            return 2\n        case .pagerNextOrFirst:\n            return 1\n        case .pagerNextOrDismiss:\n            return 1\n        case .pagerNext:\n            return 1\n        case .pagerPrevious:\n            return 1\n        case .formSubmit:\n            return 0\n        case .formValidate:\n            return -1\n        }\n    }\n}\n\nextension Array where Element == ThomasButtonClickBehavior {\n    var sortedBehaviors: [Element] {\n        return self.sorted { $0.sortOrder < $1.sortOrder }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasButtonTapEffect.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasButtonTapEffect: ThomasSerializable {\n    case `default`\n    case none\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    enum EffectType: String, Codable {\n        case `default` = \"default\"\n        case none = \"none\"\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type: EffectType = try container.decode(EffectType.self, forKey: .type)\n        self = switch(type) {\n        case .default: .default\n        case .none: .none\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch (self) {\n        case .default: try container.encode(EffectType.default, forKey: .type)\n        case .none: try container.encode(EffectType.none, forKey: .type)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasColor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct ThomasColor: ThomasSerializable {\n    let defaultColor: HexColor\n    let selectors: [Selector]?\n\n    enum CodingKeys: String, CodingKey {\n        case defaultColor = \"default\"\n        case selectors\n    }\n\n    struct HexColor: ThomasSerializable {\n        let type: String = \"hex\"\n        var hex: String\n        var alpha: Double?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case hex\n            case alpha\n        }\n    }\n\n    struct Selector: ThomasSerializable {\n        let darkMode: Bool?\n        let platform: ThomasPlatform?\n        let color: HexColor\n\n        enum CodingKeys: String, CodingKey {\n            case platform\n            case darkMode = \"dark_mode\"\n            case color\n        }\n    }\n}\nextension ThomasColor.HexColor {\n    func toColor() -> Color {\n        // Use the new AirshipColor resolver instead of AirshipColorUtils\n        let color = AirshipColor.resolveColor(self.hex)\n        let alpha = self.alpha ?? 1.0\n\n        // Combine the hex color with the explicit alpha multiplier\n        let finalColor = color.opacity(alpha)\n\n        /// Clear needs to be replaced by tappable clear to prevent SwiftUI from passing through tap events\n        /// Note: We check if the resulting alpha is 0 (either from hex or explicit alpha)\n        if alpha == 0 || color == .clear {\n            return ThomasConstants.tappableClearColor\n        }\n\n        return finalColor\n    }\n}\n\nextension ThomasColor {\n    func toColor(_ colorScheme: ColorScheme) -> Color {\n        let isDarkMode = colorScheme == .dark\n\n        for selector in selectors ?? [] {\n            // Platform filtering\n            if let platform = selector.platform {\n                if platform != .ios { continue }\n            }\n\n            // Dark mode filtering\n            if let selectorDarkMode = selector.darkMode, isDarkMode != selectorDarkMode {\n                continue\n            }\n\n            return selector.color.toColor()\n        }\n\n        // Fallback to default if no selectors matched\n        return defaultColor.toColor()\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasConstants.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct ThomasConstants {\n    static let disabledColor = Color.gray.opacity(0.5)\n    static let tappableClearColor = Color.white.opacity(0.001)\n    private init() {}\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasConstrainedSize.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasConstrainedSize: ThomasSerializable {\n    var minWidth: ThomasSizeConstraint?\n    var width: ThomasSizeConstraint\n    var maxWidth: ThomasSizeConstraint?\n    var minHeight: ThomasSizeConstraint?\n    var height: ThomasSizeConstraint\n    var maxHeight: ThomasSizeConstraint?\n\n    private enum CodingKeys: String, CodingKey {\n        case minWidth = \"min_width\"\n        case width\n        case maxWidth = \"max_width\"\n        case minHeight = \"min_height\"\n        case height\n        case maxHeight = \"max_height\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic protocol ThomasDelegate: Sendable {\n\n    @MainActor\n    func onVisibilityChanged(isVisible: Bool, isForegrounded: Bool)\n\n    @MainActor\n    func onReportingEvent(_ event: ThomasReportingEvent)\n\n    @MainActor\n    func onDismissed(cancel: Bool)\n\n    @MainActor\n    func onStateChanged(_ state: AirshipJSON)\n}\n\npublic extension ThomasDelegate {\n    @MainActor\n    func onStateChanged(_ state: AirshipJSON) {\n        // no-op\n    }\n}\n\n@MainActor\npublic final class ThomasDismissHandle {\n    private var onDismissBlocks: [(Bool) -> Void] = []\n\n    public init() {}\n\n    /// Adds a block to be called when ``dismiss(cancel:)`` is invoked. All blocks are run; order is the order they were added.\n    func addOnDismiss(_ block: @escaping (Bool) -> Void) {\n        onDismissBlocks.append(block)\n    }\n\n    public func dismiss(cancel: Bool = false) {\n        for block in onDismissBlocks {\n            block(cancel)\n        }\n        onDismissBlocks.removeAll()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasDirection.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasDirection: String, ThomasSerializable {\n    case vertical\n    case horizontal\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasDisplayListener.swift",
    "content": "import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol ThomasLayoutMessageAnalyticsProtocol: AnyObject, Sendable {\n    @MainActor\n    func recordEvent(\n        _ event: any ThomasLayoutEvent,\n        layoutContext: ThomasLayoutContext?\n    )\n}\n\n/// NOTE: For internal use only. :nodoc:\n@MainActor\npublic final class ThomasDisplayListener: ThomasDelegate {\n    /// NOTE: For internal use only. :nodoc:\n    public enum DisplayResult: Sendable, Equatable {\n        case cancel\n        case finished\n    }\n    \n    private let analytics: any ThomasLayoutMessageAnalyticsProtocol\n    private var onDismiss: (@MainActor @Sendable (DisplayResult) -> Void)?\n\n    public init(\n        analytics: any ThomasLayoutMessageAnalyticsProtocol,\n        onDismiss: @escaping @MainActor @Sendable (DisplayResult) -> Void\n    ) {\n        self.analytics = analytics\n        self.onDismiss = onDismiss\n    }\n\n    public func onVisibilityChanged(isVisible: Bool, isForegrounded: Bool) {\n        if isVisible, isForegrounded {\n            analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        }\n    }\n\n    public func onReportingEvent(_ event: ThomasReportingEvent) {\n        switch(event) {\n        case .buttonTap(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutButtonTapEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .formDisplay(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutFormDisplayEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .formResult(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutFormResultEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .gesture(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutGestureEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .pageAction(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutPageActionEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .pagerCompleted(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutPagerCompletedEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .pageSwipe(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutPageSwipeEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .pageView(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutPageViewEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .pagerSummary(let event, let layoutContext):\n            analytics.recordEvent(\n                ThomasLayoutPagerSummaryEvent(data: event),\n                layoutContext: layoutContext\n            )\n        case .dismiss(let event, let displayTime, let layoutContext):\n            switch(event) {\n            case .buttonTapped(identifier: let identifier, description: let description):\n                analytics.recordEvent(\n                    ThomasLayoutResolutionEvent.buttonTap(\n                        identifier: identifier,\n                        description: description,\n                        displayTime: displayTime\n                    ),\n                    layoutContext: layoutContext\n                )\n            case .timedOut:\n                analytics.recordEvent(\n                    ThomasLayoutResolutionEvent.timedOut(displayTime: displayTime),\n                    layoutContext: layoutContext\n                )\n            case .userDismissed:\n                analytics.recordEvent(\n                    ThomasLayoutResolutionEvent.userDismissed(displayTime: displayTime),\n                    layoutContext: layoutContext\n                )\n            @unknown default:\n                AirshipLogger.error(\"Unhandled dismiss type event \\(event)\")\n                analytics.recordEvent(\n                    ThomasLayoutResolutionEvent.userDismissed(displayTime: displayTime),\n                    layoutContext: layoutContext\n                )\n            }\n\n\n        @unknown default: AirshipLogger.error(\"Unhandled IAX event \\(event)\")\n        }\n    }\n\n    public func onDismissed(cancel: Bool) {\n        self.onDismiss?(cancel ? .cancel : .finished)\n        self.onDismiss = nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasEmailRegistrationOptions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasEmailRegistrationOption: ThomasSerializable, Hashable {\n    case doubleOptIn(DoubleOptIn)\n    case commercial(Commercial)\n    case transactional(Transactional)\n\n    struct DoubleOptIn: ThomasSerializable, Hashable {\n        let type: EmailRegistrationType = .doubleOptIn\n        var properties: AirshipJSON?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case properties\n        }\n    }\n\n    struct Commercial: ThomasSerializable, Hashable {\n        let type: EmailRegistrationType = .commercial\n        var optedIn: Bool\n        var properties: AirshipJSON?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case properties\n            case optedIn = \"commercial_opted_in\"\n        }\n    }\n\n    struct Transactional: ThomasSerializable, Hashable {\n        let type: EmailRegistrationType = .transactional\n        var properties: AirshipJSON?\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case properties\n        }\n    }\n\n    enum EmailRegistrationType: String, Codable {\n        case doubleOptIn = \"double_opt_in\"\n        case commercial = \"commercial\"\n        case transactional = \"transactional\"\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .doubleOptIn(let properties):\n            try properties.encode(to: encoder)\n        case .commercial(let properties):\n            try properties.encode(to: encoder)\n        case .transactional(let properties):\n            try properties.encode(to: encoder)\n        }\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(EmailRegistrationType.self, forKey: .type)\n        switch type {\n        case .doubleOptIn:\n            self = .doubleOptIn(\n                try DoubleOptIn(from: decoder)\n            )\n        case .commercial:\n            self = .commercial(\n                try Commercial(from: decoder)\n            )\n        case .transactional:\n            self = .transactional(\n                try Transactional(from: decoder)\n            )\n        }\n    }\n}\n\nextension ThomasEmailRegistrationOption {\n    func makeContactOptions(date: Date = Date.now) -> EmailRegistrationOptions {\n        switch (self) {\n        case .commercial(let properties):\n            return .commercialOptions(\n                transactionalOptedIn: nil,\n                commercialOptedIn: properties.optedIn ? date : nil,\n                properties: properties.properties?.unWrap() as? [String: Any]\n            )\n        case .doubleOptIn(let properties):\n            return .options(\n                properties: properties.properties?.unWrap() as? [String: Any],\n                doubleOptIn: true\n            )\n        case .transactional(let properties):\n            return .options(\n                transactionalOptedIn: nil,\n                properties: properties.properties?.unWrap() as? [String: Any],\n                doubleOptIn: false\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasEnableBehavior.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasEnableBehavior: String, ThomasSerializable {\n    case formValidation = \"form_validation\"\n    case formSubmission = \"form_submission\"\n    case pagerNext = \"pager_next\"\n    case pagerPrevious = \"pager_previous\"\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasEnvironment.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n@MainActor\nclass ThomasEnvironment: ObservableObject {\n    private let delegate: any ThomasDelegate\n    private let pagerTracker: ThomasPagerTracker\n    private let timer: any AirshipTimerProtocol\n    private let stateStorage: (any ThomasStateStorage)?\n    let extensions: ThomasExtensions?\n    let imageLoader: AirshipImageLoader\n\n    private var state: [String: Any] = [:]\n\n    func retrieveState<T: ThomasStateProvider>(identifier: String, create: () -> T) -> T {\n        let key = \"\\(identifier):\\(T.self)\"\n        if let existing = self.state[key] as? T {\n            return existing\n        }\n        \n        if let stored = stateStorage?.retrieve(identifier: identifier, builder: create) {\n            self.state[key] = stored\n            return stored\n        }\n        \n        let new = create()\n        state[key] = new\n        stateStorage?.store(new, identifier: identifier)\n        \n        return new\n    }\n\n    @Published\n    var isDismissed = false\n\n    @Published\n    var focusedID: String? = nil\n\n    private var onDismiss: (() -> Void)?\n    private var dismissHandle: ThomasDismissHandle?\n    private var subscriptions: Set<AnyCancellable> = Set()\n\n    @Published private(set) var keyboardState: KeyboardState = .hidden\n\n\n    @MainActor\n    init(\n        delegate: any ThomasDelegate,\n        extensions: ThomasExtensions?,\n        pagerTracker: ThomasPagerTracker? = nil,\n        timer: (any AirshipTimerProtocol)? = nil,\n        stateStorage: (any ThomasStateStorage)? = nil,\n        dismissHandle: ThomasDismissHandle? = nil,\n        onDismiss: (() -> Void)? = nil\n    ) {\n        self.delegate = delegate\n        self.extensions = extensions\n        self.pagerTracker = pagerTracker ?? ThomasPagerTracker()\n        self.timer = timer ?? AirshipTimer()\n        self.onDismiss = onDismiss\n        self.imageLoader = AirshipImageLoader(\n            imageProvider: extensions?.imageProvider\n        )\n        self.stateStorage = stateStorage\n        \n        #if !os(tvOS) && !os(watchOS) && !os(macOS)\n        self.subscribeKeyboard()\n        #endif\n\n        self.dismissHandle = dismissHandle\n        dismissHandle?.addOnDismiss { [weak self] cancel in\n            self?.dismiss(cancel: cancel)\n        }\n    }\n\n    @MainActor\n    func onVisibilityChanged(isVisible: Bool, isForegrounded: Bool) {\n        if isVisible, isForegrounded {\n            timer.start()\n        } else {\n            timer.stop()\n        }\n\n        self.delegate.onVisibilityChanged(\n            isVisible: isVisible,\n            isForegrounded: isForegrounded\n        )\n    }\n\n    @MainActor\n    func submitForm(\n        result: ThomasFormResult,\n        channels: [ThomasFormField.Channel],\n        attributes: [ThomasFormField.Attribute],\n        layoutState: LayoutState\n    ) {\n        self.delegate.onReportingEvent(\n            .formResult(\n                .init(forms: result.formData),\n                makeLayoutContext(layoutState: layoutState)\n            )\n        )\n\n        applyAttributes(attributes)\n        registerChannels(channels)\n    }\n\n    private func registerChannels(\n        _ channels: [ThomasFormField.Channel]\n    ) {\n        channels.forEach { channelRegistration in\n            switch(channelRegistration) {\n            case .email(let address, let options):\n                Airship.contact.registerEmail(\n                    address,\n                    options: options.makeContactOptions()\n                )\n            case .sms(let address, let options):\n                Airship.contact.registerSMS(\n                    address,\n                    options: options.makeContactOptions()\n                )\n            }\n            \n        }\n    }\n\n    private func applyAttributes(\n        _ attributes: [ThomasFormField.Attribute]\n    ) {\n        guard !attributes.isEmpty else { return }\n        let channelEditor = Airship.channel.editAttributes()\n        let contactEditor = Airship.contact.editAttributes()\n\n        attributes.forEach { attribute in\n\n            if let name = attribute.attributeName.channel {\n                channelEditor.set(\n                    attributeValue: attribute.attributeValue,\n                    attribute: name\n                )\n            }\n\n            if let name = attribute.attributeName.contact {\n                contactEditor.set(\n                    attributeValue: attribute.attributeValue,\n                    attribute: name\n                )\n            }\n        }\n\n        channelEditor.apply()\n        contactEditor.apply()\n    }\n\n    @MainActor\n    func formDisplayed(_ formState: ThomasFormState, layoutState: LayoutState) {\n        self.delegate.onReportingEvent(\n            .formDisplay(\n                .init(\n                    identifier: formState.identifier,\n                    formType: formState.formTypeString\n                ),\n                makeLayoutContext(layoutState: layoutState)\n            )\n        )\n    }\n\n    @MainActor\n    func buttonTapped(\n        buttonIdentifier: String,\n        reportingMetadata: AirshipJSON?,\n        layoutState: LayoutState\n    ) {\n        self.delegate.onReportingEvent(\n            .buttonTap(\n                .init(\n                    identifier: buttonIdentifier,\n                    reportingMetadata: reportingMetadata\n                ),\n                makeLayoutContext(layoutState: layoutState)\n            )\n        )\n    }\n\n    @MainActor\n    func pageViewed(\n        pagerState: PagerState,\n        pageInfo: ThomasPageInfo,\n        layoutState: LayoutState\n    ) {\n        let pageViewedEvent = ThomasReportingEvent.PageViewEvent(\n            identifier: pagerState.identifier,\n            pageIdentifier: pageInfo.identifier,\n            pageIndex: pageInfo.index,\n            pageViewCount: pageInfo.viewCount,\n            pageCount: pagerState.reportingPageCount,\n            completed: pagerState.completed\n        )\n        pagerTracker.onPageView(pageEvent: pageViewedEvent, currentDisplayTime: timer.time)\n        self.delegate.onReportingEvent(\n            .pageView(\n                pageViewedEvent,\n                makeLayoutContext(layoutState: layoutState)\n            )\n        )\n    }\n\n    @MainActor\n    func pagerCompleted(\n        pagerState: PagerState,\n        layoutState: LayoutState\n    ) {\n        self.delegate.onReportingEvent(\n            .pagerCompleted(\n                .init(\n                    identifier: pagerState.identifier,\n                    pageIndex: pagerState.pageIndex,\n                    pageCount: pagerState.reportingPageCount,\n                    pageIdentifier: pagerState.currentPageId ?? \"\"\n                ),\n                makeLayoutContext(layoutState: layoutState)\n            )\n        )\n    }\n\n    @MainActor\n    func dismiss(\n        buttonIdentifier: String,\n        buttonDescription: String,\n        cancel: Bool,\n        layoutState: LayoutState\n    ) {\n        tryDismiss { displayTime in\n            self.delegate.onReportingEvent(\n                .dismiss(\n                    .buttonTapped(\n                        identifier: buttonIdentifier,\n                        description: buttonDescription\n                    ),\n                    displayTime,\n                    makeLayoutContext(layoutState: layoutState)\n                )\n            )\n            self.delegate.onDismissed(cancel: cancel)\n        }\n    }\n\n    @MainActor\n    func dismiss(cancel: Bool = false, layoutState: LayoutState? = nil) {\n        tryDismiss { displayTime in\n            self.delegate.onReportingEvent(\n                .dismiss(\n                    .userDismissed,\n                    displayTime,\n                    makeLayoutContext(layoutState: layoutState)\n                )\n            )\n            self.delegate.onDismissed(cancel: cancel)\n        }\n    }\n\n    @MainActor\n    func timedOut(layoutState: LayoutState? = nil) {\n        tryDismiss { displayTime in\n            self.delegate.onReportingEvent(\n                .dismiss(\n                    .timedOut,\n                    displayTime,\n                    makeLayoutContext(layoutState: layoutState)\n                )\n            )\n            self.delegate.onDismissed(cancel: false)\n        }\n    }\n    \n    @MainActor\n    func pageGesture(\n        identifier: String?,\n        reportingMetadata: AirshipJSON?,\n        layoutState: LayoutState\n    ) {\n        if let identifier {\n            self.delegate.onReportingEvent(\n                .gesture(\n                    .init(\n                        identifier: identifier,\n                        reportingMetadata: reportingMetadata\n                    ),\n                    makeLayoutContext(layoutState: layoutState)\n                )\n            )\n        }\n    }\n    \n    @MainActor\n    func pageAutomated(\n        identifier: String?,\n        reportingMetadata: AirshipJSON?,\n        layoutState: LayoutState\n    ) {\n        if let identifier {\n            self.delegate.onReportingEvent(\n                .pageAction(\n                    .init(\n                        identifier: identifier,\n                        reportingMetadata: reportingMetadata\n                    ),\n                    makeLayoutContext(layoutState: layoutState)\n                )\n            )\n        }\n    }\n    \n    @MainActor\n    func pageSwiped(\n        pagerState: PagerState,\n        from: ThomasPageInfo,\n        to: ThomasPageInfo,\n        layoutState: LayoutState\n    ) {\n        self.delegate.onReportingEvent(\n            .pageSwipe(\n                .init(\n                    identifier: pagerState.identifier,\n                    toPageIndex: to.index,\n                    toPageIdentifier: to.identifier,\n                    fromPageIndex: from.index,\n                    fromPageIdentifier: from.identifier\n                ),\n                makeLayoutContext(layoutState: layoutState)\n            )\n        )\n    }\n\n    @MainActor\n    func onStateChange(_ state: AirshipJSON) {\n        self.delegate.onStateChanged(state)\n    }\n\n    private func emitPagerSummaryEvents() {\n        pagerTracker.summary.forEach { summary in\n            delegate.onReportingEvent(\n                .pagerSummary(\n                    summary,\n                    makeLayoutContext(layoutState: nil)\n                )\n            )\n        }\n    }\n\n    @MainActor\n    private func tryDismiss(callback: (TimeInterval) -> Void) {\n        if !self.isDismissed {\n            self.isDismissed = true\n\n            timer.stop()\n            \n            pagerTracker.stopAll(currentDisplayTime: timer.time)\n            emitPagerSummaryEvents()\n\n            callback(timer.time)\n            onDismiss?()\n            self.onDismiss = nil\n        }\n    }\n\n    @MainActor\n    func runActions(\n        _ actionsPayload: ThomasActionsPayload?,\n        layoutState: LayoutState?\n    ) {\n        guard let actionsPayload = actionsPayload?.value else { return }\n        guard let runner = extensions?.actionRunner else {\n            Task {\n                await ActionRunner.run(actionsPayload: actionsPayload, situation: .automation, metadata: [:])\n            }\n            return\n        }\n\n        runner.runAsync(\n            actions: actionsPayload,\n            layoutContext: makeLayoutContext(layoutState: layoutState)\n        )\n    }\n\n    @MainActor\n    func runAction(\n        _ actionName: String,\n        arguments: ActionArguments,\n        layoutState: LayoutState?\n    ) async -> ActionResult {\n        guard let runner = extensions?.actionRunner else {\n            return await ActionRunner.run(actionName: actionName, arguments: arguments)\n        }\n\n        return await runner.run(\n            actionName: actionName,\n            arguments: arguments,\n            layoutContext: makeLayoutContext(layoutState: layoutState)\n        )\n    }\n\n    private func makeLayoutContext(layoutState: LayoutState?) -> ThomasLayoutContext {\n        var context = ThomasLayoutContext()\n        if let pager = layoutState?.pagerState {\n            context.pager = .init(\n                identifier: pager.identifier,\n                pageIdentifier: pager.currentPageId ?? \"\",\n                pageIndex: pager.pageIndex,\n                completed: pager.completed,\n                count: pager.reportingPageCount,\n                pageHistory: pagerTracker.viewedPages(\n                    pagerIdentifier: pager.identifier\n                )\n            )\n        }\n\n        if let form = layoutState?.formState {\n            context.form = .init(\n                identifier: form.identifier,\n                submitted: form.status == .submitted,\n                type: form.formTypeString,\n                responseType: form.formResponseType\n            )\n        }\n\n        if let form = layoutState?.buttonState {\n            context.button = .init(\n                identifier: form.identifier\n            )\n        }\n\n        return context\n    }\n\n    #if !os(tvOS) && !os(watchOS) && !os(macOS)\n    @MainActor\n    private func subscribeKeyboard() {\n        Publishers.Merge3(\n            NotificationCenter.default\n                .publisher(for: UIResponder.keyboardDidShowNotification)\n                .map { _ in\n                    return KeyboardState.visible\n                },\n            NotificationCenter.default\n                .publisher(for: UIResponder.keyboardWillShowNotification)\n                .map { notification in\n                    let duration =\n                        notification.userInfo?[\n                            UIResponder.keyboardAnimationDurationUserInfoKey\n                        ] as? Double\n                    return KeyboardState.displaying(duration ?? 0.25)\n                },\n            NotificationCenter.default\n                .publisher(for: UIResponder.keyboardDidHideNotification)\n                .map { _ in\n                    return KeyboardState.hidden\n                }\n        )\n        .removeDuplicates()\n        .subscribe(on: DispatchQueue.main)\n        .sink { [weak self] value in\n            self?.keyboardState = value\n        }\n        .store(in: &self.subscriptions)\n    }\n\n    #endif\n}\n\n\nextension ThomasFormState {\n    fileprivate var formTypeString: String {\n        switch self.formType {\n        case .form:\n            return \"form\"\n        case .nps(_):\n            return \"nps\"\n        }\n    }\n}\n\nextension AttributesEditor {\n    fileprivate func set(\n        attributeValue: ThomasAttributeValue,\n        attribute: String\n    ) {\n        switch attributeValue {\n        case .string(let value):\n            self.set(string: value, attribute: attribute)\n\n        case .number(let value):\n            self.set(double: value, attribute: attribute)\n        }\n    }\n}\n\nenum KeyboardState: Equatable {\n    case hidden\n    case displaying(Double)\n    case visible\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic enum ThomasReportingEvent: Sendable {\n    case buttonTap(ButtonTapEvent, ThomasLayoutContext)\n    case formDisplay(FormDisplayEvent, ThomasLayoutContext)\n    case formResult(FormResultEvent, ThomasLayoutContext)\n    case gesture(GestureEvent, ThomasLayoutContext)\n    case pageAction(PageActionEvent, ThomasLayoutContext)\n    case pagerCompleted(PagerCompletedEvent, ThomasLayoutContext)\n    case pageSwipe(PageSwipeEvent, ThomasLayoutContext)\n    case pageView(PageViewEvent, ThomasLayoutContext)\n    case pagerSummary(PagerSummaryEvent, ThomasLayoutContext)\n    case dismiss(DismissEvent, TimeInterval, ThomasLayoutContext)\n\n    public enum DismissEvent: Sendable {\n        case buttonTapped(identifier: String, description: String)\n        case timedOut\n        case userDismissed\n    }\n\n    public struct PageViewEvent: Encodable, Sendable {\n        public var identifier: String\n        public var pageIdentifier: String\n        public var pageIndex: Int\n        public var pageViewCount: Int\n        public var pageCount: Int\n        public var completed: Bool\n\n\n        public init(identifier: String, pageIdentifier: String, pageIndex: Int, pageViewCount: Int, pageCount: Int, completed: Bool) {\n            self.identifier = identifier\n            self.pageIdentifier = pageIdentifier\n            self.pageIndex = pageIndex\n            self.pageViewCount = pageViewCount\n            self.pageCount = pageCount\n            self.completed = completed\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"pager_identifier\"\n            case pageIndex = \"page_index\"\n            case pageCount = \"page_count\"\n            case pageViewCount = \"viewed_count\"\n            case pageIdentifier = \"page_identifier\"\n            case completed\n        }\n    }\n\n    public struct PagerCompletedEvent: Encodable, Sendable {\n        public var identifier: String\n        public var pageIndex: Int\n        public var pageCount: Int\n        public var pageIdentifier: String\n\n        public init(identifier: String, pageIndex: Int, pageCount: Int, pageIdentifier: String) {\n            self.identifier = identifier\n            self.pageIndex = pageIndex\n            self.pageCount = pageCount\n            self.pageIdentifier = pageIdentifier\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"pager_identifier\"\n            case pageIndex = \"page_index\"\n            case pageCount = \"page_count\"\n            case pageIdentifier = \"page_identifier\"\n        }\n    }\n\n    public struct PageSwipeEvent: Encodable, Sendable {\n        public var identifier: String\n        public var toPageIndex: Int\n        public var toPageIdentifier: String\n        public var fromPageIndex: Int\n        public var fromPageIdentifier: String\n\n        public init(identifier: String, toPageIndex: Int, toPageIdentifier: String, fromPageIndex: Int, fromPageIdentifier: String) {\n            self.identifier = identifier\n            self.toPageIndex = toPageIndex\n            self.toPageIdentifier = toPageIdentifier\n            self.fromPageIndex = fromPageIndex\n            self.fromPageIdentifier = fromPageIdentifier\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"pager_identifier\"\n            case toPageIndex = \"to_page_index\"\n            case toPageIdentifier = \"to_page_identifier\"\n            case fromPageIndex = \"from_page_index\"\n            case fromPageIdentifier = \"from_page_identifier\"\n        }\n    }\n\n    public struct GestureEvent: Encodable, Sendable {\n        public var identifier: String\n        public var reportingMetadata: AirshipJSON?\n\n        public init(identifier: String, reportingMetadata: AirshipJSON? = nil) {\n            self.identifier = identifier\n            self.reportingMetadata = reportingMetadata\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"gesture_identifier\"\n            case reportingMetadata = \"reporting_metadata\"\n        }\n    }\n\n    public struct PageActionEvent: Encodable, Sendable {\n        public var identifier: String\n        public var reportingMetadata: AirshipJSON?\n\n        public init(identifier: String, reportingMetadata: AirshipJSON? = nil) {\n            self.identifier = identifier\n            self.reportingMetadata = reportingMetadata\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"action_identifier\"\n            case reportingMetadata = \"reporting_metadata\"\n        }\n    }\n\n    public struct ButtonTapEvent: Encodable, Sendable {\n        public var identifier: String\n        public var reportingMetadata: AirshipJSON?\n\n        public init(identifier: String, reportingMetadata: AirshipJSON? = nil) {\n            self.identifier = identifier\n            self.reportingMetadata = reportingMetadata\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"button_identifier\"\n            case reportingMetadata = \"reporting_metadata\"\n        }\n    }\n\n    public struct FormResultEvent: Encodable, Sendable {\n        public var forms: AirshipJSON\n\n        public init(forms: AirshipJSON) {\n            self.forms = forms\n        }\n    }\n\n    public struct FormDisplayEvent: Encodable, Sendable {\n        public var identifier: String\n        public var formType: String\n        public var responseType: String?\n\n        public init(identifier: String, formType: String, responseType: String? = nil) {\n            self.identifier = identifier\n            self.formType = formType\n            self.responseType = responseType\n        }\n        \n        enum CodingKeys: String, CodingKey {\n            case identifier = \"form_identifier\"\n            case formType = \"form_type\"\n            case responseType = \"form_response_type\"\n        }\n    }\n\n    public struct PagerSummaryEvent: Encodable, Sendable, Equatable, Hashable {\n        public var identifier: String\n        public var viewedPages: [ThomasViewedPageInfo]\n        public var pageCount: Int\n        public var completed: Bool\n\n        public init(\n            identifier: String,\n            viewedPages: [ThomasViewedPageInfo],\n            pageCount: Int,\n            completed: Bool\n        ) {\n            self.identifier = identifier\n            self.viewedPages = viewedPages\n            self.pageCount = pageCount\n            self.completed = completed\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier = \"pager_identifier\"\n            case viewedPages = \"viewed_pages\"\n            case pageCount = \"page_count\"\n            case completed = \"completed\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasEventHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasEventHandler: ThomasSerializable {\n    let type: EventType\n    let stateActions: [ThomasStateAction]\n\n    enum EventType: String, ThomasSerializable {\n        case tap\n        case formInput = \"form_input\"\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n        case stateActions = \"state_actions\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormDataCollector.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@MainActor\nclass ThomasFormDataCollector: ObservableObject {\n    private let formState: ThomasFormState?\n    private let pagerState: PagerState?\n\n    private var subscriptions: Set<AnyCancellable> = Set()\n\n    init(formState: ThomasFormState? = nil, pagerState: PagerState? = nil) {\n        self.formState = formState\n        self.pagerState = pagerState\n\n        pagerState?.$currentPageId\n            .removeDuplicates()\n            // Using this over RunLoop.main as it seems to prevent\n            // some unwanted UI jank with form validation enablement\n            .receive(on: DispatchQueue.main)\n            .sink { [weak formState] _ in\n                formState?.dataChanged()\n            }\n            .store(in: &subscriptions)\n    }\n\n    func with(\n        formState: ThomasFormState? = nil,\n        pagerState: PagerState? = nil\n    ) -> ThomasFormDataCollector {\n        let newFormState = formState ?? self.formState\n        let newPagerState = pagerState ?? self.pagerState\n\n        if newFormState === self.formState, newPagerState === self.pagerState {\n            return self\n        }\n\n        return .init(\n            formState: newFormState,\n            pagerState: newPagerState\n        )\n    }\n\n    func updateField(_ field: ThomasFormField, pageID: String?) {\n        formState?.updateField(field) { [weak pagerState] in\n            guard let pageID else { return true }\n            guard let pagerState else { return false }\n\n            let pageIDs = pagerState.pageItems.map { $0.id }\n\n            // Make sure the page ID is within the current history\n            guard\n                let current = pagerState.currentPageId,\n                let currentIndex = pageIDs.lastIndex(of: current),\n                let lastIndex = pageIDs.lastIndex(of: pageID),\n                lastIndex <= currentIndex\n            else {\n                return false\n            }\n            return true\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormField.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@MainActor\nfinal class ThomasFormField: Sendable {\n\n    struct Result: Equatable, Sendable {\n        var value: Value\n        var channels: [Channel]? = nil\n        var attributes: [Attribute]? = nil\n    }\n    \n    enum Status: Equatable, Sendable {\n\n        /// The field is valid and passes all validation checks.\n        case valid(Result)\n\n        /// The field is invalid due to errors or missing information.\n        case invalid\n\n        /// The field is awaiting validation, meaning it's in a pending state.\n        case pending\n\n        /// An error occurred while validating the field.\n        case error\n\n        var isValid: Bool {\n            if case .valid(_) = self {\n                return true\n            }\n            return false\n        }\n    }\n\n    enum Channel: Sendable, Equatable, Hashable {\n        case email(String, ThomasEmailRegistrationOption)\n        case sms(String, ThomasSMSRegistrationOption)\n    }\n\n    struct Attribute: Sendable, Equatable, Hashable {\n        let attributeName: ThomasAttributeName\n        let attributeValue: ThomasAttributeValue\n    }\n\n    enum Value: ThomasSerializable {\n        case toggle(Bool)\n        case radio(AirshipJSON?)\n        case multipleCheckbox(Set<AirshipJSON>)\n        case form(responseType: String?, children: [String: Value])\n        case npsForm(responseType: String?, scoreID: String, children: [String: Value])\n        case text(String?)\n        case email(String?)\n        case sms(String?, ThomasSMSLocale?)\n        case score(AirshipJSON?)\n    }\n\n    private enum FieldType: Sendable {\n        // Immediate result. If nil, its invalid.\n        case just(Result?)\n\n        // Async result\n        case async(any ThomasFormFieldPendingRequest)\n    }\n\n    var status: Status {\n        switch(self.fieldType) {\n        case .just(let result):\n            if let result {\n                .valid(result)\n            } else {\n                .invalid\n            }\n        case .async(let pending):\n            pending.result?.status ?? .pending\n        }\n    }\n\n    func cancel() {\n        switch(self.fieldType) {\n        case .just:\n            break\n        case .async(let operation):\n            operation.cancel()\n        }\n    }\n\n\n    private let fieldType: FieldType\n\n    let identifier: String\n    let input: Value\n\n    /// Initializes a validator instance.\n    /// - Parameter method: The method used to perform the validation.\n    private init(\n        identifier: String,\n        input: Value,\n        fieldType: FieldType\n    ) {\n        self.identifier = identifier\n        self.input = input\n        self.fieldType = fieldType\n    }\n\n    static func asyncField(\n        identifier: String,\n        input: Value,\n        processDelay: TimeInterval = 1.0,\n        processor: (any ThomasFormFieldProcessor)? = nil,\n        resultBlock: @escaping @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult\n    ) -> Self {\n        let actualProcessor = processor ?? DefaultThomasFormFieldProcessor()\n        return .init(\n            identifier: identifier,\n            input: input,\n            fieldType: .async(\n                actualProcessor.submit(\n                    processDelay: processDelay,\n                    resultBlock: resultBlock\n                )\n            )\n        )\n    }\n\n    static func invalidField(\n        identifier: String,\n        input: Value\n    ) -> Self {\n        return .init(\n            identifier: identifier,\n            input: input,\n            fieldType: .just(nil)\n        )\n    }\n\n    static func validField(\n        identifier: String,\n        input: Value,\n        result: Result\n    ) -> Self {\n        return .init(\n            identifier: identifier,\n            input: input,\n            fieldType: .just(result)\n        )\n    }\n\n    var statusUpdates: AsyncStream<Status> {\n        switch(self.fieldType) {\n        case .async(let pending):\n            return pending.resultUpdates {\n                $0?.status ?? .pending\n            }\n        case .just:\n            return AsyncStream { continuation in\n                continuation.yield(status)\n                continuation.finish()\n            }\n        }\n    }\n\n    func process(retryErrors: Bool = true) async {\n        switch(self.fieldType) {\n        case .async(let pending):\n            await pending.process(retryErrors: retryErrors)\n        case .just:\n            break\n        }\n    }\n}\n\nfileprivate extension ThomasFormFieldPendingResult {\n    var status: ThomasFormField.Status {\n        switch (self) {\n        case .valid(let result): .valid(result)\n        case .invalid: .invalid\n        case .error: .error\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormFieldProcessor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasFormFieldPendingResult: Equatable, Sendable {\n    case valid(ThomasFormField.Result)\n    case invalid\n    case error\n\n    var isError: Bool {\n        if case .error = self {\n            return true\n        }\n        return false\n    }\n}\n\n@MainActor\nprotocol ThomasFormFieldPendingRequest: Sendable {\n    var result: ThomasFormFieldPendingResult? { get }\n\n    func resultUpdates<T: Sendable>(mapper: @escaping @Sendable (ThomasFormFieldPendingResult?) -> T) -> AsyncStream<T>\n\n    func process(retryErrors: Bool) async\n\n    func cancel()\n}\n\n@MainActor\nprotocol ThomasFormFieldProcessor: Sendable {\n    func submit(\n        processDelay: TimeInterval,\n        resultBlock: @escaping @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult\n    ) -> any ThomasFormFieldPendingRequest\n}\n\nfinal class DefaultThomasFormFieldProcessor: ThomasFormFieldProcessor {\n\n    private let date: any AirshipDateProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n\n    init(\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper  = DefaultAirshipTaskSleeper()\n    ) {\n        self.date = date\n        self.taskSleeper = taskSleeper\n    }\n\n    @MainActor\n    func submit(\n        processDelay: TimeInterval,\n        resultBlock: @escaping @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult\n    ) -> any ThomasFormFieldPendingRequest {\n        AsyncOperation(\n            date: self.date,\n            taskSleeper: self.taskSleeper,\n            processDelay: processDelay,\n            resultBlock: resultBlock\n        )\n    }\n\n    @MainActor\n    final class AsyncOperation: ThomasFormFieldPendingRequest {\n        var result: ThomasFormFieldPendingResult? {\n            self.lastResult\n        }\n\n        func resultUpdates<T: Sendable>(mapper: @escaping @Sendable (ThomasFormFieldPendingResult?) -> T) -> AsyncStream<T> {\n            return AsyncStream { continuation in\n                let id = UUID().uuidString\n                onResults[id] = { [continuation] result in\n                    continuation.yield(mapper(result))\n                }\n\n                continuation.yield(mapper(lastResult))\n\n                continuation.onTermination = { [weak self] _ in\n                    Task { @MainActor in\n                        self?.onResults[id] = nil\n                    }\n                }\n            }\n        }\n\n        private var onResults: [String: (ThomasFormFieldPendingResult) -> Void] = [:]\n        private let resultBlock: @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult\n\n        private let date: any AirshipDateProtocol\n        private let taskSleeper: any AirshipTaskSleeper\n\n        private var processingTask: Task<Void, Never>?\n        private(set) var lastResult: ThomasFormFieldPendingResult?\n        private var nextBackOff: TimeInterval? = nil\n        private var lastAttempt: Date?\n\n        private static let initialBackOff: TimeInterval = 3.0\n        private static let maxBackfOff: TimeInterval = 15.0\n\n        /// Initializes an asynchronous validator.\n        /// - Parameters:\n        ///   - date: The `AirshipDateProtocol` instance for date handling.\n        ///   - taskSleeper: The `AirshipTaskSleeper` instance for sleeping tasks.\n        ///   - processBlock: The async block that produces the result.\n        init(\n            date: any AirshipDateProtocol,\n            taskSleeper: any AirshipTaskSleeper,\n            processDelay: TimeInterval,\n            resultBlock: @escaping @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult\n        ) {\n            self.resultBlock = resultBlock\n            self.date = date\n            self.taskSleeper = taskSleeper\n            startProcessing(additionalDelay: processDelay)\n        }\n\n        func cancel() {\n            self.processingTask?.cancel()\n            self.processingTask = nil\n        }\n\n        deinit {\n            self.processingTask?.cancel()\n        }\n\n        func process(retryErrors: Bool) async {\n            guard lastResult == nil || (lastResult?.isError == true && retryErrors == true) else {\n                return\n            }\n\n            guard let processingTask, !processingTask.isCancelled else {\n                await startProcessing().value\n                return\n            }\n            \n            await processingTask.value\n\n        }\n\n        /// Starts the validation process.\n        /// - Returns: The task performing the validation.\n        @discardableResult\n        private func startProcessing(additionalDelay: TimeInterval? = nil) -> Task<Void, Never> {\n            self.lastResult = nil\n\n            let task: Task<Void, Never> = Task { @MainActor [weak self] in\n                do {\n                    if let additionalDelay {\n                        try await self?.taskSleeper.sleep(timeInterval: additionalDelay)\n                    }\n                    try await self?.processBackOff()\n                    try Task.checkCancellation()\n                    let result = try await self?.resultBlock()\n                    try Task.checkCancellation()\n                    self?.processResult(result ?? .error)\n                } catch {\n                    self?.processResult(.error)\n                }\n            }\n\n            processingTask = task\n            return task\n        }\n\n        /// Handles backoff logic if validation fails and a retry is needed.\n        /// - Throws: An error if task is cancelled.\n        private func processBackOff() async throws {\n            guard let nextBackOff, let lastAttempt else { return }\n            let remaining = nextBackOff - date.now.timeIntervalSince(lastAttempt)\n            if (remaining > 0) {\n                try await taskSleeper.sleep(timeInterval: remaining)\n            }\n        }\n\n        /// Processes the result of a validation, including handling backoff logic.\n        /// - Parameter result: The result of the validation.\n        private func processResult(_ result: ThomasFormFieldPendingResult)  {\n            self.lastResult = result\n            self.lastAttempt = self.date.now\n            self.onResults.values.forEach { $0(result) }\n            self.processingTask = nil\n\n            if case .error = result {\n                self.nextBackOff = if let last = self.nextBackOff {\n                    min(last * 2, Self.maxBackfOff)\n                } else {\n                    Self.initialBackOff\n                }\n            } else {\n                self.nextBackOff = nil\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormPayloadGenerator.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@MainActor\nstruct ThomasFormPayloadGenerator {\n    private static let typeKey = \"type\"\n    private static let valueKey = \"value\"\n    private static let childrenKey = \"children\"\n    private static let scoreIDKey = \"score_id\"\n    private static let responseTypeKey = \"response_type\"\n    private static let statusKey = \"status\"\n    private static let resultKey = \"result\"\n    private static let dataKey = \"data\"\n\n    /**\n     * This is using an opaque AirshipJSON instead of structured types so we could expose the value to\n     * the automation framework when it was written in obj-c. Eventually we should use structured types\n     * that are encodable so we can have better type safety.\n     */\n\n    static func makeFormStatePayload(\n        status: ThomasFormState.Status,\n        fields: [ThomasFormField],\n        formType: ThomasFormState.FormType\n    ) -> AirshipJSON {\n\n        let data = AirshipJSON.makeObject { builder in\n            let childData = AirshipJSON.makeObject { builder in\n                fields.forEach {\n                    builder.set(\n                        json: makeValuePayload($0.input, status: $0.status),\n                        key: $0.identifier\n                    )\n                }\n            }\n            builder.set(json: childData, key: Self.childrenKey)\n            switch(formType) {\n            case .nps(let scoreID):\n                builder.set(string: \"nps\", key: Self.typeKey)\n                builder.set(string: scoreID, key: Self.scoreIDKey)\n            case .form:\n                builder.set(string: \"form\", key: Self.typeKey)\n            }\n\n        }\n\n        return .object(\n            [\n                Self.dataKey: data,\n                Self.statusKey: makeFormStatusPayload(status)\n            ]\n        )\n    }\n\n    static func makeFormEventPayload(\n        identifier: String,\n        formValue: ThomasFormField.Value\n    ) throws -> AirshipJSON {\n        let isForm = switch(formValue) {\n        case .form, .npsForm: true\n        default: false\n        }\n\n        guard isForm else {\n            throw AirshipErrors.error(\"Form value should be form or npsForm\")\n        }\n\n        return .object([identifier: makeValuePayload(formValue) ?? .object([:])])\n    }\n\n    private static func makeValuePayload(\n        _ value: ThomasFormField.Value,\n        status: ThomasFormField.Status? = nil\n    ) -> AirshipJSON? {\n        switch value {\n        case .toggle(let value):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"toggle\", key: Self.typeKey)\n                builder.set(bool: value, key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n        case .radio(let value):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"single_choice\", key: Self.typeKey)\n                builder.set(json: value, key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n        case .multipleCheckbox(let value):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"multiple_choice\", key: Self.typeKey)\n                builder.set(array: Array(value), key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n        case .text(let value):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"text_input\", key: Self.typeKey)\n                builder.set(string: value, key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n        case .email(let value):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"email_input\", key: Self.typeKey)\n                builder.set(string: value, key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n\n        case .sms(let value, _):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"sms_input\", key: Self.typeKey)\n                builder.set(string: value, key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n        case .score(let value):\n\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"score\", key: Self.typeKey)\n                builder.set(json: value, key: Self.valueKey)\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n            }\n        case .form(let responseType, let children):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"form\", key: Self.typeKey)\n                builder.set(string: responseType, key: Self.responseTypeKey)\n\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n\n                let children = AirshipJSON.makeObject { builder in\n                    children.forEach {\n                        builder.set(json: Self.makeValuePayload($0.value), key: $0.key)\n                    }\n                }\n\n                builder.set(json: children, key: Self.childrenKey)\n\n            }\n        case .npsForm(let responseType, let scoreID, let children):\n            return AirshipJSON.makeObject { builder in\n                builder.set(string: \"nps\", key: Self.typeKey)\n                builder.set(string: responseType, key: Self.responseTypeKey)\n                builder.set(string: scoreID, key: Self.scoreIDKey)\n\n                if let status {\n                    builder.set(json: makeFieldStatusPayload(status), key: Self.statusKey)\n                }\n                \n                let children = AirshipJSON.makeObject { builder in\n                    children.forEach {\n                        builder.set(json: Self.makeValuePayload($0.value), key: $0.key)\n                    }\n                }\n\n                builder.set(json: children, key: Self.childrenKey)\n            }\n        }\n    }\n\n    private static func makeFieldStatusPayload(_ status: ThomasFormField.Status) -> AirshipJSON {\n        AirshipJSON.makeObject { builder in\n            switch(status) {\n            case .valid(let result):\n                builder.set(string: \"valid\", key: Self.typeKey)\n                builder.set(json: makeValuePayload(result.value), key: Self.resultKey)\n            case .invalid:\n                builder.set(string: \"invalid\", key: Self.typeKey)\n            case .pending:\n                builder.set(string: \"pending\", key: Self.typeKey)\n            case .error:\n                builder.set(string: \"error\", key: Self.typeKey)\n            }\n        }\n    }\n\n    private static func makeFormStatusPayload(_ status: ThomasFormState.Status) -> AirshipJSON {\n        AirshipJSON.makeObject { builder in\n            builder.set(string: status.rawValue, key: Self.typeKey)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormResult.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic struct ThomasFormResult: Sendable, Hashable, Equatable {\n    public var identifier: String\n    public var formData: AirshipJSON\n\n    public init(identifier: String, formData: AirshipJSON) {\n        self.identifier = identifier\n        self.formData = formData\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@MainActor\nclass ThomasFormState: ObservableObject {\n\n    /// Represents the possible statuses of a form during its lifecycle.\n    enum Status: String, ThomasSerializable, Hashable {\n        /// The form is valid and all its fields are correctly filled out.\n        case valid\n\n        /// The form is invalid, possibly due to incorrect or missing information.\n        case invalid\n\n        /// An error occurred during form validation or submission.\n        case error\n\n        /// The form is currently being validated.\n        case validating\n\n        /// The form is awaiting validation to be processed.\n        case pendingValidation = \"pending_validation\"\n\n        /// The form has been submitted.\n        case submitted\n    }\n\n    enum FormType: Sendable, Equatable {\n        case nps(String)\n        case form\n    }\n\n    @MainActor\n    private struct Child {\n        var field: ThomasFormField\n        var watchTask: Task<Void, Never>\n        var predicate: (@MainActor @Sendable () -> Bool)?\n    }\n\n    // Minimum time to wait when doing async validation if\n    // all the form results are yet to be ready onValidate\n    private static let minAsyncValidationTime: TimeInterval = 1.0\n\n    @Published\n    private(set) var status: Status {\n        didSet {\n            updateFormInputEnabled(\n                isParentEnabled: self.parentFormState?.isFormInputEnabled\n            )\n        }\n    }\n    \n    @Published\n    private(set) var activeFields: [String: ThomasFormField] = [:]\n\n    @Published\n    private(set) var isVisible: Bool = false\n\n    @Published\n    private(set) var isFormInputEnabled: Bool = true\n\n    @Published\n    var isEnabled: Bool = true {\n        didSet {\n            updateFormInputEnabled(\n                isParentEnabled: self.parentFormState?.isFormInputEnabled\n            )\n        }\n    }\n\n    // On submit block\n    var onSubmit: (@Sendable @MainActor (String, ThomasFormField.Result, LayoutState) throws -> Void)?\n\n    let identifier: String\n    let formType: FormType\n    let formResponseType: String?\n    let validationMode: ThomasFormValidationMode\n\n    private var children: [Child] = []\n    private var subscriptions: Set<AnyCancellable> = Set()\n    private var processTask: Task<Bool, Never>?\n    private var lastChildStatus: [String: ThomasFormField.Status] = [:]\n    private var parentFormState: ThomasFormState?\n    private var initialValues: [String: ThomasFormField.Value] = [:]\n\n    init(\n        identifier: String,\n        formType: FormType,\n        formResponseType: String?,\n        validationMode: ThomasFormValidationMode,\n        parentFormState: ThomasFormState? = nil\n    ) {\n        self.identifier = identifier\n        self.formType = formType\n        self.formResponseType = formResponseType\n        self.validationMode = validationMode\n        self.parentFormState = parentFormState\n        self.status = if validationMode == .immediate {\n            .invalid\n        } else {\n            .pendingValidation\n        }\n        \n        parentFormState?.$isFormInputEnabled.sink { [weak self] parentEnabled in\n            self?.updateFormInputEnabled(isParentEnabled: parentEnabled)\n        }.store(in: &subscriptions)\n    }\n\n    func field(identifier: String) -> ThomasFormField? {\n        return self.children.first { child in\n            child.field.identifier == identifier\n        }?.field\n    }\n    \n    func fieldValue(identifier: String) -> ThomasFormField.Value? {\n        if let child = field(identifier: identifier)?.input {\n            return child\n        }\n        \n        return initialValues[identifier]\n    }\n\n    func lastFieldStatus(identifier: String) -> ThomasFormField.Status? {\n        return self.lastChildStatus[identifier]\n    }\n\n    func markVisible() {\n        guard !self.isVisible else { return }\n        parentFormState?.markVisible()\n        self.isVisible = true\n    }\n\n\n    func validate() async -> Bool {\n        return await self.processChildren(children: self.filteredChildren())\n    }\n\n    func submit(layoutState: LayoutState) async throws {\n        guard self.status != .submitted else {\n            throw AirshipErrors.error(\"Form already submitted\")\n        }\n\n        guard let onSubmit else {\n            throw AirshipErrors.error(\"onSubmit block missing\")\n        }\n\n        // Grab a snapshot of the children since this is a async\n        // task so data might change while we process and submit\n        let children = self.filteredChildren()\n        guard await self.processChildren(children: children) else {\n            throw AirshipErrors.error(\"Form not valid\")\n        }\n\n        var attributesResult: [ThomasFormField.Attribute] = []\n        var channelsResult: [ThomasFormField.Channel] = []\n        var resultMap: [String: ThomasFormField.Value] = [:]\n\n        try children.forEach {\n            if case let .valid(result) = $0.field.status {\n                resultMap[$0.field.identifier] = result.value\n                if let channels = result.channels {\n                    channelsResult.append(contentsOf: channels)\n                }\n\n                if let attributes = result.attributes {\n                    attributesResult.append(contentsOf: attributes)\n                }\n            } else {\n                throw AirshipErrors.error(\"Form is not valid\")\n            }\n        }\n\n        guard !resultMap.isEmpty else {\n            throw AirshipErrors.error(\"Form has no data\")\n        }\n\n        let formResult = ThomasFormField.Result(\n            value: self.formType.makeField(\n                responseType: self.formResponseType,\n                children: resultMap\n            ),\n            channels: channelsResult,\n            attributes: attributesResult\n        )\n\n        try onSubmit(self.identifier, formResult, layoutState)\n        updateStatus(.submitted)\n    }\n\n    func dataChanged() {\n        guard self.status != .submitted else { return }\n\n        let children = self.filteredChildren()\n        self.updateActiveFields(children: children)\n\n        switch(self.status) {\n        case .error, .valid, .invalid, .pendingValidation:\n            let childStatuses = children.compactMap { lastChildStatus[$0.field.identifier] }\n\n            switch (self.validationMode) {\n            case .onDemand:\n                // On demand we want to leave the child in an invalid or error state\n                // until the next validation request.\n                if childStatuses.contains(.invalid) {\n                    self.updateStatus(.invalid)\n                } else if status == .error, !childStatuses.contains(.error) {\n                    self.updateStatus(.pendingValidation)\n                } else if childStatuses.contains(.pending) {\n                    self.updateStatus(.pendingValidation)\n                } else if status != .pendingValidation {\n                    if childStatuses.contains(.error) {\n                        self.updateStatus(.error)\n                    } else {\n                        self.updateStatus(.valid)\n                    }\n                }\n            case .immediate:\n                // Immediate we want to go to pending if any pending since\n                // and schedule a task to validate\n                if childStatuses.contains(.pending) {\n                    self.updateStatus(.pendingValidation)\n                } else if childStatuses.contains(.invalid) {\n                    self.updateStatus(.invalid)\n                } else if status == .error, !childStatuses.contains(.error) {\n                    self.updateStatus(.pendingValidation)\n                } else if status != .pendingValidation {\n                    if childStatuses.contains(.error) {\n                        self.updateStatus(.error)\n                    } else {\n                        self.updateStatus(.valid)\n                    }\n                }\n            }\n\n        case .submitted, .validating:\n            break\n        }\n\n        guard validationMode == .immediate else { return }\n\n        if status != .valid, status != .invalid {\n            Task { [weak self] in\n                await self?.validate()\n            }\n        }\n    }\n\n    func updateField(\n        _ field: ThomasFormField,\n        predicate: (@Sendable @MainActor () -> Bool)? = nil\n    ) {\n        guard self.status != .submitted else { return }\n\n        self.processTask?.cancel()\n\n        if self.status == .validating {\n            updateStatus(.pendingValidation)\n        }\n\n        // Skip updating the field to pending if its invalid and the incoming data\n        // is invalid.\n        if field.status != .invalid || lastChildStatus[field.identifier] != .invalid {\n            lastChildStatus[field.identifier] = .pending\n        }\n\n        self.children.removeAll { child in\n            if child.field.identifier == field.identifier {\n                child.watchTask.cancel()\n                child.field.cancel()\n                return true\n            }\n            return false\n        }\n\n        if self.activeFields[field.identifier] != nil  {\n            self.activeFields[field.identifier] = field\n        }\n        \n        self.children.append(\n            Child(\n                field: field,\n                watchTask: Task { [weak self, field] in\n                    for await _ in field.statusUpdates {\n                        guard !Task.isCancelled else { return }\n                        self?.updateActiveFields()\n                    }\n                },\n                predicate: predicate\n            )\n        )\n\n        self.dataChanged()\n    }\n\n    private func processChildren(children: [Child]) async -> Bool  {\n        guard self.status != .submitted else { return false }\n        self.processTask?.cancel()\n\n        let needsAsync = children.contains { child in\n            child.field.status == .error || child.field.status == .pending\n        }\n        \n        await Task.yield() // Yield to allow the UI to update before processing\n        updateStatus(.validating)\n        let task = Task { [weak self] in\n\n            guard needsAsync else {\n                return self?.processingFinished(children: children) ?? false\n            }\n\n            let start = AirshipDate.shared.now\n            await withTaskGroup(of: Void.self) { group in\n                for child in children {\n                    group.addTask {\n                        await child.field.process(retryErrors: true)\n                    }\n                }\n                await group.waitForAll()\n            }\n\n            // Make sure it took the min time to avoid UI glitches\n            let end = AirshipDate.shared.now\n            let remaining = Self.minAsyncValidationTime - end.timeIntervalSince(start)\n            if (remaining > 0) {\n                try? await DefaultAirshipTaskSleeper.shared.sleep(timeInterval: remaining)\n            }\n\n            guard !Task.isCancelled else { return false }\n            return self?.processingFinished(children: children) ?? false\n        }\n\n        self.processTask = task\n        return await task.value\n    }\n\n    private func updateStatus(_ status: Status) {\n        guard self.status != .submitted, self.status != status else {\n            return\n        }\n        AirshipLogger.trace(\"Updating status \\(self.status) => \\(status)\")\n        self.status = status\n    }\n\n    private func updateActiveFields(children: [Child]) {\n        let currentKeys: Set<String> = Set(self.activeFields.values.map { $0.identifier })\n        let incomingKeys: Set<String> = Set(children.map { $0.field.identifier })\n        guard currentKeys != incomingKeys else { return }\n\n        self.activeFields = Dictionary(\n            uniqueKeysWithValues: children.map {\n                ($0.field.identifier, $0.field)\n            }\n        )\n    }\n\n    private func updateActiveFields() {\n        self.updateActiveFields(children: self.filteredChildren())\n    }\n\n    private func filteredChildren() -> [Child] {\n        return self.children.filter { value in\n            value.predicate?() ?? true\n        }\n    }\n\n    private func processingFinished(children: [Child]) -> Bool {\n        defer {\n            self.dataChanged()\n        }\n\n        var containsError: Bool = false\n        var containsInvalid: Bool = false\n        var containsValid: Bool = false\n\n        for child in children {\n            let status = child.field.status\n\n            if status == .error {\n                containsError = true\n            }\n\n            if status == .invalid {\n                containsInvalid = true\n            }\n\n            if status.isValid {\n                containsValid = true\n            }\n\n            lastChildStatus[child.field.identifier] = status\n        }\n\n        if containsInvalid {\n            updateStatus(.invalid)\n            return false\n        } else if containsError {\n            updateStatus(.error)\n\n            // If we are in immediate validation mode and we hit an error we need\n            // to retry right away since the submit button to update wont be available\n            // to retrigger a retry.\n            if validationMode == .immediate {\n                Task { [weak self] in\n                    _ = await self?.validate()\n                }\n            }\n            return false\n        } else if containsValid {\n            updateStatus(.valid)\n            return true\n        } else {\n            updateStatus(.invalid)\n            return false\n        }\n    }\n\n    private func updateFormInputEnabled(isParentEnabled: Bool?) {\n        // Check if we are in a state that allows editing\n        // inputs.\n        let statusCheck = switch(status) {\n        case .valid, .invalid, .error, .pendingValidation: true\n        case .submitted: false\n        case .validating: validationMode == .immediate\n        }\n\n        guard\n            self.isEnabled,\n            statusCheck,\n            isParentEnabled ?? true\n        else {\n            if self.isFormInputEnabled {\n                self.isFormInputEnabled = false\n            }\n            return\n        }\n\n        if !self.isFormInputEnabled {\n            self.isFormInputEnabled = true\n        }\n    }\n}\n\nfileprivate extension ThomasFormState.FormType {\n    @MainActor\n    func makeField(\n        responseType: String?,\n        children: [String: ThomasFormField.Value]\n    ) -> ThomasFormField.Value {\n        return switch(self) {\n        case .form:\n                .form(responseType: responseType, children: children)\n        case .nps(let scoreID):\n                .npsForm(responseType: responseType, scoreID: scoreID, children: children)\n        }\n    }\n}\n\n//MARK: - ThomasStateProvider\nextension ThomasFormState: ThomasStateProvider {\n    typealias SnapshotType = Snapshot\n    \n    struct Snapshot: Codable, Equatable {\n        let initialValues: [String: ThomasFormField.Value]\n        let isVisible: Bool\n        let isFormInputEnabled: Bool\n        let status: Status\n    }\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return Publishers\n            .CombineLatest4($activeFields, $isVisible, $isFormInputEnabled, $status)\n            .map { activeFields, isVisible, isFormInputEnabled, status in\n                Snapshot(\n                    initialValues: activeFields.mapValues(\\.input),\n                    isVisible: isVisible,\n                    isFormInputEnabled: isFormInputEnabled,\n                    status: status\n                )\n            }\n            .removeDuplicates()\n            .map(\\.self)\n            .eraseToAnyPublisher()\n    }\n    \n    func restorePersistentState(_ state: Snapshot) {\n        self.initialValues = state.initialValues\n        self.isVisible = state.isVisible\n        self.isFormInputEnabled = state.isFormInputEnabled\n        self.status = state.status\n    }\n    \n    func persistentStateSnapshot() -> SnapshotType {\n        return Snapshot(\n            initialValues: activeFields.mapValues(\\.input),\n            isVisible: isVisible,\n            isFormInputEnabled: isFormInputEnabled,\n            status: status\n        )\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormSubmitBehavior.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasFormSubmitBehavior: String, ThomasSerializable {\n    case submitEvent = \"submit_event\"\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasFormValidationMode.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Represents the validation modes for a form.\nenum ThomasFormValidationMode: ThomasSerializable {\n\n    /// The form is validated only when a `ThomasButtonClickBehavior.formSubmit`\n    /// or `ThomasButtonClickBehavior.formValidate` is triggered.\n    case onDemand\n\n    /// The form is validated immediately after any changes are made.\n    case immediate\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    private enum ValidationType: String, Codable {\n        case onDemand = \"on_demand\"\n        case immediate\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type: ValidationType = try container.decode(ValidationType.self, forKey: .type)\n        self = switch(type) {\n        case .onDemand: .onDemand\n        case .immediate: .immediate\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch (self) {\n        case .onDemand: try container.encode(ValidationType.onDemand, forKey: .type)\n        case .immediate: try container.encode(ValidationType.immediate, forKey: .type)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasIcon.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasIconInfo: ThomasSerializable {\n    let type: String = \"icon\"\n    var icon: Icon\n    var color: ThomasColor\n    var scale: Double?\n\n    enum Icon: String, ThomasSerializable {\n        case close\n        case checkmark\n        case forwardArrow = \"forward_arrow\"\n        case backArrow = \"back_arrow\"\n        case exclamationmarkCircleFill = \"exclamationmark_circle_fill\"\n        case progressSpinner = \"progress_spinner\"\n        case asterisk\n        case asteriskCicleFill = \"asterisk_circle_fill\"\n        case star = \"star\"\n        case starFill = \"star_fill\"\n        case heart = \"heart\"\n        case heartFill = \"heart_fill\"\n        case chevronBackward = \"chevron_backward\"\n        case chevronForward = \"chevron_forward\"\n        case pause\n        case play\n        case mute\n        case unmute\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case icon\n        case color\n        case scale\n        case type\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutButtonTapEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutButtonTapEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppButtonTap\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.ButtonTapEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutContext.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic struct ThomasLayoutContext: Encodable, Equatable, Sendable {\n    public struct Pager: Encodable, Equatable, Sendable {\n        public var identifier: String\n        public var pageIdentifier: String\n        public var pageIndex: Int\n        public var completed: Bool\n        public var count: Int\n        public var pageHistory: [ThomasViewedPageInfo] = []\n\n        public init(\n            identifier: String,\n            pageIdentifier: String,\n            pageIndex: Int,\n            completed: Bool,\n            count: Int,\n            pageHistory: [ThomasViewedPageInfo] = []\n        ) {\n            self.identifier = identifier\n            self.pageIdentifier = pageIdentifier\n            self.pageIndex = pageIndex\n            self.completed = completed\n            self.count = count\n            self.pageHistory = pageHistory\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier\n            case pageIdentifier = \"page_identifier\"\n            case pageIndex = \"page_index\"\n            case completed\n            case count\n            case pageHistory = \"page_history\"\n        }\n    }\n\n    public struct Form: Encodable, Equatable, Sendable {\n        public var identifier: String\n        public var submitted: Bool\n        public var type: String\n        public var responseType: String?\n\n        public init(identifier: String, submitted: Bool, type: String, responseType: String? = nil) {\n            self.identifier = identifier\n            self.submitted = submitted\n            self.type = type\n            self.responseType = responseType\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier\n            case submitted\n            case type\n            case responseType = \"response_type\"\n        }\n    }\n\n    public struct Button: Encodable, Equatable, Sendable {\n        public var identifier: String\n\n        public init(identifier: String) {\n            self.identifier = identifier\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case identifier\n        }\n    }\n\n    public var pager: Pager?\n    public var button: Button?\n    public var form: Form?\n\n    public init(pager: Pager? = nil, button: Button? = nil, form: Form? = nil) {\n        self.pager = pager\n        self.button = button\n        self.form = form\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutDisplayEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ThomasLayoutDisplayEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppDisplay\n    public let data: (any Sendable & Encodable)? = nil\n    \n    public init() {}\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol ThomasLayoutEvent: Sendable {\n    var name: EventType { get }\n    var data: (any Sendable&Encodable)? { get }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutEventContext.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ThomasLayoutEventContext: Encodable, Equatable, Sendable {\n    public struct Display: Encodable, Equatable, Sendable {\n        public var triggerSessionID: String\n        public var isFirstDisplay: Bool\n        public var isFirstDisplayTriggerSessionID: Bool\n        \n        public init(triggerSessionID: String, isFirstDisplay: Bool, isFirstDisplayTriggerSessionID: Bool) {\n            self.triggerSessionID = triggerSessionID\n            self.isFirstDisplay = isFirstDisplay\n            self.isFirstDisplayTriggerSessionID = isFirstDisplayTriggerSessionID\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case triggerSessionID = \"trigger_session_id\"\n            case isFirstDisplay = \"is_first_display\"\n            case isFirstDisplayTriggerSessionID = \"is_first_display_trigger_session\"\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case pager\n        case button\n        case form\n        case reportingContext = \"reporting_context\"\n        case experimentsReportingData = \"experiments\"\n        case display\n    }\n\n    var pager: ThomasLayoutContext.Pager?\n    var button:  ThomasLayoutContext.Button?\n    var form:  ThomasLayoutContext.Form?\n    var display: Display?\n\n    var reportingContext: AirshipJSON?\n    var experimentsReportingData: [AirshipJSON]?\n}\n\npublic extension ThomasLayoutEventContext {\n\n    static func makeContext(\n        reportingContext: AirshipJSON?,\n        experimentsResult: ExperimentResult?,\n        layoutContext: ThomasLayoutContext?,\n        displayContext: ThomasLayoutEventContext.Display?\n    ) -> ThomasLayoutEventContext? {\n        let pager = layoutContext?.pager\n        let button = layoutContext?.button\n        let form = layoutContext?.form\n        let reportingContext = reportingContext\n        let experimentsReportingData = experimentsResult?.reportingMetadata\n\n        guard\n            pager == nil,\n            button == nil,\n            form == nil,\n            reportingContext == nil,\n            experimentsReportingData?.isEmpty != false,\n            displayContext == nil\n        else {\n            return ThomasLayoutEventContext(\n                pager: pager,\n                button: button,\n                form: form,\n                display: displayContext,\n                reportingContext: reportingContext,\n                experimentsReportingData: experimentsReportingData\n            )\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutEventMessageID.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic enum ThomasLayoutEventMessageID: Encodable, Equatable, Sendable {\n    case legacy(identifier: String)\n    case airship(identifier: String, campaigns: AirshipJSON?)\n    case appDefined(identifier: String)\n\n    enum CodingKeys: String, CodingKey {\n        case messageID = \"message_id\"\n        case campaigns\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .legacy(identifier: let identifier):\n            var container = encoder.singleValueContainer()\n            try container.encode(identifier)\n        case .airship(identifier: let identifier, campaigns: let campaigns):\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            try container.encode(identifier, forKey: .messageID)\n            try container.encodeIfPresent(campaigns, forKey: .campaigns)\n        case .appDefined(identifier: let identifier):\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            try container.encode(identifier, forKey: .messageID)\n        }\n    }\n    \n    public var identifier: String {\n        switch self {\n        case .legacy(let identifier): return identifier\n        case .airship(let identifier, _): return identifier\n        case .appDefined(let identifier): return identifier\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutEventRecorder.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ThomasLayoutEventData {\n    let event: any ThomasLayoutEvent\n    let context: ThomasLayoutEventContext?\n    let source: ThomasLayoutEventSource\n    let messageID: ThomasLayoutEventMessageID\n    let renderedLocale: AirshipJSON?\n    \n    public init(\n        event: any ThomasLayoutEvent,\n        context: ThomasLayoutEventContext?,\n        source: ThomasLayoutEventSource,\n        messageID: ThomasLayoutEventMessageID,\n        renderedLocale: AirshipJSON?\n    ) {\n        self.event = event\n        self.context = context\n        self.source = source\n        self.messageID = messageID\n        self.renderedLocale = renderedLocale\n    }\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic protocol ThomasLayoutEventRecorderProtocol: Sendable {\n    func recordEvent(inAppEventData: ThomasLayoutEventData)\n    func recordImpressionEvent(_ event: AirshipMeteredUsageEvent)\n}\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ThomasLayoutEventRecorder: ThomasLayoutEventRecorderProtocol {\n    private let airshipAnalytics: any InternalAirshipAnalytics\n    private let meteredUsage: any AirshipMeteredUsage\n\n    public init(airshipAnalytics: any InternalAirshipAnalytics, meteredUsage: any AirshipMeteredUsage) {\n        self.airshipAnalytics = airshipAnalytics\n        self.meteredUsage = meteredUsage\n    }\n\n    public func recordEvent(inAppEventData: ThomasLayoutEventData) {\n        let eventBody = EventBody(\n            identifier: inAppEventData.messageID,\n            source: inAppEventData.source,\n            context: inAppEventData.context,\n            conversionSendID: airshipAnalytics.conversionSendID,\n            conversionPushMetadata: airshipAnalytics.conversionPushMetadata,\n            renderedLocale: inAppEventData.renderedLocale,\n            baseData: inAppEventData.event.data\n        )\n\n        do {\n            airshipAnalytics.recordEvent(\n                AirshipEvent(\n                    eventType: inAppEventData.event.name,\n                    eventData: try AirshipJSON.wrap(eventBody)\n                )\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to add event \\(inAppEventData) error \\(error)\")\n        }\n    }\n\n    public func recordImpressionEvent(_ event: AirshipMeteredUsageEvent) {\n        Task { [meteredUsage] in\n            do {\n                try await meteredUsage.addEvent(event)\n            } catch {\n                AirshipLogger.error(\"Failed to record impression: \\(error)\")\n            }\n        }\n    }\n}\n\nfileprivate struct EventBody: Encodable, Sendable {\n    var identifier: ThomasLayoutEventMessageID\n    var source: ThomasLayoutEventSource\n    var context: ThomasLayoutEventContext?\n    var conversionSendID: String?\n    var conversionPushMetadata: String?\n    var renderedLocale: AirshipJSON?\n    var baseData: (any Encodable&Sendable)?\n\n    enum CodingKeys: String, CodingKey {\n        case identifier = \"id\"\n        case source\n        case context\n        case conversionSendID = \"conversion_send_id\"\n        case conversionPushMetadata = \"conversion_metadata\"\n        case renderedLocale = \"rendered_locale\"\n        case deviceInfo = \"device\"\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: Self.CodingKeys.self)\n        try container.encode(self.identifier, forKey: Self.CodingKeys.identifier)\n        try container.encode(self.source, forKey: Self.CodingKeys.source)\n        try container.encodeIfPresent(self.context, forKey: Self.CodingKeys.context)\n        try container.encodeIfPresent(self.conversionSendID, forKey: Self.CodingKeys.conversionSendID)\n        try container.encodeIfPresent(self.conversionPushMetadata, forKey: Self.CodingKeys.conversionPushMetadata)\n        try container.encodeIfPresent(self.renderedLocale, forKey: Self.CodingKeys.renderedLocale)\n        try self.baseData?.encode(to: encoder)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutEventSource.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic enum ThomasLayoutEventSource: String, Encodable, Sendable {\n    case airship = \"urban-airship\"\n    case appDefined = \"app-defined\"\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutFormDisplayEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutFormDisplayEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppFormDisplay\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.FormDisplayEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutFormResultEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutFormResultEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppFormResult\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.FormResultEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutGestureEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutGestureEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppGesture\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.GestureEvent) {\n        self.data = data\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutPageActionEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutPageActionEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppPageAction\n    public let data: (any Sendable & Encodable)?\n    \n    public init(data: ThomasReportingEvent.PageActionEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutPageSwipeEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutPageSwipeEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppPageSwipe\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.PageSwipeEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutPageViewEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutPageViewEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppPageView\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.PageViewEvent) {\n        self.data = data\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutPagerCompletedEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutPagerCompletedEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppPagerCompleted\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.PagerCompletedEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutPagerSummaryEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasLayoutPagerSummaryEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppPagerSummary\n    public let data: (any Sendable & Encodable)?\n\n    public init(data: ThomasReportingEvent.PagerSummaryEvent) {\n        self.data = data\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutPermissionResultEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ThomasLayoutPermissionResultEvent: ThomasLayoutEvent {\n    public let name: EventType = EventType.inAppPermissionResult\n    public let data: (any Sendable & Encodable)?\n\n    public init(\n        permission: AirshipPermission,\n        startingStatus: AirshipPermissionStatus,\n        endingStatus: AirshipPermissionStatus\n    ) {\n        self.data = PermissionResultData(\n            permission: permission,\n            startingStatus: startingStatus,\n            endingStatus: endingStatus\n        )\n    }\n\n    private struct PermissionResultData: Encodable, Sendable {\n        var permission: AirshipPermission\n        var startingStatus: AirshipPermissionStatus\n        var endingStatus: AirshipPermissionStatus\n\n        enum CodingKeys: String, CodingKey {\n            case permission\n            case startingStatus = \"starting_permission_status\"\n            case endingStatus = \"ending_permission_status\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasLayoutResolutionEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// NOTE: For internal use only. :nodoc:\npublic struct ThomasLayoutResolutionEvent: ThomasLayoutEvent {\n\n    public let name: EventType = EventType.inAppResolution\n    public let data: (any Sendable & Encodable)?\n\n    private init(data: any Sendable & Encodable) {\n        self.data = data\n    }\n\n    public static func buttonTap(\n        identifier: String,\n        description: String,\n        displayTime: TimeInterval\n    ) -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .buttonTap(\n                    identifier: identifier,\n                    description: description\n                ),\n                displayTime: displayTime\n            )\n        )\n    }\n\n    public static func messageTap(displayTime: TimeInterval) -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .messageTap,\n                displayTime: displayTime\n            )\n        )\n    }\n\n    public static func userDismissed(displayTime: TimeInterval) -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .userDismissed,\n                displayTime: displayTime\n            )\n        )\n    }\n\n    public static func timedOut(displayTime: TimeInterval) -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .timedOut,\n                displayTime: displayTime\n            )\n\n        )\n    }\n\n    public static func interrupted() -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .interrupted,\n                displayTime: 0.0\n            )\n        )\n    }\n\n    public static func control(\n        experimentResult: ExperimentResult\n    ) -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .control,\n                displayTime: 0.0,\n                device: DeviceInfo(\n                    channel: experimentResult.channelID,\n                    contact: experimentResult.contactID\n                )\n            )\n        )\n    }\n\n    public static func audienceExcluded() -> ThomasLayoutResolutionEvent {\n        return ThomasLayoutResolutionEvent(\n            data: ResolutionData(\n                resolutionType: .audienceCheckExcluded,\n                displayTime: 0.0\n            )\n        )\n    }\n\n    private struct DeviceInfo: Encodable, Sendable {\n        var channel: String?\n        var contact: String?\n\n        enum CodingKeys: String, CodingKey {\n            case channel = \"channel_id\"\n            case contact = \"contact_id\"\n        }\n    }\n\n    private struct ResolutionData: Encodable, Sendable {\n\n        enum ResolutionType {\n            case buttonTap(identifier: String, description: String)\n            case messageTap\n            case userDismissed\n            case timedOut\n            case interrupted\n            case control\n            case audienceCheckExcluded\n        }\n\n        let resolutionType: ResolutionType\n        let displayTime: TimeInterval\n        var device: DeviceInfo?\n\n        enum CodingKeys: String, CodingKey {\n            case resolutionType = \"type\"\n            case displayTime = \"display_time\"\n            case buttonID = \"button_id\"\n            case buttonDescription = \"button_description\"\n        }\n\n        enum ContainerCodingKeys: String, CodingKey {\n            case resolution = \"resolution\"\n            case device = \"device\"\n        }\n\n        public func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: ContainerCodingKeys.self)\n            var resolution = container.nestedContainer(keyedBy: CodingKeys.self, forKey: .resolution)\n\n            try resolution.encode(\n                String(format: \"%.2f\", displayTime),\n                forKey: .displayTime\n            )\n\n            try container.encodeIfPresent(self.device, forKey: .device)\n\n            switch (self.resolutionType) {\n            case .buttonTap(let identifier, let description):\n                try resolution.encode(\"button_click\", forKey: .resolutionType)\n                try resolution.encode(identifier, forKey: .buttonID)\n                try resolution.encode(description, forKey: .buttonDescription)\n\n            case .messageTap:\n                try resolution.encode(\"message_click\", forKey: .resolutionType)\n            case .userDismissed:\n                try resolution.encode(\"user_dismissed\", forKey: .resolutionType)\n            case .timedOut:\n                try resolution.encode(\"timed_out\", forKey: .resolutionType)\n            case .interrupted:\n                try resolution.encode(\"interrupted\", forKey: .resolutionType)\n            case .control:\n                try resolution.encode(\"control\", forKey: .resolutionType)\n            case .audienceCheckExcluded:\n                try resolution.encode(\"audience_check_excluded\", forKey: .resolutionType)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasMargin.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasMargin: ThomasSerializable {\n    var top: CGFloat?\n    var bottom: CGFloat?\n    var start: CGFloat?\n    var end: CGFloat?\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasMarkdownOptions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasMarkDownOptions: ThomasSerializable {\n    var disabled: Bool?\n    var appearance: Appearance?\n\n    struct Appearance: ThomasSerializable {\n        var anchor: Anchor?\n        var highlight: Highlight?\n\n        struct Highlight: ThomasSerializable {\n            var color: ThomasColor?\n            var cornerRadius: Double?\n\n            enum CodingKeys: String, CodingKey {\n                case color\n                case cornerRadius = \"corner_radius\"\n            }\n        }\n\n        struct Anchor: ThomasSerializable {\n            var color: ThomasColor?\n            // Currently we only support underlined styles\n            var styles: [ThomasTextAppearance.TextStyle]?\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasMediaFit.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasMediaFit: String, ThomasSerializable {\n    case center\n    case fitCrop = \"fit_crop\"\n    case centerInside = \"center_inside\"\n    case centerCrop = \"center_crop\"\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasOrientation.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasOrientation: String, ThomasSerializable {\n    case portrait\n    case landscape\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasPagerControllerBranching.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/**\n  * Pager branching directives. These control the branching behavior of the\n  * `PagerController`.\n  */\nstruct ThomasPagerControllerBranching: ThomasSerializable {\n    /**\n      * Determines when a pager is completed, since we can not rely on the last\n      * page meaning \"completed\" in branching. The given `PagerCompletions` are\n      * evaluated to determine that completion. Evaluated in order, first match\n      * wins.\n      */\n    let completions: [ThomasPageControllerCompletion]\n    \n    enum CodingKeys: String, CodingKey {\n        case completions = \"pager_completions\"\n    }\n}\n\n/**\n  * Pager completion directives; used to determine when a pager has been\n  * completed, and optional actions to take upon completion.\n  */\nstruct ThomasPageControllerCompletion: ThomasSerializable {\n    /**\n      * Predicate to match when evaluating completion. If not provided, it is an\n      * implicit match.\n      */\n    let predicate: JSONPredicate?\n    \n    /**\n      * State actions to run when the pager completes.\n      */\n    let stateActions: [ThomasStateAction]?\n    \n    enum CodingKeys: String, CodingKey {\n        case predicate = \"when_state_matches\"\n        case stateActions = \"state_actions\"\n    }\n}\n\n/**\n  * Page branching directives, used to evaluate page behavior when the page's\n  * parent controller has branching enabled.\n  */\nstruct ThomasPageBranching: ThomasSerializable {\n    /**\n      * Controls which page should be used as the next page; only evaluated when\n      * the `PagerController` is configured for branching logic. Predicates are\n      * evaluated in order, and the first matching predicate is used. If no\n      * predicates are matched, or if this directive is not present, proceeding to\n      * the next page is blocked.\n      */\n    let nextPage: [ThomasNextPageSelector]?\n    \n    enum CodingKeys: String, CodingKey {\n        case nextPage = \"next_page\"\n    }\n    \n    private enum NextPageSelectorKeys: String, CodingKey {\n        case selectors\n    }\n    \n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        \n        let nextPageContainer = try container.nestedContainer(\n            keyedBy: NextPageSelectorKeys.self,\n            forKey: .nextPage)\n        \n        self.nextPage = try nextPageContainer.decodeIfPresent([ThomasNextPageSelector].self, forKey: .selectors)\n    }\n    \n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        \n        var nextPageContainer = container.nestedContainer(keyedBy: NextPageSelectorKeys.self, forKey: .nextPage)\n        try nextPageContainer.encode(self.nextPage, forKey: .selectors)\n    }\n}\n\nstruct ThomasNextPageSelector: ThomasSerializable {\n    /**\n      * Predicate which is matched for the given `page_id`. When `undefined`, it is\n      * an implicit match.\n      */\n    let predicate: JSONPredicate?\n    \n    /**\n      * ID of the page to be used as the next page.\n      */\n    let pageId: String\n    \n    enum CodingKeys: String, CodingKey {\n        case predicate = \"when_state_matches\"\n        case pageId = \"page_id\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasPagerTracker.swift",
    "content": "import Foundation\n\n@MainActor\nfinal class ThomasPagerTracker {\n\n    // Map of pager ID to trackers\n    private var trackers: [String: Tracker] = [:]\n    private var lastPagerPageEvent: [String: ThomasReportingEvent.PageViewEvent] = [:]\n\n    func onPageView(\n        pageEvent: ThomasReportingEvent.PageViewEvent,\n        currentDisplayTime: TimeInterval\n    ) {\n        if trackers[pageEvent.identifier] == nil {\n            trackers[pageEvent.identifier] = Tracker()\n        }\n\n        let page = Page(\n            identifier: pageEvent.pageIdentifier,\n            index: pageEvent.pageIndex\n        )\n\n        trackers[pageEvent.identifier]?.start(\n            page: page,\n            currentDisplayTime: currentDisplayTime\n        )\n\n        lastPagerPageEvent[pageEvent.identifier] = pageEvent\n    }\n\n    func stopAll(currentDisplayTime: TimeInterval) {\n        self.trackers.values.forEach { $0.stop(currentDisplayTime: currentDisplayTime) }\n    }\n\n    func viewedPages(pagerIdentifier: String) -> [ThomasViewedPageInfo] {\n        return trackers[pagerIdentifier]?.viewed ?? []\n    }\n\n    var summary: Set<ThomasReportingEvent.PagerSummaryEvent> {\n        let summary = lastPagerPageEvent.map { id, event in\n            ThomasReportingEvent.PagerSummaryEvent(\n                identifier: id,\n                viewedPages: trackers[id]?.viewed ?? [],\n                pageCount: event.pageCount,\n                completed: event.completed\n            )\n        }\n\n        return Set(summary)\n    }\n\n    @MainActor\n    fileprivate final class Tracker {\n        private var currentPage: Page?\n        var viewed: [ThomasViewedPageInfo] = []\n        private var startTime: TimeInterval?\n\n        func start(page: Page, currentDisplayTime: TimeInterval) {\n            guard currentPage != page else { return }\n            stop(currentDisplayTime: currentDisplayTime)\n            self.currentPage = page\n            self.startTime = currentDisplayTime\n        }\n\n        func stop(currentDisplayTime: TimeInterval) {\n            guard let startTime, let currentPage else { return }\n\n            viewed.append(\n                ThomasViewedPageInfo(\n                    identifier: currentPage.identifier,\n                    index: currentPage.index,\n                    displayTime: currentDisplayTime - startTime\n                )\n            )\n\n            self.startTime = nil\n            self.currentPage = nil\n        }\n    }\n\n    fileprivate struct Page: Equatable {\n        let identifier: String\n        let index: Int\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasPlatform.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nenum ThomasPlatform: String, ThomasSerializable {\n    case android\n    case ios\n    case web\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasPosition.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct ThomasPosition: ThomasSerializable {\n    var horizontal: Horizontal\n    var vertical: Vertical\n\n    enum Horizontal: String, ThomasSerializable {\n        case center\n        case start\n        case end\n    }\n\n    enum Vertical: String, ThomasSerializable {\n        case center\n        case top\n        case bottom\n    }\n}\n\nextension ThomasPosition {\n    var alignment: Alignment {\n        Alignment(horizontal: horizontal.alignment, vertical: vertical.alignment)\n    }\n}\n\nextension ThomasPosition.Vertical {\n    var alignment: VerticalAlignment {\n        switch self {\n        case .top: return VerticalAlignment.top\n        case .center: return VerticalAlignment.center\n        case .bottom: return VerticalAlignment.bottom\n        }\n    }\n}\n\nextension ThomasPosition.Horizontal {\n    var alignment: HorizontalAlignment {\n        switch self {\n        case .start: return HorizontalAlignment.leading\n        case .center: return HorizontalAlignment.center\n        case .end: return HorizontalAlignment.trailing\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasPresentationInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasPresentationInfo: ThomasSerializable {\n    case banner(Banner)\n    case modal(Modal)\n    case embedded(Embedded)\n\n    enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(PresentationType.self, forKey: .type)\n\n        self = switch type {\n        case .banner: .banner(try Banner(from: decoder))\n        case .modal: .modal(try Modal(from: decoder))\n        case .embedded: .embedded(try Embedded(from: decoder))\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .banner(let info): try info.encode(to: encoder)\n        case .modal(let info): try info.encode(to: encoder)\n        case .embedded(let info): try info.encode(to: encoder)\n        }\n    }\n\n    enum PresentationType: String, ThomasSerializable {\n        case modal\n        case banner\n        case embedded\n    }\n\n    struct Device: ThomasSerializable {\n        let orientationLock: ThomasOrientation?\n        private enum CodingKeys: String, CodingKey {\n            case orientationLock = \"lock_orientation\"\n        }\n    }\n\n    /// Keyboard avoidance methods\n    enum KeyboardAvoidanceMethod: String, ThomasSerializable {\n        /// Slide keyboard over the top\n        case overTheTop = \"over_the_top\"\n        /// Treat it as safe area\n        case safeArea = \"safe_area\"\n    }\n\n    struct iOS: ThomasSerializable {\n        var keyboardAvoidance: KeyboardAvoidanceMethod?\n\n        private enum CodingKeys: String, CodingKey {\n            case keyboardAvoidance = \"keyboard_avoidance\"\n        }\n    }\n\n    struct Banner: ThomasSerializable {\n        let type: PresentationType = .banner\n        var duration: Int?\n        var placementSelectors: [PlacementSelector<Placement>]?\n        var defaultPlacement: Placement\n        var ios: iOS?\n\n        private enum CodingKeys: String, CodingKey {\n            case duration = \"duration_milliseconds\"\n            case placementSelectors = \"placement_selectors\"\n            case defaultPlacement = \"default_placement\"\n            case type\n        }\n\n        enum Position: String, ThomasSerializable {\n            case top\n            case bottom\n        }\n\n        struct Placement: ThomasSerializable {\n            var margin: ThomasMargin?\n            var size: ThomasConstrainedSize\n            var position: Position\n            var ignoreSafeArea: Bool?\n            var border: ThomasBorder?\n            var backgroundColor: ThomasColor?\n            var nubInfo: ThomasViewInfo.NubInfo?\n            var cornerRadius: ThomasViewInfo.CornerRadiusInfo?\n\n            private enum CodingKeys: String, CodingKey {\n                case margin\n                case size\n                case position\n                case ignoreSafeArea = \"ignore_safe_area\"\n                case border\n                case backgroundColor = \"background_color\"  \n                case nubInfo = \"nub\"\n                case cornerRadius = \"corner_radius\"\n            }\n        }\n    }\n\n    struct Modal: ThomasSerializable {\n        let type: PresentationType = .modal\n        var placementSelectors: [PlacementSelector<Placement>]?\n        var defaultPlacement: Placement\n        var dismissOnTouchOutside: Bool?\n        var device: Device?\n        var ios: iOS?\n\n        private enum CodingKeys: String, CodingKey {\n            case placementSelectors = \"placement_selectors\"\n            case defaultPlacement = \"default_placement\"\n            case dismissOnTouchOutside = \"dismiss_on_touch_outside\"\n            case device\n            case type\n            case ios\n        }\n\n        struct Placement: ThomasSerializable {\n            var margin: ThomasMargin?\n            var size: ThomasConstrainedSize\n            var position: ThomasPosition?\n            var shade: ThomasColor?\n            var ignoreSafeArea: Bool?\n            var device: Device?\n            var border: ThomasBorder?\n            var backgroundColor: ThomasColor?\n            var shadow: ThomasShadow?\n\n            private enum CodingKeys: String, CodingKey {\n                case margin\n                case size\n                case position\n                case shade = \"shade_color\"\n                case ignoreSafeArea = \"ignore_safe_area\"\n                case device\n                case border\n                case backgroundColor = \"background_color\"\n                case shadow\n            }\n        }\n    }\n\n    struct Embedded: ThomasSerializable {\n        let type: PresentationType = .embedded\n        var placementSelectors: [PlacementSelector<Placement>]?\n        var defaultPlacement: Placement\n        var embeddedID: String\n\n        private enum CodingKeys: String, CodingKey {\n            case defaultPlacement = \"default_placement\"\n            case placementSelectors = \"placement_selectors\"\n            case embeddedID = \"embedded_id\"\n            case type\n        }\n\n        struct Placement: ThomasSerializable {\n            let margin: ThomasMargin?\n            let size: ThomasConstrainedSize\n            let border: ThomasBorder?\n            let backgroundColor: ThomasColor?\n\n            private enum CodingKeys: String, CodingKey {\n                case margin\n                case size\n                case border\n                case backgroundColor = \"background_color\"\n            }\n        }\n    }\n\n    struct PlacementSelector<Placement: ThomasSerializable>: ThomasSerializable {\n        var placement: Placement\n        var windowSize: ThomasWindowSize?\n        var orientation: ThomasOrientation?\n\n        private enum CodingKeys: String, CodingKey {\n            case placement\n            case windowSize = \"window_size\"\n            case orientation\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasPropertyOverride.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasPropertyOverride<T: Codable&Sendable&Equatable>: ThomasSerializable {\n    let whenStateMatches: JSONPredicate?\n    let value: T?\n\n    enum CodingKeys: String, CodingKey {\n        case whenStateMatches = \"when_state_matches\"\n        case value\n    }\n}\n\nextension ThomasPropertyOverride {\n\n    @MainActor\n    static func resolveOptional(\n        state: ThomasState,\n        overrides: [ThomasPropertyOverride<T>]?,\n        defaultValue: T? = nil\n    ) -> T? {\n        let override = overrides?.first { override in\n            return override.whenStateMatches?.evaluate(\n                json: state.state\n            ) ?? true\n        }\n\n        guard let override else {\n            return defaultValue\n        }\n\n        return override.value\n    }\n\n    @MainActor\n    static func resolveRequired(state: ThomasState, overrides: [ThomasPropertyOverride<T>]?, defaultValue: T) -> T {\n        return resolveOptional(state: state, overrides: overrides) ?? defaultValue\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasSerializable.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nprotocol ThomasSerializable: Codable, Sendable, Equatable {}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasShadow.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasShadow: ThomasSerializable {\n    let selectors: [Selector]?\n\n    enum CodingKeys: String, CodingKey {\n        case selectors\n    }\n\n    struct Selector: ThomasSerializable {\n        var shadow: Shadow\n        var platform: ThomasPlatform?\n\n        private enum CodingKeys: String, CodingKey {\n            case platform\n            case shadow\n        }\n    }\n\n    struct Shadow: ThomasSerializable {\n        var boxShadow: BoxShadow?\n\n        private enum CodingKeys: String, CodingKey {\n            case boxShadow = \"box_shadow\"\n        }\n    }\n\n    struct BoxShadow: ThomasSerializable {\n        var color: ThomasColor\n        var radius: Double\n        var blurRadius: Double\n        var offsetY: Double?\n        var offsetX: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case color\n            case radius\n            case blurRadius = \"blur_radius\"\n            case offsetY = \"offset_y\"\n            case offsetX = \"offset_x\"\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasShapeInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasShapeInfo: ThomasSerializable {\n    case rectangle(Rectangle)\n    case ellipse(Ellipse)\n\n    enum CodingKeys: String, CodingKey {\n        case type = \"type\"\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(ShapeType.self, forKey: .type)\n\n        self = switch type {\n        case .ellipse: .ellipse(try Ellipse(from: decoder))\n        case .rectangle: .rectangle(try Rectangle(from: decoder))\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .ellipse(let shape): try shape.encode(to: encoder)\n        case .rectangle(let shape): try shape.encode(to: encoder)\n        }\n    }\n\n    enum ShapeType: String, ThomasSerializable {\n        case rectangle\n        case ellipse\n    }\n\n    struct Ellipse: Codable, Equatable, Sendable {\n        let type: ShapeType = .ellipse\n        var border: ThomasBorder?\n        var scale: Double?\n        var color: ThomasColor?\n        var aspectRatio: Double?\n\n        enum CodingKeys: String, CodingKey {\n            case border\n            case color\n            case scale\n            case aspectRatio = \"aspect_ratio\"\n            case type\n        }\n    }\n\n    struct Rectangle: Codable, Equatable, Sendable {\n        let type: ShapeType = .rectangle\n        var border: ThomasBorder?\n        var scale: Double?\n        var color: ThomasColor?\n        var aspectRatio: Double?\n\n        enum CodingKeys: String, CodingKey {\n            case border\n            case color\n            case scale\n            case aspectRatio = \"aspect_ratio\"\n            case type\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasSize.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasSize: Codable, Equatable, Sendable {\n    var width: ThomasSizeConstraint\n    var height: ThomasSizeConstraint\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasSizeConstraint.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasSizeConstraint: ThomasSerializable {\n    case points(Double)\n    case percent(Double)\n    case auto\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.singleValueContainer()\n        if let sizeString = try? container.decode(String.self) {\n            if sizeString == \"auto\" {\n                self = .auto\n            } else if sizeString.last == \"%\" {\n                var perecent = sizeString\n                perecent.removeLast()\n                self = .percent(Double(perecent) ?? 0)\n            } else {\n                throw AirshipErrors.parseError(\"invalid size: \\(sizeString)\")\n            }\n        } else if let double = try? container.decode(Double.self) {\n            self = .points(double)\n        } else {\n            throw AirshipErrors.parseError(\"invalid size\")\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.singleValueContainer()\n        switch self {\n        case .auto:\n            try container.encode(\"auto\")\n        case .percent(let value):\n            try container.encode(String(format: \"%.0f%%\", value))\n        case .points(let value):\n            try container.encode(value)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasSmsLocale.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Locale configuration for a phone number\nstruct ThomasSMSLocale: ThomasSerializable {\n    /// Country locale code (two letters)\n    let countryCode: String\n    \n    /// Country phone code\n    let prefix: String\n    \n    /// Registration info\n    let registration: ThomasSMSRegistrationOption?\n\n    // Validation hints\n    let validationHints: ValidationHints?\n\n    init(\n        countryCode: String,\n        prefix: String,\n        registration: ThomasSMSRegistrationOption? = nil,\n        validationHints: ValidationHints? = nil\n    ) {\n        self.countryCode = countryCode\n        self.prefix = prefix\n        self.registration = registration\n        self.validationHints = validationHints\n    }\n    \n    struct ValidationHints: ThomasSerializable {\n        var minDigits: Int?\n        var maxDigits: Int?\n\n        enum CodingKeys: String, CodingKey {\n            case minDigits = \"min_digits\"\n            case maxDigits = \"max_digits\"\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case countryCode = \"country_code\"\n        case prefix\n        case registration\n        case validationHints = \"validation_hints\"\n    }\n}\n\nenum ThomasSMSRegistrationOption: ThomasSerializable, Hashable {\n    case optIn(OptIn)\n\n    struct OptIn: ThomasSerializable, Hashable {\n\n        let type: RegistrationType = .optIn\n        var senderID: String\n\n        enum CodingKeys: String, CodingKey {\n            case type\n            case senderID = \"sender_id\"\n        }\n    }\n\n    enum RegistrationType: String, Codable {\n        case optIn = \"opt_in\"\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .optIn(let properties):\n            try properties.encode(to: encoder)\n        }\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(RegistrationType.self, forKey: .type)\n        switch type {\n        case .optIn:\n            self = .optIn(\n                try OptIn(from: decoder)\n            )\n        }\n    }\n}\n\nextension ThomasSMSRegistrationOption {\n    func makeContactOptions(date: Date = Date.now) -> SMSRegistrationOptions {\n        switch (self) {\n        case .optIn(let properties):\n            return .optIn(senderID: properties.senderID)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@MainActor\nclass ThomasState: ObservableObject {\n    @Published private(set) var state: AirshipJSON = [:]\n\n    private var subscriptions: Set<AnyCancellable> = []\n\n    // Child State Objects\n    private let formState: ThomasFormState?\n    private let pagerState: PagerState?\n    private let videoState: VideoState?\n    private let mutableState: MutableState?\n\n    private let onStateChange: @Sendable @MainActor (AirshipJSON) -> Void\n\n    // Internal state snapshot that tracks current values\n    @MainActor\n    private struct StateSnapshot {\n        var formStatus: ThomasFormState.Status?\n        var formActiveFields: [String: ThomasFormField] = [:]\n        var formType: ThomasFormState.FormType?\n        var pagerInProgress: Bool?\n        var videoPlaying: Bool?\n        var videoMuted: Bool?\n        var mutableStateValue: AirshipJSON?\n\n        /// Generates the final AirshipJSON based strictly on this snapshot data\n        func toAirshipJSON() -> AirshipJSON {\n            // Start with the base mutable state object\n            var result: [String: AirshipJSON] = mutableStateValue?.object ?? [:]\n\n            // Add $forms\n            if let formStatus, let formType {\n                result[\"$forms\"] = [\n                    \"current\": ThomasFormPayloadGenerator.makeFormStatePayload(\n                        status: formStatus,\n                        fields: formActiveFields.map { $0.value },\n                        formType: formType\n                    )\n                ]\n            }\n\n            // Add $pagers\n            if let pagerInProgress {\n                result[\"$pagers\"] = [\n                    \"current\": [\n                        \"paused\": .bool(!pagerInProgress)\n                    ]\n                ]\n            }\n\n            if let videoPlaying, let videoMuted {\n                result[\"$video\"] = [\n                    \"current\": [\n                        \"playing\": .bool(videoPlaying),\n                        \"muted\": .bool(videoMuted)\n                    ]\n                ]\n            }\n\n            return .object(result)\n        }\n    }\n\n    private var stateSnapshot: StateSnapshot = StateSnapshot()\n    private var lastOutput: AirshipJSON = [:]\n\n    init(\n        formState: ThomasFormState? = nil,\n        pagerState: PagerState? = nil,\n        videoState: VideoState? = nil,\n        mutableState: MutableState? = nil,\n        onStateChange: @escaping @Sendable @MainActor (AirshipJSON) -> Void\n    ) {\n        self.formState = formState\n        self.pagerState = pagerState\n        self.videoState = videoState\n        self.mutableState = mutableState\n        self.onStateChange = onStateChange\n\n        setupSubscriptions()\n\n        // Initialize snapshot with current values from the passed objects\n        self.updateSnapshot(\n            formStatus: formState?.status,\n            formActiveFields: formState?.activeFields,\n            formType: formState?.formType,\n            pagerInProgress: pagerState?.inProgress,\n            videoPlaying: videoState?.isPlaying,\n            videoMuted: videoState?.isMuted,\n            mutableStateValue: mutableState?.state,\n        )\n    }\n\n    private func setupSubscriptions() {\n        formState?.$status.sink { [weak self] in self?.updateSnapshot(formStatus: $0) }.store(in: &subscriptions)\n        formState?.$activeFields.sink { [weak self] in self?.updateSnapshot(formActiveFields: $0) }.store(in: &subscriptions)\n        pagerState?.$inProgress.sink { [weak self] in self?.updateSnapshot(pagerInProgress: $0) }.store(in: &subscriptions)\n        videoState?.isPlayingPublisher.sink { [weak self] in self?.updateSnapshot(videoPlaying: $0) }.store(in: &subscriptions)\n        videoState?.isMutedPublisher.sink { [weak self] in self?.updateSnapshot(videoMuted: $0) }.store(in: &subscriptions)\n        mutableState?.$state.sink { [weak self] in self?.updateSnapshot(mutableStateValue: $0) }.store(in: &subscriptions)\n    }\n\n    private func updateSnapshot(\n        formStatus: ThomasFormState.Status? = nil,\n        formActiveFields: [String: ThomasFormField]? = nil,\n        formType: ThomasFormState.FormType? = nil,\n        pagerInProgress: Bool? = nil,\n        videoPlaying: Bool? = nil,\n        videoMuted: Bool? = nil,\n        mutableStateValue: AirshipJSON? = nil,\n    ) {\n        // Update the snapshot with provided values\n        if let val = formStatus { stateSnapshot.formStatus = val }\n        if let val = formActiveFields { stateSnapshot.formActiveFields = val }\n        if let val = formType { stateSnapshot.formType = val }\n        if let val = pagerInProgress { stateSnapshot.pagerInProgress = val }\n        if let val = videoPlaying { stateSnapshot.videoPlaying = val }\n        if let val = videoMuted { stateSnapshot.videoMuted = val }\n        if let val = mutableStateValue { stateSnapshot.mutableStateValue = val }\n\n        // Compute new output directly from the snapshot\n        let newOutput = stateSnapshot.toAirshipJSON()\n\n        // Only update if output actually changed\n        if newOutput != lastOutput {\n            AirshipLogger.trace(\"State updated: \\(newOutput.prettyJSONString) old: \\(lastOutput.prettyJSONString)\")\n            self.state = newOutput\n            self.lastOutput = newOutput\n            self.onStateChange(newOutput)\n        }\n    }\n\n    func with(\n        formState: ThomasFormState? = nil,\n        pagerState: PagerState? = nil,\n        videoState: VideoState? = nil,\n        mutableState: MutableState? = nil,\n    ) -> ThomasState {\n        let newFormState = formState ?? self.formState\n        let newPagerState = pagerState ?? self.pagerState\n        let newVideoState = videoState ?? self.videoState\n        let newMutableState = mutableState ?? self.mutableState\n\n        // Return self if nothing changed to avoid redundant copies\n        if newFormState === self.formState,\n           newPagerState === self.pagerState,\n           newVideoState === self.videoState,\n           newMutableState === self.mutableState {\n            return self\n        }\n\n        return .init(\n            formState: newFormState,\n            pagerState: newPagerState,\n            videoState: newVideoState,\n            mutableState: newMutableState,\n            onStateChange: self.onStateChange\n        )\n    }\n\n    func processStateActions(\n        _ stateActions: [ThomasStateAction],\n        formFieldValue: ThomasFormField.Value? = nil\n    ) {\n        stateActions.forEach { action in\n            switch action {\n            case .setState(let details):\n                self.mutableState?.set(key: details.key, value: details.value, ttl: details.ttl)\n            case .clearState:\n                self.mutableState?.clearState()\n            case .formValue(let details):\n                self.mutableState?.set(key: details.key, value: formFieldValue?.stateFormValue)\n            }\n        }\n    }\n\n    @MainActor\n    class MutableState: ObservableObject {\n        @Published private(set) var state: AirshipJSON\n        private var appliedState: [String: AirshipJSON] = [:]\n        private var tempMutations: [String: TempMutation] = [:]\n        private let taskSleeper: any AirshipTaskSleeper\n\n        init(\n            initialState: AirshipJSON? = nil,\n            taskSleeper: any AirshipTaskSleeper = DefaultAirshipTaskSleeper.shared\n        ) {\n            self.state = initialState ?? [:]\n            self.taskSleeper = taskSleeper\n        }\n\n        fileprivate func clearState() {\n            tempMutations.removeAll()\n            appliedState.removeAll()\n            updateState()\n        }\n\n        private func updateState() {\n            var state = self.appliedState\n            tempMutations.forEach { key, mutation in\n                state[key] = mutation.value\n            }\n            self.state = .object(state)\n        }\n\n        private func removeTempMutation(_ mutation: TempMutation) {\n            guard tempMutations[mutation.key] == mutation else { return }\n            tempMutations[mutation.key] = nil\n            self.updateState()\n        }\n\n        fileprivate func set(key: String, value: AirshipJSON?, ttl: TimeInterval? = nil) {\n            if let ttl = ttl {\n                let mutation = TempMutation(id: UUID().uuidString, key: key, value: value)\n                tempMutations[key] = mutation\n                appliedState[key] = nil\n                updateState()\n\n                Task { [weak self] in\n                    try? await self?.taskSleeper.sleep(timeInterval: ttl)\n                    self?.removeTempMutation(mutation)\n                }\n            } else {\n                tempMutations[key] = nil\n                appliedState[key] = value\n                updateState()\n            }\n        }\n    }\n\n    fileprivate struct TempMutation: Sendable, Equatable, Hashable {\n        let id: String\n        let key: String\n        let value: AirshipJSON?\n    }\n}\n\nfileprivate extension ThomasFormField.Value {\n    var stateFormValue: AirshipJSON? {\n        switch(self) {\n        case .toggle(let value): return .bool(value)\n        case .multipleCheckbox(let value): return .array(Array(value))\n        case .radio(let value): return value\n        case .sms(let value, _),\n            .email(let value),\n            .text(let value):\n            guard let value else { return nil }\n            return .string(value)\n        case .score(let value): return value\n        case .form, .npsForm: return nil\n        }\n    }\n}\n\nfileprivate extension AirshipJSON {\n    var prettyJSONString: String {\n        let encoder = JSONEncoder()\n        encoder.outputFormatting = [.prettyPrinted]\n        return (try? self.toString(encoder: encoder)) ?? \"Invalid JSON\"\n    }\n}\n\n@MainActor\nfinal class ScopedStateCache: ObservableObject {\n    private var cachedState: ThomasState?\n    \n    private let updateSubject = PassthroughSubject<any Codable, Never>()\n    private var subscription: AnyCancellable?\n    private var pendingUpdate: SnapshotType? = nil\n\n    func getOrCreate(_ createState: () -> ThomasState) -> ThomasState {\n        if let cached = cachedState { return cached }\n        let scoped = createState()\n        \n        if let pendingUpdate {\n            scoped.restorePersistentState(pendingUpdate)\n            self.pendingUpdate = nil\n        }\n        \n        cachedState = scoped\n        rebroadcastUpdates(scoped)\n        return scoped\n    }\n\n    func invalidate() {\n        cachedState = nil\n        rebroadcastUpdates(nil)\n        pendingUpdate = nil\n    }\n    \n    private func rebroadcastUpdates(_ state: ThomasState?) {\n        guard let state else {\n            subscription?.cancel()\n            subscription = nil\n            updateSubject.send(ThomasState.PersistentState(\n                formState: nil,\n                pagerState: nil,\n                mutableState: nil)\n            )\n            return\n        }\n        \n        subscription = state.updates\n            .sink { [weak self] update in\n                self?.updateSubject.send(update)\n            }\n    }\n}\n\n//MARK: - ThomasStateProvider\nextension ThomasState.MutableState: ThomasStateProvider {\n    typealias StateSnapshot = [String: AirshipJSON]\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return $state.removeDuplicates().map(\\.self).eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> StateSnapshot {\n        return self.appliedState\n    }\n    \n    func restorePersistentState(_ state: [String: AirshipJSON]) {\n        self.appliedState = state\n        DispatchQueue.main.async { self.updateState() }\n    }\n}\n\nextension ThomasState: ThomasStateProvider {\n    typealias SnapshotType = PersistentState\n    \n    struct PersistentState: Codable {\n        let formState: ThomasFormState.SnapshotType?\n        let pagerState: PagerState.SnapshotType?\n        let mutableState: MutableState.SnapshotType?\n    }\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return $state.removeDuplicates().map(\\.self).eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> PersistentState {\n        return PersistentState(\n            formState: formState?.persistentStateSnapshot(),\n            pagerState: pagerState?.persistentStateSnapshot(),\n            mutableState: mutableState?.persistentStateSnapshot()\n        )\n    }\n    \n    func restorePersistentState(_ state: PersistentState) {\n        if let form = state.formState {\n            self.formState?.restorePersistentState(form)\n        }\n        \n        if let pager = state.pagerState {\n            self.pagerState?.restorePersistentState(pager)\n        }\n        \n        if let mutable = state.mutableState {\n            self.mutableState?.restorePersistentState(mutable)\n        }\n    }\n}\n\nextension ScopedStateCache: ThomasStateProvider {\n    typealias SnapshotType = ThomasState.PersistentState\n    \n    var updates: AnyPublisher<any Codable, Never> {\n        return updateSubject\n            .compactMap({ [weak self] _ in self?.makeSnapshot(self?.cachedState) })\n            .eraseToAnyPublisher()\n    }\n    \n    func persistentStateSnapshot() -> SnapshotType {\n        return makeSnapshot(cachedState)\n    }\n    \n    func restorePersistentState(_ state: SnapshotType) {\n        if let thomasState = cachedState {\n            thomasState.restorePersistentState(state)\n        } else {\n            pendingUpdate = state\n        }\n    }\n    \n    private func makeSnapshot(_ state: ThomasState?) -> ThomasState.PersistentState {\n        return state?.persistentStateSnapshot() ?? ThomasState.PersistentState(\n            formState: nil,\n            pagerState: nil,\n            mutableState: nil\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasStateAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasStateAction: ThomasSerializable {\n    case setState(SetState)\n    case clearState\n    case formValue(SetFormValue)\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    enum ActionType: String, ThomasSerializable {\n        case setState = \"set\"\n        case clearState = \"clear\"\n        case formValue = \"set_form_value\"\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(ActionType.self, forKey: .type)\n\n        self = switch type {\n        case .setState: .setState(try SetState(from: decoder))\n        case .clearState: .clearState\n        case .formValue: .formValue(try SetFormValue(from: decoder))\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .setState(let action): try action.encode(to: encoder)\n        case .clearState:\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            try container.encode(ActionType.clearState, forKey: .type)\n        case .formValue(let action): try action.encode(to: encoder)\n        }\n    }\n\n    struct SetState: ThomasSerializable {\n        let type: ActionType = .setState\n        let key: String\n        let value: AirshipJSON?\n        let ttl: TimeInterval?\n\n        enum CodingKeys: String, CodingKey {\n            case key\n            case value\n            case type\n            case ttl = \"ttl_seconds\"\n        }\n    }\n\n    struct SetFormValue: ThomasSerializable {\n        let type: ActionType = .formValue\n        let key: String\n\n        enum CodingKeys: String, CodingKey {\n            case key\n            case type\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasStateStorage.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\nimport Combine\n\n@MainActor\nprotocol ThomasStateProvider: ObservableObject {\n    associatedtype SnapshotType: Codable\n    \n    var updates: AnyPublisher<any Codable, Never> { get }\n    func persistentStateSnapshot() -> SnapshotType\n    func restorePersistentState(_ state: SnapshotType)\n}\n\n@MainActor\npublic protocol LayoutDataStorage: Sendable {\n    var messageID: String { get }\n    \n    func prepare(restoreID: String) async\n    func store(_ state: Data?, key: String)\n    func retrieve(_ key: String) -> Data?\n    func clear()\n}\n\n/// - Note: for internal use only.  :nodoc:\n@MainActor\nprotocol ThomasStateStorage: Sendable {\n    \n    func store(_ provider: any ThomasStateProvider, identifier: String)\n    \n    func retrieve<T: ThomasStateProvider>(\n        identifier: String,\n        builder: () -> T\n    ) -> T\n}\n\n/// - Note: for internal use only.  :nodoc:\n@MainActor\nfinal class DefaultThomasStateStorage: ThomasStateStorage {\n    \n    private var store: any LayoutDataStorage\n    private var providers: [String: any ThomasStateProvider] = [:]\n    private var cancellables: [String: AnyCancellable] = [:]\n    \n    init(store: any LayoutDataStorage) {\n        self.store = store\n    }\n    \n    \n    func store(_ provider: any ThomasStateProvider, identifier: String) {\n        removeStored(forKey: identifier)\n        \n        encodeAndSave(provider.persistentStateSnapshot(), identifier: identifier)\n        \n        let subscription = monitorUpdates(provider, identifier: identifier)\n        cancellables[identifier] = subscription\n    }\n    \n    func retrieve<T>(\n        identifier: String,\n        builder: () -> T\n    ) -> T where T : ThomasStateProvider {\n        \n        //check if we have a cached value\n        if let cached = providers[identifier] {\n            if let result = cached as? T {\n                return result\n            } else {\n                removeStored(forKey: identifier)\n            }\n        }\n        \n        let result = builder()\n        if\n            let stored = store.retrieve(identifier),\n            let state = decodeState(T.SnapshotType.self, data: stored)\n        {\n            result.restorePersistentState(state)\n        }\n        \n        store(result, identifier: identifier)\n        \n        return result\n    }\n    \n    private func monitorUpdates(_ provider: any ThomasStateProvider, identifier: String) -> AnyCancellable {\n        self.providers[identifier] = provider\n        \n        return provider.updates\n            .sink { [weak self] snapshot in\n            self?.encodeAndSave(snapshot, identifier: identifier)\n        }\n    }\n    \n    private func removeStored(forKey key: String) {\n        cancellables.removeValue(forKey: key)?.cancel()\n        providers.removeValue(forKey: key)\n    }\n    \n    private func encodeAndSave(_ snapshot: any Codable, identifier: String) {\n        guard let data = try? JSONEncoder().encode(snapshot) else {\n            AirshipLogger.warn(\"Failed to encode state snapshot: \\(snapshot)\")\n            return\n        }\n        \n        self.store.store(data, key: identifier)\n    }\n    \n    private func decodeState<T: Codable>(_ type: T.Type, data: Data) -> T? {\n        do {\n            return try JSONDecoder().decode(type, from: data)\n        } catch {\n            AirshipLogger.warn(\"Failed to restore state for type \\(type): \\(error)\")\n            return nil\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasStateTrigger.swift",
    "content": "import Foundation\n\nstruct ThomasStateTriggers: ThomasSerializable {\n    var id: String\n    var triggerWhenStateMatches: JSONPredicate\n    var resetWhenStateMatches: JSONPredicate?\n    var onTrigger: TriggerActions\n\n\n    struct TriggerActions: ThomasSerializable {\n        var stateActions: [ThomasStateAction]?\n\n        enum CodingKeys: String, CodingKey {\n            case stateActions = \"state_actions\"\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case id = \"identifier\"\n        case triggerWhenStateMatches = \"trigger_when_state_matches\"\n        case resetWhenStateMatches = \"reset_when_state_matches\"\n        case onTrigger = \"on_trigger\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasTextAppearance.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct ThomasTextAppearance: ThomasSerializable {\n    var color: ThomasColor\n    var fontSize: Double\n    var alignment: TextAlignement?\n    var styles: [TextStyle]?\n    var fontFamilies: [String]?\n    var placeHolderColor: ThomasColor?\n    var lineHeightMultiplier: Double?\n    var kerning: Double?\n    var fontWeight: Double?\n\n    enum TextStyle: String, ThomasSerializable {\n        case bold\n        case italic\n        case underlined\n    }\n\n    enum TextAlignement: String, ThomasSerializable {\n        case start\n        case end\n        case center\n    }\n    \n    enum CodingKeys: String, CodingKey {\n        case color\n        case fontSize = \"font_size\"\n        case alignment\n        case styles\n        case fontFamilies = \"font_families\"\n        case placeHolderColor = \"place_holder_color\"\n        case lineHeightMultiplier = \"line_height_multiplier\"\n        case fontWeight = \"font_weight\"\n        case kerning\n    }\n}\n\nextension ThomasTextAppearance {\n\n    /// Resolves the SwiftUI Font using the AirshipFont system\n    @MainActor\n    var font: Font {\n        return AirshipFont.resolveFont(\n            size: self.fontSize,\n            families: self.fontFamilies,\n            weight: self.fontWeight,\n            isItalic: self.styles?.contains(.italic) ?? false,\n            isBold: self.styles?.contains(.bold) ?? false\n        )\n    }\n\n    /// Resolves the Native Font (UIFont/NSFont) using the AirshipFont system\n    @MainActor\n    var nativeFont: AirshipNativeFont {\n        return AirshipFont.resolveNativeFont(\n            size: self.fontSize,\n            families: self.fontFamilies,\n            weight: self.fontWeight,\n            isItalic: self.styles?.contains(.italic) ?? false,\n            isBold: self.styles?.contains(.bold) ?? false\n        )\n    }\n\n    /// Returns the scaled font size based on platform logic\n    var scaledFontSize: Double {\n        return AirshipFont.scaledSize(self.fontSize)\n    }\n\n    /// Helper to determine if a specific text style is present\n    func hasStyle(_ style: ThomasTextAppearance.TextStyle) -> Bool {\n        return self.styles?.contains(style) ?? false\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasToggleStyleInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasToggleStyleInfo: ThomasSerializable {\n    case switchStyle(Switch)\n    case checkboxStyle(Checkbox)\n\n    private enum CodingKeys: String, CodingKey {\n        case type = \"type\"\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(StyleType.self, forKey: .type)\n\n        self = switch type {\n        case .switch: .switchStyle(try Switch(from: decoder))\n        case .checkbox: .checkboxStyle(try Checkbox(from: decoder))\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        switch self {\n        case .switchStyle(let style): try style.encode(to: encoder)\n        case .checkboxStyle(let style): try style.encode(to: encoder)\n        }\n    }\n\n    enum StyleType: String, ThomasSerializable {\n        case `switch`\n        case checkbox\n    }\n\n    struct Switch: ThomasSerializable {\n        let type: StyleType = .switch\n        let colors: ToggleColors\n\n        private enum CodingKeys: String, CodingKey {\n            case colors = \"toggle_colors\"\n            case type\n        }\n\n        struct ToggleColors: ThomasSerializable {\n            var on: ThomasColor\n            var off: ThomasColor\n        }\n    }\n\n    struct Checkbox: ThomasSerializable {\n        let type: StyleType = .checkbox\n        let bindings: Bindings\n\n        private enum CodingKeys: String, CodingKey {\n            case bindings\n            case type\n        }\n\n        struct Bindings: ThomasSerializable {\n            let selected: Binding\n            let unselected: Binding\n        }\n\n        struct Binding: ThomasSerializable {\n            let shapes: [ThomasShapeInfo]?\n            let icon: ThomasIconInfo?\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasValidationInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasValidationInfo: ThomasSerializable {\n    var isRequired: Bool?\n    var onError: ErrorInfo?\n    var onEdit: EditInfo?\n    var onValid: ValidInfo?\n\n    struct ErrorInfo: ThomasSerializable {\n        var stateActions: [ThomasStateAction]?\n\n        enum CodingKeys: String, CodingKey {\n            case stateActions = \"state_actions\"\n        }\n    }\n\n    struct EditInfo: ThomasSerializable {\n        var stateActions: [ThomasStateAction]?\n\n        enum CodingKeys: String, CodingKey {\n            case stateActions = \"state_actions\"\n        }\n    }\n\n    struct ValidInfo: ThomasSerializable {\n        var stateActions: [ThomasStateAction]?\n\n        enum CodingKeys: String, CodingKey {\n            case stateActions = \"state_actions\"\n        }\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case isRequired = \"required\"\n        case onError = \"on_error\"\n        case onEdit = \"on_edit\"\n        case onValid = \"on_valid\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasViewController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n#if !os(watchOS) && !os(macOS)\n\nclass ThomasViewController<Content> : UIHostingController<Content> where Content : View {\n\n    var options: ThomasViewControllerOptions\n    var onDismiss: (() -> Void)?\n    private var scrollViewsUpdated: Bool = false\n\n    init(rootView: Content, options: ThomasViewControllerOptions = ThomasViewControllerOptions()) {\n        self.options = options\n        super.init(rootView: rootView)\n        self.view.backgroundColor = .clear\n    }\n\n    @objc\n    required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func viewWillDisappear(_ animated: Bool) {\n        super.viewWillDisappear(animated)\n        self.onDismiss?()\n    }\n\n#if !os(tvOS)\n    override var supportedInterfaceOrientations: UIInterfaceOrientationMask {\n        guard let orientation = options.orientation else {\n            return .all\n        }\n\n        switch orientation {\n        case .portrait:\n            return .portrait\n        case .landscape:\n            return .landscape\n        }\n    }\n\n    override var shouldAutorotate: Bool {\n        return self.options.orientation == nil\n    }\n#endif\n\n    override func viewDidLayoutSubviews() {\n        super.viewDidLayoutSubviews()\n        if !scrollViewsUpdated {\n            updateScrollViews(view: self.view)\n            scrollViewsUpdated = true\n        }\n    }\n\n    override func accessibilityPerformEscape() -> Bool {\n        self.onDismiss?()\n        return true\n    }\n\n    func updateScrollViews(view: UIView) {\n        view.subviews.forEach { subView in\n            if let subView = subView as? UIScrollView {\n                if (subView.bounces) {\n                    subView.bounces = false\n#if os(tvOS)\n                    subView.panGestureRecognizer.allowedTouchTypes = [NSNumber(value: UITouch.TouchType.indirect.rawValue)]\n#endif\n                }\n            }\n\n            updateScrollViews(view: subView)\n        }\n    }\n}\n\n\nclass ThomasBannerViewController: ThomasViewController<BannerView> {\n    private var centerXConstraint: NSLayoutConstraint?\n    private var topConstraint: NSLayoutConstraint?\n    private var bottomConstraint: NSLayoutConstraint?\n    private var heightConstraint: NSLayoutConstraint?\n    private var widthConstraint: NSLayoutConstraint?\n\n    private let thomasBannerConstraints: ThomasBannerConstraints\n\n    private let position: ThomasPresentationInfo.Banner.Position?\n\n    private var subscription: AnyCancellable?\n\n    init(\n        rootView: BannerView,\n        position: ThomasPresentationInfo.Banner.Position,\n        options: ThomasViewControllerOptions,\n        constraints: ThomasBannerConstraints\n    ) {\n        self.thomasBannerConstraints = constraints\n        self.position = position\n        super.init(rootView: rootView, options: options)\n    }\n\n    @objc required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func viewDidAppear(_ animated: Bool) {\n        super.viewDidAppear(animated)\n\n        createBannerConstraints()\n\n        if UIAccessibility.isVoiceOverRunning {\n            DispatchQueue.main.asyncAfter(deadline: .now() + BannerView.animationInOutDuration) {\n                UIAccessibility.post(notification: .screenChanged, argument: self)\n            }\n        }\n\n        subscription = thomasBannerConstraints.$contentPlacement.sink { [weak self] contentPlacement in\n            if let contentPlacement {\n                self?.handleBannerConstraints(contentPlacement: contentPlacement)\n            }\n        }\n    }\n\n    override func viewDidLayoutSubviews() {\n        super.viewDidLayoutSubviews()\n        self.thomasBannerConstraints.updateWindowSize(self.view.window?.frame.size)\n\n    }\n\n    override func viewWillDisappear(_ animated: Bool) {\n        subscription?.cancel()\n        super.viewWillDisappear(animated)\n    }\n\n    func createBannerConstraints() {\n        self.view.translatesAutoresizingMaskIntoConstraints = false\n\n        if let window = self.view.window {\n            centerXConstraint = self.view.centerXAnchor.constraint(equalTo: window.centerXAnchor)\n            topConstraint = self.view.topAnchor.constraint(equalTo: window.topAnchor)\n            bottomConstraint = self.view.bottomAnchor.constraint(equalTo: window.bottomAnchor)\n            heightConstraint = self.view.heightAnchor.constraint(\n                equalToConstant: thomasBannerConstraints.windowSize.height\n            )\n            widthConstraint = self.view.widthAnchor.constraint(\n                equalToConstant: thomasBannerConstraints.windowSize.width\n            )\n        }\n    }\n\n    private func handleBannerConstraints(contentPlacement: ContentPlacement) {\n        // Ensure view is still in window hierarchy before updating constraints\n        guard let window = self.view.window else { return }\n\n        // Use content size directly - margins will be handled by positioning\n        self.heightConstraint?.isActive = true\n        self.widthConstraint?.isActive = true\n        self.widthConstraint?.constant = contentPlacement.width\n        self.heightConstraint?.constant = contentPlacement.height\n\n        // Deactivate old constraints before creating new ones\n        self.centerXConstraint?.isActive = false\n        self.topConstraint?.isActive = false\n        self.bottomConstraint?.isActive = false\n\n        let edgeInsets = contentPlacement.additionalEdgeInsets\n\n        // Shift horizontal constraint by start/end margins\n        // Positive leading margin shifts right, positive trailing margin shifts left\n        let horizontalOffset = edgeInsets.leading - edgeInsets.trailing\n        self.centerXConstraint = self.view.centerXAnchor.constraint(\n            equalTo: window.centerXAnchor,\n            constant: horizontalOffset\n        )\n        self.centerXConstraint?.isActive = true\n\n        if contentPlacement.ignoreSafeArea {\n            // Anchor to window edges when ignoring safe area, shifted by margins\n            if contentPlacement.isTop {\n                self.topConstraint = self.view.topAnchor.constraint(\n                    equalTo: window.topAnchor,\n                    constant: edgeInsets.top\n                )\n            } else {\n                self.bottomConstraint = self.view.bottomAnchor.constraint(\n                    equalTo: window.bottomAnchor,\n                    constant: -edgeInsets.bottom\n                )\n            }\n        } else {\n            // Anchor to safe area layout guide when respecting safe area, shifted by margins\n            if contentPlacement.isTop {\n                self.topConstraint = self.view.topAnchor.constraint(\n                    equalTo: window.safeAreaLayoutGuide.topAnchor,\n                    constant: edgeInsets.top\n                )\n            } else {\n                self.bottomConstraint = self.view.bottomAnchor.constraint(\n                    equalTo: window.safeAreaLayoutGuide.bottomAnchor,\n                    constant: -edgeInsets.bottom\n                )\n            }\n        }\n\n        switch self.position {\n        case .top:\n            self.topConstraint?.isActive = true\n            self.bottomConstraint?.isActive = false\n\n        default:\n            self.topConstraint?.isActive = false\n            self.bottomConstraint?.isActive = true\n        }\n\n        self.view.layoutIfNeeded()\n    }\n}\n\nclass ThomasModalViewController : ThomasViewController<ModalView> {\n\n    override init(rootView: ModalView, options: ThomasViewControllerOptions) {\n        super.init(rootView: rootView, options: options)\n        self.modalPresentationStyle = .currentContext\n    }\n\n    @objc required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n\n#elseif os(macOS)\n\n@available(iOS 13.0.0, tvOS 13.0, *)\nclass ThomasViewController<Content> : NSHostingController<Content> where Content : View {\n\n    var options: ThomasViewControllerOptions\n    var onDismiss: (() -> Void)?\n    private var scrollViewsUpdated: Bool = false\n\n    init(rootView: Content, options: ThomasViewControllerOptions = ThomasViewControllerOptions()) {\n        self.options = options\n        super.init(rootView: rootView)\n        self.view.layer?.backgroundColor = NSColor.clear.cgColor\n    }\n\n    @objc\n    required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n\n    override func viewWillDisappear() {\n        super.viewWillDisappear()\n        self.onDismiss?()\n    }\n\n}\n\n\nclass ThomasBannerViewController: ThomasViewController<BannerView> {\n    private var centerXConstraint: NSLayoutConstraint?\n    private var topConstraint: NSLayoutConstraint?\n    private var bottomConstraint: NSLayoutConstraint?\n    private var heightConstraint: NSLayoutConstraint?\n    private var widthConstraint: NSLayoutConstraint?\n\n    private let thomasBannerConstraints: ThomasBannerConstraints\n\n    private let position: ThomasPresentationInfo.Banner.Position?\n\n    private var subscription: AnyCancellable?\n\n    init(rootView: BannerView,\n        position: ThomasPresentationInfo.Banner.Position,\n        options: ThomasViewControllerOptions,\n        constraints: ThomasBannerConstraints\n    ) {\n        self.thomasBannerConstraints = constraints\n\n        self.position = position\n        super.init(rootView: rootView, options: options)\n    }\n\n    @objc required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n\n    override func viewDidAppear() {\n        super.viewDidAppear()\n\n        createBannerConstraints()\n        handleBannerConstraints(size: self.thomasBannerConstraints.windowSize)\n\n        let isVoiceOverRunning = AXIsProcessTrusted()\n        if isVoiceOverRunning {\n            DispatchQueue.main.asyncAfter(deadline: .now() + BannerView.animationInOutDuration) {\n                NSAccessibility.post(element: self, notification: .layoutChanged)\n            }\n        }\n\n        subscription = thomasBannerConstraints.$windowSize.sink { [weak self] size in\n            self?.handleBannerConstraints(size: size)\n        }\n    }\n\n    override func viewWillDisappear() {\n        subscription?.cancel()\n        super.viewWillDisappear()\n    }\n\n    func createBannerConstraints() {\n        self.view.translatesAutoresizingMaskIntoConstraints = false\n        if let contentView = self.view.window?.contentView {\n            centerXConstraint = self.view.centerXAnchor.constraint(equalTo: contentView.centerXAnchor)\n            topConstraint = self.view.topAnchor.constraint(equalTo: contentView.topAnchor)\n            bottomConstraint = self.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor)\n\n            heightConstraint = self.view.heightAnchor.constraint(equalToConstant: self.thomasBannerConstraints.windowSize.height)\n            widthConstraint = self.view.widthAnchor.constraint(equalToConstant: self.thomasBannerConstraints.windowSize.width)\n        }\n    }\n\n    func handleBannerConstraints(size: CGSize) {\n        // Ensure view is still in window hierarchy before updating constraints\n        guard self.view.window != nil else { return }\n\n        self.centerXConstraint?.isActive = true\n        self.heightConstraint?.isActive = true\n        self.widthConstraint?.isActive = true\n        self.widthConstraint?.constant = size.width\n\n        switch self.position {\n        case .top:\n            self.topConstraint?.isActive = true\n            self.bottomConstraint?.isActive = false\n            self.heightConstraint?.constant = size.height + self.view.safeAreaInsets.top\n\n        default:\n            self.topConstraint?.isActive = false\n            self.bottomConstraint?.isActive = true\n            self.heightConstraint?.constant = size.height + self.view.safeAreaInsets.bottom\n        }\n\n        self.view.layoutSubtreeIfNeeded()\n    }\n}\n\nclass ThomasModalViewController : ThomasViewController<ModalView> {\n\n    override init(rootView: ModalView, options: ThomasViewControllerOptions) {\n        super.init(rootView: rootView, options: options)\n    }\n\n    @objc required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n\n\n#endif\n\nclass ThomasViewControllerOptions {\n    var orientation: ThomasOrientation?\n    var bannerPlacement: ThomasPresentationInfo.Banner.Placement?\n}\n\n@MainActor\nclass ThomasBannerConstraints: ObservableObject {\n    @Published\n    fileprivate var contentPlacement: ContentPlacement?\n\n    @Published\n    private(set) var windowSize: CGSize\n\n    init(windowSize: CGSize) {\n        self.windowSize = windowSize\n    }\n    func updateContentSize(\n        _ size: CGSize,\n        constraints: ViewConstraints,\n        placement: ThomasPresentationInfo.Banner.Placement\n    ) {\n        let width = if let width = constraints.width {\n            width\n        } else {\n            size.width\n        }\n\n        let height = if let height = constraints.height {\n            height\n        } else {\n            size.height\n        }\n\n        let additionalEdgeInsets = EdgeInsets(\n            top: placement.margin?.top ?? 0,\n            leading: placement.margin?.start ?? 0,\n            bottom: placement.margin?.bottom ?? 0,\n            trailing: placement.margin?.end ?? 0\n        )\n\n        let contentPlacement = ContentPlacement(\n            isTop: placement.position == .top,\n            additionalEdgeInsets: additionalEdgeInsets,\n            width: width,\n            height: height,\n            ignoreSafeArea: placement.ignoreSafeArea == true\n        )\n\n        if self.contentPlacement != contentPlacement {\n            self.contentPlacement = contentPlacement\n        }\n    }\n\n    func updateWindowSize(_ size: CGSize?) {\n        if self.windowSize != size, let size {\n            self.windowSize = size\n        }\n    }\n}\n\nfileprivate struct ContentPlacement: Sendable, Equatable {\n    let isTop: Bool\n    let additionalEdgeInsets: EdgeInsets\n    let width: Double\n    let height: Double\n    let ignoreSafeArea: Bool\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasViewInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Foundation\n\nindirect enum ThomasViewInfo: ThomasSerializable {\n    case container(Container)\n    case linearLayout(LinearLayout)\n    #if !os(tvOS) && !os(watchOS)\n    case webView(WebView)\n    #endif\n    case customView(CustomView)\n    case scrollLayout(ScrollLayout)\n    case media(Media)\n    case label(Label)\n    case labelButton(LabelButton)\n    case imageButton(ImageButton)\n    case stackImageButton(StackImageButton)\n    case emptyView(EmptyView)\n    case pager(Pager)\n    case pagerIndicator(PagerIndicator)\n    case storyIndicator(StoryIndicator)\n    case pagerController(PagerController)\n    case formController(FormController)\n    case checkbox(Checkbox)\n    case checkboxController(CheckboxController)\n    case radioInput(RadioInput)\n    case radioInputController(RadioInputController)\n    case textInput(TextInput)\n    case score(Score)\n    case npsController(NPSController)\n    case toggle(Toggle)\n    case stateController(StateController)\n    case buttonLayout(ButtonLayout)\n    case basicToggleLayout(BasicToggleLayout)\n    case checkboxToggleLayout(CheckboxToggleLayout)\n    case radioInputToggleLayout(RadioInputToggleLayout)\n    case iconView(IconView)\n    case scoreController(ScoreController)\n    case scoreToggleLayout(ScoreToggleLayout)\n    case videoController(VideoController)\n\n    enum ViewType: String, Codable {\n        case container\n        case linearLayout = \"linear_layout\"\n        case webView = \"web_view\"\n        case customView = \"custom_view\"\n        case scrollLayout = \"scroll_layout\"\n        case media\n        case label\n        case labelButton = \"label_button\"\n        case imageButton = \"image_button\"\n        case stackImageButton = \"stack_image_button\"\n        case buttonLayout = \"button_layout\"\n        case emptyView = \"empty_view\"\n        case pager\n        case pagerIndicator = \"pager_indicator\"\n        case storyIndicator = \"story_indicator\"\n        case pagerController = \"pager_controller\"\n        case formController = \"form_controller\"\n        case checkbox\n        case checkboxController = \"checkbox_controller\"\n        case radioInput = \"radio_input\"\n        case radioInputController = \"radio_input_controller\"\n        case textInput = \"text_input\"\n        case score\n        case npsController = \"nps_form_controller\"\n        case toggle\n        case basicToggleLayout = \"basic_toggle_layout\"\n        case checkboxToggleLayout = \"checkbox_toggle_layout\"\n        case radioInputToggleLayout = \"radio_input_toggle_layout\"\n        case stateController = \"state_controller\"\n        case iconView = \"icon_view\"\n        case scoreController = \"score_controller\"\n        case scoreToggleLayout = \"score_toggle_layout\"\n        case videoController = \"video_controller\"\n\n    }\n\n    protocol BaseInfo: ThomasSerializable {\n        var commonProperties: CommonViewProperties { get }\n        var commonOverrides: CommonViewOverrides? { get }\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case type\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(ViewType.self, forKey: .type)\n\n        self = try Self.decodeViewType(type, from: decoder)\n    }\n\n    // Separate decoding into smaller focused methods to reduce type-checking overhead\n    @inline(never)\n    private static func decodeViewType(_ type: ViewType, from decoder: any Decoder) throws -> ThomasViewInfo {\n        return switch type {\n        case .container: .container(try Container(from: decoder))\n        case .linearLayout: .linearLayout(try LinearLayout(from: decoder))\n\n        case .webView:\n#if os(tvOS) || os(watchOS)\n            throw AirshipErrors.error(\"Webview not available on tvOS and watchOS\")\n#else\n            .webView(try WebView(from: decoder))\n#endif\n\n        case .scrollLayout: .scrollLayout(try ScrollLayout(from: decoder))\n        case .media: .media(try Media(from: decoder))\n        case .label: .label(try Label(from: decoder))\n        case .labelButton: .labelButton(try LabelButton(from: decoder))\n        case .imageButton: .imageButton(try ImageButton(from: decoder))\n        case .stackImageButton: .stackImageButton(try StackImageButton(from: decoder))\n        case .emptyView: .emptyView(try EmptyView(from: decoder))\n        case .pager: .pager(try Pager(from: decoder))\n        case .pagerIndicator: .pagerIndicator(try PagerIndicator(from: decoder))\n        case .storyIndicator: .storyIndicator(try StoryIndicator(from: decoder))\n        case .pagerController: .pagerController(try PagerController(from: decoder))\n        case .formController: .formController(try FormController(from: decoder))\n        case .checkbox: .checkbox(try Checkbox(from: decoder))\n        case .checkboxController: .checkboxController(try CheckboxController(from: decoder))\n        case .radioInput: .radioInput(try RadioInput(from: decoder))\n        case .radioInputController: .radioInputController(try RadioInputController(from: decoder))\n        case .textInput: .textInput(try TextInput(from: decoder))\n        case .score: .score(try Score(from: decoder))\n        case .npsController: .npsController(try NPSController(from: decoder))\n        case .toggle: .toggle(try Toggle(from: decoder))\n        case .stateController: .stateController(try StateController(from: decoder))\n        case .customView: .customView(try CustomView(from: decoder))\n        case .buttonLayout: .buttonLayout(try ButtonLayout(from: decoder))\n        case .basicToggleLayout: .basicToggleLayout(try BasicToggleLayout(from: decoder))\n        case .checkboxToggleLayout: .checkboxToggleLayout(try CheckboxToggleLayout(from: decoder))\n        case .radioInputToggleLayout: .radioInputToggleLayout(try RadioInputToggleLayout(from: decoder))\n        case .iconView: .iconView(try IconView(from: decoder))\n        case .scoreController: .scoreController(try ScoreController(from: decoder))\n        case .scoreToggleLayout: .scoreToggleLayout(try ScoreToggleLayout(from: decoder))\n        case .videoController: .videoController(try VideoController(from: decoder))\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        try Self.encodeViewInfo(self, to: encoder)\n    }\n\n    // Separate encoding into smaller focused methods to reduce type-checking overhead\n    @inline(never)\n    private static func encodeViewInfo(_ viewInfo: ThomasViewInfo, to encoder: any Encoder) throws {\n        switch viewInfo {\n        case .container(let info): try info.encode(to: encoder)\n        case .linearLayout(let info): try info.encode(to: encoder)\n        #if !os(tvOS) && !os(watchOS)\n        case .webView(let info): try info.encode(to: encoder)\n        #endif\n        case .customView(let info): try info.encode(to: encoder)\n        case .scrollLayout(let info): try info.encode(to: encoder)\n        case .media(let info): try info.encode(to: encoder)\n        case .label(let info): try info.encode(to: encoder)\n        case .labelButton(let info): try info.encode(to: encoder)\n        case .imageButton(let info): try info.encode(to: encoder)\n        case .stackImageButton(let info): try info.encode(to: encoder)\n        case .emptyView(let info): try info.encode(to: encoder)\n        case .pager(let info): try info.encode(to: encoder)\n        case .pagerIndicator(let info): try info.encode(to: encoder)\n        case .storyIndicator(let info): try info.encode(to: encoder)\n        case .pagerController(let info): try info.encode(to: encoder)\n        case .formController(let info): try info.encode(to: encoder)\n        case .checkbox(let info): try info.encode(to: encoder)\n        case .checkboxController(let info): try info.encode(to: encoder)\n        case .radioInput(let info): try info.encode(to: encoder)\n        case .radioInputController(let info): try info.encode(to: encoder)\n        case .textInput(let info): try info.encode(to: encoder)\n        case .score(let info): try info.encode(to: encoder)\n        case .npsController(let info): try info.encode(to: encoder)\n        case .toggle(let info): try info.encode(to: encoder)\n        case .stateController(let info): try info.encode(to: encoder)\n        case .buttonLayout(let info): try info.encode(to: encoder)\n        case .basicToggleLayout(let info): try info.encode(to: encoder)\n        case .checkboxToggleLayout(let info): try info.encode(to: encoder)\n        case .radioInputToggleLayout(let info): try info.encode(to: encoder)\n        case .iconView(let info): try info.encode(to: encoder)\n        case .scoreController(let info): try info.encode(to: encoder)\n        case .scoreToggleLayout(let info): try info.encode(to: encoder)\n        case .videoController(let info): try info.encode(to: encoder)\n        }\n    }\n\n    struct Container: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .container\n            let items: [Item]\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case items\n            }\n        }\n\n        struct Item: ThomasSerializable {\n            var position: ThomasPosition\n            var margin: ThomasMargin?\n            var size: ThomasSize\n            var view: ThomasViewInfo\n            var ignoreSafeArea: Bool?\n\n            private enum CodingKeys: String, CodingKey {\n                case position\n                case margin\n                case size\n                case view\n                case ignoreSafeArea = \"ignore_safe_area\"\n            }\n        }\n    }\n\n    struct LinearLayout: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .linearLayout\n            var direction: ThomasDirection\n            var randomizeChildren: Bool?\n            var items: [Item]\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case direction\n                case randomizeChildren = \"randomize_children\"\n                case items\n            }\n        }\n\n        struct Item: ThomasSerializable {\n            var size: ThomasSize\n            var margin: ThomasMargin?\n            var view: ThomasViewInfo\n            var position: ThomasPosition?\n        }\n    }\n\n    struct ScrollLayout: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .scrollLayout\n            var direction: ThomasDirection\n            var view: ThomasViewInfo\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case direction\n                case view\n            }\n        }\n    }\n\n    struct WebView: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .webView\n            var url: String\n\n            private enum CodingKeys: String, CodingKey {\n                case url\n                case type\n            }\n        }\n    }\n\n    struct CustomView: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .customView\n            let name: String\n            let properties: AirshipJSON?\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case name\n                case properties\n            }\n        }\n    }\n\n    struct Label: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var overrides: Overrides?\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides, overrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.overrides = try decoder.decodeOverrides()\n        }\n\n        struct Overrides: ThomasSerializable {\n            var text: [ThomasPropertyOverride<String>]?\n            var ref: [ThomasPropertyOverride<String>]?\n            var refs: [ThomasPropertyOverride<[String]>]?\n            var iconStart: [ThomasPropertyOverride<LabelIcon>]?\n            var iconEnd: [ThomasPropertyOverride<LabelIcon>]?\n            var textAppearance: [ThomasPropertyOverride<ThomasTextAppearance>]?\n\n            private enum CodingKeys: String, CodingKey {\n                case text\n                case ref\n                case refs\n                case iconStart = \"icon_start\"\n                case iconEnd = \"icon_end\"\n                case textAppearance = \"text_appearance\"\n            }\n        }\n\n        enum IconType: String, Codable {\n            case type = \"floating\"\n        }\n\n        struct LabelIcon: ThomasSerializable {\n            var type: IconType\n            var icon: ThomasIconInfo\n            var space: Double\n        }\n\n        struct LabelAssociation: ThomasSerializable {\n            enum LabelAssociationTypes: String, ThomasSerializable {\n                case labels\n                case describes\n            }\n\n            var viewID: String\n            var type: LabelAssociationTypes\n            var viewType: ViewType\n\n            enum CodingKeys: String, CodingKey {\n                case viewID = \"view_id\"\n                case type\n                case viewType = \"view_type\"\n            }\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .label\n            var text: String\n            var ref: String?\n            var refs: [String]?\n            var textAppearance: ThomasTextAppearance\n            var markdown: ThomasMarkDownOptions?\n            var accessibilityRole: AccessibilityRole?\n            var iconStart: LabelIcon?\n            var iconEnd: LabelIcon?\n            var labels: LabelAssociation?\n            var isAccessibilityAlert: Bool?\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case text\n                case ref\n                case refs\n                case textAppearance = \"text_appearance\"\n                case markdown\n                case accessibilityRole = \"accessibility_role\"\n                case iconStart = \"icon_start\"\n                case iconEnd = \"icon_end\"\n                case labels\n                case isAccessibilityAlert = \"is_accessibility_alert\"\n            }\n        }\n\n        enum AccessibilityRole: Codable, Equatable, Sendable {\n            case heading(level: Int)\n\n            fileprivate enum AccessibilityRoleType: String, Codable {\n                case heading\n            }\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case level\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                var container = encoder.container(keyedBy: CodingKeys.self)\n                switch self {\n                case .heading(let level):\n                    try container.encode(AccessibilityRoleType.heading, forKey: .type)\n                    try container.encode(level, forKey: .level)\n                }\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(AccessibilityRoleType.self, forKey: .type)\n                switch type {\n                case .heading:\n                    self = .heading(level: try container.decode(Int.self, forKey: .level))\n                }\n            }\n        }\n    }\n\n    struct Media: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        enum MediaType: String, ThomasSerializable {\n            case image\n            case video\n            case youtube\n            case vimeo\n        }\n\n        struct Video: ThomasSerializable {\n            var aspectRatio: Double?\n            var showControls: Bool?\n            var autoplay: Bool?\n            var muted: Bool?\n            var loop: Bool?\n            var autoResetPosition: Bool?\n\n            enum CodingKeys: String, CodingKey {\n                case aspectRatio = \"aspect_ratio\"\n                case showControls = \"show_controls\"\n                case autoplay\n                case muted\n                case loop\n                case autoResetPosition = \"auto_reset_position\"\n            }\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .media\n            var url: String\n            var mediaType: MediaType\n            var mediaFit: ThomasMediaFit\n            var video: Video?\n            var cropPosition: ThomasPosition?\n            var identifier: String?\n\n            private enum CodingKeys: String, CodingKey {\n                case mediaType = \"media_type\"\n                case url\n                case mediaFit = \"media_fit\"\n                case video\n                case cropPosition = \"position\"\n                case type\n                case identifier\n            }\n        }\n    }\n\n    struct LabelButton: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .labelButton\n            var identifier: String\n            var clickBehaviors: [ThomasButtonClickBehavior]?\n            var actions: ThomasActionsPayload?\n            var label: ThomasViewInfo.Label\n            var reportingMetadata: AirshipJSON?\n            var tapEffect: ThomasButtonTapEffect?\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case clickBehaviors = \"button_click\"\n                case actions\n                case label\n                case type\n                case tapEffect = \"tap_effect\"\n                case reportingMetadata = \"reporting_metadata\"\n            }\n        }\n    }\n\n    struct ButtonLayout: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .buttonLayout\n            var identifier: String\n            var clickBehaviors: [ThomasButtonClickBehavior]?\n            var actions: ThomasActionsPayload?\n            var reportingMetadata: AirshipJSON?\n            var tapEffect: ThomasButtonTapEffect?\n            var accessibilityRole: AccessibilityRole?\n            var view: ThomasViewInfo\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case clickBehaviors = \"button_click\"\n                case actions\n                case type\n                case tapEffect = \"tap_effect\"\n                case view\n                case accessibilityRole = \"accessibility_role\"\n                case reportingMetadata = \"reporting_metadata\"\n            }\n        }\n\n        fileprivate enum AccessibilityRoleType: String, Codable {\n            case button\n            case container\n        }\n\n        enum AccessibilityRole: Codable, Equatable, Sendable {\n            case container\n            case button\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                var container = encoder.container(keyedBy: CodingKeys.self)\n                switch self {\n                case .button:\n                    try container.encode(AccessibilityRoleType.button, forKey: .type)\n                case .container:\n                    try container.encode(AccessibilityRoleType.container, forKey: .type)\n                }\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(AccessibilityRoleType.self, forKey: .type)\n                self = switch type {\n                case .button: .button\n                case .container: .container\n                }\n            }\n        }\n    }\n\n    struct StackImageButton: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var overrides: Overrides?\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides, overrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.overrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .imageButton\n            var identifier: String\n            var clickBehaviors: [ThomasButtonClickBehavior]?\n            var actions: ThomasActionsPayload?\n            var reportingMetadata: AirshipJSON?\n            var tapEffect: ThomasButtonTapEffect?\n            var items: [Item]\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case clickBehaviors = \"button_click\"\n                case actions\n                case type\n                case tapEffect = \"tap_effect\"\n                case items\n                case reportingMetadata = \"reporting_metadata\"\n            }\n        }\n\n        struct Overrides: ThomasSerializable {\n            var items: [ThomasPropertyOverride<[Item]>]?\n            var contentDescription: [ThomasPropertyOverride<String>]?\n            var localizedContentDescription: [ThomasPropertyOverride<ThomasAccessibleInfo.Localized>]?\n\n            enum CodingKeys: String, CodingKey {\n                case items\n                case contentDescription = \"content_description\"\n                case localizedContentDescription = \"localized_content_description\"\n            }\n        }\n\n        enum ItemType: String, Codable, Equatable, Sendable {\n            case icon\n            case shape\n            case imageURL = \"image_url\"\n        }\n\n        enum Item: Codable, Equatable, Sendable {\n            case imageURL(ImageURLItem)\n            case icon(IconItem)\n            case shape(ShapeItem)\n\n            private enum CodingKeys: String, CodingKey {\n                case type = \"type\"\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(ItemType.self, forKey: .type)\n\n                self = switch type {\n                case .imageURL: .imageURL(try ImageURLItem(from: decoder))\n                case .shape: .shape(try ShapeItem(from: decoder))\n                case .icon: .icon(try IconItem(from: decoder))\n                }\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                switch self {\n                case .icon(let info): try info.encode(to: encoder)\n                case .imageURL(let info): try info.encode(to: encoder)\n                case .shape(let info): try info.encode(to: encoder)\n                }\n            }\n\n            struct ImageURLItem: ThomasSerializable {\n                let type: ItemType = .imageURL\n                var url: String\n                var mediaFit: ThomasMediaFit\n                var cropPosition: ThomasPosition?\n\n                enum CodingKeys: String, CodingKey {\n                    case url\n                    case type\n                    case cropPosition = \"position\"\n                    case mediaFit = \"media_fit\"\n                }\n            }\n\n            struct ShapeItem: ThomasSerializable {\n                let type: ItemType = .shape\n                var shape: ThomasShapeInfo\n\n                enum CodingKeys: String, CodingKey {\n                    case type\n                    case shape\n                }\n            }\n\n            struct IconItem: ThomasSerializable {\n                let type: ItemType = .icon\n                var icon: ThomasIconInfo\n\n                enum CodingKeys: String, CodingKey {\n                    case type\n                    case icon\n                }\n            }\n        }\n    }\n\n    struct ImageButton: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .imageButton\n            var identifier: String\n            var clickBehaviors: [ThomasButtonClickBehavior]?\n            var actions: ThomasActionsPayload?\n            var reportingMetadata: AirshipJSON?\n            var tapEffect: ThomasButtonTapEffect?\n            var image: ButtonImage\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case clickBehaviors = \"button_click\"\n                case actions\n                case type\n                case tapEffect = \"tap_effect\"\n                case image\n                case reportingMetadata = \"reporting_metadata\"\n            }\n        }\n\n        enum ButtonImageType: String, Codable, Equatable, Sendable {\n            case url\n            case icon\n        }\n\n        enum ButtonImage: Codable, Equatable, Sendable {\n            case url(ImageURL)\n            case icon(ThomasIconInfo)\n\n            private enum CodingKeys: String, CodingKey {\n                case type = \"type\"\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(ButtonImageType.self, forKey: .type)\n\n                self = switch type {\n                case .url: .url(try ImageURL(from: decoder))\n                case .icon: .icon(try ThomasIconInfo(from: decoder))\n                }\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                switch self {\n                case .icon(let info): try info.encode(to: encoder)\n                case .url(let info): try info.encode(to: encoder)\n                }\n            }\n\n            struct ImageURL: ThomasSerializable {\n                let type: ButtonImageType = .url\n                var url: String\n                var mediaFit: ThomasMediaFit?\n                var cropPosition: ThomasPosition?\n\n                enum CodingKeys: String, CodingKey {\n                    case url\n                    case type\n                    case cropPosition = \"position\"\n                    case mediaFit = \"media_fit\"\n                }\n            }\n        }\n    }\n\n    struct NubInfo: ThomasSerializable {\n        var size: ThomasSize\n        var margin: ThomasMargin?\n        var color: ThomasColor\n    }\n\n    struct CornerRadiusInfo: ThomasSerializable {\n        var topLeft: Double?\n        var topRight: Double?\n        var bottomLeft: Double?\n        var bottomRight: Double?\n\n        private enum CodingKeys: String, CodingKey {\n            case topLeft = \"top_left\"\n            case topRight = \"top_right\"\n            case bottomLeft = \"bottom_left\"\n            case bottomRight = \"bottom_right\"\n        }\n    }\n\n    struct EmptyView: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n\n        init(commonProperties: CommonViewProperties, commonOverrides: CommonViewOverrides? = nil, properties: Properties) {\n            self.commonProperties = commonProperties\n            self.commonOverrides = commonOverrides\n            self.properties = properties\n        }\n\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .emptyView\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n            }\n        }\n    }\n\n    struct Pager: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .pager\n            let disableSwipe: Bool?\n            let items: [Item]\n            let gestures: [Gesture]?\n            let disableSwipePredicate: [DisableSwipeSelector]?\n\n            enum CodingKeys: String, CodingKey {\n                case items = \"items\"\n                case disableSwipe = \"disable_swipe\"\n                case gestures = \"gestures\"\n                case type\n                case disableSwipePredicate = \"disable_swipe_when\"\n            }\n        }\n\n        struct Item: ThomasSerializable, Identifiable {\n            let identifier: String\n            let view: ThomasViewInfo\n            let displayActions: ThomasActionsPayload?\n            let automatedActions: [ThomasAutomatedAction]?\n            let accessibilityActions: [ThomasAccessibilityAction]?\n            let stateActions: [ThomasStateAction]?\n            let branching: ThomasPageBranching?\n\n            enum CodingKeys: String, CodingKey {\n                case identifier = \"identifier\"\n                case view = \"view\"\n                case displayActions = \"display_actions\"\n                case automatedActions = \"automated_actions\"\n                case accessibilityActions = \"accessibility_actions\"\n                case stateActions = \"state_actions\"\n                case branching\n            }\n            \n            var id: String { return identifier }\n        }\n        \n        struct DisableSwipeSelector: ThomasSerializable {\n            let predicate: JSONPredicate?\n            let direction: Direction\n            \n            enum CodingKeys: String, CodingKey {\n                case predicate = \"when_state_matches\"\n                case direction = \"directions\"\n            }\n            \n            private enum DirectionCodingKeys: String, CodingKey {\n                case type\n            }\n            \n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                predicate = try container.decodeIfPresent(JSONPredicate.self, forKey: .predicate)\n                \n                let directionContainer = try container.nestedContainer(keyedBy: DirectionCodingKeys.self, forKey: .direction)\n                direction = try directionContainer.decode(Direction.self, forKey: .type)\n            }\n            \n            func encode(to encoder: any Encoder) throws {\n                var container = encoder.container(keyedBy: CodingKeys.self)\n                try container.encodeIfPresent(predicate, forKey: .predicate)\n                \n                var nested = container.nestedContainer(keyedBy: DirectionCodingKeys.self, forKey: .direction)\n                try nested.encode(direction, forKey: .type)\n            }\n        }\n        \n        enum Direction: String, ThomasSerializable {\n            case horizontal = \"horizontal\"\n        }\n\n        indirect enum Gesture: ThomasSerializable {\n            case swipeGesture(Swipe)\n            case tapGesture(Tap)\n            case holdGesture(Hold)\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(GestureType.self, forKey: .type)\n\n                self = switch type {\n                case .tap: .tapGesture(try Tap(from: decoder))\n                case .swipe: .swipeGesture(try Swipe(from: decoder))\n                case .hold: .holdGesture(try Hold(from: decoder))\n                }\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                switch self {\n                case .swipeGesture(let gesture): try gesture.encode(to: encoder)\n                case .tapGesture(let gesture): try gesture.encode(to: encoder)\n                case .holdGesture(let gesture): try gesture.encode(to: encoder)\n                }\n            }\n\n            enum GestureLocation: String, Codable, Equatable, Sendable {\n                case top\n                case bottom\n                case start\n                case end\n                case left\n                case right\n                case any\n            }\n\n            enum GestureDirection: String, ThomasSerializable {\n                case up\n                case down\n            }\n\n            enum GestureType: String, Codable, Equatable, Sendable {\n                case tap\n                case swipe\n                case hold\n            }\n\n            protocol Info: ThomasSerializable {\n                var reportingMetadata: AirshipJSON? { get }\n                var type: GestureType { get }\n                var identifier: String { get }\n            }\n\n            struct GestureBehavior: ThomasSerializable {\n                var actions: [ThomasActionsPayload]?\n                var behaviors: [ThomasButtonClickBehavior]?\n            }\n\n            struct Swipe: Info {\n                let type: GestureType = .swipe\n                var identifier: String\n                var reportingMetadata: AirshipJSON?\n                var direction: GestureDirection\n                var behavior: GestureBehavior\n\n                enum CodingKeys: String, CodingKey {\n                    case identifier\n                    case reportingMetadata = \"reporting_metadata\"\n                    case direction\n                    case behavior\n                    case type\n                }\n            }\n\n            struct Tap: Info {\n                let type: GestureType = .tap\n                var identifier: String\n                var reportingMetadata: AirshipJSON?\n                var location: GestureLocation\n                var behavior: GestureBehavior\n\n                enum CodingKeys: String, CodingKey {\n                    case identifier\n                    case location\n                    case behavior\n                    case type\n                }\n            }\n\n            struct Hold: Info {\n                let type: GestureType = .hold\n                var identifier: String\n                var reportingMetadata: AirshipJSON?\n                var pressBehavior: GestureBehavior\n                var releaseBehavior: GestureBehavior\n\n                enum CodingKeys: String, CodingKey {\n                    case identifier = \"identifier\"\n                    case pressBehavior = \"press_behavior\"\n                    case releaseBehavior = \"release_behavior\"\n                    case type\n                }\n            }\n        }\n    }\n\n    enum ProgressType: String, Decodable {\n        case linear = \"linear\"\n    }\n\n    struct Progress: Decodable, Equatable, Sendable {\n        var type: ProgressType\n        var color: ThomasColor\n\n        enum CodingKeys: String, CodingKey {\n            case type = \"type\"\n            case color = \"color\"\n        }\n    }\n\n    struct PagerIndicator: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .pagerIndicator\n            var bindings: Bindings\n            var spacing: Double\n            var automatedAccessibilityActions: [ThomasAutomatedAccessibilityAction]?\n\n            enum CodingKeys: String, CodingKey {\n                case bindings = \"bindings\"\n                case spacing = \"spacing\"\n                case type\n                case automatedAccessibilityActions = \"automated_accessibility_actions\"\n            }\n\n            struct Bindings: Codable, Equatable, Sendable {\n                var selected: Binding\n                var unselected: Binding\n            }\n\n            struct Binding: Codable, Equatable, Sendable {\n                var shapes: [ThomasShapeInfo]?\n                var icon: ThomasIconInfo?\n            }\n        }\n    }\n\n    struct StoryIndicator: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .storyIndicator\n            var source: Source\n            var style: Style\n            var automatedAccessibilityActions: [ThomasAutomatedAccessibilityAction]?\n\n            enum CodingKeys: String, CodingKey {\n                case source = \"source\"\n                case style = \"style\"\n                case type\n                case automatedAccessibilityActions = \"automated_accessibility_actions\"\n            }\n        }\n\n        struct Source: ThomasSerializable {\n            let type: IndicatorType\n\n            enum IndicatorType: String, ThomasSerializable {\n                case pager = \"pager\"\n                case currentPage = \"current_page\"\n            }\n        }\n\n        enum Style: ThomasSerializable {\n            case linearProgress(LinearProgress)\n\n            enum StyleType: String, Codable, Equatable, Sendable {\n                case linearProgress = \"linear_progress\"\n            }\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(StyleType.self, forKey: .type)\n\n                self = switch type {\n                case .linearProgress: .linearProgress(try LinearProgress(from: decoder))\n                }\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                switch self {\n                case .linearProgress(let style): try style.encode(to: encoder)\n                }\n            }\n\n            enum LayoutDirection: String, ThomasSerializable {\n                case vertical = \"vertical\"\n                case horizontal = \"horizontal\"\n            }\n\n            enum ProgressSizingType: String, Codable, Equatable, Sendable {\n                case equal\n                case pageDuration = \"page_duration\"\n            }\n\n            struct LinearProgress: ThomasSerializable {\n                let type: StyleType = .linearProgress\n                var direction: LayoutDirection\n                var sizing: ProgressSizingType?\n                var spacing: Double?\n                var progressColor: ThomasColor\n                var trackColor: ThomasColor\n                var inactiveSegmentScaler: Double?\n\n                private enum CodingKeys: String, CodingKey {\n                    case type\n                    case direction\n                    case sizing\n                    case spacing\n                    case progressColor = \"progress_color\"\n                    case trackColor = \"track_color\"\n                    case inactiveSegmentScaler = \"inactive_segment_scaler\"\n                }\n            }\n        }\n    }\n\n    struct PagerController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .pagerController\n            var view: ThomasViewInfo\n            var identifier: String\n            let branching: ThomasPagerControllerBranching?\n\n            enum CodingKeys: String, CodingKey {\n                case view = \"view\"\n                case identifier = \"identifier\"\n                case type\n                case branching\n            }\n        }\n    }\n\n    struct VideoController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct GroupInfo: ThomasSerializable {\n            var identifier: String\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .videoController\n            var view: ThomasViewInfo\n            var identifier: String\n            var videoScope: [String]?\n            var muteGroup: GroupInfo?\n            var playGroup: GroupInfo?\n\n            enum CodingKeys: String, CodingKey {\n                case view\n                case identifier\n                case type\n                case videoScope = \"video_scope\"\n                case muteGroup = \"mute_group\"\n                case playGroup = \"play_group\"\n            }\n        }\n    }\n\n    struct FormController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .formController\n            var identifier: String\n            var submit: ThomasFormSubmitBehavior?\n            var view: ThomasViewInfo\n            var responseType: String?\n            var formEnableBehaviors: [ThomasEnableBehavior]?\n            var validationMode: ThomasFormValidationMode?\n\n            enum CodingKeys: String, CodingKey {\n                case identifier = \"identifier\"\n                case submit = \"submit\"\n                case view = \"view\"\n                case responseType = \"response_type\"\n                case formEnableBehaviors = \"form_enabled\"\n                case type\n                case validationMode = \"validation_mode\"\n            }\n        }\n    }\n\n    struct StateController: BaseInfo {\n        static let defaultIdentifier: String = \"default\"\n        \n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        \n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n        \n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n        \n        struct Properties: ThomasSerializable {\n            let type: ViewType = .stateController\n            var view: ThomasViewInfo\n            var initialState: AirshipJSON?\n            var identifier: String?\n            \n            enum CodingKeys: String, CodingKey {\n                case view\n                case type\n                case initialState = \"initial_state\"\n                case identifier\n            }\n        }\n        \n        var identifier: String {\n            return properties.identifier ?? Self.defaultIdentifier\n        }\n    }\n\n    struct NPSController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .npsController\n            var identifier: String\n            var submit: ThomasFormSubmitBehavior?\n            var npsIdentifier: String\n            var view: ThomasViewInfo\n            var responseType: String?\n            var formEnableBehaviors: [ThomasEnableBehavior]?\n            var validationMode: ThomasFormValidationMode?\n\n            enum CodingKeys: String, CodingKey {\n                case identifier\n                case submit\n                case view\n                case npsIdentifier = \"nps_identifier\"\n                case responseType = \"response_type\"\n                case formEnableBehaviors = \"form_enabled\"\n                case type\n                case validationMode = \"validation_mode\"\n            }\n        }\n    }\n\n    struct CheckboxController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .checkboxController\n            var identifier: String\n            var view: ThomasViewInfo\n            var minSelection: Int?\n            var maxSelection: Int?\n\n            enum CodingKeys: String, CodingKey {\n                case identifier\n                case view\n                case type\n                case minSelection = \"min_selection\"\n                case maxSelection = \"max_selection\"\n            }\n        }\n    }\n\n    struct RadioInputController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .radioInputController\n            var identifier: String\n            var view: ThomasViewInfo\n            var attributeName: ThomasAttributeName?\n\n            enum CodingKeys: String, CodingKey {\n                case identifier\n                case view\n                case attributeName = \"attribute_name\"\n                case type\n            }\n        }\n    }\n\n    struct ScoreController: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .radioInputController\n            var identifier: String\n            var view: ThomasViewInfo\n            var attributeName: ThomasAttributeName?\n\n            enum CodingKeys: String, CodingKey {\n                case identifier\n                case view\n                case attributeName = \"attribute_name\"\n                case type\n            }\n        }\n    }\n\n\n    struct ScoreToggleLayout: BaseInfo {\n        let properties: Properties\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.properties = try decoder.decodeProperties()\n            self.commonProperties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .scoreToggleLayout\n            var identifier: String\n            var attributeValue: ThomasAttributeValue?\n            var onToggleOn: ToggleActions\n            var onToggleOff: ToggleActions\n            var view: ThomasViewInfo\n            var reportingValue: AirshipJSON\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case attributeValue = \"attribute_value\"\n                case onToggleOn = \"on_toggle_on\"\n                case onToggleOff = \"on_toggle_off\"\n                case view\n                case reportingValue = \"reporting_value\"\n                case type\n            }\n        }\n    }\n\n\n    struct TextInput: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n        var commonOverrides: CommonViewOverrides?\n        var overrides: Overrides?\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides, overrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.overrides = try decoder.decodeOverrides()\n        }\n\n        enum IconEndType: String, Codable {\n            case floating = \"floating\"\n        }\n\n        struct IconEndInfo: ThomasSerializable {\n            var type: IconEndType = .floating\n            var icon: ThomasIconInfo\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .textInput\n            var identifier: String\n            var attributeName: ThomasAttributeName?\n            var placeholder: String?\n            var textAppearance: ThomasTextAppearance\n            var inputType: TextInputType\n            var iconEnd: IconEndInfo?\n            var emailRegistration: ThomasEmailRegistrationOption?\n            var smsLocales: [ThomasSMSLocale]?\n\n            enum CodingKeys: String, CodingKey {\n                case attributeName = \"attribute_name\"\n                case textAppearance = \"text_appearance\"\n                case identifier\n                case placeholder = \"place_holder\"\n                case inputType = \"input_type\"\n                case type\n                case iconEnd = \"icon_end\"\n                case emailRegistration = \"email_registration\"\n                case smsLocales = \"locales\"\n            }\n        }\n\n        struct Overrides: ThomasSerializable {\n            var iconEnd: [ThomasPropertyOverride<IconEndInfo>]?\n\n            enum CodingKeys: String, CodingKey {\n                case iconEnd = \"icon_end\"\n            }\n        }\n\n        enum TextInputType: String, ThomasSerializable {\n            case email\n            case number\n            case text\n            case textMultiline = \"text_multiline\"\n            case sms\n        }\n    }\n\n\n    struct Toggle: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n        var commonOverrides: CommonViewOverrides?\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .toggle\n            var identifier: String\n            var style: ThomasToggleStyleInfo\n            var attributeName: ThomasAttributeName?\n            var attributeValue: ThomasAttributeValue?\n\n            enum CodingKeys: String, CodingKey {\n                case style\n                case identifier\n                case attributeName = \"attribute_name\"\n                case attributeValue = \"attribute_value\"\n                case type\n            }\n        }\n    }\n\n    struct ToggleActions: ThomasSerializable {\n        var stateActions: [ThomasStateAction]?\n\n        enum CodingKeys: String, CodingKey {\n            case stateActions = \"state_actions\"\n        }\n    }\n\n    struct BasicToggleLayout: BaseInfo {\n        let properties: Properties\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.properties = try decoder.decodeProperties()\n            self.commonProperties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .basicToggleLayout\n            var identifier: String\n            var attributeName: ThomasAttributeName?\n            var attributeValue: ThomasAttributeValue?\n            var onToggleOn: ToggleActions\n            var onToggleOff: ToggleActions\n            var view: ThomasViewInfo\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case attributeName = \"attribute_name\"\n                case attributeValue = \"attribute_value\"\n                case onToggleOn = \"on_toggle_on\"\n                case onToggleOff = \"on_toggle_off\"\n                case view\n                case type\n            }\n        }\n    }\n\n    struct CheckboxToggleLayout: BaseInfo {\n        let properties: Properties\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.properties = try decoder.decodeProperties()\n            self.commonProperties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .checkboxToggleLayout\n            var identifier: String\n            var onToggleOn: ToggleActions\n            var onToggleOff: ToggleActions\n            var view: ThomasViewInfo\n            var reportingValue: AirshipJSON\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case onToggleOn = \"on_toggle_on\"\n                case onToggleOff = \"on_toggle_off\"\n                case view\n                case reportingValue = \"reporting_value\"\n                case type\n            }\n        }\n    }\n\n    struct RadioInputToggleLayout: BaseInfo {\n        let properties: Properties\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.properties = try decoder.decodeProperties()\n            self.commonProperties = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .radioInputToggleLayout\n            var identifier: String\n            var attributeValue: ThomasAttributeValue?\n            var onToggleOn: ToggleActions\n            var onToggleOff: ToggleActions\n            var view: ThomasViewInfo\n            var reportingValue: AirshipJSON\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case attributeValue = \"attribute_value\"\n                case onToggleOn = \"on_toggle_on\"\n                case onToggleOff = \"on_toggle_off\"\n                case view\n                case reportingValue = \"reporting_value\"\n                case type\n            }\n        }\n    }\n    \n    struct Checkbox: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .checkbox\n            var reportingValue: AirshipJSON\n            var style: ThomasToggleStyleInfo\n            var identifier: String? // Added later so its treated as optional.\n\n            enum CodingKeys: String, CodingKey {\n                case style\n                case reportingValue = \"reporting_value\"\n                case type\n                case identifier\n            }\n        }\n    }\n\n    struct RadioInput: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .radioInput\n            var reportingValue: AirshipJSON\n            var style: ThomasToggleStyleInfo\n            var attributeValue: ThomasAttributeValue?\n            var identifier: String? // Added later so its treated as optional.\n\n            enum CodingKeys: String, CodingKey {\n                case style\n                case identifier\n                case reportingValue = \"reporting_value\"\n                case attributeValue = \"attribute_value\"\n                case type\n            }\n        }\n    }\n\n    struct Score: BaseInfo {\n        var commonProperties: CommonViewProperties\n        var commonOverrides: CommonViewOverrides?\n\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var validation: ThomasValidationInfo\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible, validation,\n                overrides: commonOverrides\n            )\n        }\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.validation = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .score\n            var identifier: String\n            var style: ScoreStyle\n            var attributeName: ThomasAttributeName?\n\n            private enum CodingKeys: String, CodingKey {\n                case identifier\n                case style\n                case attributeName = \"attribute_name\"\n                case type\n            }\n        }\n\n        enum ScoreStyle: ThomasSerializable {\n            case numberRange(NumberRange)\n\n            enum ScoreStyleType: String, ThomasSerializable {\n                case numberRange = \"number_range\"\n            }\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n            }\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.container(keyedBy: CodingKeys.self)\n                let type = try container.decode(ScoreStyleType.self, forKey: .type)\n\n                self = switch type {\n                case .numberRange: .numberRange(try NumberRange(from: decoder))\n                }\n            }\n\n            func encode(to encoder: any Encoder) throws {\n                switch self {\n                case .numberRange(let info): try info.encode(to: encoder)\n                }\n            }\n\n            struct NumberRange: ThomasSerializable {\n                let type: ScoreStyleType = .numberRange\n                var spacing: Double?\n                var bindings: Bindings\n                var start: Int\n                var end: Int\n                let wrapping: Wrapping?\n\n                struct Wrapping: ThomasSerializable {\n                    let lineSpacing: Double?\n                    let maxItemsPerLine: Int?\n\n                    enum CodingKeys: String, CodingKey {\n                        case lineSpacing = \"line_spacing\"\n                        case maxItemsPerLine = \"max_items_per_line\"\n                    }\n                }\n\n                enum CodingKeys: String, CodingKey {\n                    case spacing\n                    case bindings\n                    case start\n                    case end\n                    case type\n                    case wrapping\n                }\n\n                struct Bindings: ThomasSerializable {\n                    var selected: Binding\n                    var unselected: Binding\n                }\n\n                struct Binding: ThomasSerializable {\n                    var shapes: [ThomasShapeInfo]?\n                    var textAppearance: ThomasTextAppearance?\n\n                    private enum CodingKeys: String, CodingKey {\n                        case shapes\n                        case textAppearance = \"text_appearance\"\n                    }\n                }\n            }\n\n        }\n    }\n\n    struct IconView: BaseInfo {\n        var properties: Properties\n        var accessible: ThomasAccessibleInfo\n        var commonProperties: ThomasViewInfo.CommonViewProperties\n        var commonOverrides: ThomasViewInfo.CommonViewOverrides?\n        var overrides: Overrides?\n\n        init(from decoder: any Decoder) throws {\n            self.commonProperties = try decoder.decodeProperties()\n            self.properties = try decoder.decodeProperties()\n            self.accessible = try decoder.decodeProperties()\n            self.commonOverrides = try decoder.decodeOverrides()\n            self.overrides = try decoder.decodeOverrides()\n        }\n\n        func encode(to encoder: any Encoder) throws {\n            try encoder.encode(\n                properties: commonProperties, properties, accessible,\n                overrides: commonOverrides, overrides\n            )\n        }\n\n        struct Properties: ThomasSerializable {\n            let type: ViewType = .iconView\n            var icon: ThomasIconInfo\n\n            private enum CodingKeys: String, CodingKey {\n                case type\n                case icon\n            }\n        }\n\n        struct Overrides: ThomasSerializable {\n            var icon: [ThomasPropertyOverride<ThomasIconInfo>]?\n        }\n    }\n\n    struct CommonViewOverrides: ThomasSerializable {\n        var border: [ThomasPropertyOverride<ThomasBorder>]?\n        var backgroundColor: [ThomasPropertyOverride<ThomasColor>]?\n\n        enum CodingKeys: String, CodingKey {\n            case border\n            case backgroundColor = \"background_color\"\n        }\n    }\n\n    struct CommonViewProperties: ThomasSerializable {\n        var border: ThomasBorder?\n        var backgroundColor: ThomasColor?\n        var visibility: ThomasVisibilityInfo?\n        var eventHandlers: [ThomasEventHandler]?\n        var enabled: [ThomasEnableBehavior]?\n        var stateTriggers: [ThomasStateTriggers]?\n\n        enum CodingKeys: String, CodingKey {\n            case border\n            case backgroundColor = \"background_color\"\n            case visibility\n            case eventHandlers = \"event_handlers\"\n            case enabled\n            case stateTriggers = \"state_triggers\"\n        }\n    }\n}\n\nfileprivate extension Encoder {\n    func encode(properties: (any Encodable)?..., overrides: (any Encodable)?...) throws {\n        try properties.forEach { codable in\n            try codable?.encode(to: self)\n        }\n\n        let overrides = overrides.compactMap { $0 }\n        if !overrides.isEmpty {\n            try ViewOverridesEncodable(overrides: overrides).encode(to: self)\n        }\n    }\n}\n\nfileprivate extension Decoder {\n    func decodeProperties<T: Decodable>() throws -> T {\n        return try T(from: self)\n    }\n\n    func decodeOverrides<T: Decodable>() throws -> T? {\n        return try ViewOverridesDecodable<T>(from: self).overrides\n    }\n}\n\nfileprivate struct ViewOverridesEncodable: Encodable {\n    private let wrapper: Wrapper?\n\n    init(overrides: [any Encodable]) {\n        self.wrapper = Wrapper(overrides: overrides)\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case wrapper = \"view_overrides\"\n    }\n\n    struct Wrapper: Encodable {\n        var overrides: [any Encodable]\n        func encode(to encoder: any Encoder) throws {\n            try overrides.forEach {\n                try $0.encode(to: encoder)\n            }\n        }\n    }\n}\n\nfileprivate struct ViewOverridesDecodable<T: Decodable>: Decodable {\n    var overrides: T?\n    enum CodingKeys: String, CodingKey {\n        case overrides = \"view_overrides\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasViewedPageInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/// - Note: for internal use only.  :nodoc:\npublic struct ThomasViewedPageInfo: Encodable, Sendable, Equatable, Hashable {\n    public var identifier: String\n    public var index: Int\n    public var displayTime: TimeInterval\n\n    public init(identifier: String, index: Int, displayTime: TimeInterval) {\n        self.identifier = identifier\n        self.index = index\n        self.displayTime = displayTime\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case identifier = \"page_identifier\"\n        case index = \"page_index\"\n        case displayTime = \"display_time\"\n    }\n\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(self.identifier, forKey: .identifier)\n        try container.encode(self.index, forKey: .index)\n\n        try container.encode(\n            String(format: \"%.2f\", displayTime),\n            forKey: .displayTime\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasVisibilityInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nstruct ThomasVisibilityInfo: ThomasSerializable {\n    let invertWhenStateMatches: JSONPredicate\n    let defaultVisibility: Bool\n\n    private enum CodingKeys: String, CodingKey {\n        case invertWhenStateMatches = \"invert_when_state_matches\"\n        case defaultVisibility = \"default\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ThomasWindowSize.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nenum ThomasWindowSize: String, ThomasSerializable {\n    case small\n    case medium\n    case large\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ToggleLayout.swift",
    "content": "\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct ToggleLayout<Content> : View  where Content : View {\n    @EnvironmentObject private var thomasState: ThomasState\n\n    @Binding private var isOn: Bool\n    private let onToggleOn: ThomasViewInfo.ToggleActions\n    private let onToggleOff: ThomasViewInfo.ToggleActions\n    private let content: () -> Content\n\n    init(\n        isOn: Binding<Bool>,\n        onToggleOn: ThomasViewInfo.ToggleActions,\n        onToggleOff: ThomasViewInfo.ToggleActions,\n        @ViewBuilder content: @escaping () -> Content\n    ) {\n        self._isOn = isOn\n        self.onToggleOn = onToggleOn\n        self.onToggleOff = onToggleOff\n        self.content = content\n    }\n\n    var body: some View {\n        Toggle(isOn: $isOn.animation()) {\n            content().background(Color.airshipTappableClear)\n        }\n        .airshipOnChangeOf(self.isOn) { isOn in\n            self.handleStateActions(isOn)\n        }\n        .toggleStyle(PlainButtonToggleStyle())\n        .accessibilityRemoveTraits(.isSelected)\n    }\n\n    private func handleStateActions(_ isOn: Bool) {\n        let actions: ThomasViewInfo.ToggleActions = isOn ? onToggleOn : onToggleOff\n        guard let stateActions = actions.stateActions else { return }\n        thomasState.processStateActions(stateActions)\n    }\n}\n\nfileprivate struct PlainButtonToggleStyle: ToggleStyle {\n    func makeBody(configuration: Self.Configuration) -> some View {\n        Button {\n            configuration.isOn.toggle()\n        }\n        label: {\n            configuration.label\n        }\n#if os(tvOS)\n        .buttonStyle(TVButtonStyle())\n#else\n        .buttonStyle(.plain)\n#endif\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/TouchViewModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if !os(tvOS)\nfileprivate struct TouchViewModifier: ViewModifier {\n    @GestureState var isPressed: Bool = false\n    let onChange: (Bool) -> Void\n\n    func body(content: Content) -> some View {\n        content\n            .simultaneousGesture(\n                TapGesture()\n            )\n            .gesture(\n                LongPressGesture(minimumDuration: 0.1)\n                    .sequenced(before: LongPressGesture(minimumDuration: .infinity))\n                    .updating($isPressed) { value, state, transaction in\n                        switch value {\n                            case .second(true, nil):\n                                state = true\n                            default: break\n                        }\n                    }\n            )\n        .airshipOnChangeOf(self.isPressed) { value in\n            onChange(value)\n        }\n    }\n}\n\nextension View {\n    func onTouch(onChange: @escaping (Bool) -> Void) -> some View {\n        self.modifier(TouchViewModifier(onChange: onChange))\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/UAAppIntegrationDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport UserNotifications\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(WatchKit)\nimport WatchKit\n#endif\n\n/// Delegate for Airship auto-integration events.\nprotocol AppIntegrationDelegate: AnyObject, Sendable {\n    @MainActor\n    func didRegisterForRemoteNotifications(deviceToken: Data)\n    \n    @MainActor\n    func didFailToRegisterForRemoteNotifications(error: any Error)\n    \n    @MainActor\n    func onBackgroundAppRefresh()\n    \n    @MainActor\n    func presentationOptions(for notification: UNNotification, completionHandler: @Sendable @escaping (UNNotificationPresentationOptions) -> Void)\n    \n    @MainActor\n    func willPresentNotification(notification: UNNotification, presentationOptions: UNNotificationPresentationOptions, completionHandler: @Sendable @escaping () -> Void)\n    \n#if !os(tvOS)\n    @MainActor\n    func didReceiveNotificationResponse(response: UNNotificationResponse, completionHandler: @Sendable @escaping () -> Void)\n#endif\n    \n#if os(watchOS)\n    @MainActor\n    func didReceiveRemoteNotification(userInfo: [AnyHashable: Any], isForeground: Bool, completionHandler: @Sendable @escaping (WKBackgroundFetchResult) -> Void)\n#elseif os(macOS)\n    @MainActor\n    func didReceiveRemoteNotification(userInfo: [AnyHashable: Any], isForeground: Bool)\n#else\n    @MainActor\n    func didReceiveRemoteNotification(userInfo: [AnyHashable: Any], isForeground: Bool, completionHandler: @Sendable @escaping (UIBackgroundFetchResult) -> Void)\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/UACoreData.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import CoreData\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n/// - Note: For internal use only. :nodoc:\npublic actor UACoreData {\n    private static let managedContextStoreDirectory: String = \"com.urbanairship.no-backup\"\n\n    private let name: String\n    private let modelURL: URL\n    private let storeNames: [String]\n    public nonisolated let inMemory: Bool\n\n\n    private var shouldPrepareCoreData: Bool = false\n    private var coreDataPrepared: Bool = false\n    private var prepareCoreDataTask: Task<Void, any Error>?\n\n    private var _container: NSPersistentContainer?\n    private var container: NSPersistentContainer {\n        get async throws {\n            try await prepareCoreData()\n            guard let container = _container  else {\n                throw AirshipErrors.error(\"Failed to get container\")\n            }\n            return container\n        }\n    }\n\n    private var _context: NSManagedObjectContext?\n    private var context: NSManagedObjectContext {\n        get async throws {\n            if let context = _context {\n                return context\n            }\n\n            let context = try await container.newBackgroundContext()\n            _context = context\n            return context\n        }\n    }\n\n    public init(\n        name: String,\n        modelURL: URL,\n        inMemory: Bool = false,\n        stores: [String]\n    ) {\n        self.name = name\n        self.modelURL = modelURL\n        self.inMemory = inMemory\n        self.storeNames = stores\n        \n#if !os(watchOS) && !os(macOS)\n        Task { @MainActor [weak self] in\n            if (UIApplication.shared.isProtectedDataAvailable) {\n                await self?.protectedDataAvailable()\n            } else {\n                guard let self else { return }\n                NotificationCenter.default.addObserver(forName: UIApplication.protectedDataDidBecomeAvailableNotification, object: nil, queue: nil, using: { _ in\n                    Task { [weak self] in\n                        await self?.protectedDataAvailable()\n                    }\n                })\n            }\n        }\n#endif\n    }\n\n\n    public func perform(\n        skipIfStoreNotCreated: Bool = false,\n        _ block: @Sendable @escaping (NSManagedObjectContext) throws -> Void\n    ) async throws {\n        if (skipIfStoreNotCreated) {\n            guard self.inMemory || self.storesExistOnDisk() else {\n                return\n            }\n        }\n\n        let context = try await self.context\n        try await withCheckedThrowingContinuation { continuation in\n            context.perform {\n                do {\n                    try block(context)\n                    try context.saveIfChanged()\n                    continuation.resume()\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    public func performWithNullableResult<T: Sendable>(\n        skipIfStoreNotCreated: Bool = false,\n        _ block: @Sendable @escaping (NSManagedObjectContext) throws -> T\n    ) async throws -> T? {\n        if (skipIfStoreNotCreated) {\n            guard self.inMemory || self.storesExistOnDisk() else {\n                return nil\n            }\n        }\n\n        return try await performWithResult(block)\n    }\n\n    public func performWithResult<T: Sendable>(\n        _ block: @Sendable @escaping (NSManagedObjectContext) throws -> T\n    ) async throws -> T {\n        let context = try await self.context\n\n        return try await withCheckedThrowingContinuation { continuation in\n            context.perform {\n                do {\n                    let result = try block(context)\n                    try context.saveIfChanged()\n                    continuation.resume(returning: result)\n                } catch {\n                    continuation.resume(throwing: error)\n                }\n            }\n        }\n    }\n\n    public func deleteStoresOnDisk() throws {\n        for name in self.storeNames {\n            guard let storeURL = self.storeURL(name) else {\n                continue\n            }\n\n            if FileManager.default.fileExists(atPath: storeURL.path) {\n                try FileManager.default.removeItem(atPath: storeURL.path)\n            }\n        }\n    }\n\n    private func protectedDataAvailable() {\n        Task {\n            if self.shouldPrepareCoreData {\n                _ = try? await self.prepareCoreData()\n            }\n        }\n    }\n\n    private func prepareCoreData() async throws {\n        if (coreDataPrepared) {\n            return\n        }\n\n        try? await prepareCoreDataTask?.value\n\n        let task = Task {\n            let container = try (_container ?? makeContainer())\n            if (_container == nil) {\n                _container = container\n            } \n\n            if !coreDataPrepared {\n                try await prepareStore()\n                try await loadStores(container: container)\n                coreDataPrepared = true\n            }\n        }\n\n        prepareCoreDataTask = task\n        try await task.value\n    }\n\n    private func prepareStore() async throws {\n        if !inMemory {\n            guard let storeDirectory = self.storeSQLDirectory() else {\n                throw AirshipErrors.error(\"Unable to get store directory.\")\n            }\n\n            let fileManager = FileManager.default\n            if !fileManager.fileExists(atPath: storeDirectory.path) {\n                do {\n                    try fileManager.createDirectory(\n                        at: storeDirectory,\n                        withIntermediateDirectories: true,\n                        attributes: nil\n                    )\n                } catch {\n                    throw AirshipErrors.error(\n                        \"Failed to create airship SQL directory. \\(error)\"\n                    )\n                }\n            }\n\n            for name in self.storeNames {\n                if let storeURL = self.storeURL(name) {\n                    correctFilePermissions(url: storeURL)\n                }\n            }\n        }\n    }\n\n    private func loadStores(container: NSPersistentContainer) async throws {\n        let remaining = AirshipAtomicValue(container.persistentStoreDescriptions.count)\n        let errorMessage = AirshipAtomicValue<String?>(nil)\n\n        try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<Void, Error>) -> Void in\n            container.loadPersistentStores { description, error in\n                if let error {\n                    AirshipLogger.error(\n                        \"Failed to create store \\(description): \\(error)\"\n                    )\n\n                    errorMessage.update { msg in\n                        if let msg {\n                            return \"\\(msg), \\(error.localizedDescription)\"\n                        } else {\n                            return error.localizedDescription\n                        }\n                    }\n                }\n\n                remaining.update { current in\n                    current - 1\n                }\n\n                if (remaining.value == 0) {\n                    if let msg = errorMessage.value {\n                        continuation.resume(throwing: AirshipErrors.error(msg))\n                    } else {\n                        continuation.resume()\n                    }\n                }\n            }\n        }\n    }\n\n    private func storeSQLDirectory() -> URL? {\n        let fileManager = FileManager.default\n\n        #if os(tvOS)\n        let baseDirectory =\n            fileManager.urls(\n                for: .cachesDirectory,\n                in: .userDomainMask\n            )\n            .last\n        #else\n        let baseDirectory =\n            fileManager.urls(\n                for: .libraryDirectory,\n                in: .userDomainMask\n            )\n            .last\n        #endif\n\n        return baseDirectory?.appendingPathComponent(\n            Self.managedContextStoreDirectory\n        )\n    }\n\n    private func storeURL(_ storeName: String?) -> URL? {\n        return storeSQLDirectory()?.appendingPathComponent(storeName ?? \"\")\n    }\n\n    private func storesExistOnDisk() -> Bool {\n        for name in self.storeNames {\n            let storeURL = self.storeURL(name)\n            if storeURL != nil\n                && FileManager.default.fileExists(atPath: storeURL?.path ?? \"\")\n            {\n                return true\n            }\n        }\n\n        return false\n    }\n\n    private func makeContainer() throws -> NSPersistentContainer {\n        guard let mom = NSManagedObjectModel(contentsOf: self.modelURL) else {\n            throw AirshipErrors.error(\"Failed to create managed object model \\(self.modelURL)\")\n        }\n\n        let container = NSPersistentContainer(name: self.name, managedObjectModel: mom)\n\n        if inMemory {\n            let description = NSPersistentStoreDescription(url: URL(fileURLWithPath: \"/dev/null\"))\n            description.type = NSInMemoryStoreType\n            container.persistentStoreDescriptions = [description]\n        } else {\n            container.persistentStoreDescriptions =  self.storeNames.compactMap { store in\n                guard let storeURL = self.storeURL(store) else {\n                    return nil\n                }\n\n                let description = NSPersistentStoreDescription(url: storeURL)\n                description.type = NSSQLiteStoreType\n                description.shouldAddStoreAsynchronously = true\n                description.shouldMigrateStoreAutomatically = true\n                description.shouldInferMappingModelAutomatically = true\n                return description\n            }\n        }\n        return container\n    }\n\n    private func correctFilePermissions(url: URL) {\n        do {\n            guard (FileManager.default.fileExists(atPath: url.path)) else {\n                return\n            }\n            let attributes = [FileAttributeKey.protectionKey: FileProtectionType.completeUntilFirstUserAuthentication]\n            try FileManager.default.setAttributes(attributes, ofItemAtPath: url.path)\n        } catch(let exception) {\n            AirshipLogger.error(\"Failed to set file attribute \\(exception)\")\n        }\n    }\n}\n\n\nfileprivate extension NSManagedObjectContext {\n    func saveIfChanged() throws {\n        if hasChanges {\n            try save()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/UARemoteDataMapping.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport CoreData\n\n@objc(UARemoteDataMappingV3toV4)\nclass UARemoteDataMappingV3toV4: NSEntityMigrationPolicy {\n\n    override func createDestinationInstances(\n        forSource source: NSManagedObject,\n        in mapping: NSEntityMapping,\n        manager: NSMigrationManager\n    ) throws {\n\n        // data -> JSON data\n\n        guard source.entity.name == RemoteDataStore.remoteDataEntity else {\n            return\n        }\n\n        let type = source.value(forKey: \"type\") as? String\n        let timestamp = source.value(forKey: \"timestamp\") as? Date\n        let data = source.value(forKey: \"data\") as? [AnyHashable: Any]\n        let remoteDataInfo = source.value(forKey: \"remoteDataInfo\") as? Data\n\n        let newRemoteDataEntity = NSEntityDescription.insertNewObject(\n            forEntityName: RemoteDataStore.remoteDataEntity,\n            into: manager.destinationContext\n        )\n\n        newRemoteDataEntity.setValue(type, forKey: \"type\")\n        newRemoteDataEntity.setValue(timestamp, forKey: \"timestamp\")\n        newRemoteDataEntity.setValue(AirshipJSONUtils.toData(data), forKey: \"data\")\n        newRemoteDataEntity.setValue(remoteDataInfo, forKey: \"remoteDataInfo\")\n    }\n}\n\n@objc(UARemoteDataMappingV2toV4)\nclass UARemoteDataMappingV2toV4: NSEntityMigrationPolicy {\n\n    override func createDestinationInstances(\n        forSource source: NSManagedObject,\n        in mapping: NSEntityMapping,\n        manager: NSMigrationManager\n    ) throws {\n\n        // data -> JSON data\n        // metadata -> drop\n\n        guard source.entity.name == RemoteDataStore.remoteDataEntity else {\n            return\n        }\n\n        let type = source.value(forKey: \"type\") as? String\n        let timestamp = source.value(forKey: \"timestamp\") as? Date\n        let data = source.value(forKey: \"data\") as? [AnyHashable: Any]\n\n        let newRemoteDataEntity = NSEntityDescription.insertNewObject(\n            forEntityName: RemoteDataStore.remoteDataEntity,\n            into: manager.destinationContext\n        )\n\n        newRemoteDataEntity.setValue(type, forKey: \"type\")\n        newRemoteDataEntity.setValue(timestamp, forKey: \"timestamp\")\n        newRemoteDataEntity.setValue(AirshipJSONUtils.toData(data), forKey: \"data\")\n    }\n}\n\n\n@objc(UARemoteDataMappingV1toV4)\nclass UARemoteDataMappingV1toV4: NSEntityMigrationPolicy {\n\n    override func createDestinationInstances(\n        forSource source: NSManagedObject,\n        in mapping: NSEntityMapping,\n        manager: NSMigrationManager\n    ) throws {\n\n        // data -> JSON data\n\n        guard source.entity.name == RemoteDataStore.remoteDataEntity else {\n            return\n        }\n\n        let type = source.value(forKey: \"type\") as? String\n        let timestamp = source.value(forKey: \"timestamp\") as? Date\n        let data = source.value(forKey: \"data\") as? [AnyHashable: Any]\n\n        let newRemoteDataEntity = NSEntityDescription.insertNewObject(\n            forEntityName: RemoteDataStore.remoteDataEntity,\n            into: manager.destinationContext\n        )\n\n        newRemoteDataEntity.setValue(type, forKey: \"type\")\n        newRemoteDataEntity.setValue(timestamp, forKey: \"timestamp\")\n        newRemoteDataEntity.setValue(AirshipJSONUtils.toData(data), forKey: \"data\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/UNNotificationRegistrar.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Foundation\n@preconcurrency import UserNotifications\n\n/// UNNotificationCenter notification registrar\nstruct UNNotificationRegistrar: NotificationRegistrar {\n\n    #if !os(tvOS)\n    @MainActor\n    func setCategories(_ categories: Set<UNNotificationCategory>) {\n        UNUserNotificationCenter.current().setNotificationCategories(categories)\n    }\n    #endif\n\n    @MainActor\n    func checkStatus() async -> (UNAuthorizationStatus, AirshipAuthorizedNotificationSettings) {\n        let settings = await UNUserNotificationCenter.current().notificationSettings()\n        return (settings.authorizationStatus, AirshipAuthorizedNotificationSettings.from(settings: settings))\n    }\n\n    func updateRegistration(\n        options: UNAuthorizationOptions,\n        skipIfEphemeral: Bool\n    ) async -> Void {\n\n        let requestOptions = options\n        let (status, settings) = await checkStatus()\n\n        // Skip registration if no options are enable and we are requesting no options\n        if settings == [] && requestOptions == [] {\n            return\n        }\n\n#if !os(tvOS) && !os(watchOS) && !os(macOS)\n        // Skip registration for ephemeral if skipRegistrationIfEphemeral\n        if status == .ephemeral && skipIfEphemeral {\n            return\n        }\n#endif\n\n        var granted = false\n        // Request\n        do {\n            granted = try await UNUserNotificationCenter.current().requestAuthorization(options: options)\n        } catch {\n            AirshipLogger.error(\n                \"requestAuthorizationWithOptions failed with error: \\(error)\"\n            )\n        }\n        AirshipLogger.debug(\n            \"requestAuthorizationWithOptions \\(granted)\"\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/URLAllowList.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n/// Delegate protocol for accepting and rejecting URLs.\npublic protocol URLAllowListDelegate: Sendable {\n    /**\n     * Called when a URL has been allowed by the SDK, but before the URL is fetched.\n     *\n     * - Parameters:\n     *   - url: The URL allowed by the SDK.\n     *   - scope: The scope of the desired match.\n     *\n     * - Returns: `true` to accept this URL, `false`  to reject this URL.\n     */\n    func allowURL(_ url: URL, scope: URLAllowListScope) -> Bool\n}\n\n\n/// Class for accepting and verifying webview URLs.\n///\n/// URL allow list entries are written as URL patterns with optional wildcard matching:\n///\n/// ~~~\n/// <scheme> := <any char combination, '*' are treated as wildcards>\n///\n/// <host> := '*' | '*.'<any char combination except '/' and '*'> | <any char combination except '/' and '*'>\n///\n/// <path> := <any char combination, '*' are treated as wildcards>\n///\n/// <pattern> := '*' | <scheme>://<host>/<path> | <scheme>://<host> | <scheme>:/<path> | <scheme>:///<path>\n/// ~~~\n///\n/// A single wildcard will match any URI.\n/// Wildcards in the scheme pattern will match any characters, and a single wildcard in the scheme will match any scheme.\n/// The wildcard in a host pattern `\"*.mydomain.com\"` will match anything within the mydomain.com domain.\n/// Wildcards in the path pattern will match any characters, including subdirectories.\n///\n/// Note that NSURL does not support internationalized domains containing non-ASCII characters.\n/// All URL allow list entries for internationalized domains must be in ASCII IDNA format as\n/// specified in https://tools.ietf.org/html/rfc3490\npublic protocol AirshipURLAllowList: Sendable {\n\n    /// The URL allow list delegate.\n    ///\n    /// - note: The delegate is not retained.\n    @MainActor\n    var delegate: (any URLAllowListDelegate)? { get set }\n\n    /// If set, this block will be called when a URL is checked to override if it should be allowed or not.\n    /// This will be called in place of the `URLAllowListDelegate` delegate.\n    @MainActor\n    var onAllowURL: (@MainActor @Sendable (URL, URLAllowListScope) -> Bool)? { get set }\n\n    /// Determines whether a given URL is allowed, with the implicit scope `URLAllowListScope.all`.\n    ///\n    /// - Parameters:\n    ///   - url: The URL under consideration.\n    ///\n    /// - Returns: `true` if the URL is allowed, `false` otherwise.\n    @MainActor\n    func isAllowed(_ url: URL?) -> Bool\n\n    /// Determines whether a given URL is allowed.\n    ///\n    /// - Parameters:\n    ///   - url: The URL under consideration.\n    ///   - scope: The scope of the desired match.\n    ///\n    /// - Returns: `true` if the URL is allowed, `false` otherwise.\n    @MainActor\n    func isAllowed(_ url: URL?, scope: URLAllowListScope) -> Bool\n\n    /// Add an entry to the URL allow list.\n    ///\n    /// - Parameters:\n    ///   - patternString: A URL allow list pattern string.\n    ///   - scope: The scope of the pattern.\n    ///\n    /// - Returns: `true` if the URL allow list pattern was validated and added, `false` otherwise.\n    @discardableResult\n    @MainActor\n    func addEntry(_ patternString: String, scope: URLAllowListScope) -> Bool\n\n    /// Add an entry to the URL allow list, with the implicit scope `URLAllowListScope.all`.\n    ///\n    /// - Parameter patternString: A URL allow list pattern string.\n    ///\n    /// - Returns: `true` if the URL allow list pattern was validated and added, `false` otherwise.\n    @discardableResult\n    @MainActor\n    func addEntry(_ patternString: String) -> Bool\n}\n\n@MainActor\nfinal class DefaultAirshipURLAllowList: AirshipURLAllowList {\n    /// `<scheme> := <any chars (no spaces), '*' will match 0 or more characters>`\n    private static let schemeRegex: String = \"^([^\\\\s]*)$\"\n\n    /// `<host> := '*' | *.<valid host characters> | <valid host characters>`\n    private static let hostRegex: String = \"^((\\\\*)|(\\\\*\\\\.[^/\\\\*]+)|([^/\\\\*]+))$\"\n\n    /// `<path> | <scheme> := <any chars (no spaces), '*' will match 0 or more characters>`\n    private static let pathRegex: String = \"^([^\\\\s]*)$\"\n\n    /// Regular expression to escape from a pattern\n    private static let escapeRegexCharacters: [String] = [\n        \"\\\\\", \".\", \"[\", \"]\", \"{\", \"}\", \"(\", \")\", \"^\", \"$\", \"?\", \"+\", \"|\", \"*\",\n    ]\n\n    private let schemePatternValidator: NSRegularExpression = try! NSRegularExpression(\n        pattern: schemeRegex,\n        options: .useUnicodeWordBoundaries\n    )\n    private let hostPatternValidator: NSRegularExpression = try! NSRegularExpression(\n        pattern: hostRegex,\n        options: .useUnicodeWordBoundaries\n    )\n    private let pathPatternValidator: NSRegularExpression = try! NSRegularExpression(\n        pattern: pathRegex,\n        options: .useUnicodeWordBoundaries\n    )\n\n    @MainActor\n    private var entries: Set<AllowListEntry> = []\n\n    init(airshipConfig: AirshipConfig) {\n        addEntry(\"https://*.urbanairship.com\")\n        addEntry(\"https://*.asnapieu.com\")\n\n        if let initialConfigURL = airshipConfig.initialConfigURL {\n            addEntry(initialConfigURL)\n        }\n\n        // Open only\n        addEntry(\"mailto:\", scope: .openURL)\n        addEntry(\"sms:\", scope: .openURL)\n        addEntry(\"tel:\", scope: .openURL)\n\n        if (airshipConfig.urlAllowList == nil && airshipConfig.urlAllowListScopeOpenURL == nil) {\n            addEntry(\"*\", scope: .openURL)\n        }\n\n#if os(macOS)\n        // Allow-list the System Settings root and specific panes\n        addEntry(\n            \"x-apple.systempreferences:\",\n            scope: .openURL\n        )\n#elseif !os(watchOS)\n        // iOS, tvOS, visionOS\n        addEntry(\n            UIApplication.openSettingsURLString,\n            scope: .openURL\n        )\n#endif\n\n        airshipConfig.urlAllowList?.forEach {\n            addEntry($0)\n        }\n\n        airshipConfig.urlAllowListScopeOpenURL?.forEach {\n            addEntry($0, scope: .openURL)\n        }\n\n        airshipConfig.urlAllowListScopeJavaScriptInterface?.forEach {\n            addEntry($0, scope: .javaScriptInterface)\n        }\n    }\n\n    init() {\n    }\n\n\n    @MainActor\n    var delegate: (any URLAllowListDelegate)? = nil\n\n    @MainActor\n    var onAllowURL: (@MainActor @Sendable (URL, URLAllowListScope) -> Bool)? = nil\n\n    @discardableResult\n    @MainActor\n    func addEntry(_ patternString: String) -> Bool {\n        return addEntry(patternString, scope: .all)\n    }\n\n    @discardableResult\n    @MainActor\n    func addEntry(\n        _ patternString: String,\n        scope: URLAllowListScope\n    ) -> Bool {\n        if patternString.isEmpty {\n            AirshipLogger.error(\n                \"Invalid URL allow list pattern: \\(patternString)\"\n            )\n            return false\n        }\n\n        let escapedPattern = Self.escapeSchemeWildcard(patternString)\n\n        if patternString == \"*\" {\n            let entry = AllowListEntry.entryWithMatcher(\n                matcherForScheme(\"\", host: \"\", path: \"\"),\n                scope: scope,\n                pattern: patternString\n            )\n            entries.insert(entry)\n            return true\n        }\n\n        guard let url = URL(string: escapedPattern) else {\n            AirshipLogger.error(\n                \"Unable to parse URL for pattern: \\(patternString)\"\n            )\n            return false\n        }\n\n        // Scheme WILDCARD -> *\n        let scheme =\n        url.scheme?.replacingOccurrences(of: \"WILDCARD\", with: \"*\") ?? \"\"\n        if scheme.isEmpty\n            || !Self.validatePattern(\n                scheme,\n                expression: schemePatternValidator\n            )\n        {\n            AirshipLogger.error(\n                \"Invalid scheme '\\(scheme)' in URL allow list pattern: \\(patternString)\"\n            )\n            return false\n        }\n\n        let host = url.host ?? \"\"\n        if !host.isEmpty\n            && !Self.validatePattern(\n                host,\n                expression: hostPatternValidator\n            )\n        {\n            AirshipLogger.error(\n                \"Invalid host '\\(host)' in URL allow list pattern: \\(patternString)\"\n            )\n            return false\n        }\n\n        let path = Self.pathForUrl(url) ?? \"\"\n        if !path.isEmpty\n            && !Self.validatePattern(\n                path,\n                expression: pathPatternValidator\n            )\n        {\n            AirshipLogger.error(\n                \"Invalid path '\\(path)' in URL allow list pattern: \\(patternString)\"\n            )\n            return false\n        }\n\n        let entry = AllowListEntry.entryWithMatcher(\n            matcherForScheme(scheme, host: host, path: path),\n            scope: scope,\n            pattern: patternString\n        )\n        entries.insert(entry)\n\n        return true\n    }\n\n    @MainActor\n    func isAllowed(_ url: URL?) -> Bool {\n        return isAllowed(url, scope: .all)\n    }\n\n    @MainActor\n    func isAllowed(_ url: URL?, scope: URLAllowListScope) -> Bool {\n        guard let url = url else {\n            return false\n        }\n\n        var match = false\n\n        var matchedScope: URLAllowListScope = []\n\n        for entry in entries {\n            if entry.matcher(url) {\n                matchedScope.formUnion(entry.scope)\n            }\n        }\n\n        match = matchedScope.contains(scope)\n\n        // If the url is allowed, allow the delegate or block to reject the url\n        if match {\n            match = if let onAllowURL {\n                onAllowURL(url, scope)\n            } else if let delegate {\n                delegate.allowURL(url, scope: scope)\n            } else {\n                match\n            }\n        }\n\n        return match\n    }\n\n    // MARK: - Internal types / helpers\n\n    /// Escapes URL allow list pattern strings so that they don't contain unanticipated regex characters.\n    private static func escapeRegexString(\n        _ input: String,\n        escapingWildcards: Bool\n    ) -> String {\n        var input = input\n\n        // Prefix all special characters with a backslash\n        for char in escapeRegexCharacters {\n            input = input.replacingOccurrences(\n                of: char,\n                with: \"\\\\\".appending(char)\n            )\n        }\n\n        // If wildcards are intended, transform them in to the appropriate regex pattern.\n        if !escapingWildcards {\n            input = input.replacingOccurrences(of: \"\\\\*\", with: \".*\")\n        }\n\n        return input\n    }\n\n    private static func validatePattern(\n        _ pattern: String,\n        expression: NSRegularExpression\n    ) -> Bool {\n        let matches = expression.numberOfMatches(\n            in: pattern,\n            options: [],\n            range: NSMakeRange(0, pattern.count)\n        )\n\n        return matches > 0\n    }\n\n    private static func escapeSchemeWildcard(_ pattern: String) -> String {\n        var components = pattern.components(separatedBy: \":\")\n        guard components.count > 1 else {\n            return pattern\n        }\n        let schemeComponent = components.removeFirst()\n            .replacingOccurrences(\n                of: \"*\",\n                with: \"WILDCARD\"\n            )\n        var array = [schemeComponent]\n        array.append(contentsOf: components)\n\n        return array.joined(separator: \":\")\n    }\n\n    private static func compilePattern(_ pattern: String)\n    -> NSRegularExpression?\n    {\n        var pattern = pattern\n        if !pattern.hasPrefix(\"^\") {\n            pattern = \"^\".appending(pattern)\n        }\n        if !pattern.hasSuffix(\"$\") {\n            pattern = pattern.appending(\"$\")\n        }\n\n        return try? NSRegularExpression(pattern: pattern, options: [])\n    }\n\n    private static func pathForUrl(_ url: URL) -> String? {\n        // URL path using CoreFoundation, which preserves trailing slashes\n        guard let path = CFURLCopyPath(url as CFURL) as String? else {\n            // If the path is nil then it's nonstandard, use the resource specifier as path\n            return (url as NSURL).resourceSpecifier\n        }\n\n        return path\n    }\n\n    private func matcherForScheme(_ scheme: String, host: String, path: String)\n    -> AllowListMatcher\n    {\n        let schemeRegex: NSRegularExpression?\n        if scheme.isEmpty || scheme == \"*\" {\n            schemeRegex = nil\n        } else {\n            schemeRegex = Self.compilePattern(\n                Self.escapeRegexString(scheme, escapingWildcards: false)\n            )\n        }\n\n        let hostRegex: NSRegularExpression?\n        if host.isEmpty || host == \"*\" {\n            hostRegex = nil\n        } else if host.hasPrefix(\"*.\") {\n            let substring = host[host.index(host.startIndex, offsetBy: 2)...]\n            hostRegex = Self.compilePattern(\n                \"(.*\\\\.)?\"\n                    .appending(\n                        Self.escapeRegexString(\n                            String(substring),\n                            escapingWildcards: true\n                        )\n                    )\n            )\n        } else {\n            hostRegex = Self.compilePattern(\n                \"(.*\\\\.)?\"\n                    .appending(\n                        Self.escapeRegexString(\n                            host,\n                            escapingWildcards: true\n                        )\n                    )\n            )\n        }\n\n        let pathRegex: NSRegularExpression?\n        if path.isEmpty || path == \"/*\" || path == \"*\" {\n            pathRegex = nil\n        } else {\n            pathRegex = Self.compilePattern(\n                Self.escapeRegexString(path, escapingWildcards: false)\n            )\n        }\n\n        return { @MainActor (url: URL) -> Bool in\n            let scheme = url.scheme ?? \"\"\n            if let expression = schemeRegex,\n               scheme.isEmpty\n                || !Self.validatePattern(\n                    scheme,\n                    expression: expression\n                )\n            {\n                return false\n            }\n\n            let host = url.host ?? \"\"\n            if let expression = hostRegex,\n               host.isEmpty\n                || !Self.validatePattern(\n                    host,\n                    expression: expression\n                )\n            {\n                return false\n            }\n\n            let path = Self.pathForUrl(url) ?? \"\"\n            if let expression = pathRegex,\n               path.isEmpty\n                || !Self.validatePattern(\n                    path,\n                    expression: expression\n                )\n            {\n                return false\n            }\n\n            return true\n        }\n    }\n\n    /// Block mapping URLs to allow list status.\n    private typealias AllowListMatcher = @MainActor @Sendable (URL) -> Bool\n\n    private struct AllowListEntry: Hashable, Sendable {\n        let matcher: AllowListMatcher\n        let scope: URLAllowListScope\n\n        // Pattern is only used for hashing\n        private let pattern: String\n\n        static func entryWithMatcher(\n            _ matcher: @escaping AllowListMatcher,\n            scope: URLAllowListScope,\n            pattern: String\n        ) -> AllowListEntry {\n            return AllowListEntry(\n                matcher: matcher,\n                scope: scope,\n                pattern: pattern\n            )\n        }\n\n        static func == (\n            lhs: DefaultAirshipURLAllowList.AllowListEntry,\n            rhs: DefaultAirshipURLAllowList.AllowListEntry\n        ) -> Bool {\n            lhs.scope == rhs.scope && lhs.pattern == rhs.pattern\n        }\n\n        func hash(into hasher: inout Hasher) {\n            hasher.combine(scope.rawValue)\n            hasher.combine(pattern)\n        }\n    }\n}\n\n// Scope options for URL allow list matching\npublic struct URLAllowListScope: OptionSet, Sendable, Equatable {\n    public let rawValue: Int\n\n    // Scope that is checked before loading the JavaScript Native Bridge into\n    // a WebView when using the `NativeBridge`\n    public static let javaScriptInterface: URLAllowListScope = URLAllowListScope(rawValue: 1 << 0)\n\n    // Scope that is checked before opening a URL.\n    public static let openURL: URLAllowListScope = URLAllowListScope(rawValue: 1 << 1)\n\n    // All scopes\n    public static let all: URLAllowListScope = [.javaScriptInterface, .openURL]\n\n    public init(rawValue: Int) {\n        self.rawValue = rawValue\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/UrlInfo.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Url Info\n/// - Note: for internal use only.  :nodoc:\npublic enum URLInfo: Sendable, Equatable {\n    case web(url: String, requireNetwork: Bool = true)\n    case video(url: String, requireNetwork: Bool = true)\n    case image(url: String, prefetch: Bool = true)\n}\n\nextension AirshipLayout {\n    public var urlInfos: [URLInfo] {\n        let urls: [[URLInfo]?] = extract { info in\n            switch info {\n            case .media(let info):\n                return switch info.properties.mediaType {\n                case .image:\n                    [.image(url: info.properties.url)]\n                case .youtube:\n                    [.video(url: info.properties.url)]\n                case .vimeo:\n                    [.video(url: info.properties.url)]\n                case .video:\n                    [.video(url: info.properties.url)]\n                }\n            #if !os(tvOS) && !os(watchOS)\n            case .webView(let info):\n                return [.web(url: info.properties.url)]\n            #endif\n            case .imageButton(let info):\n                return switch info.properties.image {\n                case .url(let imageModel):\n                    [.image(url: imageModel.url)]\n                case .icon:\n                    nil\n                }\n            case .stackImageButton(let info):\n                var images: [URLInfo] = []\n                for item in info.properties.items {\n                    switch item {\n                    case .imageURL(let info):\n                        images.append(.image(url: info.url))\n                    case .icon, .shape:\n                        break\n                    }\n                }\n\n                if let overrides = info.overrides?.items {\n                    for override in overrides {\n                        guard let item = override.value else { continue }\n                        for value in item {\n                            switch value {\n                            case .imageURL(let info):\n                                images.append(.image(url: info.url))\n                            case .icon, .shape:\n                                break\n                            }\n                        }\n                    }\n                }\n\n                return images\n\n            default: return nil\n            }\n        }\n\n        return urls.compactMap { $0 }.reduce(into: []) { result, urlArray in\n              result.append(contentsOf: urlArray)\n          }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ValidatableHelper.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n@MainActor\nfinal class ValidatableHelper : ObservableObject {\n\n    enum ValidationAction: Equatable, Hashable {\n        case edit\n        case error\n        case valid\n        case none\n    }\n\n    private var subscriptionState: [String: State] = [:]\n\n    private final class State {\n        var lastValue: (any Equatable)?\n        var isInitialValue: Bool\n        var subscription: AnyCancellable?\n        var lastAction: ValidationAction?\n\n        init(isInitialValue: Bool, lastValue: (any Equatable)? = nil, subscription: AnyCancellable? = nil) {\n            self.isInitialValue = isInitialValue\n            self.lastValue = lastValue\n            self.subscription = subscription\n        }\n    }\n\n    func subscribe<T: Equatable>(\n        forIdentifier identifier: String,\n        formState: ThomasFormState,\n        initialValue: T?,\n        valueUpdates: Published<T>.Publisher,\n        validatables: ThomasValidationInfo,\n        onStateActions: @escaping @MainActor ([ThomasStateAction]) -> Void\n    ) {\n        let state: State = subscriptionState[identifier] ?? State(\n            isInitialValue: true,\n            lastValue: initialValue\n        )\n\n        subscriptionState[identifier] = state\n        state.subscription?.cancel()\n\n        state.subscription = Publishers.CombineLatest(\n            formState.$status,\n            valueUpdates\n        )\n        .receive(on: RunLoop.main)\n        .map { (status, value) -> ValidationAction in\n            let fieldStatus = formState.lastFieldStatus(\n                identifier: identifier\n            )\n\n            var didEdit = false\n            if value != state.lastValue as? Published<T>.Publisher.Output {\n                state.lastValue = value\n                didEdit = true\n                state.isInitialValue = false\n            }\n\n            guard let fieldStatus else {\n                return didEdit ? .edit : .none\n            }\n\n            switch (status) {\n            case .valid, .error, .invalid:\n                switch(fieldStatus) {\n                case .valid: return .valid\n                case .invalid:\n                    return if !state.isInitialValue || formState.validationMode == .onDemand {\n                        .error\n                    } else {\n                        .none\n                    }\n                case .pending: return .edit\n                case .error: return .none\n                }\n\n            default: return didEdit ? .edit : .none\n            }\n        }\n        .filter {\n            $0 != state.lastAction\n        }\n        .sink { action in\n            state.lastAction = action\n\n            let actions: [ThomasStateAction]? = switch(action) {\n            case .edit:\n                validatables.onEdit?.stateActions\n            case .error:\n                validatables.onError?.stateActions\n            case .valid:\n                validatables.onValid?.stateActions\n            case .none:\n                nil\n            }\n\n            guard let actions else { return }\n            onStateActions(actions)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/VideoController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Controller view for managing video playback state.\n@MainActor\nstruct VideoController: View {\n    let info: ThomasViewInfo.VideoController\n    let constraints: ViewConstraints\n\n    @EnvironmentObject var environment: ThomasEnvironment\n    @EnvironmentObject var state: ThomasState\n    @EnvironmentObject var parentVideoState: VideoState\n\n    init(\n        info: ThomasViewInfo.VideoController,\n        constraints: ViewConstraints\n    ) {\n        self.info = info\n        self.constraints = constraints\n    }\n\n    var body: some View {\n        Content(\n            info: self.info,\n            constraints: constraints,\n            environment: environment,\n            parentState: state,\n            parentVideoState: parentVideoState\n        )\n    }\n\n    @MainActor\n    struct Content: View {\n        let info: ThomasViewInfo.VideoController\n        let constraints: ViewConstraints\n\n        @Environment(\\.layoutState) var layoutState\n\n        @StateObject var videoState: VideoState\n        @StateObject var state: ThomasState\n\n        init(\n            info: ThomasViewInfo.VideoController,\n            constraints: ViewConstraints,\n            environment: ThomasEnvironment,\n            parentState: ThomasState,\n            parentVideoState: VideoState\n        ) {\n            self.info = info\n            self.constraints = constraints\n\n            let videoGroups = parentVideoState.videoGroups.copy()\n\n            let muteGroup = if let id = info.properties.muteGroup?.identifier {\n                videoGroups.muteGroup(for: id)\n            } else {\n                VideoGroupState()\n            }\n\n            let playGroup = if let id = info.properties.playGroup?.identifier {\n                videoGroups.playGroup(for: id)\n            } else {\n                VideoGroupState()\n            }\n\n            let videoState = VideoState(\n                identifier: info.properties.identifier,\n                videoScope: info.properties.videoScope,\n                videoGroups: videoGroups,\n                muteGroup: muteGroup,\n                playGroup: playGroup\n            )\n\n            self._videoState = StateObject(wrappedValue: videoState)\n\n            self._state = StateObject(\n                wrappedValue: parentState.with(videoState: videoState)\n            )\n        }\n\n        var body: some View {\n            ViewFactory.createView(self.info.properties.view, constraints: constraints)\n                .constraints(constraints)\n                .thomasCommon(self.info)\n                .environmentObject(self.videoState)\n                .environmentObject(self.state)\n                .accessibilityElement(children: .contain)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/VideoGroupState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n@MainActor\nclass VideoGroupState: ObservableObject {\n    @Published var isMuted: Bool = false\n    @Published var isPlaying: Bool = false\n\n    private(set) var isMutedInitialized: Bool = false\n    private(set) var isPlayingInitialized: Bool = false\n\n    func initializeMuted(_ muted: Bool) {\n        guard !isMutedInitialized else { return }\n        isMutedInitialized = true\n        isMuted = muted\n    }\n\n    func initializePlaying(_ playing: Bool) {\n        guard !isPlayingInitialized else { return }\n        isPlayingInitialized = true\n        isPlaying = playing\n    }\n}\n\n@MainActor\nclass VideoGroups {\n    private var muteGroups: [String: VideoGroupState]\n    private var playGroups: [String: VideoGroupState]\n\n    init(\n        muteGroups: [String: VideoGroupState] = [:],\n        playGroups: [String: VideoGroupState] = [:]\n    ) {\n        self.muteGroups = muteGroups\n        self.playGroups = playGroups\n    }\n\n    func muteGroup(for id: String) -> VideoGroupState {\n        if let existing = muteGroups[id] { return existing }\n        let group = VideoGroupState()\n        muteGroups[id] = group\n        return group\n    }\n\n    func playGroup(for id: String) -> VideoGroupState {\n        if let existing = playGroups[id] { return existing }\n        let group = VideoGroupState()\n        playGroups[id] = group\n        return group\n    }\n\n    func copy() -> VideoGroups {\n        VideoGroups(muteGroups: muteGroups, playGroups: playGroups)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/VideoMediaNativeView.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(watchOS) && !os(macOS)\n\nimport SwiftUI\nimport AVFoundation\nimport AVKit\nimport Combine\n\n\n@MainActor\nprivate class VideoControlsObserver: ObservableObject {\n    var timeObserver: Any?\n    var endTimeObserver: (any NSObjectProtocol)?\n    var statusObserver: NSKeyValueObservation?\n    weak var player: AVPlayer?\n    var isCancelled: Bool = false\n\n    /// Called by wrapper\n    internal func cleanup() {\n        if let timeObserver = timeObserver, let player = player {\n            player.removeTimeObserver(timeObserver)\n        }\n        timeObserver = nil\n\n        if let observer = endTimeObserver {\n            NotificationCenter.default.removeObserver(observer)\n            endTimeObserver = nil\n        }\n        statusObserver?.invalidate()\n        statusObserver = nil\n        player = nil\n    }\n}\n\n\n@MainActor\nstruct VideoMediaNativeView: View {\n    let info: ThomasViewInfo.Media\n    let videoIdentifier: String?\n    let constraints: ViewConstraints\n    let videoAspectRatio: CGFloat\n    let onMediaReady: @MainActor () -> Void\n\n    @State private var hasError: Bool = false\n    @State private var player: AVPlayer?\n    @State private var isPlaying: Bool = false\n    @State private var currentTime: Double = 0\n    @State private var duration: Double = 1.0\n    @State private var isControlsVisible: Bool = true\n    @State private var controlsTimer: Timer?\n\n    @EnvironmentObject var videoState: VideoState\n    @Environment(\\.isVisible) var isVisible\n\n    private var showControls: Bool {\n        info.properties.video?.showControls ?? true\n    }\n\n    private var shouldLoop: Bool {\n        info.properties.video?.loop ?? false\n    }\n\n    var body: some View {\n        NativeVideoPlayer(\n            info: info,\n            videoIdentifier: videoIdentifier,\n            onMediaReady: onMediaReady,\n            hasError: $hasError,\n            player: $player\n        )\n        .airshipApplyIf(self.constraints.width == nil || self.constraints.height == nil) {\n            $0.aspectRatio(videoAspectRatio, contentMode: ContentMode.fill)\n        }\n        .fitVideo(\n            mediaFit: self.info.properties.mediaFit,\n            cropPosition: self.info.properties.cropPosition,\n            constraints: constraints,\n            videoAspectRatio: videoAspectRatio\n        )\n        .modifier(\n            VideoControls(\n                hasError: hasError,\n                showControls: showControls,\n                shouldLoop: shouldLoop,\n                player: player,\n                isPlaying: $isPlaying,\n                currentTime: $currentTime,\n                duration: $duration,\n                isControlsVisible: $isControlsVisible,\n                controlsTimer: $controlsTimer\n            )\n        )\n    }\n}\n\ninternal struct VideoControls: ViewModifier {\n    let hasError: Bool\n    let showControls: Bool\n    let shouldLoop: Bool\n    let player: AVPlayer?\n    @Binding\n    var isPlaying: Bool\n    @Binding\n    var currentTime: Double\n    @Binding\n    var duration: Double\n    @Binding\n    var isControlsVisible: Bool\n    @Binding\n    var controlsTimer: Timer?\n\n    @StateObject\n    private var observer = VideoControlsObserver()\n    @State\n    private var isDraggingSlider: Bool = false\n\n    func body(content: Content) -> some View {\n        content\n            .overlay(\n                GeometryReader { geometry in\n                    ZStack {\n                        if hasError {\n                            VideoErrorView()\n                                .frame(width: geometry.size.width, height: geometry.size.height)\n                        } else if showControls && isControlsVisible && player?.currentItem?.status == .readyToPlay {\n                            VideoControlsView(\n                                player: player,\n                                isPlaying: $isPlaying,\n                                currentTime: $currentTime,\n                                duration: $duration,\n                                isDraggingSlider: $isDraggingSlider,\n                                size: geometry.size,\n                                onInteraction: resetHideTimer\n                            )\n                            .frame(width: geometry.size.width, height: geometry.size.height)\n                            .transition(.opacity)\n                        }\n                    }\n                }\n                    .allowsHitTesting(hasError || (showControls && isControlsVisible))\n            )\n            .onTapGesture {\n                if showControls && !hasError {\n                    toggleControlsVisibility()\n                }\n            }\n            .onAppear {\n                setupPlayerObservers()\n                if showControls {\n                    startHideTimer()\n                }\n            }\n            .onDisappear {\n                cleanup()\n                /// Do final observer cleanup\n                observer.cleanup()\n            }\n            .airshipOnChangeOf(player) { _ in\n                cleanup()\n                setupPlayerObservers()\n                if showControls {\n                    startHideTimer()\n                }\n            }\n    }\n\n    private func toggleControlsVisibility() {\n        withAnimation(.easeInOut(duration: 0.3)) {\n            isControlsVisible.toggle()\n        }\n\n        if isControlsVisible {\n            startHideTimer()\n        } else {\n            controlsTimer?.invalidate()\n        }\n    }\n\n    private func resetHideTimer() {\n        if isControlsVisible {\n            startHideTimer()\n        }\n    }\n\n    private func startHideTimer() {\n        controlsTimer?.invalidate()\n\n        let visibilityBinding = _isControlsVisible\n        let observer = observer\n        controlsTimer = Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in\n            Task { @MainActor in\n                guard !observer.isCancelled else { return }\n                withAnimation(.easeInOut(duration: 0.3)) {\n                    visibilityBinding.wrappedValue = false\n                }\n            }\n        }\n    }\n\n    private func setupPlayerObservers() {\n        guard let player = player else { return }\n\n        observer.cleanup()\n        observer.isCancelled = false\n        observer.player = player\n\n        let isPlayingBinding = _isPlaying\n        let currentTimeBinding = _currentTime\n        let durationBinding = _duration\n        let isDraggingBinding = _isDraggingSlider\n\n        if !shouldLoop {\n            observer.endTimeObserver = NotificationCenter.default.addObserver(\n                forName: .AVPlayerItemDidPlayToEndTime,\n                object: player.currentItem,\n                queue: .main\n            ) { _ in\n                Task { @MainActor in\n                    isPlayingBinding.wrappedValue = false\n                    player.seek(to: .zero)\n                }\n            }\n        }\n        let interval = CMTime(seconds: 0.1, preferredTimescale: CMTimeScale(NSEC_PER_SEC))\n        observer.timeObserver = player.addPeriodicTimeObserver(forInterval: interval, queue: .main\n        ) { time in\n            if !isDraggingBinding.wrappedValue {\n                currentTimeBinding.wrappedValue = time.seconds\n            }\n\n            isPlayingBinding.wrappedValue = player.rate > 0\n\n            if let currentItem = player.currentItem {\n                let duration = currentItem.duration\n                if duration.isNumeric && !duration.isIndefinite {\n                    durationBinding.wrappedValue = duration.seconds\n                }\n            }\n        }\n        \n        isPlaying = player.rate > 0\n        if let currentItem = player.currentItem {\n            let duration = currentItem.duration\n            if duration.isNumeric && !duration.isIndefinite {\n                self.duration = duration.seconds\n            }\n        }\n\n        if let currentItem = player.currentItem {\n            let currentTime = currentItem.currentTime()\n            if currentTime.isNumeric {\n                self.currentTime = currentTime.seconds\n            }\n        }\n        observer.statusObserver = player.currentItem?.observe(\\.status, options: [.new, .initial]) { item, _ in\n            Task { @MainActor in\n                if item.status == .readyToPlay {\n                    let duration = item.duration\n                    if duration.isNumeric && !duration.isIndefinite {\n                        durationBinding.wrappedValue = duration.seconds\n                    }\n\n                    let currentTime = item.currentTime()\n                    if currentTime.isNumeric {\n                        currentTimeBinding.wrappedValue = currentTime.seconds\n                    }\n\n                    isPlayingBinding.wrappedValue = player.rate > 0\n                }\n            }\n        }\n    }\n\n    private func cleanup() {\n        controlsTimer?.invalidate()\n        controlsTimer = nil\n        observer.isCancelled = true\n        observer.cleanup()\n    }\n\n}\n\nprivate struct VideoErrorView: View {\n    var body: some View {\n        Color.black\n            .overlay(\n                Image(systemName: \"play.slash.fill\")\n                    .font(.system(size: 60))\n                    .foregroundColor(.white.opacity(0.5))\n            )\n    }\n}\n\nprivate struct VideoControlsView: View {\n    let player: AVPlayer?\n    @Binding\n    var isPlaying: Bool\n    @Binding\n    var currentTime: Double\n    @Binding\n    var duration: Double\n    @Binding\n    var isDraggingSlider: Bool\n    let size: CGSize\n    let onInteraction: () -> Void\n\n    @State\n    private var isMuted: Bool = false\n    @State\n    private var wasPlayingBeforeDrag: Bool = false\n\n    private var scaleFactor: CGFloat {\n        let baseControlsWidth: CGFloat = 320\n        let baseControlsHeight: CGFloat = 180\n\n        let widthScale = size.width / baseControlsWidth\n        let heightScale = size.height / baseControlsHeight\n\n        let scale = min(widthScale, heightScale, 1.0)\n\n        return max(scale, 0.2)\n    }\n\n    private var centerControlSize: CGFloat {\n        50 * scaleFactor\n    }\n\n    private var skipButtonSize: CGFloat {\n        30 * scaleFactor\n    }\n\n    private var controlSpacing: CGFloat {\n        60 * scaleFactor\n    }\n\n    private var controlPadding: CGFloat {\n        40 * scaleFactor\n    }\n\n    private var fontSize: CGFloat {\n        14 * scaleFactor\n    }\n\n    private var iconSize: CGFloat {\n        18 * scaleFactor\n    }\n\n    private var bottomBarPadding: CGFloat {\n        12 * scaleFactor\n    }\n\n    private var progressBarSpacing: CGFloat {\n        10 * scaleFactor\n    }\n\n    private var cornerRadius: CGFloat {\n        20 * scaleFactor\n    }\n\n    var body: some View {\n        ZStack {\n            Color.clear\n                .contentShape(Rectangle())\n\n            VStack(spacing: 0) {\n                Spacer()\n\n                HStack(spacing: controlSpacing) {\n                    Button(action: {\n                        skipBackward()\n                        onInteraction()\n                    }) {\n                        Image(systemName: \"gobackward.15\")\n                            .font(.system(size: skipButtonSize))\n                            .foregroundColor(.white)\n                    }\n\n                    Button(action: {\n                        togglePlayPause()\n                        onInteraction()\n                    }) {\n                        Image(systemName: isPlaying ? \"pause.fill\" : \"play.fill\")\n                            .font(.system(size: centerControlSize))\n                            .foregroundColor(.white)\n                    }\n\n                    Button(action: {\n                        skipForward()\n                        onInteraction()\n                    }) {\n                        Image(systemName: \"goforward.15\")\n                            .font(.system(size: skipButtonSize))\n                            .foregroundColor(.white)\n                    }\n                }\n                .padding(controlPadding)\n                .background(Color.black.opacity(0.3))\n                .clipShape(RoundedRectangle(cornerRadius: cornerRadius))\n\n                Spacer()\n                VStack(spacing: progressBarSpacing) {\n                    GeometryReader { geometry in\n                        ZStack(alignment: .leading) {\n                            RoundedRectangle(cornerRadius: 2 * scaleFactor)\n                                .fill(Color.white.opacity(0.3))\n                                .frame(height: 4 * scaleFactor)\n                                .frame(maxHeight: .infinity)\n\n                            RoundedRectangle(cornerRadius: 2 * scaleFactor)\n                                .fill(Color.white)\n                                .frame(width: max(0, (duration > 0 ? CGFloat(currentTime / duration) : 0) * geometry.size.width), height: 4 * scaleFactor)\n                                .frame(maxHeight: .infinity)\n\n                            Circle()\n                                .fill(Color.white)\n                                .frame(width: 12 * scaleFactor, height: 12 * scaleFactor)\n                                .position(\n                                    x: max(6 * scaleFactor, min(geometry.size.width - 6 * scaleFactor, (duration > 0 ? CGFloat(currentTime / duration) : 0) * geometry.size.width)),\n                                    y: geometry.size.height / 2\n                                )\n                        }\n                        .contentShape(Rectangle())\n                        #if os(tvOS)\n                        .focusable()\n                        .onMoveCommand { direction in\n                            let stepSize: Double = 5.0 // 5 second steps\n                            switch direction {\n                            case .left:\n                                let newTime = max(0, currentTime - stepSize)\n                                currentTime = newTime\n                                seek(to: newTime)\n                                onInteraction()\n                            case .right:\n                                let newTime = min(duration, currentTime + stepSize)\n                                currentTime = newTime\n                                seek(to: newTime)\n                                onInteraction()\n                            default:\n                                break\n                            }\n                        }\n                        #else\n                        .gesture(\n                            DragGesture()\n                                .onChanged { value in\n                                    if !isDraggingSlider {\n                                        wasPlayingBeforeDrag = isPlaying\n                                    }\n                                    isDraggingSlider = true\n                                    let progress = max(0, min(1, value.location.x / geometry.size.width))\n                                    currentTime = progress * duration\n                                    player?.pause()\n                                    onInteraction()\n                                }\n                                .onEnded { _ in\n                                    isDraggingSlider = false\n                                    seek(to: currentTime)\n                                    if wasPlayingBeforeDrag {\n                                        player?.play()\n                                    }\n                                    onInteraction()\n                                }\n                        )\n                        .gesture(\n                            DragGesture(minimumDistance: 0)\n                                .onEnded { value in\n                                    let progress = max(0, min(1, value.location.x / geometry.size.width))\n                                    currentTime = progress * duration\n                                    seek(to: currentTime)\n                                    onInteraction()\n                                }\n                        )\n                        #endif\n                    }\n                    .frame(height: max(20 * scaleFactor, 20))\n                    HStack {\n                        Text(formatTime(currentTime))\n                            .font(.system(size: fontSize))\n                            .foregroundColor(.white)\n                            .monospacedDigit()\n                            .lineLimit(1)\n\n                        Spacer()\n\n                        Text(\"-\\(formatTime(max(0, duration - currentTime)))\")\n                            .font(.system(size: fontSize))\n                            .foregroundColor(.white)\n                            .monospacedDigit()\n                            .lineLimit(1)\n\n                        Button(action: {\n                            toggleMute()\n                            onInteraction()\n                        }) {\n                            Image(systemName: isMuted ? \"speaker.slash.fill\" : \"speaker.wave.2.fill\")\n                                .font(.system(size: iconSize))\n                                .foregroundColor(.white)\n                                .frame(width: iconSize * 1.5, height: iconSize * 1.5)\n                        }\n                    }\n                }\n                .padding(bottomBarPadding)\n                .background(\n                    LinearGradient(\n                        gradient: Gradient(colors: [Color.black.opacity(0), Color.black.opacity(0.8)]),\n                        startPoint: .top,\n                        endPoint: .bottom\n                    )\n                )\n            }\n        }\n        .onAppear {\n            if let player = player {\n                isMuted = player.isMuted\n            }\n        }\n    }\n\n    private func togglePlayPause() {\n        guard let player = player else { return }\n\n        if isPlaying {\n            player.pause()\n        } else {\n            player.play()\n        }\n\n        isPlaying = player.rate > 0\n    }\n\n    private func seek(to time: Double) {\n        guard let player = player else { return }\n\n        let targetTime = CMTime(seconds: time, preferredTimescale: CMTimeScale(NSEC_PER_SEC))\n        player.seek(to: targetTime, toleranceBefore: .zero, toleranceAfter: .zero)\n    }\n\n    private func skipBackward() {\n        let newTime = max(currentTime - 15, 0)\n        seek(to: newTime)\n    }\n\n    private func skipForward() {\n        let newTime = min(currentTime + 15, duration)\n        seek(to: newTime)\n    }\n\n    private func toggleMute() {\n        guard let player = player else { return }\n        player.isMuted.toggle()\n        isMuted = player.isMuted\n    }\n\n    private static let hourFormatter: DateComponentsFormatter = {\n        let f = DateComponentsFormatter()\n        f.unitsStyle = .positional\n        f.zeroFormattingBehavior = .pad\n        f.allowedUnits = [.hour, .minute, .second]\n        return f\n    }()\n\n    private static let minuteFormatter: DateComponentsFormatter = {\n        let f = DateComponentsFormatter()\n        f.unitsStyle = .positional\n        f.zeroFormattingBehavior = .pad\n        f.allowedUnits = [.minute, .second]\n        return f\n    }()\n\n    private func formatTime(_ interval: TimeInterval, locale: Locale = Locale.current) -> String {\n        if interval.isNaN || interval.isInfinite {\n            return \"--:--\"\n        }\n\n        let effectiveInterval = max(0, interval)\n\n        let showsHours = effectiveInterval >= 3600.0\n        let formatter = showsHours ? Self.hourFormatter : Self.minuteFormatter\n\n        var calendar = Calendar.current\n        calendar.locale = locale\n        formatter.calendar = calendar\n\n        return formatter.string(from: effectiveInterval) ?? (showsHours ? \"00:00:00\" : \"00:00\")\n    }\n\n}\n\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/VideoMediaWebView.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Foundation\nimport SwiftUI\nimport WebKit\n\n\n@MainActor\nprivate class WeakScriptMessageHandler: NSObject, WKScriptMessageHandler {\n    weak var delegate: (any WKScriptMessageHandler)?\n\n    init(_ delegate: any WKScriptMessageHandler) {\n        self.delegate = delegate\n        super.init()\n    }\n\n    func userContentController(\n        _ userContentController: WKUserContentController,\n        didReceive message: WKScriptMessage\n    ) {\n        delegate?.userContentController(userContentController, didReceive: message)\n    }\n}\n\nstruct VideoMediaWebView: AirshipNativeViewRepresentable {\n\n#if os(macOS)\n    typealias NSViewType = WKWebView\n    func makeNSView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateNSView(_ nsView: WKWebView, context: Context) {\n        updateView(nsView, context: context)\n    }\n\n    static func dismantleNSView(_ nsView: WKWebView, coordinator: Coordinator) {\n        coordinator.teardown()\n    }\n#else\n    typealias UIViewType = WKWebView\n    func makeUIView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateUIView(_ uiView: WKWebView, context: Context) {\n        updateView(uiView, context: context)\n    }\n\n    static func dismantleUIView(_ uiView: WKWebView, coordinator: Coordinator) {\n        coordinator.teardown()\n    }\n#endif\n\n    let info: ThomasViewInfo.Media\n    let videoIdentifier: String?\n    let onMediaReady: @MainActor () -> Void\n    @Environment(\\.isVisible) var isVisible\n    @State private var isLoaded: Bool = false\n    @EnvironmentObject var pagerState: PagerState\n    @EnvironmentObject var videoState: VideoState\n    @EnvironmentObject var thomasEnvironment: ThomasEnvironment\n    @Environment(\\.layoutDirection) var layoutDirection\n\n    private var url: String {\n        self.info.properties.url\n    }\n\n    private var styleForVideo: String {\n        switch(self.info.properties.mediaFit) {\n        case .centerInside:\n            return \"object-fit: contain\"\n        case .center:\n            return \"object-fit: none\"\n        case .fitCrop:\n            guard let position = self.info.properties.cropPosition else {\n                return \"object-fit: cover\"\n            }\n\n            let horizontal = switch(position.horizontal) {\n            case .center:\n                \"center\"\n            case .start:\n                if layoutDirection == .leftToRight {\n                    \"left\"\n                } else {\n                    \"right\"\n                }\n            case .end:\n                if layoutDirection == .leftToRight {\n                    \"right\"\n                } else {\n                    \"left\"\n                }\n            }\n\n            let vertical = switch(position.vertical) {\n            case .center:\n                \"center\"\n            case .top:\n                \"top\"\n            case .bottom:\n                \"bottom\"\n            }\n\n            return \"width: 100vw; height: 100vh; object-fit: cover; object-position: \\(horizontal) \\(vertical)\"\n        case .centerCrop:\n            return \"object-fit: cover\"\n        }\n    }\n\n    private var baseURL: URL? {\n        let bundleIdentifier = Bundle.main.bundleIdentifier ?? \"com.airship.sdk\"\n        return URL(string: \"https://\\(bundleIdentifier)\")\n    }\n\n    @MainActor\n    func makeWebView(context: Context) -> WKWebView {\n        let contentController = WKUserContentController()\n        contentController.add(WeakScriptMessageHandler(context.coordinator), name: \"callback\")\n\n        let config = WKWebViewConfiguration()\n        config.userContentController = contentController\n\n#if os(macOS)\n        let webView = WKWebView(frame: .zero, configuration: config)\n        webView.setAccessibilityElement(true)\n        webView.setAccessibilityLabel(self.info.accessible.contentDescription)\n        webView.layer?.backgroundColor = .clear\n        webView.setValue(false, forKey: \"drawsBackground\") // For transparency\n#else\n        config.allowsInlineMediaPlayback = true\n        config.mediaTypesRequiringUserActionForPlayback = []\n        config.allowsPictureInPictureMediaPlayback = true\n        let webView = WKWebView(frame: .zero, configuration: config)\n        webView.isAccessibilityElement = true\n        webView.accessibilityLabel = self.info.accessible.contentDescription\n        webView.scrollView.isScrollEnabled = false\n        webView.isOpaque = false\n        webView.backgroundColor = .clear\n        webView.scrollView.backgroundColor = .clear\n        webView.scrollView.contentInsetAdjustmentBehavior = .never\n#endif\n\n        webView.navigationDelegate = context.coordinator\n        context.coordinator.configure(webView: webView)\n\n        if #available(iOS 16.4, *) {\n            webView.isInspectable = Airship.isFlying && Airship.config.airshipConfig.isWebViewInspectionEnabled\n        }\n\n        loadMediaContent(webView: webView)\n\n        return webView\n    }\n\n    @MainActor\n    private func updateView(_ view: WKWebView, context: Context) {\n        let isVisible = isVisible\n        let isLoaded = isLoaded\n        let inProgress = pagerState.inProgress\n        let isDismissed = thomasEnvironment.isDismissed\n        Task { @MainActor [weak coordinator = context.coordinator] in\n            coordinator?.update(\n                isVisible: isVisible,\n                isLoaded: isLoaded,\n                inProgress: inProgress,\n                isDismissed: isDismissed\n            )\n        }\n    }\n\n    @MainActor\n    private func loadMediaContent(webView: WKWebView) {\n        let video = self.info.properties.video\n\n        switch(info.properties.mediaType) {\n        case .image:\n            return\n        case .video:\n            let html = String(\n                format: \"\"\"\n                        <body style=\"margin:0; background-color:transparent;\">\n                            <video id=\"video\" playsinline %@ %@ %@ %@ height=\"100%%\" width=\"100%%\" src=\"%@\" style=\"%@\"></video>\n                        \n                            <script>\n                                let videoElement = document.getElementById(\"video\");\n                                            \n                                videoElement.addEventListener(\"canplay\", (event) => {\n                                    webkit.messageHandlers.callback.postMessage('mediaReady');\n                                    webkit.messageHandlers.callback.postMessage(videoElement.muted ? 'muted' : 'unmuted');\n                                });\n                                videoElement.addEventListener(\"play\",  () => { webkit.messageHandlers.callback.postMessage('playing'); });\n                                videoElement.addEventListener(\"pause\", () => { webkit.messageHandlers.callback.postMessage('paused'); });\n                                videoElement.addEventListener(\"ended\", () => { webkit.messageHandlers.callback.postMessage('ended'); });\n                            </script>\n                        </body>\n                        \"\"\",\n                video?.showControls ?? true ? \"controls\" : \"\",\n                video?.autoplay ?? false ? \"autoplay\" : \"\",\n                video?.muted ?? false ? \"muted\" : \"\",\n                video?.loop ?? false ? \"loop\" : \"\",\n                url,\n                styleForVideo\n            )\n            webView.loadHTMLString(html, baseURL: baseURL)\n        case .youtube:\n            if let videoID = Self.retrieveYoutubeVideoID(url: url) {\n                let html = String(\n                    format: \"\"\"\n                        <head>\n                            <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no\">\n                            <style>\n                                body { margin:0; background-color:transparent; overflow:hidden; }\n                                #player { width:100vw; height:100vh; }\n                            </style>\n                        </head>\n                        <body>\n                            <div id=\"player\"></div>\n                        \n                            <script>\n                              var tag = document.createElement('script');\n                        \n                              tag.src = \"https://www.youtube.com/iframe_api\";\n                              var firstScriptTag = document.getElementsByTagName('script')[0];\n                              firstScriptTag.parentNode.insertBefore(tag, firstScriptTag);\n                        \n                              var player;\n                              function onYouTubeIframeAPIReady() {\n                                player = new YT.Player('player', {\n                                  height: '100%%',\n                                  width: '100%%',\n                                  videoId: '%@',\n                                  playerVars: {\n                                    'playsinline': 1,\n                                    'modestbranding': 1,\n                                    'controls': %@,\n                                    'autoplay': %@,\n                                    'mute': %@,\n                                    'loop': %@\n                                  },\n                                  events: {\n                                    'onReady': onPlayerReady,\n                                    'onStateChange': function(event) {\n                                      if (event.data === YT.PlayerState.PLAYING) {\n                                        webkit.messageHandlers.callback.postMessage('playing');\n                                      } else if (event.data === YT.PlayerState.PAUSED) {\n                                        webkit.messageHandlers.callback.postMessage('paused');\n                                      } else if (event.data === YT.PlayerState.ENDED) {\n                                        webkit.messageHandlers.callback.postMessage('ended');\n                                      }\n                                    }\n                                  }\n                                });\n                              }\n                        \n                              function onPlayerReady(event) {\n                                webkit.messageHandlers.callback.postMessage('mediaReady');\n                                webkit.messageHandlers.callback.postMessage(player.isMuted() ? 'muted' : 'unmuted');\n                              }\n                            </script>\n                        </body>\n                        \"\"\",\n                    videoID,\n                    video?.showControls ?? true ? \"1\" : \"0\",\n                    video?.autoplay ?? false ? \"1\" : \"0\",\n                    video?.muted ?? false ? \"1\" : \"0\",\n                    video?.loop ?? false ? \"1, \\'playlist\\': \\'\\(videoID)\\'\" : \"0\"\n                )\n                webView.loadHTMLString(html, baseURL: baseURL)\n            } else {\n                let suffix = url.contains(\"?\") ? \"&playsinline=1\" : \"?playsinline=1\"\n                if let videoUrl = URL(string: \"\\(url)\\(suffix)\") {\n                    webView.load(URLRequest(url: videoUrl))\n                }\n            }\n        case .vimeo:\n            let html = String(\n                format: \"\"\"\n                    <head><meta name=\"viewport\" content=\"initial-scale=1,maximum-scale=1\"></head>\n                    <body style=\"margin:0; background-color:transparent;\">                  \n                        <iframe id=\"vimeoIframe\"\n                          src=\"%@&playsinline=1\"\n                          width=\"100%%\" height=\"100%%\" frameborder=\"0\"\n                          webkitallowfullscreen mozallowfullscreen allowfullscreen>\n                        </iframe>\n                      \n                        <script src=\"https://player.vimeo.com/api/player.js\"></script>\n                        <script>\n                            const vimeoIframe = document.querySelector('#vimeoIframe');\n                            const vimeoPlayer = new Vimeo.Player(vimeoIframe);\n                                                \n                            vimeoPlayer.ready().then(function() {\n                              webkit.messageHandlers.callback.postMessage('mediaReady');\n                              vimeoPlayer.on('play',  function() { webkit.messageHandlers.callback.postMessage('playing'); });\n                              vimeoPlayer.on('pause', function() { webkit.messageHandlers.callback.postMessage('paused'); });\n                              vimeoPlayer.on('ended', function() { webkit.messageHandlers.callback.postMessage('ended'); });\n                              return vimeoPlayer.getMuted();\n                            }).then(function(muted) {\n                              webkit.messageHandlers.callback.postMessage(muted ? 'muted' : 'unmuted');\n                            });\n                        </script>\n                    </body>\n                    \"\"\",\n                url\n            )\n            webView.loadHTMLString(html, baseURL: baseURL)\n        }\n    }\n\n    static func retrieveYoutubeVideoID(url: String) -> String? {\n        do {\n            let regex = try NSRegularExpression(pattern: \"embed/([a-zA-Z0-9_-]+)\")\n            guard let match = regex.firstMatch(in: url, range: NSRange(url.startIndex..., in: url)),\n                  let range = Range(match.range(at: 1), in: url) else {\n                return nil\n            }\n            return String(url[range])\n        } catch {\n            return nil\n        }\n    }\n\n    private var video: ThomasViewInfo.Media.Video? {\n        self.info.properties.video\n    }\n\n    func makeCoordinator() -> Coordinator {\n        let video = self.info.properties.video\n        return Coordinator(\n            isLoaded: $isLoaded,\n            videoIdentifier: videoIdentifier,\n            videoState: videoState,\n            mediaType: info.properties.mediaType,\n            isAutoplay: video?.autoplay ?? false,\n            showControls: video?.showControls ?? true,\n            autoResetPosition: video?.autoResetPosition ?? ((video?.autoplay ?? false) && !(video?.showControls ?? true)),\n            isLooping: video?.loop ?? false,\n            isMuted: video?.muted ?? false,\n            onMediaReady: onMediaReady\n        )\n    }\n\n    // MARK: - Coordinator\n\n    class Coordinator: NSObject, WKNavigationDelegate, WKScriptMessageHandler {\n        private var isLoaded: Binding<Bool>\n        private var videoIdentifier: String?\n        private var videoState: VideoState\n        private var onMediaReady: @MainActor () -> Void\n        private let challengeResolver: ChallengeResolver\n        private weak var webView: WKWebView?\n\n        private var lastIsVisible: Bool = false\n        private var lastIsLoaded: Bool = false\n        private var lastInProgress: Bool = true\n\n        /// Tracks whether the system (visibility change, pager, backgrounding) initiated a pause.\n        /// When true, incoming \"paused\" JS callbacks won't clear `localIsPlaying`.\n        private var isSystemPausing: Bool = false\n\n        /// Tracks playing intent from JS callbacks. `nil` = initial (autoplay should trigger),\n        /// `true` = playing/was playing, `false` = user explicitly paused.\n        /// Guarded by `isSystemPausing` so system pauses don't clear user intent.\n        private var localIsPlaying: Bool? = nil\n\n        private let mediaType: ThomasViewInfo.Media.MediaType\n        private let isAutoplay: Bool\n        private let showControls: Bool\n        private let autoResetPosition: Bool\n        private let isLooping: Bool\n        private let isMuted: Bool\n\n        private var appStateTask: Task<Void, Never>?\n\n        init(\n            isLoaded: Binding<Bool>,\n            videoIdentifier: String?,\n            videoState: VideoState,\n            mediaType: ThomasViewInfo.Media.MediaType,\n            isAutoplay: Bool,\n            showControls: Bool,\n            autoResetPosition: Bool,\n            isLooping: Bool,\n            isMuted: Bool,\n            resolver: ChallengeResolver = .shared,\n            onMediaReady: @escaping @MainActor () -> Void\n        ) {\n            self.isLoaded = isLoaded\n            self.videoIdentifier = videoIdentifier\n            self.videoState = videoState\n            self.mediaType = mediaType\n            self.isAutoplay = isAutoplay\n            self.showControls = showControls\n            self.autoResetPosition = autoResetPosition\n            self.isLooping = isLooping\n            self.isMuted = isMuted\n            self.onMediaReady = onMediaReady\n            self.challengeResolver = resolver\n\n            super.init()\n\n            AirshipLogger.trace(\"VideoMediaWebView Coordinator init, mediaType: \\(mediaType)\")\n\n            appStateTask = Task { @MainActor [weak self] in\n                for await state in AppStateTracker.shared.stateUpdates {\n                    guard !Task.isCancelled else { return }\n                    if state == .active {\n                        self?.handleForeground()\n                    } else {\n                        self?.systemPause()\n                    }\n                }\n            }\n        }\n\n        deinit {\n            appStateTask?.cancel()\n            AirshipLogger.trace(\"VideoMediaWebView Coordinator deinit\")\n        }\n\n        @MainActor\n        func configure(webView: WKWebView) {\n            self.webView = webView\n        }\n\n        @MainActor\n        func teardown() {\n            appStateTask?.cancel()\n            appStateTask = nil\n            \n            if let videoIdentifier {\n                videoState.unregister(videoIdentifier: videoIdentifier)\n            }\n            \n            self.webView?.stopLoading()\n            self.webView?.navigationDelegate = nil\n            self.webView?.configuration.userContentController.removeAllScriptMessageHandlers()\n            self.webView?.pauseAllMediaPlayback()\n#if !os(macOS)\n            if #unavailable(iOS 26.3) {\n                if self.webView?.superview != nil {\n                    self.webView?.removeFromSuperview()\n                }\n            }\n#endif\n            self.webView = nil\n        }\n\n        // MARK: - Playback Control\n\n        @MainActor\n        private func play() {\n            guard let webView else { return }\n            switch mediaType {\n            case .video:\n                webView.evaluateJavaScript(\"videoElement.play();\")\n            case .youtube:\n                webView.evaluateJavaScript(\"player.playVideo();\")\n            case .vimeo:\n                webView.evaluateJavaScript(\"vimeoPlayer.play();\")\n            case .image:\n                break\n            }\n        }\n\n        @MainActor\n        private func pause() {\n            guard let webView else { return }\n            switch mediaType {\n            case .video:\n                webView.evaluateJavaScript(\"videoElement.pause();\")\n            case .youtube:\n                webView.evaluateJavaScript(\"player.pauseVideo();\")\n            case .vimeo:\n                webView.evaluateJavaScript(\"vimeoPlayer.pause();\")\n            case .image: break\n            }\n        }\n\n        @MainActor\n        private func reset() {\n            guard\n                autoResetPosition,\n                let webView\n            else {\n                return\n            }\n\n            switch mediaType {\n            case .video:\n                webView.evaluateJavaScript(\"videoElement.currentTime = 0;\")\n            case .youtube:\n                webView.evaluateJavaScript(\"player.seekTo(0);\")\n            case .vimeo:\n                webView.evaluateJavaScript(\"vimeoPlayer.setCurrentTime(0);\")\n            case .image:\n                break\n            }\n        }\n\n        @MainActor\n        private func mute() {\n            guard let webView else { return }\n            switch mediaType {\n            case .video:\n                webView.evaluateJavaScript(\"videoElement.muted = true;\")\n            case .youtube:\n                webView.evaluateJavaScript(\"player.mute();\")\n            case .vimeo:\n                webView.evaluateJavaScript(\"vimeoPlayer.setMuted(true);\")\n            case .image:\n                break\n            }\n        }\n\n        @MainActor\n        private func unmute() {\n            guard let webView else { return }\n            switch mediaType {\n            case .video:\n                webView.evaluateJavaScript(\"videoElement.muted = false;\")\n            case .youtube:\n                webView.evaluateJavaScript(\"player.unMute();\")\n            case .vimeo:\n                webView.evaluateJavaScript(\"vimeoPlayer.setMuted(false);\")\n            case .image:\n                break\n            }\n        }\n\n        // MARK: - State Management\n\n        @MainActor\n        func update(isVisible: Bool, isLoaded: Bool, inProgress: Bool, isDismissed: Bool) {\n            guard !isDismissed else {\n                teardown()\n                return\n            }\n\n            let didChange = lastIsVisible != isVisible\n                || lastIsLoaded != isLoaded\n                || lastInProgress != inProgress\n            lastIsVisible = isVisible\n            lastIsLoaded = isLoaded\n            lastInProgress = inProgress\n\n            guard didChange else { return }\n\n            if inProgress, isVisible, isLoaded {\n                handleResume()\n            } else {\n                if !isVisible {\n                    self.reset()\n                }\n                systemPause()\n            }\n        }\n\n        @MainActor\n        private func systemPause() {\n            isSystemPausing = true\n            pause()\n        }\n\n        @MainActor\n        private func handleForeground() {\n            guard lastIsVisible, lastIsLoaded, lastInProgress else { return }\n            handleResume()\n        }\n\n        @MainActor\n        private func handleResume() {\n            let shouldPlay: Bool\n            if videoState.shouldControl(videoIdentifier: videoIdentifier) {\n                shouldPlay = videoState.isPlaying\n            } else if isAutoplay {\n                shouldPlay = localIsPlaying != false\n            } else {\n                shouldPlay = localIsPlaying == true\n            }\n            isSystemPausing = false\n            if shouldPlay {\n                localIsPlaying = true\n                play()\n            }\n        }\n\n\n        // MARK: - Video State Registration\n\n        @MainActor\n        private func registerWithVideoState() {\n            guard let videoId = videoIdentifier,\n                  videoState.shouldControl(videoIdentifier: videoId) else {\n                return\n            }\n\n            videoState.register(\n                videoIdentifier: videoId,\n                play: { [weak self] in self?.play() },\n                pause: { [weak self] in self?.pause() },\n                mute: { [weak self] in self?.mute() },\n                unmute: { [weak self] in self?.unmute() }\n            )\n\n            videoState.playGroup.initializePlaying(isAutoplay)\n            videoState.muteGroup.initializeMuted(isMuted)\n        }\n\n        // MARK: - WKNavigationDelegate\n\n        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {\n            Task { @MainActor [weak self] in\n                guard let self else { return }\n                self.isLoaded.wrappedValue = true\n                self.registerWithVideoState()\n            }\n        }\n\n        func webView(_ webView: WKWebView, respondTo challenge: URLAuthenticationChallenge) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n            return await challengeResolver.resolve(challenge)\n        }\n\n        // MARK: - WKScriptMessageHandler\n\n        @MainActor\n        func userContentController(\n            _ userContentController: WKUserContentController,\n            didReceive message: WKScriptMessage\n        ) {\n            guard let response = message.body as? String else {\n                return\n            }\n\n            let canControlVideo = lastIsVisible && videoState.shouldControl(videoIdentifier: videoIdentifier)\n\n            switch response {\n            case \"mediaReady\":\n                onMediaReady()\n                if canControlVideo {\n                    if videoState.isMuted { mute() } else { unmute() }\n                }\n            case \"playing\":\n                if canControlVideo {\n                    videoState.updatePlayingState(true)\n                } else {\n                    localIsPlaying = true\n                }\n            case \"paused\":\n                if canControlVideo && !isSystemPausing {\n                    videoState.updatePlayingState(false)\n                } else if !isSystemPausing {\n                    localIsPlaying = false\n                }\n            case \"ended\" where !isLooping:\n                if canControlVideo && !isSystemPausing {\n                    videoState.updatePlayingState(false)\n                } else if !isSystemPausing {\n                    localIsPlaying = false\n                }\n            case \"muted\" where canControlVideo && showControls:\n                videoState.updateMutedState(true)\n            case \"unmuted\" where canControlVideo && showControls:\n                videoState.updateMutedState(false)\n            default:\n                break\n            }\n        }\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Source/VideoState.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n/// State class managing video playback state within a VideoController scope.\n@MainActor\nclass VideoState: ObservableObject {\n    /// The unique identifier for this video controller\n    let identifier: String\n\n    /// Optional array of video identifiers to control. If nil, controls all videos.\n    let videoScope: [String]?\n\n    /// Shared group container that flows through the VideoState chain.\n    let videoGroups: VideoGroups\n\n    let muteGroup: VideoGroupState\n    let playGroup: VideoGroupState\n\n    var isPlaying: Bool { playGroup.isPlaying }\n    var isMuted: Bool { muteGroup.isMuted }\n\n    var isPlayingPublisher: AnyPublisher<Bool, Never> {\n        playGroup.$isPlaying.eraseToAnyPublisher()\n    }\n\n    var isMutedPublisher: AnyPublisher<Bool, Never> {\n        muteGroup.$isMuted.eraseToAnyPublisher()\n    }\n\n    /// Registry of video callbacks\n    private var registeredVideos: [String: VideoRegistration] = [:]\n    private var subscriptions: Set<AnyCancellable> = Set()\n\n    struct VideoRegistration {\n        let play: @MainActor () -> Void\n        let pause: @MainActor () -> Void\n        let mute: @MainActor () -> Void\n        let unmute: @MainActor () -> Void\n    }\n\n    init(\n        identifier: String,\n        videoScope: [String]? = nil,\n        videoGroups: VideoGroups = VideoGroups(),\n        muteGroup: VideoGroupState = VideoGroupState(),\n        playGroup: VideoGroupState = VideoGroupState()\n    ) {\n        self.identifier = identifier\n        self.videoScope = videoScope\n        self.videoGroups = videoGroups\n        self.muteGroup = muteGroup\n        self.playGroup = playGroup\n\n        setupGroupSync()\n    }\n\n    /// False when this is the default fallback state injected at the root with no real VideoController ancestor.\n    private var hasController: Bool {\n        guard !identifier.isEmpty else {\n            return false\n        }\n        return true\n    }\n\n    /// Determines if this controller should control a video with the given identifier\n    func shouldControl(videoIdentifier: String?) -> Bool {\n        guard hasController else { return false }\n        guard let scope = videoScope else {\n            return true\n        }\n        guard let videoId = videoIdentifier else {\n            return false\n        }\n        return scope.contains(videoId)\n    }\n\n    /// Register a video with its control callbacks\n    func register(\n        videoIdentifier: String,\n        play: @escaping @MainActor () -> Void,\n        pause: @escaping @MainActor () -> Void,\n        mute: @escaping @MainActor () -> Void,\n        unmute: @escaping @MainActor () -> Void\n    ) {\n        guard shouldControl(videoIdentifier: videoIdentifier) else { return }\n        guard hasController else { return }\n\n        registeredVideos[videoIdentifier] = VideoRegistration(\n            play: play,\n            pause: pause,\n            mute: mute,\n            unmute: unmute\n        )\n    }\n\n    /// Unregister a video\n    func unregister(videoIdentifier: String) {\n        registeredVideos.removeValue(forKey: videoIdentifier)\n    }\n\n    // MARK: - Playback Control\n\n    func play() {\n        guard hasController else { return }\n        registeredVideos.values.forEach { $0.play() }\n        playGroup.isPlaying = true\n    }\n\n    func pause() {\n        guard hasController else { return }\n        registeredVideos.values.forEach { $0.pause() }\n        playGroup.isPlaying = false\n    }\n\n    func togglePlay() {\n        guard hasController else { return }\n        if isPlaying { pause() } else { play() }\n    }\n\n    // MARK: - Mute Control\n\n    func mute() {\n        guard hasController else { return }\n        registeredVideos.values.forEach { $0.mute() }\n        muteGroup.isMuted = true\n    }\n\n    func unmute() {\n        guard hasController else { return }\n        registeredVideos.values.forEach { $0.unmute() }\n        muteGroup.isMuted = false\n    }\n\n    func toggleMute() {\n        guard hasController else { return }\n        if isMuted { unmute() } else { mute() }\n    }\n\n    // MARK: - State Updates from Videos\n\n    func updatePlayingState(_ playing: Bool) {\n        if !playGroup.isPlayingInitialized {\n            playGroup.initializePlaying(playing)\n        } else if isPlaying != playing {\n            playGroup.isPlaying = playing\n        }\n    }\n\n    func updateMutedState(_ muted: Bool) {\n        if !muteGroup.isMutedInitialized {\n            muteGroup.initializeMuted(muted)\n        } else if isMuted != muted {\n            muteGroup.isMuted = muted\n        }\n    }\n\n    // MARK: - Group Sync\n\n    private func setupGroupSync() {\n        muteGroup.objectWillChange.sink { [weak self] _ in\n            Task { @MainActor in\n                guard let self else { return }\n                self.objectWillChange.send()\n                let muted = self.muteGroup.isMuted\n                if muted {\n                    self.registeredVideos.values.forEach { $0.mute() }\n                } else {\n                    self.registeredVideos.values.forEach { $0.unmute() }\n                }\n            }\n        }.store(in: &subscriptions)\n\n        playGroup.objectWillChange.sink { [weak self] _ in\n            Task { @MainActor in\n                guard let self else { return }\n                self.objectWillChange.send()\n                let playing = self.playGroup.isPlaying\n                if playing {\n                    self.registeredVideos.values.forEach { $0.play() }\n                } else {\n                    self.registeredVideos.values.forEach { $0.pause() }\n                }\n            }\n        }.store(in: &subscriptions)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ViewConstraints.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct ViewConstraints: Equatable {\n\n    enum SafeAreaInsetsMode {\n        // Insets will be passed on to children\n        case ignore\n\n        // Insets will be consumed\n        case consume\n\n        // Insets will be consumed and applied as margins if the size is percent\n        case consumeMargin\n    }\n\n    static let emptyEdgeSet = EdgeInsets(\n        top: 0,\n        leading: 0,\n        bottom: 0,\n        trailing: 0\n    )\n\n    var maxWidth: CGFloat?\n    var maxHeight: CGFloat?\n    var width: CGFloat?\n    var height: CGFloat?\n    var safeAreaInsets: EdgeInsets\n    var isHorizontalFixedSize: Bool\n    var isVerticalFixedSize: Bool\n\n    init(\n        width: CGFloat? = nil,\n        height: CGFloat? = nil,\n        maxWidth: CGFloat? = nil,\n        maxHeight: CGFloat? = nil,\n        isHorizontalFixedSize: Bool = false,\n        isVerticalFixedSize: Bool = false,\n        safeAreaInsets: EdgeInsets = emptyEdgeSet\n    ) {\n\n        self.width = width\n        self.height = height\n        self.maxWidth = maxWidth\n        self.maxHeight = maxHeight\n        self.safeAreaInsets = safeAreaInsets\n        self.isHorizontalFixedSize = isHorizontalFixedSize\n        self.isVerticalFixedSize = isVerticalFixedSize\n    }\n\n    init(size: CGSize, safeAreaInsets: EdgeInsets) {\n        self.init(\n            width: size.width + safeAreaInsets.trailing + safeAreaInsets.leading,\n            height: size.height + safeAreaInsets.top + safeAreaInsets.bottom,\n            isHorizontalFixedSize: true,\n            isVerticalFixedSize: true,\n            safeAreaInsets: safeAreaInsets\n        )\n    }\n\n    func contentConstraints(\n        _ constrainedSize: ThomasConstrainedSize,\n        contentSize: CGSize?,\n        margin: ThomasMargin?\n    ) -> ViewConstraints {\n\n        let verticalMargins: CGFloat = margin?.verticalMargins ?? 0.0\n        let horizontalMargins: CGFloat = margin?.horiztonalMargins ?? 0.0\n\n        let parentWidth: CGFloat? = self.width?.subtract(horizontalMargins)\n        let parentHeight: CGFloat? = self.height?.subtract(verticalMargins)\n\n        let childMinWidth: CGFloat? = constrainedSize.minWidth?.calculateSize(\n            parentWidth\n        )\n\n        let childMaxWidth: CGFloat? = constrainedSize.maxWidth?.calculateSize(\n            parentWidth\n        )\n\n        var childWidth: CGFloat? = constrainedSize.width.calculateSize(\n            parentWidth\n        )\n\n        childWidth = childWidth?.bound(\n            minValue: childMinWidth,\n            maxValue: childMaxWidth\n        )\n\n        let childMinHeight: CGFloat? = constrainedSize.minHeight?.calculateSize(\n            parentHeight\n        )\n        let childMaxHeight: CGFloat? = constrainedSize.maxHeight?.calculateSize(\n            parentHeight\n        )\n\n        var childHeight: CGFloat? = constrainedSize.height.calculateSize(\n            parentHeight\n        )\n\n        childHeight = childHeight?.bound(\n            minValue: childMinHeight,\n            maxValue: childMaxHeight\n        )\n\n        let isVerticalFixedSize: Bool = constrainedSize.height.isFixedSize(\n            self.isVerticalFixedSize\n        )\n        let isHorizontalFixedSize: Bool = constrainedSize.width.isFixedSize(\n            self.isHorizontalFixedSize\n        )\n\n        if let contentSize = contentSize {\n            if let maxWidth = childMaxWidth, contentSize.width >= maxWidth {\n                childWidth = maxWidth\n            } else if let minWidth = childMinWidth,\n                contentSize.width <= minWidth\n            {\n                childWidth = minWidth\n            }\n\n            if let maxHeight = childMaxHeight, contentSize.height >= maxHeight {\n                childHeight = maxHeight\n            } else if let minHeight = childMinHeight,\n                contentSize.height <= minHeight\n            {\n                childHeight = minHeight\n            }\n        }\n\n        return ViewConstraints(\n            width: childWidth,\n            height: childHeight,\n            maxWidth: childWidth ?? parentWidth,\n            maxHeight: childHeight ?? parentHeight,\n            isHorizontalFixedSize: isHorizontalFixedSize,\n            isVerticalFixedSize: isVerticalFixedSize,\n            safeAreaInsets: self.safeAreaInsets\n        )\n    }\n\n    func childConstraints(\n        _ size: ThomasSize,\n        margin: ThomasMargin?,\n        padding: Double = 0,\n        safeAreaInsetsMode: SafeAreaInsetsMode = .ignore\n    ) -> ViewConstraints {\n\n        let parentWidth: CGFloat? = self.width?.subtract(padding * 2)\n        let parentHeight: CGFloat? = self.height?.subtract(padding * 2)\n\n        var horizontalMargins: CGFloat = margin?.horiztonalMargins ?? 0.0\n        var verticalMargins: CGFloat = margin?.verticalMargins ?? 0.0\n\n        var safeAreaInsets: EdgeInsets = self.safeAreaInsets\n        switch safeAreaInsetsMode {\n        case .ignore:\n            break\n        case .consume:\n            safeAreaInsets = ViewConstraints.emptyEdgeSet\n        case .consumeMargin:\n            horizontalMargins =\n                horizontalMargins + self.safeAreaInsets.leading\n                + self.safeAreaInsets.trailing\n            verticalMargins =\n                verticalMargins + self.safeAreaInsets.top\n                + self.safeAreaInsets.bottom\n            safeAreaInsets = ViewConstraints.emptyEdgeSet\n        }\n\n        var childWidth: CGFloat? = size.width.calculateSize(parentWidth)\n        var childHeight: CGFloat? = size.height.calculateSize(parentHeight)\n\n        if size.width.isPercent, let width = childWidth, let parentWidth = parentWidth {\n            childWidth = max(0, min(width, parentWidth.subtract(horizontalMargins)))\n        }\n\n        if size.height.isPercent, let height = childHeight, let parentHeight = parentHeight {\n            childHeight = max(0, min(height, parentHeight.subtract(verticalMargins)))\n        }\n\n        let isVerticalFixedSize: Bool = size.height.isFixedSize(\n            self.isVerticalFixedSize\n        )\n\n        let isHorizontalFixedSize: Bool = size.width.isFixedSize(\n            self.isHorizontalFixedSize\n        )\n\n        let maxWidth = (parentWidth ?? self.maxWidth?.subtract(padding * 2))?.subtract(horizontalMargins)\n        let maxHeight = (parentHeight ?? self.maxHeight?.subtract(padding * 2))?.subtract(verticalMargins)\n\n        return ViewConstraints(\n            width: childWidth,\n            height: childHeight,\n            maxWidth: childWidth ?? maxWidth,\n            maxHeight: childHeight ?? maxHeight,\n            isHorizontalFixedSize: isHorizontalFixedSize,\n            isVerticalFixedSize: isVerticalFixedSize,\n            safeAreaInsets: safeAreaInsets\n        )\n    }\n}\n\nextension ThomasSizeConstraint {\n    func calculateSize(_ parentSize: CGFloat?) -> CGFloat? {\n        switch self {\n        case .points(let points):\n            return points\n        case .percent(let percent):\n            guard let parentSize = parentSize else {\n                return nil\n            }\n            return percent / 100.0 * parentSize\n        case .auto:\n            return nil\n        }\n    }\n\n    func isFixedSize(_ isParentFixed: Bool) -> Bool {\n        switch self {\n        case .points(_):\n            return true\n        case .percent(_):\n            return isParentFixed\n        case .auto:\n            return false\n        }\n    }\n\n    var isAuto: Bool {\n        switch self {\n        case .points(_):\n            return false\n        case .percent(_):\n            return false\n        case .auto:\n            return true\n        }\n    }\n\n    var isPercent: Bool {\n        switch self {\n        case .points(_):\n            return false\n        case .percent(_):\n            return true\n        case .auto:\n            return false\n        }\n    }\n}\n\nextension ThomasMargin {\n    var verticalMargins: CGFloat {\n        return (self.bottom ?? 0.0) + (self.top ?? 0.0)\n    }\n\n    var horiztonalMargins: CGFloat {\n        return (self.start ?? 0.0) + (self.end ?? 0.0)\n    }\n}\n\nextension CGFloat {\n    func subtract(_ value: CGFloat) -> CGFloat {\n        return self - value\n    }\n\n    func bound(minValue: CGFloat? = nil, maxValue: CGFloat? = nil) -> CGFloat {\n        var value = self\n        if let minValue = minValue {\n            value = CGFloat.maximum(value, minValue)\n        }\n\n        if let maxValue = maxValue {\n            value = CGFloat.minimum(value, maxValue)\n        }\n\n        return value\n    }\n\n    /// Returns self if finite, otherwise returns nil.\n    /// Guards against NaN and infinity crashing SwiftUI frame/offset/position modifiers.\n    var safeValue: CGFloat? {\n        self.isFinite ? self : nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ViewExtensions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\nextension View {\n\n    @ViewBuilder\n    func foreground(_ color: ThomasColor?, colorScheme: ColorScheme) -> some View {\n        if let color = color {\n            self.foregroundColor(color.toColor(colorScheme))\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    internal func applyMargin(edge: Edge.Set, margin: CGFloat?) -> some View {\n        if let margin = margin {\n            self.padding(edge, margin)\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func margin(_ margin: ThomasMargin?) -> some View {\n        if let margin = margin {\n            self.applyMargin(edge: .leading, margin: margin.start)\n                .applyMargin(edge: .top, margin: margin.top)\n                .applyMargin(edge: .trailing, margin: margin.end)\n                .applyMargin(edge: .bottom, margin: margin.bottom)\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func constraints(\n        _ constraints: ViewConstraints,\n        alignment: Alignment? = nil,\n        fixedSize: Bool = false\n    ) -> some View {\n        self.frame(\n            idealWidth: constraints.width,\n            maxWidth: constraints.width,\n            idealHeight: constraints.height,\n            maxHeight: constraints.height,\n            alignment: alignment ?? .center\n        )\n        .airshipApplyIf(fixedSize) { view in\n            view.fixedSize(\n                horizontal: constraints.isHorizontalFixedSize\n                    && constraints.width != nil,\n                vertical: constraints.isVerticalFixedSize\n                    && constraints.height != nil\n            )\n        }\n    }\n    \n    @ViewBuilder\n    internal func thomasToggleStyle(\n        _ style: ThomasToggleStyleInfo,\n        constraints: ViewConstraints\n    ) -> some View {\n        switch style {\n        case .checkboxStyle(let style):\n            self.toggleStyle(\n                AirshipCheckboxToggleStyle(\n                    viewConstraints: constraints,\n                    info: style\n                )\n            )\n        case .switchStyle(let style):\n            self.toggleStyle(\n                AirshipSwitchToggleStyle(\n                    info: style\n                )\n            )\n        }\n    }\n    \n    @ViewBuilder\n    public func airshipApplyIf<Content: View>(\n        _ predicate: @autoclosure () -> Bool,\n        @ViewBuilder transform: (Self) -> Content\n    ) -> some View {\n        if predicate() {\n            transform(self)\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    public func airshipGeometryGroupCompat() -> some View {\n        if #available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, *) {\n            self.geometryGroup()\n        } else {\n            self.transformEffect(.identity)\n        }\n    }\n\n    @ViewBuilder\n    internal func addTapGesture(action: @escaping () -> Void) -> some View {\n        self.onTapGesture(perform: action)\n            .accessibilityAction(.default, action)\n    }\n\n    @ViewBuilder\n    internal func accessible(\n        _ accessible: ThomasAccessibleInfo?,\n        associatedLabel: String?,\n        fallbackContentDescription: String? = nil,\n        hideIfDescriptionIsMissing: Bool\n    ) -> some View {\n        let contentDescription = accessible?.resolveContentDescription ?? fallbackContentDescription\n        if accessible?.accessibilityHidden == true {\n            self.accessibilityHidden(true)\n        } else if let contentDescription, let associatedLabel {\n            self.accessibilityLabel(associatedLabel)\n                .accessibilityHint(contentDescription)\n        } else if let contentDescription {\n            self.accessibilityLabel(contentDescription)\n        } else if let associatedLabel {\n            self.accessibilityLabel(associatedLabel)\n        }else if hideIfDescriptionIsMissing {\n            self.accessibilityHidden(true)\n        } else {\n            self\n        }\n    }\n}\n\ninternal struct ThomasCommonScope: OptionSet {\n    let rawValue: UInt\n\n    public static let background = ThomasCommonScope(rawValue: 1 << 0)\n    public static let stateTriggers = ThomasCommonScope(rawValue: 1 << 1)\n    public static let eventHandlers = ThomasCommonScope(rawValue: 1 << 2)\n    public static let enableBehaviors = ThomasCommonScope(rawValue: 1 << 3)\n    public static let visibility = ThomasCommonScope(rawValue: 1 << 4)\n\n    static let all: ThomasCommonScope = [.background, .stateTriggers, .eventHandlers, .enableBehaviors, .visibility]\n}\n\nfileprivate extension ThomasViewInfo.BaseInfo {\n    var hasBackground: Bool {\n        return commonProperties.border != nil ||\n               commonProperties.backgroundColor != nil ||\n               (commonOverrides?.border?.isEmpty == false) ||\n               (commonOverrides?.backgroundColor?.isEmpty == false)\n    }\n}\n\nextension View {\n\n    @ViewBuilder\n    internal func thomasCommon(\n        _ info: any ThomasViewInfo.BaseInfo,\n        formInputID: String? = nil,\n        scope: ThomasCommonScope = .all\n    ) -> some View {\n\n        let commonOverrides = info.commonOverrides\n        let commonProperties = info.commonProperties\n\n        self.viewModifiers {\n            if scope.contains(.background), info.hasBackground {\n                BackgroundViewModifier(\n                    backgroundColor: commonProperties.backgroundColor,\n                    backgroundColorOverrides: commonOverrides?.backgroundColor,\n                    border: commonProperties.border,\n                    borderOverrides: commonOverrides?.border,\n                    shadow: nil\n                )\n            }\n\n            if scope.contains(.stateTriggers), let triggers = commonProperties.stateTriggers, !triggers.isEmpty {\n                StateTriggerModifier(\n                    triggers: triggers\n                )\n            }\n\n            if scope.contains(.eventHandlers), let handlers = commonProperties.eventHandlers, !handlers.isEmpty {\n                EventHandlerViewModifier(\n                    eventHandlers: handlers,\n                    formInputID: formInputID\n                )\n            }\n\n            if scope.contains(.enableBehaviors), let behaviors = commonProperties.enabled, !behaviors.isEmpty {\n                if behaviors.contains(.formValidation) {\n                    ValidFormButtonEnableBehavior(onApply: nil)\n                }\n\n                if behaviors.contains(.pagerNext) {\n                    PagerNextButtonEnableBehavior(onApply: nil)\n                }\n\n                if behaviors.contains(.pagerPrevious) {\n                    PagerPreviousButtonEnableBehavior(onApply: nil)\n                }\n\n                if behaviors.contains(.formSubmission) {\n                    FormSubmissionEnableBehavior(onApply: nil)\n                }\n            }\n\n            if scope.contains(.visibility), let visibilityInfo = commonProperties.visibility {\n                VisibilityViewModifier(visibilityInfo: visibilityInfo)\n            }\n        }\n    }\n\n    internal func viewModifiers<Modifiers: ViewModifier>(\n        @AirshipViewModifierBuilder modifiers: () -> Modifiers\n    ) -> some View {\n        self.modifier(modifiers())\n    }\n\n    internal func overlayView<T: View>(\n        alignment: Alignment = .center,\n        @ViewBuilder content: () -> T\n    ) -> some View {\n        overlay(\n            Group(content: content),\n            alignment: alignment\n        )\n    }\n}\n\n\n@resultBuilder\nstruct AirshipViewModifierBuilder {\n\n    static func buildBlock() -> EmptyModifier {\n        EmptyModifier()\n    }\n\n    @MainActor\n    static func buildOptional<VM0: ViewModifier>(_ vm0: VM0?)\n        -> some ViewModifier\n    {\n        return Optional(viewModifier: vm0)\n    }\n\n    static func buildBlock<VM0: ViewModifier>(_ vm0: VM0) -> some ViewModifier {\n        return vm0\n    }\n\n    static func buildBlock<VM0: ViewModifier, VM1: ViewModifier>(\n        _ vm0: VM0,\n        _ vm1: VM1\n    ) -> some ViewModifier {\n        return vm0.concat(vm1)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2) -> some ViewModifier {\n        return vm0.concat(vm1).concat(vm2)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3) -> some ViewModifier {\n        return vm0.concat(vm1).concat(vm2).concat(vm3)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier,\n        VM4: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3, _ vm4: VM4)\n        -> some ViewModifier\n    {\n        return vm0.concat(vm1).concat(vm2).concat(vm3).concat(vm4)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier,\n        VM4: ViewModifier,\n        VM5: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3, _ vm4: VM4, _ vm5: VM5)\n        -> some ViewModifier\n    {\n        return vm0.concat(vm1).concat(vm2).concat(vm3).concat(vm4).concat(vm5)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier,\n        VM4: ViewModifier,\n        VM5: ViewModifier,\n        VM6: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3, _ vm4: VM4, _ vm5: VM5, _ vm6: VM6)\n        -> some ViewModifier\n    {\n        return vm0.concat(vm1).concat(vm2).concat(vm3).concat(vm4).concat(vm5).concat(vm6)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier,\n        VM4: ViewModifier,\n        VM5: ViewModifier,\n        VM6: ViewModifier,\n        VM7: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3, _ vm4: VM4, _ vm5: VM5, _ vm6: VM6, _ vm7: VM7)\n        -> some ViewModifier\n    {\n        return vm0.concat(vm1).concat(vm2).concat(vm3).concat(vm4).concat(vm5).concat(vm6).concat(vm7)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier,\n        VM4: ViewModifier,\n        VM5: ViewModifier,\n        VM6: ViewModifier,\n        VM7: ViewModifier,\n        VM8: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3, _ vm4: VM4, _ vm5: VM5, _ vm6: VM6, _ vm7: VM7, _ vm8: VM8)\n        -> some ViewModifier\n    {\n        return vm0.concat(vm1).concat(vm2).concat(vm3).concat(vm4).concat(vm5).concat(vm6).concat(vm7).concat(vm8)\n    }\n\n    static func buildBlock<\n        VM0: ViewModifier,\n        VM1: ViewModifier,\n        VM2: ViewModifier,\n        VM3: ViewModifier,\n        VM4: ViewModifier,\n        VM5: ViewModifier,\n        VM6: ViewModifier,\n        VM7: ViewModifier,\n        VM8: ViewModifier,\n        VM9: ViewModifier\n    >(_ vm0: VM0, _ vm1: VM1, _ vm2: VM2, _ vm3: VM3, _ vm4: VM4, _ vm5: VM5, _ vm6: VM6, _ vm7: VM7, _ vm8: VM8, _ vm9: VM9)\n        -> some ViewModifier\n    {\n        return vm0.concat(vm1).concat(vm2).concat(vm3).concat(vm4).concat(vm5).concat(vm6).concat(vm7).concat(vm8).concat(vm9)\n    }\n\n    private struct Optional<Modifier: ViewModifier>: ViewModifier {\n        let viewModifier: Modifier?\n\n        func body(content: Content) -> some View {\n            if let viewModifier = viewModifier {\n                content.modifier(viewModifier)\n            } else {\n                content\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/ViewFactory.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// View factory. Inflates views based on type.\n\nstruct ViewFactory {\n    @MainActor\n    @ViewBuilder\n    static func createView(\n        _ viewInfo: ThomasViewInfo,\n        constraints: ViewConstraints\n    ) -> some View {\n        switch viewInfo {\n        case .container(let info):\n            Container(info: info, constraints: constraints)\n        case .linearLayout(let info):\n            LinearLayout(info: info, constraints: constraints)\n        case .scrollLayout(let info):\n            ScrollLayout(info: info, constraints: constraints)\n        case .label(let info):\n            Label(info: info, constraints: constraints)\n        case .media(let info):\n            Media(info: info, constraints: constraints)\n        case .labelButton(let info):\n            LabelButton(info: info, constraints: constraints)\n        case .emptyView(let info):\n            AirshipEmptyView(info: info, constraints: constraints)\n        case .formController(let info):\n            FormController(info: .form(info), constraints: constraints)\n        case .npsController(let info):\n            FormController(info: .nps(info), constraints: constraints)\n        case .textInput(let info):\n            TextInput(info: info, constraints: constraints)\n        case .pagerController(let info):\n            PagerController(info: info, constraints: constraints)\n        case .pagerIndicator(let info):\n            PagerIndicator(info: info, constraints: constraints)\n        case .storyIndicator(let info):\n            StoryIndicator(info: info, constraints: constraints)\n        case .pager(let info):\n            Pager(info: info, constraints: constraints)\n        #if !os(tvOS) && !os(watchOS)\n        case .webView(let info):\n            AirshipWebView(info: info, constraints: constraints)\n        #endif\n        case .imageButton(let info):\n            ImageButton(info: info, constraints: constraints)\n        case .stackImageButton(let info):\n            StackImageButton(info: info, constraints: constraints)\n        case .checkbox(let info):\n            Checkbox(info: info, constraints: constraints)\n        case .checkboxController(let info):\n            CheckboxController(info: info, constraints: constraints)\n        case .toggle(let info):\n            AirshipToggle(info: info, constraints: constraints)\n        case .radioInputController(let info):\n            RadioInputController(info: info, constraints: constraints)\n        case .radioInput(let info):\n            RadioInput(info: info, constraints: constraints)\n        case .score(let info):\n            Score(info: info, constraints: constraints)\n        case .stateController(let info):\n            StateController(info: info, constraints: constraints)\n        case .customView(let info):\n            CustomView(info: info, constraints: constraints)\n        case .buttonLayout(let info):\n            ButtonLayout(info: info, constraints: constraints)\n        case .basicToggleLayout(let info):\n            BasicToggleLayout(info: info, constraints: constraints)\n        case .checkboxToggleLayout(let info):\n            CheckboxToggleLayout(info: info, constraints: constraints)\n        case .radioInputToggleLayout(let info):\n            RadioInputToggleLayout(info: info, constraints: constraints)\n        case .iconView(let info):\n            IconView(info: info, constraints: constraints)\n        case .scoreController(let info):\n            ScoreController(info: info, constraints: constraints)\n        case .scoreToggleLayout(let info):\n            ScoreToggleLayout(info: info, constraints: constraints)\n        case .videoController(let info):\n            VideoController(info: info, constraints: constraints)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/VisibilityViewModifier.swift",
    "content": "import Foundation\nimport SwiftUI\n\n\ninternal struct VisibilityViewModifier: ViewModifier {\n    let visibilityInfo: ThomasVisibilityInfo\n\n    @EnvironmentObject var thomasState: ThomasState\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        if isVisible() {\n            content\n        }\n    }\n\n    func isVisible() -> Bool {\n        let predicate = visibilityInfo.invertWhenStateMatches\n        guard predicate.evaluate(json: thomasState.state) else {\n            return visibilityInfo.defaultVisibility\n        }\n        return !visibilityInfo.defaultVisibility\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Source/WorkBackgroundTasks.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\nprotocol WorkBackgroundTasksProtocol: Sendable {\n    @MainActor\n    func beginTask(\n        _ name: String,\n        expirationHandler: (@Sendable () -> Void)?\n    ) throws -> any AirshipCancellable\n}\n\nfinal class WorkBackgroundTasks: WorkBackgroundTasksProtocol, Sendable {\n\n    #if !os(watchOS) && !os(macOS)\n    private let requestMap: AirshipMainActorValue<[UInt: UIBackgroundTaskIdentifier]> = AirshipMainActorValue([:])\n    private let nextRequestID: AirshipMainActorValue<UInt> = AirshipMainActorValue(0)\n    #endif\n\n    @MainActor\n    func beginTask(\n        _ name: String,\n        expirationHandler: (@Sendable () -> Void)? = nil\n    ) throws -> any AirshipCancellable {\n#if os(watchOS) || os(macOS)\n        let cancellable: CancellableValueHolder<UInt> = CancellableValueHolder(value: 0) { _ in\n        }\n        return cancellable\n#else\n\n        AirshipLogger.trace(\"Requesting task: \\(name)\")\n\n        let requestID = nextRequestID.value\n        nextRequestID.update { $0 += 1}\n\n        let cancellable: CancellableValueHolder<UInt> = CancellableValueHolder(value: requestID) { requestID in\n            Task { @MainActor in\n                self.cancel(requestID: requestID)\n            }\n        }\n\n        let application = UIApplication.shared\n\n        let bgTask = application.beginBackgroundTask(withName: name) {\n            AirshipLogger.trace(\"Task expired: \\(name)\")\n            self.cancel(requestID: requestID)\n            expirationHandler?()\n        }\n\n        self.requestMap.update { $0[requestID] = bgTask }\n\n        guard let task = self.requestMap.value[requestID], task != UIBackgroundTaskIdentifier.invalid else {\n            throw AirshipErrors.error(\"Unable to request background time.\")\n        }\n\n        AirshipLogger.trace(\"Task granted: \\(name)\")\n        return cancellable\n        #endif\n    }\n\n    @MainActor\n    private func cancel(requestID: UInt) {\n#if !os(watchOS) && !os(macOS)\n        let taskID = self.requestMap.value[requestID]\n        self.requestMap.update { $0.removeValue(forKey: requestID) }\n\n        guard let taskID = taskID, taskID != UIBackgroundTaskIdentifier.invalid else {\n            return\n        }\n\n        UIApplication.shared.endBackgroundTask(taskID)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/WorkConditionsMonitor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\nstruct WorkConditionsMonitor: @unchecked Sendable {\n    private let cancellable: AnyCancellable\n    private let conditionsSubject: PassthroughSubject<Void, Never> = PassthroughSubject<Void, Never>()\n    private let networkMonitor: any AirshipNetworkCheckerProtocol\n\n    init(\n        networkMonitor: AirshipNetworkChecker = AirshipNetworkChecker()\n    ) {\n        self.networkMonitor = networkMonitor\n        self.cancellable = Publishers.CombineLatest(\n            NotificationCenter.default.publisher(\n                for: AppStateTracker.didBecomeActiveNotification\n            ),\n            NotificationCenter.default.publisher(\n                for: AppStateTracker.didEnterBackgroundNotification\n            )\n        )\n        .receive(on: RunLoop.main)\n        .sink { [conditionsSubject] _ in\n            conditionsSubject.send()\n        }\n\n        Task { @MainActor [conditionsSubject] in\n            for await _ in networkMonitor.connectionUpdates {\n                conditionsSubject.send()\n            }\n        }\n    }\n\n    @MainActor\n    func checkConditions(workRequest: AirshipWorkRequest) -> Bool {\n        if workRequest.requiresNetwork == true {\n            return networkMonitor.isConnected\n        }\n\n        return true\n    }\n\n    @MainActor\n    private func waitConditionsUpdate() async {\n        return await withCheckedContinuation { continuation in\n            var cancellable: AnyCancellable? = nil\n            cancellable = self.conditionsSubject\n                .first()\n                .sink { _ in\n                    continuation.resume()\n                    cancellable?.cancel()\n                }\n        }\n    }\n\n    @MainActor\n    func awaitConditions(workRequest: AirshipWorkRequest) async {\n        while checkConditions(workRequest: workRequest) == false {\n            await waitConditionsUpdate()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/WorkRateLimiterActor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\nactor WorkRateLimiter {\n\n    private struct RateLimiterState: Sendable {\n        let rate: Int\n        let timeInterval: TimeInterval\n        var hits: [Date] = []\n\n        mutating func prune(now: Date) {\n            let cutoff = now.addingTimeInterval(-timeInterval)\n            hits.removeAll { $0 <= cutoff }\n\n            if hits.count > rate {\n                hits.removeFirst(hits.count - rate)\n            }\n        }\n    }\n\n    enum Status: Sendable {\n        case overLimit(TimeInterval)\n        case withinLimit(Int)\n    }\n\n    private var limiters: [String: RateLimiterState] = [:]\n    private let date: any AirshipDateProtocol\n\n    init(date: any AirshipDateProtocol = AirshipDate()) {\n        self.date = date\n    }\n\n    func set(_ key: String, rate: Int, timeInterval: TimeInterval) throws {\n        guard rate > 0, timeInterval > 0 else {\n            throw AirshipErrors.error(\"Rate and time interval must be greater than 0\")\n        }\n\n        var newState = RateLimiterState(\n            rate: rate,\n            timeInterval: timeInterval,\n            hits: []\n        )\n        // Reserve rate + 1 capacity. We prune after adding a value so it should only ever grow by 1 more than the rate.\n        newState.hits.reserveCapacity(rate + 1)\n        self.limiters[key] = newState\n    }\n\n    func nextAvailable(_ keys: Set<String>) -> TimeInterval {\n        keys.reduce(0.0) { maxDelay, key in\n            guard case let .overLimit(delay)? = status(key) else {\n                return maxDelay\n            }\n            return max(maxDelay, delay)\n        }\n    }\n\n    func trackIfWithinLimit(_ keys: Set<String>) -> Bool {\n        guard !keys.isEmpty else {\n            return true\n        }\n\n        // Check first\n        for key in keys {\n            if case .overLimit? = status(key) {\n                return false\n            }\n        }\n\n        let now = date.now\n        keys.forEach { track($0, now: now) }\n        return true\n    }\n\n    private func status(_ key: String) -> Status? {\n        guard var limiter = self.limiters[key] else {\n            AirshipLogger.debug(\"No rule for key \\(key)\")\n            return nil\n        }\n\n        let now = date.now\n\n        // Save the struct back with pruned hits\n        limiter.prune(now: now)\n        self.limiters[key] = limiter\n\n        let count = limiter.hits.count\n        guard count >= limiter.rate else {\n            return .withinLimit(limiter.rate - count)\n        }\n\n        let oldestHitIndex = count - limiter.rate\n\n        guard oldestHitIndex >= 0, oldestHitIndex < limiter.hits.count else {\n            AirshipLogger.error(\"Rate limiter index check failed for key \\(key). Count: \\(count), Rate: \\(limiter.rate)\")\n            return .overLimit(limiter.timeInterval)\n        }\n\n        let gate = limiter.hits[oldestHitIndex]\n        let wait = limiter.timeInterval - now.timeIntervalSince(gate)\n        return .overLimit(max(wait, 0))\n    }\n\n    private func track(_ key: String, now: Date) {\n        guard var limiter = self.limiters[key] else {\n            AirshipLogger.debug(\"No rule for key \\(key)\")\n            return\n        }\n\n        // Append and then prune the state\n        limiter.hits.append(now)\n        limiter.prune(now: now)\n\n        // Save the updated struct back\n        self.limiters[key] = limiter\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/Worker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n/// Worker that handles queuing tasks and performing the actual work\nactor Worker {\n    private let workContinuation: AsyncStream<PendingRequest>.Continuation\n    private let workStream: AsyncStream<PendingRequest>\n    private var pending: [PendingRequest] = []\n    private var inProgress: Set<PendingRequest> = Set()\n\n    private var tasks: Set<Task<Void, any Error>> = Set()\n    private var nextPendingID: Int = 0\n\n    private static let initialBackOff: TimeInterval = 30.0\n    private static let maxBackOff: TimeInterval = 120.0\n\n    let workID: String\n    private let conditionsMonitor: WorkConditionsMonitor\n    private let rateLimiter: WorkRateLimiter\n    private let backgroundTasks: any WorkBackgroundTasksProtocol\n    private let workHandler: (AirshipWorkRequest) async throws -> AirshipWorkResult\n    private let notificationCenter: NotificationCenter = NotificationCenter.default\n\n    init(\n        workID: String,\n        conditionsMonitor: WorkConditionsMonitor,\n        rateLimiter: WorkRateLimiter,\n        backgroundTasks: any WorkBackgroundTasksProtocol,\n        workHandler: @escaping (AirshipWorkRequest) async throws ->\n            AirshipWorkResult\n    ) {\n        self.workID = workID\n        self.conditionsMonitor = conditionsMonitor\n        self.rateLimiter = rateLimiter\n        self.backgroundTasks = backgroundTasks\n        self.workHandler = workHandler\n\n        (self.workStream, self.workContinuation) = AsyncStream<PendingRequest>.airshipMakeStreamWithContinuation()\n    }\n\n    deinit {\n        workContinuation.finish()\n        tasks.forEach { $0.cancel() }\n        tasks.removeAll()\n    }\n\n    func addWork(request: AirshipWorkRequest) {\n        guard request.workID == self.workID else {\n            AirshipLogger.error(\"Invalid request: \\(request.workID)\")\n            return\n        }\n\n        var queueWork = false\n        switch request.conflictPolicy {\n        case .append:\n            queueWork = true\n\n        case .replace:\n            pending.removeAll()\n            tasks.forEach { $0.cancel() }\n            tasks.removeAll()\n            queueWork = true\n\n        case .keepIfNotStarted:\n            queueWork = Set(pending).subtracting(inProgress).isEmpty\n        }\n\n        if queueWork {\n            let pendingID = nextPendingID\n            nextPendingID += 1\n            let pendingRequest = PendingRequest(id: pendingID, request: request)\n            pending.append(pendingRequest)\n            workContinuation.yield(pendingRequest)\n        }\n    }\n\n    func run() async {\n        for await next in self.workStream {\n            let task: Task<Void, any Error> = Task { [weak self] in\n                var attempt = 1\n                while await self?.isValidRequest(next) == true {\n                    let cancellableValueHolder: CancellableValueHolder<Task<Void, any Error>> = CancellableValueHolder { task in\n                        task.cancel()\n                    }\n\n                    await withTaskCancellationHandler { [attempt] in\n                        let task = Task { [weak self] in\n                            try Task.checkCancellation()\n                            try await self?.process(\n                                pendingRequest: next,\n                                attempt: attempt\n                            ) {\n                                cancellableValueHolder.cancel()\n                            }\n                        }\n\n                        cancellableValueHolder.value = task\n                        try? await task.result.get()\n                    } onCancel: {\n                        cancellableValueHolder.cancel()\n                    }\n                    attempt += 1\n                }\n            }\n\n            tasks.insert(task)\n            try? await task.result.get()\n            tasks.remove(task)\n        }\n    }\n\n    private func isValidRequest(_ pendingRequest: PendingRequest) -> Bool {\n        return self.pending.contains(pendingRequest)\n    }\n\n    private func removeRequest(_ pendingRequest: PendingRequest) {\n        self.pending.removeAll { request in\n            request.id == pendingRequest.id\n        }\n    }\n\n    private func process(\n        pendingRequest: PendingRequest,\n        attempt: Int,\n        onCancel: @escaping @Sendable () -> Void\n    ) async throws {\n        let canonicalID = \"\\(workID)(\\(pendingRequest.id))\"\n        try await prepare(pendingRequest: pendingRequest)\n        try Task.checkCancellation()\n\n        let backgroundTask = try await backgroundTasks.beginTask(\"Airship: \\(canonicalID)\") {\n            onCancel()\n        }\n\n        var result: AirshipWorkResult = .failure\n\n        inProgress.insert(pendingRequest)\n        do {\n            result = try await self.workHandler(pendingRequest.request)\n        } catch {\n            AirshipLogger.debug(\"Failed to execute work \\(canonicalID): \\(error)\")\n        }\n        inProgress.remove(pendingRequest)\n\n        if result == .success {\n            // Success\n            AirshipLogger.trace(\"Work \\(canonicalID) finished\")\n            self.removeRequest(pendingRequest)\n            backgroundTask.cancel()\n        } else {\n            AirshipLogger.trace(\"Work \\(canonicalID) failed\")\n            backgroundTask.cancel()\n            try Task.checkCancellation()\n            let backOff = min(\n                Worker.maxBackOff,\n                Double(attempt) * Worker.initialBackOff\n            )\n\n            AirshipLogger.trace(\"Work \\(canonicalID) backing off for \\(backOff) seconds.\")\n            let cancellable = self.notificationCenter\n                .publisher(\n                    for: AppStateTracker.didEnterBackgroundNotification\n                )\n                .first()\n                .sink { _ in\n                    onCancel()\n                }\n\n            try await Self.sleep(backOff)\n            cancellable.cancel()\n        }\n    }\n\n    func calculateBackgroundWaitTime(\n        maxTime: TimeInterval\n    ) async -> TimeInterval {\n        guard let pending = self.pending.first else {\n            return 0.0\n        }\n\n        return await calculateBackgroundWaitTime(\n            workRequest: pending.request,\n            maxTime: maxTime\n        )\n    }\n\n\n    private func calculateBackgroundWaitTime(\n        workRequest: AirshipWorkRequest,\n        maxTime: TimeInterval\n    ) async -> TimeInterval {\n        guard\n            await self.conditionsMonitor.checkConditions(\n                workRequest: workRequest\n            ),\n            let rateLimitIDs = workRequest.rateLimitIDs,\n            !rateLimitIDs.isEmpty\n        else {\n            return 0.0\n        }\n\n        let wait = await self.rateLimiter.nextAvailable(rateLimitIDs)\n\n        if wait > maxTime {\n            return 0.0\n        }\n        return wait\n    }\n\n    private func prepare(pendingRequest: PendingRequest) async throws {\n        let workRequest = pendingRequest.request\n\n        if workRequest.initialDelay > 0 {\n            let timeSinceRequest = Date().timeIntervalSince(pendingRequest.date)\n            if timeSinceRequest < workRequest.initialDelay {\n                try await Self.sleep(workRequest.initialDelay - timeSinceRequest)\n            }\n        }\n\n        guard let rateLimitIDs = workRequest.rateLimitIDs, !rateLimitIDs.isEmpty else {\n            await self.conditionsMonitor.awaitConditions(\n                workRequest: workRequest\n            )\n            return\n        }\n\n        repeat {\n            let rateLimit = await rateLimiter.nextAvailable(\n                rateLimitIDs\n            )\n            if rateLimit > 0 {\n                try await Self.sleep(rateLimit)\n            }\n            await self.conditionsMonitor.awaitConditions(\n                workRequest: workRequest\n            )\n        } while await !rateLimiter.trackIfWithinLimit(rateLimitIDs)\n    }\n\n    private static func sleep(_ time: TimeInterval) async throws {\n        guard time > 0 else { return }\n        let sleep = UInt64(time * 1_000_000_000)\n        try await Task.sleep(nanoseconds: sleep)\n    }\n\n    fileprivate struct PendingRequest: Equatable, Sendable, Hashable {\n        let id: Int\n        let request: AirshipWorkRequest\n        let date: Date = Date()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Source/WrappingLayout.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n/// Wrapping layout will attempt to wrap items with a specified max items per line when parent width\n/// is constrained. Display can break when parent height is exceeded - especially in landscape or when excessive\n/// item padding is specified.\ninternal struct WrappingLayout: Layout {\n    /// View constraints to apply\n    var viewConstraints: ViewConstraints\n\n    /// Minimum number of lines to display\n    var minLines: Int\n    private static let defaultMinLines: Int = 1\n\n    /// Spacing applied around each item\n    var itemSpacing: CGFloat\n    private static let defaultItemSpacing: CGFloat = 0\n\n    /// Spacing applied for each wrapped line\n    var lineSpacing: CGFloat\n    private static let defaultLineSpacing: CGFloat = 0\n\n    /// Maximum number of items to display per line\n    var maxItemsPerLine: Int\n    private static let defaultMaxItemsPerLine: Int = 11\n\n    init(\n        viewConstraints: ViewConstraints,\n        minLines: Int? = Self.defaultMinLines,\n        itemSpacing: CGFloat? = Self.defaultItemSpacing,\n        lineSpacing: CGFloat? = Self.defaultLineSpacing,\n        maxItemsPerLine: Int? = Self.defaultMaxItemsPerLine\n    ) {\n        self.viewConstraints = viewConstraints\n        self.minLines = minLines ?? Self.defaultMinLines\n        self.itemSpacing = itemSpacing ?? Self.defaultItemSpacing\n        self.lineSpacing = lineSpacing ?? Self.defaultLineSpacing\n        self.maxItemsPerLine = maxItemsPerLine ?? Self.defaultMaxItemsPerLine\n    }\n\n    \n    func sizeThatFits(\n        proposal: ProposedViewSize,\n        subviews: Subviews,\n        cache: inout ()\n    ) -> CGSize {\n        guard !subviews.isEmpty else {\n            AirshipLogger.debug(\"WrappingLayout - subviews are empty. Returning zero size.\")\n            return .zero\n        }\n\n        // Get the maximum width and height of the subviews\n        let itemSizes = subviews.map { $0.sizeThatFits(.unspecified) }\n        let itemWidth = itemSizes.map { $0.width }.max() ?? 0\n        let itemHeight = itemSizes.map { $0.height }.max() ?? 0\n\n        let totalItems = subviews.count\n\n        // Determine the maximum width from viewConstraints or proposal\n        let maxWidth = viewConstraints.width ?? proposal.width ?? viewConstraints.maxWidth ?? .infinity\n\n        // Calculate the number of items per line\n        let itemsInLine = calculateItemsInLine(\n            totalItems: totalItems,\n            maxItemsPerLine: maxItemsPerLine,\n            itemWidth: itemWidth,\n            itemSpacing: itemSpacing,\n            maxWidth: maxWidth\n        )\n\n        let linesNeeded = calculateLinesNeeded(totalItems: totalItems, itemsInLine: itemsInLine)\n\n        // Calculate total height\n        let totalHeight = calculateTotalHeight(linesNeeded: linesNeeded, itemHeight: itemHeight, lineSpacing: lineSpacing)\n\n        // Apply viewConstraints maxHeight if available\n        let finalHeight = min(totalHeight, viewConstraints.maxHeight ?? totalHeight)\n        let finalWidth = min(maxWidth, viewConstraints.maxWidth ?? maxWidth)\n\n        return CGSize(width: finalWidth, height: finalHeight)\n    }\n\n    func placeSubviews(\n        in bounds: CGRect,\n        proposal: ProposedViewSize,\n        subviews: Subviews,\n        cache: inout ()\n    ) {\n        guard !subviews.isEmpty else {\n            AirshipLogger.debug(\"WrappingLayout - subviews are empty.\")\n            return\n        }\n\n        // Get the maximum width and height of the subviews\n        let itemSizes = subviews.map { $0.sizeThatFits(.unspecified) }\n        let itemWidth = itemSizes.map { $0.width }.max() ?? 0\n        let itemHeight = itemSizes.map { $0.height }.max() ?? 0\n\n        let totalItems = subviews.count\n\n        // Use bounds width and height directly\n        let availableWidth = bounds.width\n        let availableHeight = bounds.height\n\n        // Calculate the number of items per line\n        let itemsInLine = calculateItemsInLine(\n            totalItems: totalItems,\n            maxItemsPerLine: maxItemsPerLine,\n            itemWidth: itemWidth,\n            itemSpacing: itemSpacing,\n            maxWidth: availableWidth\n        )\n\n        let linesNeeded = calculateLinesNeeded(totalItems: totalItems, itemsInLine: itemsInLine)\n\n        // Calculate total content height\n        let totalContentHeight = calculateTotalHeight(linesNeeded: linesNeeded, itemHeight: itemHeight, lineSpacing: lineSpacing)\n\n        // Adjust yPosition to center content vertically\n        var yPosition = (bounds.minY + (availableHeight - totalContentHeight) / 2.0).safeValue ?? bounds.minY\n\n        var currentIndex = 0\n\n        for _ in 0..<linesNeeded {\n            let itemsInThisLine = min(itemsInLine, totalItems - currentIndex)\n            let totalLineWidth = CGFloat(itemsInThisLine) * itemWidth + CGFloat(itemsInThisLine - 1) * itemSpacing\n\n            // Center the line within the available width\n            var xPosition = (bounds.minX + (availableWidth - totalLineWidth) / 2.0).safeValue ?? bounds.minX\n\n            for _ in 0..<itemsInThisLine {\n                if currentIndex >= subviews.count { break }\n                let subview = subviews[currentIndex]\n                let subviewProposal = ProposedViewSize(width: itemWidth, height: itemHeight)\n                subview.place(\n                    at: CGPoint(x: xPosition, y: yPosition),\n                    anchor: .topLeading,\n                    proposal: subviewProposal\n                )\n                xPosition += itemWidth + itemSpacing\n                currentIndex += 1\n            }\n            yPosition += itemHeight + lineSpacing\n        }\n    }\n\n    // MARK: - Utilities\n    private func calculateLinesNeeded(\n        totalItems: Int,\n        itemsInLine: Int\n    ) -> Int {\n        let safeItemsInLine = itemsInLine > 0 ? itemsInLine : 1\n        return max(minLines, Int(ceil(Double(totalItems) / Double(safeItemsInLine))))\n    }\n\n    private func calculateTotalHeight(\n        linesNeeded: Int,\n        itemHeight: CGFloat,\n        lineSpacing: CGFloat\n    ) -> CGFloat {\n        return CGFloat(linesNeeded) * itemHeight + CGFloat(linesNeeded - 1) * lineSpacing\n    }\n\n    private func calculateItemsInLine(\n        totalItems: Int,\n        maxItemsPerLine: Int,\n        itemWidth: CGFloat,\n        itemSpacing: CGFloat,\n        maxWidth: CGFloat\n    ) -> Int {\n        var itemsInLine = min(totalItems, maxItemsPerLine)\n        while (CGFloat(itemsInLine) * itemWidth + CGFloat(itemsInLine - 1) * itemSpacing) > maxWidth && itemsInLine > 1 {\n            itemsInLine -= 1\n        }\n        return itemsInLine\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/APNSEnvironmentTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class APNSEnvironmentTest: XCTestCase {\n\n    func testProductionProfileParsing() throws {\n        let profilePath = Bundle(for: self.classForCoder).path(\n            forResource: \"production-embedded\",\n            ofType: \"mobileprovision\"\n        )\n\n        let isProduction = try APNSEnvironment.isProduction(profilePath)\n        XCTAssertTrue(isProduction)\n    }\n\n    func testDevelopmentProfileParsing() throws {\n        let profilePath = Bundle(for: self.classForCoder).path(\n            forResource: \"development-embedded\",\n            ofType: \"mobileprovision\"\n        )\n\n        let isProduction = try APNSEnvironment.isProduction(profilePath)\n        XCTAssertFalse(isProduction)\n    }\n\n    func testMissingEmbeddedProfile() {\n        do {\n            _ = try APNSEnvironment.isProduction(nil)\n            XCTFail()\n        } catch {}\n    }\n\n    func testInvalidEmbeddedProfilePath() {\n        do {\n            _ = try APNSEnvironment.isProduction(\"Neat\")\n            XCTFail()\n        } catch {}\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AccountEventTemplateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class AccountEventTemplateTest: XCTestCase {\n\n    func testRegistered() {\n        let event = CustomEvent(accountTemplate: .registered)\n        XCTAssertEqual(\"registered_account\", event.eventName)\n        XCTAssertEqual(\"account\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testLoggedIn() {\n        let event = CustomEvent(accountTemplate: .loggedIn)\n        XCTAssertEqual(\"logged_in\", event.eventName)\n        XCTAssertEqual(\"account\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testLoggedOut() {\n        let event = CustomEvent(accountTemplate: .loggedOut)\n        XCTAssertEqual(\"logged_out\", event.eventName)\n        XCTAssertEqual(\"account\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testProperties() {\n        let properties = CustomEvent.AccountProperties(\n            category: \"some category\",\n            type: \"some type\",\n            isLTV: true,\n            userID: \"some user\"\n        )\n\n        let event = CustomEvent(accountTemplate: .loggedOut, properties: properties)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"user_id\": \"some user\",\n            \"category\": \"some category\",\n            \"type\": \"some type\",\n            \"ltv\": true\n        ]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ActionArgumentsTest.swift",
    "content": "///* Copyright Airship and Contributors */\n//\n//import XCTest\n//\n//@testable import AirshipCore\n//\n//final class ActionArgumentTest: XCTestCase {\n//\n//    override func setUpWithError() throws {\n//        try super.setUpWithError()\n//    }\n//\n//    /*\n//     * Test the argumentsWithValue:withSituation factory method sets the values correctly\n//     */\n//    func testArgumentsWithValue() {\n//        var args = ActionArguments(value: \"some-value\", situation: .backgroundPush)\n//        XCTAssertEqual(\"some-value\", args.value as! String)\n//        XCTAssertEqual(.backgroundPush, args.situation)\n//\n//        args = ActionArguments(value: \"whatever\", situation: .manualInvocation)\n//        XCTAssertEqual(.manualInvocation, args.situation)\n//        \n//        args = ActionArguments(value: \"whatever\", situation: .foregroundPush)\n//        XCTAssertEqual(.foregroundPush, args.situation)\n//        \n//        args = ActionArguments(value: \"whatever\", situation: .launchedFromPush)\n//        XCTAssertEqual(.launchedFromPush, args.situation)\n//\n//        args = ActionArguments(value: \"whatever\", situation: .webViewInvocation)\n//        XCTAssertEqual(.webViewInvocation, args.situation)\n//        \n//        args = ActionArguments(value: \"whatever\", situation: .foregroundInteractiveButton)\n//        XCTAssertEqual(.foregroundInteractiveButton, args.situation)\n//        \n//        args = ActionArguments(value: \"whatever\", situation: .backgroundInteractiveButton)\n//        XCTAssertEqual(.backgroundInteractiveButton, args.situation)\n//        \n//        args = ActionArguments(value: \"whatever\", situation: .automation)\n//        XCTAssertEqual(.automation, args.situation)\n//    }\n//    \n//    /*\n//     * Test the override of the description method\n//     */\n//    /*\n//    func testDescription() {\n//        let args = ActionArguments(value: \"foo\", situation: .manualInvocation)\n//        let expectedDescription = \"UAActionArgument with situation: Manual Invocation, value: \\(args.value!)\"\n//        XCTAssertEqual(args.description, expectedDescription)\n//    }\n//    */\n//}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ActionRegistryTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ActionRegistryTest: AirshipBaseTest {\n\n    private var registry: DefaultAirshipActionRegistry!\n\n    @MainActor\n    override func setUpWithError() throws {\n        try super.setUpWithError()\n        self.registry = DefaultAirshipActionRegistry()\n    }\n\n    @MainActor\n    func testRegisterAction() async {\n        let action = EmptyAction()\n\n        registry.registerEntry(\n            names: [\"name\", \"alias\", \"another-name\"],\n            entry: ActionEntry(action: action)\n        )\n\n        await validateIsRegistered(\n            action: action,\n            names: [\"name\", \"alias\", \"another-name\"]\n        )\n    }\n\n    @MainActor\n    func testRegisterActionClosure() async {\n        let action = EmptyAction()\n\n        var called = 0\n        registry.registerEntry(\n            names: [\"name\", \"alias\", \"another-name\"]\n        ) {\n            called += 1\n            return ActionEntry(action: action)\n        }\n\n        await validateIsRegistered(\n            action: action,\n            names: [\"name\", \"alias\", \"another-name\"]\n        )\n\n        XCTAssertEqual(called, 1)\n    }\n\n    @MainActor\n    func testRegisterActionNameConflict() async {\n        let action = EmptyAction()\n        let anotherAction = EmptyAction()\n\n        registry.registerEntry(\n            names: [\"name\", \"alias\", \"another-name\"],\n            entry: ActionEntry(action: action)\n        )\n\n        await validateIsRegistered(\n            action: action,\n            names: [\"name\", \"alias\", \"another-name\"]\n        )\n\n        registry.registerEntry(\n            names: [\"name\", \"what\"],\n            entry: ActionEntry(action: anotherAction)\n        )\n\n\n        await validateIsRegistered(\n            action: anotherAction,\n            names: [\"name\", \"what\"]\n        )\n\n        // First entry should still be registered under 'alias' and 'another-name'\n        await validateIsRegistered(\n            action: action,\n            names: [\"alias\", \"another-name\"]\n        )\n    }\n\n    @MainActor\n    func testUpdateAction() async {\n        let action = EmptyAction()\n        let other = EmptyAction()\n\n        registry.registerEntry(\n            names: [\"name\", \"alias\", \"another-name\"],\n            entry: ActionEntry(action: action)\n        )\n\n        registry.updateEntry(name: \"alias\", action: other)\n        await validateIsRegistered(\n            action: other,\n            names: [\"name\", \"alias\", \"another-name\"]\n        )\n    }\n\n    @MainActor\n    func testUpdateActionForSituation() async {\n        let action = EmptyAction()\n        let other = EmptyAction()\n\n        registry.registerEntry(\n            names: [\"name\", \"alias\", \"another-name\"],\n            entry: ActionEntry(action: action)\n        )\n\n        registry.updateEntry(name: \"alias\", situation: .manualInvocation, action: other)\n        await validateIsRegistered(\n            action: action,\n            names: [\"name\", \"alias\", \"another-name\"]\n        )\n\n        let entry = registry.entry(name: \"name\")!\n        XCTAssertTrue(other === entry.action(situation: .manualInvocation))\n    }\n\n    func validateIsRegistered (\n        action: AirshipAction,\n        names: [String]\n    ) async {\n        for name in names {\n            let entry = await self.registry.entry(name: name)\n            XCTAssertTrue(entry?.action === action)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AddCustomEventActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AddCustomEventActionTest: AirshipBaseTest {\n\n    private let analytics = TestAnalytics()\n    private var airship: TestAirshipInstance!\n    private let action = AddCustomEventAction()\n\n    @MainActor\n    override func setUpWithError() throws {\n        airship = TestAirshipInstance()\n        self.airship.components = [analytics]\n        self.airship.makeShared()\n    }\n    \n    // Test custom event action accepts all the situations.\n    func testAcceptsArgumentsAllSituations() async throws {\n        let dict = [\"event_name\": \"event name\"]\n        await verifyAcceptsArguments(withValue: try AirshipJSON.wrap(dict), shouldAccept: true)\n    }\n    \n    func testAcceptsNewEventNameAndValueAllSituations() async throws {\n        let dict = [\"name\": \"name\"]\n        await verifyAcceptsArguments(withValue: try AirshipJSON.wrap(dict), shouldAccept: true)\n    }\n    \n    \n    // Test that it rejects invalid argument values.\n    func testAcceptsArgumentsNo() async throws {\n        let invalidDict = [\"invalid_key\": \"event name\"]\n        await verifyAcceptsArguments(withValue: try AirshipJSON.wrap(invalidDict), shouldAccept: false)\n        \n        await verifyAcceptsArguments(withValue: AirshipJSON.null, shouldAccept: false)\n        await verifyAcceptsArguments(withValue: try AirshipJSON.wrap(\"not a dictionary\"), shouldAccept: false)\n        await verifyAcceptsArguments(withValue: AirshipJSON.object([:]), shouldAccept: false)\n        await verifyAcceptsArguments(withValue: AirshipJSON.array([]), shouldAccept: false)\n    }\n\n    \n    // Test performing the action actually creates and adds the event from a NSNumber event value.\n    func testPerformNSNumber() async throws {\n        let dict: [String: Any] = [\n            \"event_name\": \"event name\",\n            \"transaction_id\": \"transaction ID\",\n            \"event_value\": 123.45,\n            \"interaction_type\": \"interaction type\",\n            \"interaction_id\": \"interaction ID\"\n        ]\n        \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(dict),\n            situation: .manualInvocation\n        )\n        \n        try await verifyPerformWithArgs(args: args, expectedResult: nil)\n        \n        XCTAssertEqual(1, self.analytics.customEvents.count);\n        let event  = try XCTUnwrap(self.analytics.customEvents.first)\n        XCTAssertEqual(\"event name\", event.eventName);\n        XCTAssertEqual(\"transaction ID\", event.transactionID);\n        XCTAssertEqual(\"interaction type\", event.interactionType);\n        XCTAssertEqual(\"interaction ID\", event.interactionID);\n        XCTAssertEqual(123.45, event.eventValue);\n    }\n    \n    \n     // Test performing the action actually creates and adds the event from a string\n     // event value.\n    func testPerformString() async throws {\n        let dict: [String : Any] = [\n            \"event_name\": \"event name\",\n            \"transaction_id\": \"transaction ID\",\n            \"event_value\": \"123.45\",\n            \"interaction_type\": \"interaction type\",\n            \"interaction_id\": \"interaction ID\"\n        ]\n    \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(dict),\n            situation: .manualInvocation\n        )\n        \n        try await verifyPerformWithArgs(args: args, expectedResult: nil)\n    \n        XCTAssertEqual(1, self.analytics.customEvents.count);\n        let event  = try XCTUnwrap(self.analytics.customEvents.first)\n        XCTAssertEqual(\"event name\", event.eventName);\n        XCTAssertEqual(\"transaction ID\", event.transactionID);\n        XCTAssertEqual(\"interaction type\", event.interactionType);\n        XCTAssertEqual(\"interaction ID\", event.interactionID);\n        XCTAssertEqual(123.45, event.eventValue);\n    }\n    \n   func testPerformPrefersNewNames() async throws {\n       let dict: [String : Any] = [\n        \"name\": \"new event name\",\n        \"event_name\": \"event name\",\n        \"transaction_id\": \"transaction ID\",\n        \"event_value\": \"123.45\",\n        \"value\": \"321.21\",\n        \"interaction_type\": \"interaction type\",\n        \"interaction_id\": \"interaction ID\"\n       ]\n   \n       let args = ActionArguments(\n           value: try AirshipJSON.wrap(dict),\n           situation: .manualInvocation\n       )\n       \n       try await verifyPerformWithArgs(args: args, expectedResult: nil)\n   \n       XCTAssertEqual(1, self.analytics.customEvents.count);\n       let event  = try XCTUnwrap(self.analytics.customEvents.first)\n       XCTAssertEqual(\"new event name\", event.eventName);\n       XCTAssertEqual(\"transaction ID\", event.transactionID);\n       XCTAssertEqual(\"interaction type\", event.interactionType);\n       XCTAssertEqual(\"interaction ID\", event.interactionID);\n       XCTAssertEqual(321.21, event.eventValue);\n   }\n    \n    \n     // Test perform with invalid event name should result in error.\n    func testPerformInvalidCustomEventName() async throws {\n        let dict: [String: Any] = [\n            \"event_name\": \"\",\n            \"transaction_id\": \"transaction ID\",\n            \"event_value\": \"123.45\",\n            \"interaction_type\": \"interaction type\",\n            \"interaction_id\": \"interaction ID\"\n        ]\n    \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(dict),\n            situation: .manualInvocation\n        )\n        \n        do {\n            try await verifyPerformWithArgs(args: args, expectedResult: nil)\n            XCTFail(\"Should throw\")\n        } catch {\n            XCTAssertNotNil(error)\n        }\n        \n    }\n    \n    \n     // Test auto filling in the interaction ID and type from an mcrap when left\n     // empty.\n    func testInteractionEmptyMCRAP() async throws {\n        \n        let eventPayload = [\n            \"event_name\": \"event name\",\n            \"transaction_id\": \"transaction ID\",\n            \"event_value\": \"123.45\"\n        ]\n    \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(eventPayload),\n            situation: .manualInvocation,\n            metadata: [ActionArguments.inboxMessageIDMetadataKey: \"message ID\"]\n        )\n\n        try await verifyPerformWithArgs(args: args, expectedResult: nil)\n    \n        XCTAssertEqual(1, self.analytics.customEvents.count);\n        let event  = try XCTUnwrap(self.analytics.customEvents.first)\n        XCTAssertEqual(\"event name\", event.eventName);\n        XCTAssertEqual(\"transaction ID\", event.transactionID);\n        XCTAssertEqual(\"ua_mcrap\", event.interactionType);\n        XCTAssertEqual(\"message ID\", event.interactionID);\n        XCTAssertEqual(123.45, event.eventValue);\n    }\n    \n     // Test not modifying the interaction ID and type when it is set and triggered\n    // from an mcrap.\n    func testInteractionSetMCRAP() async throws {\n        let eventPayload = [\n            \"event_name\": \"event name\",\n            \"transaction_id\": \"transaction ID\",\n            \"event_value\": \"123.45\",\n            \"interaction_type\": \"interaction type\",\n            \"interaction_id\": \"interaction ID\"\n        ]\n    \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(eventPayload),\n            situation: .manualInvocation,\n            metadata: [ActionArguments.inboxMessageIDMetadataKey: \"message ID\"]\n        )\n        \n        try await verifyPerformWithArgs(args: args, expectedResult: nil)\n    \n        XCTAssertEqual(1, self.analytics.customEvents.count);\n        let event  = try XCTUnwrap(self.analytics.customEvents.first)\n        XCTAssertEqual(\"event name\", event.eventName);\n        XCTAssertEqual(\"transaction ID\", event.transactionID);\n        XCTAssertEqual(\"interaction type\", event.interactionType);\n        XCTAssertEqual(\"interaction ID\", event.interactionID);\n        XCTAssertEqual(123.45, event.eventValue);\n    }\n    \n    \n    \n    // Test setting the conversion send ID on the event if the action arguments has\n    // a push payload meta data.\n    func testSetConversionSendIdFromPush() async throws {\n        let dict: [String: String] = [\n            \"event_name\": \"event name\",\n            \"transaction_id\": \"transaction ID\",\n            \"event_value\": \"123.45\",\n            \"interaction_type\": \"interaction type\",\n            \"interaction_id\": \"interaction ID\"\n        ]\n    \n        let notification: [String: Any] = [\n            \"_\": \"send ID\",\n            \"com.urbanairship.metadata\": \"send metadata\",\n            \"apns\": [\n                \"alert\": \"oh hi\"\n            ]\n        ]\n    \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(dict),\n            situation: .manualInvocation,\n            metadata: [ActionArguments.pushPayloadJSONMetadataKey: try AirshipJSON.wrap(notification)]\n        )\n    \n        try await verifyPerformWithArgs(args: args, expectedResult: nil)\n    \n        XCTAssertEqual(1, self.analytics.customEvents.count);\n        let event  = try XCTUnwrap(self.analytics.customEvents.first)\n        XCTAssertEqual(\"event name\", event.eventName);\n        XCTAssertEqual(\"transaction ID\", event.transactionID);\n        XCTAssertEqual(\"interaction type\", event.interactionType);\n        XCTAssertEqual(\"interaction ID\", event.interactionID);\n        XCTAssertEqual(\"send ID\", event.data[\"conversion_send_id\"] as! String);\n        XCTAssertEqual(\"send metadata\", event.data[\"conversion_metadata\"] as! String);\n        XCTAssertEqual(123.45, event.eventValue);\n    }\n    \n    // Test settings properties on a custom event.\n    //\n    func testSetCustomProperties() async throws {\n        let dict: [String : Any] =  [\n            \"event_name\": \"event name\",\n            \"properties\":\n                [\n                    \"array\": [\"string\", \"another string\"],\n                    \"bool\": true,\n                    \"number\": 123,\n                    \"string\": \"string value\"\n                ] as [String : Any]\n        ]\n    \n    \n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(dict),\n            situation: .manualInvocation\n        )\n\n        try await verifyPerformWithArgs(args: args, expectedResult: nil)\n    \n        XCTAssertEqual(1, self.analytics.customEvents.count);\n        let event  = try XCTUnwrap(self.analytics.customEvents.first)\n        XCTAssertEqual(\"event name\", event.eventName);\n        XCTAssertEqual(try! AirshipJSON.wrap(dict[\"properties\"]), try! AirshipJSON.wrap(event.properties));\n    }\n    \n    \n    // Helper method to verify accepts arguments.\n    func verifyAcceptsArguments(\n        withValue value: AirshipJSON,\n        shouldAccept: Bool\n    ) async {\n        \n        let situations = [ActionSituation.webViewInvocation,\n                          .foregroundPush,\n                          .backgroundPush,\n                          .launchedFromPush,\n                          .manualInvocation,\n                          .foregroundInteractiveButton,\n                          .backgroundInteractiveButton,\n                          .automation]\n        \n        for situation in situations {\n            let args = ActionArguments(value: value, situation: situation)\n            var accepts = false\n            do {\n                try await verifyPerformWithArgs(args: args)\n                accepts = true\n            } catch {\n                accepts = false\n            }\n            \n            if (shouldAccept) {\n                XCTAssertTrue(accepts, \"Add custom event action should accept value  \\(String(describing: value)) in situation \\(situation)\");\n            } else {\n                XCTAssertFalse(accepts, \"Add custom event action should not accept value \\(String(describing: value)) in situation \\(situation)\");\n            }\n        }\n    }\n    \n    // Helper method to verify perform.\n    func verifyPerformWithArgs(args: ActionArguments, expectedResult: AirshipJSON? = nil) async throws {\n    \n        let result = try await self.action.perform(arguments: args)\n          \n        XCTAssertEqual(result, expectedResult, \"Result status should match expected result status.\");\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AddTagsActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AddTagsActionTest: XCTestCase {\n\n    private let simpleValue = [\"tag\", \"another tag\"]\n    private let complexValue: [String: AnyHashable] = [\n        \"channel\": [\n            \"channel_tag_group\": [\"channel_tag_1\", \"channel_tag_2\"],\n            \"other_channel_tag_group\": [\"other_channel_tag_1\"]\n        ],\n        \"named_user\": [\n            \"named_user_tag_group\": [\"named_user_tag_1\", \"named_user_tag_2\"],\n            \"other_named_user_tag_group\": [\"other_named_user_tag_1\"]\n        ],\n        \"device\": [ \"tag\", \"another_tag\"]\n    ]\n\n    private let channel = TestChannel()\n    private let contact = TestContact()\n    private var action: AddTagsAction!\n\n    override func setUp() async throws {\n        action = AddTagsAction(\n            channel: { [channel] in return channel },\n            contact: { [contact] in return contact }\n        )\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(value: try! AirshipJSON.wrap(simpleValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in validSituations {\n            let args = ActionArguments(value: try! AirshipJSON.wrap(complexValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(value: try! AirshipJSON.wrap(simpleValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testPerformSimple() async throws {\n        self.channel.tags = [\"foo\", \"bar\"]\n        \n        let updates = await self.action.tagMutations\n\n        _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try! AirshipJSON.wrap(simpleValue),\n                situation: .manualInvocation\n            )\n        )\n        \n        var iterator = updates.makeAsyncIterator()\n        let tagsAction = await iterator.next()\n        XCTAssertEqual(TagActionMutation.channelTags(simpleValue), tagsAction)\n\n        XCTAssertEqual(\n            [\"foo\", \"bar\", \"tag\", \"another tag\"],\n            channel.tags\n        )\n    }\n\n    func testPerformComplex() async throws {\n        self.channel.tags = [\"foo\", \"bar\"]\n\n        let tagGroupsSet = self.expectation(description: \"tagGroupsSet\")\n        tagGroupsSet.expectedFulfillmentCount = 2\n\n        self.channel.tagGroupEditor = TagGroupsEditor { updates in\n            let expected = [\n                TagGroupUpdate(\n                    group: \"channel_tag_group\",\n                    tags: [\"channel_tag_1\", \"channel_tag_2\"],\n                    type: .add\n                ),\n                TagGroupUpdate(\n                    group: \"other_channel_tag_group\",\n                    tags: [\"other_channel_tag_1\"],\n                    type: .add\n                )\n            ]\n\n            XCTAssertEqual(Set(expected), Set(updates))\n            tagGroupsSet.fulfill()\n        }\n\n        self.contact.tagGroupEditor = TagGroupsEditor { updates in\n            let expected = [\n                TagGroupUpdate(\n                    group: \"named_user_tag_group\",\n                    tags: [\"named_user_tag_1\", \"named_user_tag_2\"],\n                    type: .add\n                ),\n                TagGroupUpdate(\n                    group: \"other_named_user_tag_group\",\n                    tags: [\"other_named_user_tag_1\"],\n                    type: .add\n                )\n            ]\n\n            XCTAssertEqual(Set(expected), Set(updates))\n            tagGroupsSet.fulfill()\n        }\n        \n        let updates = await self.action.tagMutations\n\n        _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try! AirshipJSON.wrap(complexValue),\n                situation: .manualInvocation\n            )\n        )\n        \n        var expectedActions: [TagActionMutation] = [\n            .channelTagGroups([\"channel_tag_group\": [\"channel_tag_1\", \"channel_tag_2\"], \"other_channel_tag_group\": [\"other_channel_tag_1\"]]),\n            .contactTagGroups([\"named_user_tag_group\": [\"named_user_tag_1\", \"named_user_tag_2\"], \"other_named_user_tag_group\": [\"other_named_user_tag_1\"]]),\n            .channelTags([\"tag\", \"another_tag\"]),\n        ]\n        \n        for await item in updates {\n            XCTAssertEqual(item, expectedActions.removeFirst())\n            if (expectedActions.isEmpty) {\n                break\n            }\n        }\n\n        XCTAssertEqual(\n            [\"foo\", \"bar\", \"tag\", \"another_tag\"],\n            channel.tags\n        )\n        await fulfillment(of: [tagGroupsSet])\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipAnalyticFeedTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipCore\n\nfinal class AirshipAnalyticFeedTest: XCTestCase {\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var privacyManager: TestPrivacyManager!\n\n    override func setUp() async throws {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: dataStore,\n            config: .testConfig(),\n            defaultEnabledFeatures: .all\n        )\n    }\n\n    func testFeed() async throws {\n        let feed = makeFeed()\n        var updates = await feed.updates.makeAsyncIterator()\n\n        let result = await feed.notifyEvent(.screen(screen: \"foo\"))\n        XCTAssertTrue(result)\n\n        let next = await updates.next()\n        XCTAssertEqual(next, .screen(screen: \"foo\"))\n    }\n\n    func testFeedAnalyticsDisabled() async throws {\n        let feed = makeFeed()\n        privacyManager.disableFeatures(.analytics)\n        var updates = await feed.updates.makeAsyncIterator()\n\n        var result = await feed.notifyEvent(.screen(screen: \"foo\"))\n        XCTAssertFalse(result)\n\n        privacyManager.enableFeatures(.analytics)\n        result = await feed.notifyEvent(.screen(screen: \"bar\"))\n        XCTAssertTrue(result)\n\n        let next = await updates.next()\n        XCTAssertEqual(next, .screen(screen: \"bar\"))\n    }\n\n    func testFeedDisabled() async throws {\n        let feed = makeFeed(enabled: false)\n        let result = await feed.notifyEvent(.screen(screen: \"foo\"))\n        XCTAssertFalse(result)\n    }\n\n    private func makeFeed(enabled: Bool = true) -> AirshipAnalyticsFeed  {\n        return AirshipAnalyticsFeed(privacyManager: privacyManager, isAnalyticsEnabled: enabled)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipAsyncChannelTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class AirshipAsyncChannelTest: XCTestCase {\n\n    private let channel = AirshipAsyncChannel<Int>()\n\n    func testSingleListener() async throws {\n        var stream = await channel.makeStream().makeAsyncIterator()\n\n        var sent: [Int] = []\n        for i in 0...5 {\n            sent.append(i)\n            await channel.send(i)\n        }\n\n        var received: [Int] = []\n        for _ in 0...5 {\n            received.append(await stream.next()!)\n        }\n\n        XCTAssertEqual(sent, received)\n    }\n\n    func testMultipleListeners() async throws {\n        let streams = [\n            await channel.makeStream().makeAsyncIterator(),\n            await channel.makeStream().makeAsyncIterator(),\n            await channel.makeStream().makeAsyncIterator()\n        ]\n\n        var sent: [Int] = []\n        for i in 0...5 {\n            sent.append(i)\n            await channel.send(i)\n        }\n\n        for var stream in streams {\n            var received: [Int] = []\n            for _ in 0...5 {\n                received.append(await stream.next()!)\n            }\n            XCTAssertEqual(sent, received)\n        }\n    }\n\n    func testNonIsolatedDedupingStreamMapped() async throws {\n        var updates = channel.makeNonIsolatedDedupingStream(\n            initialValue: {\n                \"1\"\n            },\n            transform: { int in\n                \"\\(int)\"\n            }\n        ).makeAsyncIterator()\n\n        // Wait for first so we know the task is setup to listen for changes\n        let first = await updates.next()\n        XCTAssertEqual(first, \"1\")\n\n        await channel.send(2)\n        await channel.send(2)\n        await channel.send(2)\n\n        await channel.send(3)\n        await channel.send(3)\n        await channel.send(4)\n\n        var received: [String] = []\n        for _ in 0...2 {\n            received.append(await updates.next()!)\n        }\n\n        XCTAssertEqual([\"2\", \"3\", \"4\"], received)\n    }\n\n    func testNonIsolatedDedupingStream() async throws {\n        var updates = channel.makeNonIsolatedDedupingStream(\n            initialValue: {\n                1\n            }\n        ).makeAsyncIterator()\n        await channel.send(1)\n\n        // Wait for first so we know the task is setup to listen for changes\n        let first = await updates.next()\n        XCTAssertEqual(first, 1)\n\n        await channel.send(1)\n        await channel.send(1)\n\n        await channel.send(2)\n        await channel.send(2)\n        await channel.send(3)\n\n        var received: [Int] = []\n        for _ in 0...1 {\n            received.append(await updates.next()!)\n        }\n\n        XCTAssertEqual([2, 3], received)\n    }\n\n    func testNonIsolatedStreamMapped() async throws {\n        var updates = channel.makeNonIsolatedStream(\n            initialValue: {\n                \"1\"\n            },\n            transform: { int in\n                \"\\(int)\"\n            }\n        ).makeAsyncIterator()\n\n        // Wait for first so we know the task is setup to listen for changes\n        let first = await updates.next()\n        XCTAssertEqual(first, \"1\")\n\n        await channel.send(1)\n        await channel.send(2)\n        await channel.send(2)\n\n        await channel.send(3)\n        await channel.send(4)\n\n        var received: [String] = []\n        for _ in 0...4 {\n            received.append(await updates.next()!)\n        }\n\n        XCTAssertEqual([\"1\", \"2\", \"2\", \"3\", \"4\"], received)\n    }\n\n    func testNonIsolatedStream() async throws {\n        var updates = channel.makeNonIsolatedStream(\n            initialValue: { 1 }\n        ).makeAsyncIterator()\n\n        // Wait for first so we know the task is setup to listen for changes\n        let first = await updates.next()\n        XCTAssertEqual(first, 1)\n\n        await channel.send(1)\n        await channel.send(1)\n        await channel.send(1)\n\n        await channel.send(2)\n        await channel.send(2)\n        await channel.send(3)\n\n        var received: [Int] = []\n        for _ in 0...5 {\n            received.append(await updates.next()!)\n        }\n\n        XCTAssertEqual([1, 1, 1, 2, 2, 3], received)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipBase64Test.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nclass AirshipBase64Test: XCTestCase {\n\n    // Examples from Wikipedia page on base64 encoding\n    // http://en.wikipedia.org/wiki/Base64\n    // These test strings were encoded/decoded with Python 2.7.2 base64 lib to check for errors\n    // Note the period (.), it is part of the encoding, as well as the '=' sign, it is used\n    // for padding.\n\n    //>>> one = base64.b64encode('pleasure.')\n    //>>> print(one)\n    //cGxlYXN1cmUu\n    //>>> one == 'cGxlYXN1cmUu'\n    //True\n    //>>> one = base64.b64encode('leasure.')\n    //>>> one == 'bGVhc3VyZS4='\n    //True\n    //>>> one = base64.b64encode('easure.')\n    //>>> one == 'ZWFzdXJlLg=='\n    //True\n    //>>>\n\n    let pleasure = \"pleasure.\"\n    let pleasure64 = \"cGxlYXN1cmUu\"\n\n    let leasure = \"leasure.\"\n    let leasure64 = \"bGVhc3VyZS4=\"\n\n    let easure = \"easure.\"\n    let easure64 = \"ZWFzdXJlLg==\"\n    let easure64PartiallyPadded = \"ZWFzdXJlLg=\"\n    let easure64Unpadded = \"ZWFzdXJlLg\"\n    let easure64Newline = \"ZWFzdXJlLg\\n\"\n    let easure64InterstitialNewline = \"ZWFzdXJlLg=\\n=\"\n\n    func testBase64Encode() {\n        let dataToEncode = pleasure.data(using: .ascii)!\n        let encoded = AirshipBase64.string(from: dataToEncode)\n        XCTAssertTrue(encoded == pleasure64)\n\n        let dataToEncode2 = leasure.data(using: .ascii)!\n        let encoded2 = AirshipBase64.string(from: dataToEncode2)\n        XCTAssertTrue(encoded2 == leasure64)\n\n        let dataToEncode3 = easure.data(using: .ascii)!\n        let encoded3 = AirshipBase64.string(from: dataToEncode3)\n        XCTAssertTrue(encoded3 == easure64)\n    }\n\n    func testBase64Decode() {\n        var decodedData = AirshipBase64.data(from: pleasure64)!\n        var decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == pleasure)\n\n        decodedData = AirshipBase64.data(from: leasure64)!\n        decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == leasure)\n\n        decodedData = AirshipBase64.data(from: easure64)!\n        decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == easure)\n\n        decodedData = AirshipBase64.data(from: easure64PartiallyPadded)!\n        decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == easure)\n\n        decodedData = AirshipBase64.data(from: easure64Unpadded)!\n        decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == easure)\n\n        decodedData = AirshipBase64.data(from: easure64Newline)!\n        decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == easure)\n\n        decodedData = AirshipBase64.data(from: easure64InterstitialNewline)!\n        decodedString = String(data: decodedData, encoding: .ascii)!\n        XCTAssertTrue(decodedString == easure)\n    }\n\n    func testBase64DecodeInvalidString() {\n        XCTAssertNoThrow(AirshipBase64.data(from: \".\"))\n        XCTAssertNoThrow(AirshipBase64.data(from: \" \"))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipBaseTest.swift",
    "content": "///* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\npublic let testExpectationTimeOut = 10.0\n\nclass AirshipBaseTest: XCTestCase {\n    \n    /**\n     * A preference data store unique to this test. The dataStore is created\n     * lazily when first used.\n     */\n    lazy var dataStore: PreferenceDataStore = {\n        return PreferenceDataStore(appKey: UUID().uuidString)\n    }()\n    \n    /**\n     * A preference airship with unique appkey/secret. A runtime config is created\n     * lazily when first used.\n     */\n    lazy var config: RuntimeConfig = RuntimeConfig.testConfig()    \n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipCacheTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AirshipCacheTest: XCTestCase {\n\n    private let date = UATestDate(offset: 0, dateOverride: Date())\n    private let coreData: UACoreData = CoreDataAirshipCache.makeCoreData(\n        appKey: UUID().uuidString,\n        inMemory: true\n    )!\n    private var cache: CoreDataAirshipCache!\n\n    override func setUpWithError() throws {\n        self.cache = CoreDataAirshipCache(\n            coreData: coreData,\n            appVersion: \"some-app-version\",\n            sdkVersion: \"some-sdk-version\",\n            date: self.date\n        )\n    }\n\n    func testCacheTTL() async throws {\n        await self.cache.setCachedValue(\"cache value\", key: \"some key\", ttl: 10.0)\n        var value: String? = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertEqual(\"cache value\", value)\n\n        date.offset += 9.9\n        value = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertEqual(\"cache value\", value)\n\n        date.offset += 0.1\n        value = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertNil(value)\n    }\n\n    func testCacheNil() async throws {\n        await self.cache.setCachedValue(\"cache value\", key: \"some key\", ttl: 10.0)\n        var value: String? = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertEqual(\"cache value\", value)\n\n        value = nil\n        await self.cache.setCachedValue(value, key: \"some key\", ttl: 10.0)\n        XCTAssertNil(value)\n    }\n\n    func testOverwriteCache() async throws {\n        await self.cache.setCachedValue(\"cache value\", key: \"some key\", ttl: 10.0)\n        await self.cache.setCachedValue(\"some other cache value\", key: \"some key\", ttl: 10.0)\n\n        let value: String? = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertEqual(\"some other cache value\", value)\n    }\n\n    func testCache() async throws {\n        await self.cache.setCachedValue(\"some value\", key: \"some key\", ttl: 10.0)\n        await self.cache.setCachedValue(\"some other value\", key: \"some other key\", ttl: 10.0)\n\n        var value: String? = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertEqual(\"some value\", value)\n\n        value = await self.cache.getCachedValue(key: \"some other key\")\n        XCTAssertEqual(\"some other value\", value)\n\n        value = await self.cache.getCachedValue(key: \"some null key\")\n        XCTAssertNil(value)\n    }\n\n    func testOverwriteCacheClearedSDKVersionChange() async throws {\n        await self.cache.setCachedValue(\"cache value\", key: \"some key\", ttl: 10.0)\n\n        self.cache = CoreDataAirshipCache(\n            coreData: coreData,\n            appVersion: \"some-app-version\",\n            sdkVersion: \"some-other-sdk-version\",\n            date: self.date\n        )\n\n        let value: String? = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertNil(value)\n    }\n\n    func testOverwriteCacheClearedAppVersionChange() async throws {\n        await self.cache.setCachedValue(\"cache value\", key: \"some key\", ttl: 10.0)\n\n        self.cache = CoreDataAirshipCache(\n            coreData: coreData,\n            appVersion: \"some-other-app-version\",\n            sdkVersion: \"some-sdk-version\",\n            date: self.date\n        )\n\n        let value: String? = await self.cache.getCachedValue(key: \"some key\")\n        XCTAssertNil(value)\n    }\n}\n\npublic enum TestAirshipCoreDataCache {\n    static func makeCache(date: AirshipDateProtocol) -> AirshipCache {\n        return CoreDataAirshipCache(\n            coreData: CoreDataAirshipCache.makeCoreData(appKey: UUID().uuidString)!,\n            appVersion: \"version\",\n            sdkVersion: \"sdk\",\n            date: date\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipColorTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\nimport SwiftUI\n\n@Suite struct AirshipColorTests {\n\n    @Test\n    func testResolveNativeColorAARRGGBB() throws {\n        let color = try #require(AirshipColor.resolveNativeColor(\"#FFFF0000\"))\n\n        var red: CGFloat = 0.0\n        var green: CGFloat = 0.0\n        var blue: CGFloat = 0.0\n        var alpha: CGFloat = 0.0\n\n        color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n\n        #expect(red == 1.0)\n        #expect(green == 0)\n        #expect(blue == 0)\n        #expect(alpha == 1.0)\n\n        #expect(try AirshipColor.hexString(color) == \"#FFFF0000\")\n\n        // Lowercase and no hash\n        let color2 = try #require(AirshipColor.resolveNativeColor(\"8000ff00\"))\n        color2.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n\n        #expect(red == 0)\n        #expect(green == 1.0)\n        #expect(blue == 0)\n        #expect(Double(alpha).isApproximately(0.5, within: 0.01))\n\n        #expect(try AirshipColor.hexString(color2) == \"#8000FF00\")\n    }\n\n    @Test\n    func testResolveNativeColorRRGGBB() throws {\n        let color = try #require(AirshipColor.resolveNativeColor(\"FF0000\"))\n\n        var red: CGFloat = 0.0\n        var green: CGFloat = 0.0\n        var blue: CGFloat = 0.0\n        var alpha: CGFloat = 0.0\n\n        color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n\n        #expect(red == 1.0)\n        #expect(green == 0)\n        #expect(blue == 0)\n        #expect(alpha == 1.0)\n\n        // Lowercase and no hash\n        let color2 = try #require(AirshipColor.resolveNativeColor(\"00ff80\"))\n        color2.getRed(&red, green: &green, blue: &blue, alpha: &alpha)\n\n        #expect(red == 0)\n        #expect(green == 1.0)\n        #expect(Double(blue).isApproximately(0.5, within: 0.01))\n        #expect(alpha == 1.0)\n    }\n\n    @Test\n    func testResolveNativeColorInvalid() {\n        #expect(AirshipColor.resolveNativeColor(\"Not a color\") == nil)\n        #expect(AirshipColor.resolveNativeColor(\"#FF00\") == nil) // Too short\n        #expect(AirshipColor.resolveNativeColor(\"#FFFF00FF00\") == nil) // Too long\n    }\n    \n    @Test\n    func testStringToColorExtension() {\n        let color = \"#FFFF0000\".airshipToColor()\n        // verifying it returns a valid View/Color\n        #expect(String(describing: color).count > 0)\n    }\n}\n\nextension Double {\n    func isApproximately(_ other: Double, within tolerance: Double) -> Bool {\n        return abs(self - other) <= tolerance\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipConfigTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class AirshipConfigTest: XCTestCase {\n    func testEmptyConfig() {\n        let config = AirshipConfig()\n        verifyDefaultConfig(config)\n    }\n\n    func testConfigFromEmptyJSON() throws {\n        let config: AirshipConfig = try AirshipJSON.wrap([:]).decode()\n        verifyDefaultConfig(config)\n    }\n\n    func testOldPlistFormat() throws {\n        let path = Bundle(for: self.classForCoder).path(\n            forResource: \"AirshipConfig-Valid-Legacy\",\n            ofType: \"plist\"\n        )\n        let config = try AirshipConfig(fromPlist: path!)\n        XCTAssertEqual(config.productionAppKey, \"0A00000000000000000000\")\n        XCTAssertEqual(config.productionAppSecret, \"0A00000000000000000000\")\n        XCTAssertEqual(config.developmentAppKey, \"0A00000000000000000000\")\n        XCTAssertEqual(config.developmentAppSecret, \"0A00000000000000000000\")\n        XCTAssertEqual(config.developmentLogLevel, .verbose)\n        XCTAssertEqual(config.inProduction, true)\n    }\n\n    func testPlistParsing() throws {\n        let path = Bundle(for: self.classForCoder).path(\n            forResource: \"AirshipConfig-Valid\",\n            ofType: \"plist\"\n        )\n\n        let config = try AirshipConfig(fromPlist: path!)\n        XCTAssertEqual(config.productionAppKey, \"0A00000000000000000000\")\n        XCTAssertEqual(config.productionAppSecret, \"0A00000000000000000000\")\n        XCTAssertEqual(config.developmentAppKey, \"0A00000000000000000000\")\n        XCTAssertEqual(config.developmentAppSecret, \"0A00000000000000000000\")\n        XCTAssertEqual(config.developmentLogLevel, .error)\n        XCTAssertEqual(config.developmentLogPrivacyLevel, .private)\n        XCTAssertEqual(config.productionLogLevel, .verbose)\n        XCTAssertEqual(config.productionLogPrivacyLevel, .public)\n        XCTAssertTrue(config.isChannelCreationDelayEnabled)\n        XCTAssertTrue(config.isExtendedBroadcastsEnabled)\n        XCTAssertEqual(config.inProduction, true)\n        XCTAssertEqual(config.enabledFeatures, [.inAppAutomation, .push])\n        XCTAssertTrue(config.resetEnabledFeatures)\n        XCTAssertEqual(config.messageCenterStyleConfig, \"ValidUAMessageCenterDefaultStyle\")\n    }\n\n    private func verifyDefaultConfig(\n        _ config: AirshipConfig,\n        file: StaticString = #filePath,\n        line: UInt = #line\n    ) {\n        XCTAssertNil(config.developmentAppKey)\n        XCTAssertNil(config.developmentAppSecret)\n        XCTAssertNil(config.productionAppKey)\n        XCTAssertNil(config.productionAppSecret)\n        XCTAssertNil(config.defaultAppKey)\n        XCTAssertNil(config.defaultAppSecret)\n        XCTAssertNil(config.logHandler)\n        XCTAssertEqual(config.site, .us)\n        XCTAssertEqual(config.developmentLogLevel, .debug)\n        XCTAssertEqual(config.developmentLogPrivacyLevel, .private)\n        XCTAssertEqual(config.productionLogLevel, .error)\n        XCTAssertEqual(config.productionLogPrivacyLevel, .private)\n        XCTAssertNil(config.inProduction)\n        XCTAssertTrue(config.isAutomaticSetupEnabled)\n        XCTAssertTrue(config.isAnalyticsEnabled)\n        XCTAssertFalse(config.clearUserOnAppRestore)\n        XCTAssertNil(config.urlAllowList)\n        XCTAssertNil(config.urlAllowListScopeJavaScriptInterface)\n        XCTAssertNil(config.urlAllowListScopeOpenURL)\n        XCTAssertFalse(config.clearNamedUserOnAppRestore)\n        XCTAssertTrue(config.isChannelCaptureEnabled)\n        XCTAssertFalse(config.isChannelCreationDelayEnabled)\n        XCTAssertFalse(config.isExtendedBroadcastsEnabled)\n        XCTAssertTrue(config.requestAuthorizationToUseNotifications)\n        XCTAssertTrue(config.requireInitialRemoteConfigEnabled)\n        XCTAssertFalse(config.autoPauseInAppAutomationOnLaunch)\n        XCTAssertFalse(config.resetEnabledFeatures)\n        XCTAssertFalse(config.isWebViewInspectionEnabled)\n        XCTAssertNil(config.connectionChallengeResolver)\n        XCTAssertNil(config.restoreChannelID)\n        XCTAssertNil(config.itunesID)\n        XCTAssertNil(config.messageCenterStyleConfig)\n        XCTAssertEqual(config.enabledFeatures, .all)\n        XCTAssertNil(config.initialConfigURL)\n        XCTAssertFalse(config.useUserPreferredLocale)\n        XCTAssertTrue(config.restoreMessageCenterOnReinstall)\n    }\n\n    func testValidation() throws {\n        var config = AirshipConfig()\n\n        // Not set\n        verifyThrows {\n            try config.validateCredentials(inProduction: true)\n        }\n        verifyThrows {\n            try config.validateCredentials(inProduction: false)\n        }\n\n        // App key & secret match\n        config.developmentAppKey = \"0A00000000000000000000\"\n        config.developmentAppSecret = \"0A00000000000000000000\"\n        verifyThrows {\n            try config.validateCredentials(inProduction: false)\n        }\n\n        // Should not throw\n        config.developmentAppSecret = \"0B00000000000000000000\"\n        try config.validateCredentials(inProduction: false)\n\n        // Production still not set\n        verifyThrows {\n            try config.validateCredentials(inProduction: true)\n        }\n\n        // Invalid key\n        config.productionAppKey = \"NOT VALID\"\n        config.productionAppSecret = \"0A00000000000000000000\"\n        verifyThrows {\n            try config.validateCredentials(inProduction: true)\n        }\n\n        // Invalid secret\n        config.productionAppKey = \"0A00000000000000000000\"\n        config.productionAppSecret = \"NOT VALID\"\n        verifyThrows {\n            try config.validateCredentials(inProduction: true)\n        }\n\n        // Both invalid\n        config.productionAppKey = \"NOT VALID KEY\"\n        config.productionAppSecret = \"NOT VALID\"\n        verifyThrows {\n            try config.validateCredentials(inProduction: true)\n        }\n\n        // Both valid\n        config.productionAppKey = \"0A00000000000000000000\"\n        config.productionAppSecret = \"0B00000000000000000000\"\n        try config.validateCredentials(inProduction: true)\n    }\n\n    private func verifyThrows(\n        block: () throws -> Void,\n        file: StaticString = #filePath,\n        line: UInt = #line\n    ) {\n        do {\n            try block()\n            XCTFail()\n        } catch {}\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipContactTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\nimport Combine\nimport Foundation\n\nclass AirshipContactTest: XCTestCase {\n    private let channel: TestChannel = TestChannel()\n    private let apiClient: TestContactSubscriptionListAPIClient = TestContactSubscriptionListAPIClient()\n    private let contactChannelsProvider: TestContactChannelsProvider = TestContactChannelsProvider()\n    private let apiChannel: TestChannelsListAPIClient = TestChannelsListAPIClient()\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(\n        notificationCenter: NotificationCenter()\n    )\n    private let date: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let audienceOverridesProvider: DefaultAudienceOverridesProvider = DefaultAudienceOverridesProvider()\n    private let contactManager: TestContactManager = TestContactManager()\n    private var contactQueue: AirshipAsyncSerialQueue!\n    private var contact: DefaultAirshipContact!\n    private var privacyManager: DefaultAirshipPrivacyManager!\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n    private var subscriptionProvider: SubscriptionListProviderProtocol!\n\n    override func setUp() async throws {\n        self.privacyManager = await DefaultAirshipPrivacyManager(\n            dataStore: self.dataStore,\n            config: self.config,\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n        \n        self.subscriptionProvider = SubscriptionListProvider(\n            audienceOverrides: self.audienceOverridesProvider,\n            apiClient: self.apiClient,\n            date: self.date,\n            privacyManager: self.privacyManager)\n\n        self.channel.identifier = \"channel id\"\n        await setupContact()\n        self.contact.airshipReady()\n        await self.waitOnContactQueue() // waits for the initial setup task\n    }\n\n    @MainActor\n    func setupContact()  {\n        contactQueue = AirshipAsyncSerialQueue(priority: .high)\n\n        self.contact =  DefaultAirshipContact(\n            dataStore: self.dataStore,\n            config: config,\n            channel: self.channel,\n            privacyManager: self.privacyManager,\n            contactChannelsProvider: self.contactChannelsProvider, \n            subscriptionListProvider: subscriptionProvider,\n            date: self.date,\n            notificationCenter: self.notificationCenter,\n            audienceOverridesProvider: self.audienceOverridesProvider,\n            contactManager: self.contactManager,\n            serialQueue: contactQueue\n        )\n    }\n\n    func testMigrateNamedUser() async throws {\n        await self.verifyOperations([])\n\n        let attributeDate = AirshipDateFormatter.string(fromDate: self.date.now, format: .isoDelimitter)\n\n        let attributePayload = [\n            \"action\": \"remove\",\n            \"key\": \"some-attribute\",\n            \"timestamp\": attributeDate\n        ]\n\n        let attributeMutation = AttributePendingMutations(mutationsPayload: [\n            attributePayload\n        ])\n        let attributeData = try! NSKeyedArchiver.archivedData(\n            withRootObject: [attributeMutation],\n            requiringSecureCoding: true\n        )\n\n        dataStore.setObject(\n            attributeData,\n            forKey: DefaultAirshipContact.legacyPendingAttributesKey\n        )\n\n        let tagMutation = TagGroupsMutation(\n            adds: [\"some-group\": Set([\"tag\"])],\n            removes: nil,\n            sets: nil\n        )\n        let tagData = try! NSKeyedArchiver.archivedData(\n            withRootObject: [tagMutation],\n            requiringSecureCoding: true\n        )\n        dataStore.setObject(tagData, forKey: DefaultAirshipContact.legacyPendingTagGroupsKey)\n\n        self.dataStore.setObject(\n            \"named-user\",\n            forKey: DefaultAirshipContact.legacyNamedUserKey\n        )\n\n        await setupContact()\n\n        await verifyOperations(\n            [\n                .identify(\"named-user\"),\n                .update(\n                    tagUpdates: [ TagGroupUpdate(group: \"some-group\", tags: [\"tag\"], type: .add) ],\n                    attributeUpdates: [ AttributeUpdate.remove(attribute: \"some-attribute\", date: AirshipDateFormatter.date(fromISOString: attributeDate)!) ],\n                    subscriptionListsUpdates: nil\n                )\n            ]\n        )\n    }\n\n    /// Test skip calling identify on the legacy named user if we already have contact data\n    func testSkipMigrateLegacyNamedUser() async throws {\n        let tagMutation = TagGroupsMutation(\n            adds: [\"some-group\": Set([\"tag\"])],\n            removes: nil,\n            sets: nil\n        )\n        let tagData = try! NSKeyedArchiver.archivedData(\n            withRootObject: [tagMutation],\n            requiringSecureCoding: true\n        )\n        dataStore.setObject(tagData, forKey: DefaultAirshipContact.legacyPendingTagGroupsKey)\n\n        self.dataStore.setObject(\n            \"named-user\",\n            forKey: DefaultAirshipContact.legacyNamedUserKey\n        )\n\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some contact ID\", isStable: false, namedUserID: nil)\n        )\n\n        await setupContact()\n\n        let _ = await contact.namedUserID\n\n        await verifyOperations(\n            [\n                .update(\n                    tagUpdates: [ TagGroupUpdate(group: \"some-group\", tags: [\"tag\"], type: .add) ],\n                    attributeUpdates: nil,\n                    subscriptionListsUpdates: nil\n                )\n            ]\n        )\n    }\n\n    @MainActor\n    func testChannelCreatedEnqueuesUpdateTask() async throws {\n        notificationCenter.post(\n            name: AirshipNotifications.ChannelCreated.name\n        )\n\n        await verifyOperations([.resolve])\n    }\n\n    func testStableVerifiedContactID() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: false, namedUserID: nil)\n        )\n\n        let contactManager = self.contactManager\n        let channel = self.channel\n        let date = self.date.now\n\n        let payloadTaskStarted = self.expectation(description: \"payload task started\")\n\n        let payloadTask = Task {\n            payloadTaskStarted.fulfill()\n            return await channel.channelPayload\n        }\n\n        await fulfillment(of: [payloadTaskStarted])\n        await contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(\n                contactID: \"some-other-contact-id\",\n                isStable: false,\n                namedUserID: nil,\n                resolveDate: date.advanced(by: -DefaultAirshipContact.defaultVerifiedContactIDAge)\n            )\n        )\n\n        await contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(\n                contactID: \"some-stable-contact-id\",\n                isStable: true,\n                namedUserID: nil,\n                resolveDate: date.advanced(by: -DefaultAirshipContact.defaultVerifiedContactIDAge)\n            )\n        )\n        await contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-stable-verified-contact-id\", isStable: true, namedUserID: nil, resolveDate: date)\n        )\n\n        let payload = await payloadTask.value\n        XCTAssertEqual(\"some-stable-verified-contact-id\", payload.channel.contactID)\n        await verifyOperations([.verify(date)])\n    }\n\n    func testStableVerifiedContactIDAlreadyUpToDate() async throws {\n        let date = self.date.now\n\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil, resolveDate: date)\n        )\n\n        let channel = self.channel\n\n        let payload = await channel.channelPayload\n        XCTAssertEqual(\"some-contact-id\", payload.channel.contactID)\n        await verifyOperations([])\n    }\n\n    @MainActor\n    func testMaxAgeStableVerifiedContactID() async throws {\n        self.config.updateRemoteConfig(\n            RemoteConfig(\n                contactConfig: .init(\n                    foregroundIntervalMilliseconds: nil,\n                    channelRegistrationMaxResolveAgeMilliseconds: 1000\n                )\n            )\n        )\n\n        let date = self.date.now\n\n        // Ensure stale age > 1 s max-age to avoid race\n        let staleDate = date.advanced(by: -2)\n\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil, resolveDate: staleDate)\n        )\n\n        let contactManager = self.contactManager\n        let channel = self.channel\n\n        let payloadTaskStarted = self.expectation(description: \"payload task started\")\n\n        let payloadTask = Task { @MainActor in\n            payloadTaskStarted.fulfill()\n            return await channel.channelPayload\n        }\n\n        await fulfillment(of: [payloadTaskStarted])\n\n        await contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-stable-verified-contact-id\", isStable: true, namedUserID: nil, resolveDate: date)\n        )\n\n        let payload = await payloadTask.value\n        XCTAssertEqual(\"some-stable-verified-contact-id\", payload.channel.contactID)\n        await verifyOperations([.verify(date)])\n    }\n\n    func testExtendRegistrationPaylaodOnChannelCreate() async throws {\n        self.channel.identifier = nil\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: false, namedUserID: nil)\n        )\n        XCTAssertEqual(1, self.channel.extenders.count)\n        let payload = await self.channel.channelPayload\n        XCTAssertEqual(\"some-contact-id\", payload.channel.contactID)\n    }\n\n    func testExtendRegistrationPayloadGeneratesContactID() async throws {\n        self.channel.identifier = nil\n        await self.contactManager.clearGenerateDefaultContactIDCalledFlag()\n        _ = await self.channel.channelPayload\n        let generated = await self.contactManager.generateDefaultContactIDCalled\n        XCTAssertTrue(generated)\n    }\n\n    func testForegroundResolves() async throws {\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve])\n    }\n\n    func testRefreshContactChannelsOnActive() async throws {\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        XCTAssertTrue(contactChannelsProvider.refreshedCalled)\n    }\n\n    @MainActor\n    func testRefreshContactChannelsOnPush() async throws {\n        _ = await self.contact.receivedRemoteNotification(\n            try! AirshipJSON.wrap(\n                [\n                    \"com.urbanairship.contact.update\": NSNumber(value: true)\n                ]\n            )\n        )\n\n        XCTAssertTrue(contactChannelsProvider.refreshedCalled)\n    }\n\n    func testForegroundSkipsResolves() async throws {\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve])\n\n        // Default is 60 seconds\n        self.date.offset += DefaultAirshipContact.defaultForegroundResolveInterval - 1.0\n\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve])\n\n        self.date.offset += 1\n\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve, .resolve])\n    }\n\n    func testForegroundSkipsResolvesConfigValue() async throws {\n        await self.config.updateRemoteConfig(\n            RemoteConfig(\n                contactConfig: .init(\n                    foregroundIntervalMilliseconds: 1000,\n                    channelRegistrationMaxResolveAgeMilliseconds: nil\n                )\n            )\n        )\n\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve])\n\n        self.date.offset += 0.5\n\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve])\n\n        self.date.offset += 0.5\n\n        notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification\n        )\n\n        await verifyOperations([.resolve, .resolve])\n    }\n\n    func testIdentify() async throws {\n        self.contact.identify(\"cool user 1\")\n        await self.verifyOperations([.identify(\"cool user 1\")])\n    }\n\n    func testReset() async throws {\n        self.contact.reset()\n        await self.verifyOperations([.reset])\n    }\n\n\n    func testRegisterEmail() async throws {\n        let options = EmailRegistrationOptions.options(\n            transactionalOptedIn: Date(),\n            properties: [\"interests\": \"newsletter\"],\n            doubleOptIn: true\n        )\n        self.contact.registerEmail(\n            \"ua@airship.com\",\n            options: options\n        )\n\n        await self.verifyOperations([.registerEmail(address: \"ua@airship.com\", options: options)])\n    }\n\n    func testRegisterSMS() async throws {\n        let options = SMSRegistrationOptions.optIn(senderID: \"28855\")\n        self.contact.registerSMS(\n            \"15035556789\",\n            options: options\n        )\n\n        await self.verifyOperations([.registerSMS(msisdn: \"15035556789\", options: options)])\n    }\n\n    func testRegisterOpen() async throws {\n        let options = OpenRegistrationOptions.optIn(\n            platformName: \"my_platform\",\n            identifiers: [\"model\": \"4\"]\n        )\n\n        self.contact.registerOpen(\n            \"open_address\",\n            options: options\n        )\n\n        await self.verifyOperations([.registerOpen(address: \"open_address\", options: options)])\n    }\n\n    func testAssociateChannel() async throws {\n        self.contact.associateChannel(\n            \"some-channel-id\",\n            type: .email\n        )\n        await self.verifyOperations([.associateChannel(\n            channelID: \"some-channel-id\",\n            channelType: .email\n        )])\n    }\n\n    func testEdits() async throws {\n        self.contact.editTagGroups() { editor in\n            editor.add([\"neat\"], group: \"cool\")\n        }\n\n        self.contact.editAttributes() { editor in\n            editor.set(int: 1, attribute: \"one\")\n        }\n\n        self.contact.editSubscriptionLists() { editor in\n            editor.subscribe(\"some id\", scope: .app)\n        }\n    }\n\n    @MainActor\n    func testResolveSkippedContactsDisabled() async throws {\n        self.privacyManager.disableFeatures(.contacts)\n        notificationCenter.post(name: AirshipNotifications.ChannelCreated.name)\n        await self.verifyOperations([.reset])\n    }\n\n    @MainActor\n    func testTagsAndAttributesSkippedContactsDisabled() async throws {\n        self.privacyManager.disableFeatures(.contacts)\n\n        self.contact.editTagGroups() { editor in\n            editor.add([\"neat\"], group: \"cool\")\n        }\n\n        self.contact.editAttributes() { editor in\n            editor.set(int: 1, attribute: \"one\")\n        }\n\n        self.contact.editSubscriptionLists() { editor in\n            editor.subscribe(\"some id\", scope: .app)\n        }\n\n        await self.verifyOperations([.reset])\n    }\n\n    @MainActor\n    func testIdentifySkippedContactsDisabled() async throws {\n        self.privacyManager.disableFeatures(.contacts)\n        await self.verifyOperations([.reset])\n        self.contact.identify(\"cat\")\n        await self.verifyOperations([.reset])\n    }\n\n    @MainActor\n    func testResetOnDisableContacts() async throws {\n        self.privacyManager.disableFeatures(.contacts)\n        await self.verifyOperations([.reset])\n    }\n\n    func testFetchSubscriptionLists() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        let apiResult: [String: [ChannelScope]] = [\"neat\": [.web]]\n        let expected = apiResult\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let lists:[String: [ChannelScope]] = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n    }\n\n    func testFetchSubscriptionListsCached() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        var apiResult: [String: [ChannelScope]] = [\"neat\": [.web]]\n        var expected = apiResult\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Populate cache\n        var lists: [String: [ChannelScope]] = try await self.contact.fetchSubscriptionLists()\n\n        XCTAssertEqual(expected, lists)\n\n        apiResult = [\"something else\": [.web]]\n\n        lists = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n\n        self.date.offset += 599  // 1 second before cache should invalidate\n        lists = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n\n        self.date.offset += 1\n\n        // From api\n        expected = apiResult\n        lists = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n    }\n    \n    func testFetchSubscriptionListsReset() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        var apiResult: [String: [ChannelScope]] = [\"neat\": [.web]]\n        var expected = apiResult\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Populate cache\n        var lists: [String: [ChannelScope]] = try await self.contact.fetchSubscriptionLists()\n\n        XCTAssertEqual(expected, lists)\n\n        apiResult = [\"something else\": [.web]]\n        \n        lists = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n\n        await subscriptionProvider.refresh()\n        \n        lists = try await self.contact.fetchSubscriptionLists()\n        expected = apiResult\n        XCTAssertEqual(expected, lists)\n    }\n\n    @MainActor\n    func testFetchSubscriptionListsCachedDifferentContactID() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        var apiResult: [String: [ChannelScope]] = [\"neat\": [ChannelScope.web]]\n        var expected = apiResult\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Populate cache\n        var lists:[String: [ChannelScope]] = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n\n        apiResult = [\"something else\": [.web]]\n\n        // From cache\n        lists = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n\n\n        // Resolve a new contact ID\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-other-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-other-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // From api\n        expected = apiResult\n        lists = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n    }\n\n    func testFetchWaitsForStableContactID() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: false, namedUserID: nil)\n        )\n\n        let apiResult: [String: [ChannelScope]] = [\"neat\": [.web]]\n        let expected = apiResult\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-stable-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let contactManager = self.contactManager\n\n        DispatchQueue.main.async {\n            Task {\n                await contactManager.setCurrentContactIDInfo(\n                    ContactIDInfo(contactID: \"some-other-contact-id\", isStable: false, namedUserID: nil)\n                )\n\n                await contactManager.setCurrentContactIDInfo(\n                    ContactIDInfo(contactID: \"some-stable-contact-id\", isStable: true, namedUserID: nil)\n                )\n            }\n        }\n       \n        let lists:[String: [ChannelScope]] = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual(expected, lists)\n    }\n\n    func testNotifyRemoteLogin() async throws {\n        self.contact.notifyRemoteLogin()\n        await verifyOperations([.verify(self.date.now, required: true)])\n    }\n\n    func testFetchSubscriptionListsOverrides() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        let apiResult: [String: [ChannelScope]] = [\"neat\": [.web, .app]]\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        /// Local history\n        await self.audienceOverridesProvider.contactUpdated(\n            contactID: \"some-contact-id\",\n            tags: nil,\n            attributes: nil,\n            subscriptionLists: [\n                ScopedSubscriptionListUpdate(listId: \"neat\", type: .unsubscribe, scope: .web, date: self.date.now)\n            ], channels: []\n        )\n\n        // Pending\n        await self.contactManager.setPendingAudienceOverrides(\n            ContactAudienceOverrides(\n                subscriptionLists: [\n                    ScopedSubscriptionListUpdate(listId: \"neat\", type: .subscribe, scope: .sms, date: self.date.now)\n                ]\n            ))\n\n        let lists:[String: [ChannelScope]] = try await self.contact.fetchSubscriptionLists()\n        XCTAssertEqual([\"neat\": [.app, .sms]], lists)\n    }\n\n    func testFetchSubscriptionListsFails() async throws {\n        await self.contactManager.setCurrentContactIDInfo(\n            ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n        )\n\n        self.apiClient.fetchSubscriptionListsCallback = {\n            identifier in\n            XCTAssertEqual(\"some-contact-id\", identifier)\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        do {\n            let _ = try await self.contact.fetchSubscriptionLists()\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testAudienceOverrides() async throws {\n        let update = ContactAudienceUpdate(\n            contactID: \"some-contact-id\",\n            tags:  [\n                TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .add)\n            ],\n            attributes: [\n                AttributeUpdate(attribute: \"some attribute\", type: .set, jsonValue: \"cool\", date: self.date.now)\n            ],\n            subscriptionLists: [\n                ScopedSubscriptionListUpdate(listId: \"some list\", type: .unsubscribe, scope: .app, date: self.date.now)\n            ], contactChannels: []\n        )\n\n        let pending = ContactAudienceOverrides(\n            tags:  [\n                TagGroupUpdate(group: \"some other group\", tags: [\"tag\"], type: .add)\n            ],\n            attributes: [\n                AttributeUpdate(attribute: \"some other attribute\", type: .set, jsonValue: \"cool\", date: self.date.now)\n            ],\n            subscriptionLists: [\n                ScopedSubscriptionListUpdate(listId: \"some other list\", type: .unsubscribe, scope: .app, date: self.date.now)\n            ]\n        )\n\n        await self.contactManager.setPendingAudienceOverrides(pending)\n        await self.contactManager.dispatchAudienceUpdate(update)\n\n        let overrides = await self.audienceOverridesProvider.contactOverrides(contactID: \"some-contact-id\")\n        XCTAssertEqual(overrides.tags, update.tags! + pending.tags)\n        XCTAssertEqual(overrides.attributes, update.attributes! + pending.attributes)\n        XCTAssertEqual(overrides.subscriptionLists, update.subscriptionLists! + pending.subscriptionLists)\n    }\n\n    func testAudienceOverridesStableID() async throws {\n        let updateFoo = ContactAudienceUpdate(\n            contactID: \"foo\",\n            tags:  [\n                TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .add)\n            ],\n            attributes: [\n                AttributeUpdate(attribute: \"some attribute\", type: .set, jsonValue: \"cool\", date: self.date.now)\n            ],\n            subscriptionLists: [\n                ScopedSubscriptionListUpdate(listId: \"some list\", type: .unsubscribe, scope: .app, date: self.date.now)\n            ], contactChannels: []\n        )\n\n        let updateBar = ContactAudienceUpdate(\n            contactID: \"bar\",\n            tags:  [\n                TagGroupUpdate(group: \"some other group\", tags: [\"tag\"], type: .add)\n            ],\n            attributes: [\n                AttributeUpdate(attribute: \"some other attribute\", type: .set, jsonValue: \"cool\", date: self.date.now)\n            ],\n            subscriptionLists: [\n                ScopedSubscriptionListUpdate(listId: \"some other list\", type: .unsubscribe, scope: .app, date: self.date.now)\n            ], contactChannels: []\n        )\n\n        await self.contactManager.dispatchAudienceUpdate(updateFoo)\n        await self.contactManager.dispatchAudienceUpdate(updateBar)\n\n        let contactManager = self.contactManager\n        Task.detached(priority: .high) {\n            await contactManager.setCurrentContactIDInfo(\n                ContactIDInfo(contactID: \"foo\", isStable: false, namedUserID: nil)\n            )\n\n            await contactManager.setCurrentContactIDInfo(\n                ContactIDInfo(contactID: \"bar\", isStable: true, namedUserID: nil)\n            )\n        }\n\n        let overrides = await self.audienceOverridesProvider.contactOverrides()\n        XCTAssertEqual(overrides.tags, updateBar.tags)\n        XCTAssertEqual(overrides.attributes, updateBar.attributes)\n        XCTAssertEqual(overrides.subscriptionLists, updateBar.subscriptionLists)\n    }\n\n    @MainActor\n    func testGenerateDefaultContactInfo() async throws {\n        // Should be called on migrate if no named user ID\n        var isCalled = await self.contactManager.generateDefaultContactIDCalled\n        XCTAssertTrue(isCalled)\n\n        // Clear it\n        await self.contactManager.clearGenerateDefaultContactIDCalledFlag()\n\n        // Trigger it to be called when privacy manager enables contacts\n        self.privacyManager.disableFeatures(.all)\n        self.privacyManager.enableFeatures(.contacts)\n        await self.waitOnContactQueue()\n\n        isCalled = await self.contactManager.generateDefaultContactIDCalled\n        XCTAssertTrue(isCalled)\n    }\n\n    func testNamedUserID() async throws {\n        await self.contactManager.setCurrentNamedUserID(\"some named user\")\n        let namedUser = await self.contact.namedUserID\n        XCTAssertEqual(\"some named user\", namedUser)\n    }\n\n    @MainActor\n    func testConflictEvents() async throws {\n        let event = ContactConflictEvent(\n            tags: [:],\n            attributes: [:],\n            associatedChannels: [],\n            subscriptionLists: [:],\n            conflictingNamedUserID: \"neat\"\n        )\n\n        let expectation = XCTestExpectation()\n        let subscription = self.contact.conflictEventPublisher.sink { conflict in\n            XCTAssertEqual(event, conflict)\n            expectation.fulfill()\n        }\n\n        self.contactManager.contactUpdatesContinuation.yield(.conflict(event))\n        await fulfillment(of: [expectation])\n        subscription.cancel()\n    }\n\n    @MainActor\n    func testConflictEventNotificationCenter() async throws {\n        let event = ContactConflictEvent(\n            tags: [:],\n            attributes: [:],\n            associatedChannels: [],\n            subscriptionLists: [:],\n            conflictingNamedUserID: \"neat\"\n        )\n\n        let expectation = XCTestExpectation()\n        self.notificationCenter.addObserver(forName: AirshipNotifications.ContactConflict.name, object: nil, queue: nil) { notification in\n            XCTAssertEqual(event, notification.userInfo?[AirshipNotifications.ContactConflict.eventKey] as? ContactConflictEvent)\n            expectation.fulfill()\n        }\n\n        self.contactManager.contactUpdatesContinuation.yield(.conflict(event))\n        await fulfillment(of: [expectation])\n    }\n\n    private func verifyOperations(_ operations: [ContactOperation], file: StaticString = #filePath, line: UInt = #line) async {\n        let expectation = XCTestExpectation()\n        let contactManager = self.contactManager\n        let file = file\n        let line = line\n        self.contactQueue.enqueue {\n            let contactOperations = await contactManager.operations\n            XCTAssertEqual(operations, contactOperations, file: file, line: line)\n            expectation.fulfill()\n        }\n\n        await fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n    private func waitOnContactQueue() async {\n        let expectation = XCTestExpectation()\n        self.contactQueue.enqueue {\n            expectation.fulfill()\n        }\n\n        await fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n}\n\n\nfileprivate actor TestContactManager: ContactManagerProtocol {\n\n    private var _currentNamedUserID: String? = nil\n    private var _currentContactIDInfo: ContactIDInfo? = nil\n    private var _pendingAudienceOverrides = ContactAudienceOverrides()\n    private var _onAudienceUpdatedCallback: (@Sendable (ContactAudienceUpdate) async -> Void)?\n\n    let contactUpdates: AsyncStream<ContactUpdate>\n    let contactUpdatesContinuation: AsyncStream<ContactUpdate>.Continuation\n    let channelUpdates: AsyncStream<[ContactChannel]>\n    let channelUpdatesContinuation: AsyncStream<[ContactChannel]>.Continuation\n\n    func validateSMS(_ msisdn: String, sender: String) async throws -> Bool {\n        return true\n    }\n\n    private(set) var operations: [ContactOperation] = []\n    var generateDefaultContactIDCalled: Bool = false\n\n    init() {\n        (\n            self.contactUpdates,\n            self.contactUpdatesContinuation\n        ) = AsyncStream<ContactUpdate>.airshipMakeStreamWithContinuation()\n        (\n            self.channelUpdates,\n            self.channelUpdatesContinuation\n        ) = AsyncStream<[ContactChannel]>.airshipMakeStreamWithContinuation()\n    }\n\n    func onAudienceUpdated(\n        onAudienceUpdatedCallback: (@Sendable (AirshipCore.ContactAudienceUpdate) async -> Void)?\n    ) {\n        self._onAudienceUpdatedCallback = onAudienceUpdatedCallback\n    }\n\n    func dispatchAudienceUpdate(_ update: ContactAudienceUpdate) async {\n        await self._onAudienceUpdatedCallback!(update)\n    }\n\n    func addOperation(_ operation: ContactOperation) {\n        operations.append(operation)\n    }\n\n    func clearGenerateDefaultContactIDCalledFlag() {\n        self.generateDefaultContactIDCalled = false\n    }\n\n    func generateDefaultContactIDIfNotSet() {\n        generateDefaultContactIDCalled = true\n    }\n\n    func setCurrentNamedUserID(_ namedUserID: String) {\n        self._currentNamedUserID = namedUserID\n        self.contactUpdatesContinuation.yield(.namedUserUpdate(namedUserID))\n\n    }\n\n    func currentNamedUserID() -> String? {\n        return self._currentNamedUserID\n    }\n\n    func setEnabled(enabled: Bool) {\n\n    }\n\n    func setCurrentContactIDInfo(_ contactIDInfo: ContactIDInfo) {\n        self._currentContactIDInfo = contactIDInfo\n        self.contactUpdatesContinuation.yield(.contactIDUpdate(contactIDInfo))\n    }\n\n    func currentContactIDInfo() -> ContactIDInfo? {\n        return _currentContactIDInfo\n    }\n\n    func setPendingAudienceOverrides(_ overrides: ContactAudienceOverrides) {\n        self._pendingAudienceOverrides = overrides\n    }\n    func pendingAudienceOverrides(contactID: String) -> ContactAudienceOverrides {\n        return self._pendingAudienceOverrides\n    }\n\n    func resolveAuth(identifier: String) async throws -> String {\n        return \"\"\n    }\n\n    func authTokenExpired(token: String) async {\n\n    }\n\n    func resetIfNeeded() {\n\n        addOperation(.reset)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipDateFormatterTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nclass AirshipDateFormatterTest: XCTestCase {\n\n    private var gregorianUTC: Calendar = {\n        var calendar = Calendar(identifier: .gregorian)\n        calendar.timeZone = TimeZone(secondsFromGMT: 0)!\n        return calendar\n    }()\n    \n    func components(for date: Date) -> DateComponents {\n        return gregorianUTC.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date)\n    }\n\n    func validateDateFormatter(_ format: AirshipDateFormatter.Format, withFormatString formatString: String) {\n        guard let date = AirshipDateFormatter.date(fromISOString: formatString) else {\n            XCTFail(\"Failed to parse date from format string\")\n            return\n        }\n\n        let components = self.components(for: date)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 45)\n        XCTAssertEqual(components.second, 22)\n\n        XCTAssertEqual(formatString, AirshipDateFormatter.string(fromDate: date, format: format))\n    }\n\n    func testISODateFormatterUTC() {\n        validateDateFormatter(.iso, withFormatString: \"2020-12-15 11:45:22\")\n    }\n\n    func testISODateFormatterUTCWithDelimiter() {\n        validateDateFormatter(.isoDelimitter, withFormatString: \"2020-12-15T11:45:22\")\n    }\n\n    func testParseISO8601FromTimeStamp() {\n        // yyyy\n        var date = AirshipDateFormatter.date(fromISOString: \"2020\")!\n        var components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 1)\n        XCTAssertEqual(components.day, 1)\n        XCTAssertEqual(components.hour, 0)\n        XCTAssertEqual(components.minute, 0)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 1)\n        XCTAssertEqual(components.hour, 0)\n        XCTAssertEqual(components.minute, 0)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM-dd\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 0)\n        XCTAssertEqual(components.minute, 0)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM-dd'T'hh\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15T11\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 0)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM-dd hh\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15 11\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 0)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM-dd'T'hh:mm\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15T11:45\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 45)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM-dd hh:mm\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15 11:45\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 45)\n        XCTAssertEqual(components.second, 0)\n\n        // yyyy-MM-dd'T'hh:mm:ss\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15T11:45:22\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 45)\n        XCTAssertEqual(components.second, 22)\n\n        // yyyy-MM-dd hh:mm:ss\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15T11:45:22\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 45)\n        XCTAssertEqual(components.second, 22)\n        let dateWithoutSubseconds = date\n\n        // yyyy-MM-ddThh:mm:ss.SSS\n        date = AirshipDateFormatter.date(fromISOString: \"2020-12-15T11:45:22.123\")!\n        components = self.components(for: date)\n        XCTAssertNotNil(components)\n        XCTAssertEqual(components.year, 2020)\n        XCTAssertEqual(components.month, 12)\n        XCTAssertEqual(components.day, 15)\n        XCTAssertEqual(components.hour, 11)\n        XCTAssertEqual(components.minute, 45)\n        XCTAssertEqual(components.second, 22)\n        let seconds = date.timeIntervalSince(dateWithoutSubseconds)\n        XCTAssertEqual(seconds, 0.123, accuracy: 0.0001)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipDeviceIDTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AirshipDeviceIDTest: XCTestCase {\n\n    private let appKey: String = UUID().uuidString\n    private let keychain: TestKeyChainAccess = TestKeyChainAccess()\n    private var deviceID: AirshipDeviceID!\n\n    override func setUp() async throws {\n        self.deviceID = AirshipDeviceID(appKey: self.appKey, keychain: keychain)\n    }\n\n    func testGenerateDeviceID() async {\n        let id = await deviceID.value\n        XCTAssertNotNil(id)\n        let fromStore = await self.keychain.readCredentails(identifier: \"com.urbanairship.deviceID\", appKey: appKey)\n        XCTAssertEqual(fromStore?.password, id)\n    }\n\n    func testRestoreFromKeychain() async {\n        let first = await deviceID.value\n        XCTAssertNotNil(first)\n\n        self.deviceID = AirshipDeviceID(appKey: self.appKey, keychain: keychain)\n        let second = await deviceID.value\n\n        XCTAssertEqual(first, second)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipEventsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\nimport Combine\n\n@testable import AirshipCore\n\nclass AirshipEventsTest: XCTestCase {\n\n    @MainActor\n    func testForegroundAppInitEvent() throws {\n        let sessionEvent = SessionEvent(\n            type: .foregroundInit,\n            date: Date(),\n            sessionState: SessionState(\n                conversionSendID: UUID().uuidString,\n                conversionMetadata: UUID().uuidString\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n           \"notification_types\": [],\n           \"notification_authorization\": \"not_determined\",\n           \"time_zone\": \\(TimeZone.current.secondsFromGMT()),\n           \"daylight_savings\": \"\\(TimeZone.current.isDaylightSavingTime().toString())\",\n           \"package_version\": \"\\(AirshipUtils.bundleShortVersionString()!)\",\n           \"foreground\": \"true\",\n           \"os_version\": \"\\(UIDevice.current.systemVersion)\",\n           \"lib_version\": \"\\(AirshipVersion.version)\",\n           \"push_id\": \"\\(sessionEvent.sessionState.conversionSendID!)\",\n           \"metadata\": \"\\(sessionEvent.sessionState.conversionMetadata!)\"\n        }\n        \"\"\"\n\n        let event = AirshipEvents.sessionEvent(\n            sessionEvent: sessionEvent,\n            push: EventTestPush()\n        )\n\n        XCTAssertEqual(event.eventType.reportingName, \"app_init\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    @MainActor\n    func testBackgroundAppInitEvent() throws {\n        let sessionEvent = SessionEvent(\n            type: .backgroundInit,\n            date: Date(),\n            sessionState: SessionState(\n                conversionSendID: UUID().uuidString,\n                conversionMetadata: UUID().uuidString\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n           \"notification_types\": [],\n           \"notification_authorization\": \"not_determined\",\n           \"time_zone\": \\(TimeZone.current.secondsFromGMT()),\n           \"daylight_savings\": \"\\(TimeZone.current.isDaylightSavingTime().toString())\",\n           \"package_version\": \"\\(AirshipUtils.bundleShortVersionString()!)\",\n           \"foreground\": \"false\",\n           \"os_version\": \"\\(UIDevice.current.systemVersion)\",\n           \"lib_version\": \"\\(AirshipVersion.version)\",\n           \"push_id\": \"\\(sessionEvent.sessionState.conversionSendID!)\",\n           \"metadata\": \"\\(sessionEvent.sessionState.conversionMetadata!)\"\n        }\n        \"\"\"\n\n        let event = AirshipEvents.sessionEvent(\n            sessionEvent: sessionEvent,\n            push: EventTestPush()\n        )\n\n        XCTAssertEqual(event.eventType.reportingName, \"app_init\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    @MainActor\n    func testAppForegroundEvent() throws {\n        let sessionEvent = SessionEvent(\n            type: .foreground,\n            date: Date(),\n            sessionState: SessionState(\n                conversionSendID: UUID().uuidString,\n                conversionMetadata: UUID().uuidString\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n           \"notification_types\": [],\n           \"notification_authorization\": \"not_determined\",\n           \"time_zone\": \\(TimeZone.current.secondsFromGMT()),\n           \"daylight_savings\": \"\\(TimeZone.current.isDaylightSavingTime().toString())\",\n           \"package_version\": \"\\(AirshipUtils.bundleShortVersionString()!)\",\n           \"os_version\": \"\\(UIDevice.current.systemVersion)\",\n           \"lib_version\": \"\\(AirshipVersion.version)\",\n           \"push_id\": \"\\(sessionEvent.sessionState.conversionSendID!)\",\n           \"metadata\": \"\\(sessionEvent.sessionState.conversionMetadata!)\"\n        }\n        \"\"\"\n\n        let event = AirshipEvents.sessionEvent(\n            sessionEvent: sessionEvent,\n            push: EventTestPush()\n        )\n\n        XCTAssertEqual(event.eventType.reportingName, \"app_foreground\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    @MainActor\n    func testAppBackgroundEvent() throws {\n        let sessionEvent = SessionEvent(\n            type: .background,\n            date: Date(),\n            sessionState: SessionState(\n                conversionSendID: UUID().uuidString,\n                conversionMetadata: UUID().uuidString\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n           \"push_id\": \"\\(sessionEvent.sessionState.conversionSendID!)\",\n           \"metadata\": \"\\(sessionEvent.sessionState.conversionMetadata!)\"\n        }\n        \"\"\"\n\n        let event = AirshipEvents.sessionEvent(\n            sessionEvent: sessionEvent,\n            push: EventTestPush()\n        )\n\n        XCTAssertEqual(event.eventType.reportingName, \"app_background\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    func testScreenTracking() throws {\n        let expectedBody = \"\"\"\n        {\n           \"screen\": \"test_screen\",\n           \"previous_screen\": \"previous_screen\",\n           \"duration\": \"1.000\",\n           \"exited_time\": \"1.000\",\n           \"entered_time\": \"0.000\"\n        }\n        \"\"\"\n\n        let event = try AirshipEvents.screenTrackingEvent(\n            screen: \"test_screen\",\n            previousScreen: \"previous_screen\",\n            startDate: Date(timeIntervalSince1970: 0),\n            duration: 1\n        )\n\n        XCTAssertEqual(event.eventType.reportingName, \"screen_tracking\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n\n    func testScreenValidation() throws {\n        var screenName = \"\"\n            .padding(\n                toLength: 255,\n                withPad: \"test_screen_name\",\n                startingAt: 0\n            )\n\n        _ = try AirshipEvents.screenTrackingEvent(\n            screen: screenName,\n            previousScreen: nil,\n            startDate: Date(),\n            duration: 1\n        )\n\n        screenName = \"\"\n            .padding(\n                toLength: 256,\n                withPad: \"test_screen_name\",\n                startingAt: 0\n            )\n\n        do {\n            _ = try AirshipEvents.screenTrackingEvent(\n                screen: screenName,\n                previousScreen: nil,\n                startDate: Date(),\n                duration: 1\n            )\n            XCTFail()\n        } catch {}\n\n\n        do {\n            _ = try AirshipEvents.screenTrackingEvent(\n                screen: \"\",\n                previousScreen: nil,\n                startDate: Date(),\n                duration: 1\n            )\n            XCTFail()\n        } catch {}\n    }\n\n\n    func testInstallAttributeTest() throws {\n        let expectedBody = \"\"\"\n        {\n           \"app_store_purchase_date\": \"100.000\",\n           \"app_store_ad_impression_date\": \"99.000\",\n        }\n        \"\"\"\n\n        let event = AirshipEvents.installAttirbutionEvent(\n            appPurchaseDate: Date(timeIntervalSince1970: 100.0),\n            iAdImpressionDate: Date(timeIntervalSince1970: 99.0)\n        )\n        \n        XCTAssertEqual(event.eventType.reportingName, \"install_attribution\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    func testInstallAttributeNoDatesTest() throws {\n        let expectedBody = \"\"\"\n        {\n        }\n        \"\"\"\n\n        let event = AirshipEvents.installAttirbutionEvent()\n\n        XCTAssertEqual(event.eventType.reportingName, \"install_attribution\")\n        XCTAssertEqual(event.priority, .normal)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    func testInteractiveNotificationEventTest() throws {\n\n        let expectedBody = \"\"\"\n        {\n           \"foreground\": \"true\",\n           \"button_id\": \"action_identifier\",\n           \"button_description\": \"action_title\",\n           \"button_group\": \"category_id\",\n           \"send_id\": \"send ID\",\n           \"user_input\": \"some response text\"\n        }\n        \"\"\"\n        \n        let event = AirshipEvents.interactiveNotificationEvent(\n            action: UNNotificationAction(\n                identifier: \"action_identifier\",\n                title: \"action_title\",\n                options: .foreground\n            ),\n            category: \"category_id\",\n            notification: [\n                \"_\": \"send ID\",\n                \"aps\": [\n                    \"alert\": \"sample alert!\",\n                    \"badge\": 2,\n                    \"sound\": \"cat\",\n                    \"category\": \"category_id\"\n                ]\n            ],\n            responseText: \"some response text\"\n        )\n\n        XCTAssertEqual(event.eventType.reportingName, \"interactive_notification_action\")\n        XCTAssertEqual(event.priority, .high)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n}\n\nprivate final class EventTestPush: AirshipPush, @unchecked Sendable {\n    var onAPNSRegistrationFinished: (@MainActor @Sendable (AirshipCore.APNSRegistrationResult) -> Void)?\n    \n    var onNotificationRegistrationFinished: (@MainActor @Sendable (AirshipCore.NotificationRegistrationResult) -> Void)?\n    \n    var onNotificationAuthorizedSettingsDidChange: (@MainActor @Sendable (AirshipCore.AirshipAuthorizedNotificationSettings) -> Void)?\n    \n\n    var quietTime: QuietTimeSettings?\n    \n\n    func enableUserPushNotifications() async -> Bool {\n        return true\n    }\n\n    func enableUserPushNotifications(fallback: PromptPermissionFallback) async -> Bool {\n        return true\n    }\n\n\n    func setBadgeNumber(_ newBadgeNumber: Int) async {\n\n    }\n\n    func resetBadge() async {\n\n    }\n\n    var autobadgeEnabled: Bool = false\n\n    var timeZone: NSTimeZone?\n\n    var quietTimeEnabled: Bool = false\n\n    func setQuietTimeStartHour(_ startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) {\n\n    }\n\n    var notificationStatusPublisher: AnyPublisher<AirshipCore.AirshipNotificationStatus, Never> {\n        fatalError(\"not implemented\")\n    }\n\n    var notificationStatus: AirshipCore.AirshipNotificationStatus {\n        fatalError(\"not implemented\")\n    }\n    \n    let notificationStatusUpdates: AsyncStream<AirshipNotificationStatus>\n    let statusUpdateContinuation: AsyncStream<AirshipNotificationStatus>.Continuation\n\n    var isPushNotificationsOptedIn: Bool = false\n\n    var deviceToken: String?\n\n    var combinedCategories: Set<UNNotificationCategory> = []\n\n    var backgroundPushNotificationsEnabled = true\n\n    var userPushNotificationsEnabled = true\n\n    var extendedPushNotificationPermissionEnabled = false\n\n    var requestExplicitPermissionWhenEphemeral = false\n\n    var notificationOptions: UNAuthorizationOptions = [.alert, .sound, .badge]\n\n    var customCategories: Set<UNNotificationCategory> = []\n\n    var accengageCategories: Set<UNNotificationCategory> = []\n\n    var requireAuthorizationForDefaultCategories = false\n\n    var pushNotificationDelegate: PushNotificationDelegate?\n\n    var registrationDelegate: RegistrationDelegate?\n\n    var launchNotificationResponse: UNNotificationResponse?\n\n    var authorizedNotificationSettings: AirshipAuthorizedNotificationSettings = []\n\n    var authorizationStatus: UNAuthorizationStatus = .notDetermined\n\n    var userPromptedForNotifications = false\n\n    var defaultPresentationOptions: UNNotificationPresentationOptions = [\n        .list, .sound, .badge,\n    ]\n\n    var badgeNumber: Int = 0\n\n    // Notification callbacks\n    var onForegroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> Void)?\n\n#if !os(watchOS)\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> UIBackgroundFetchResult)?\n#else\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> WKBackgroundFetchResult)?\n#endif\n\n#if !os(tvOS)\n    var onNotificationResponseReceived: (@MainActor @Sendable (UNNotificationResponse) async -> Void)?\n#endif\n\n    var onExtendPresentationOptions: (@MainActor @Sendable (UNNotificationPresentationOptions, UNNotification) async -> UNNotificationPresentationOptions)?\n\n    init() {\n        (self.notificationStatusUpdates, self.statusUpdateContinuation) = AsyncStream<AirshipNotificationStatus>.airshipMakeStreamWithContinuation()\n    }\n}\n\nprivate final class InternalPush: InternalAirshipPush {\n    \n    var deviceToken: String? = \"a12312ad\"\n\n    func dispatchUpdateAuthorizedNotificationTypes() {}\n\n    func didRegisterForRemoteNotifications(_ deviceToken: Data) {}\n\n    func didFailToRegisterForRemoteNotifications(_ error: Error) {}\n\n    func didReceiveRemoteNotification(\n        _ userInfo: [AnyHashable: Any],\n        isForeground: Bool\n    ) async -> UABackgroundFetchResult {\n        return .noData\n    }\n\n    func presentationOptionsForNotification(\n        _ notification: UNNotification\n    ) async -> UNNotificationPresentationOptions {\n        return []\n    }\n\n    func didReceiveNotificationResponse(\n        _ response: UNNotificationResponse\n    ) async {}\n\n    var combinedCategories: Set<UNNotificationCategory> = Set()\n}\n\nfileprivate extension TimeInterval {\n    func toString() -> String {\n        String(\n            format: \"%0.3f\",\n            self\n        )\n    }\n}\n\nfileprivate extension Bool {\n    func toString() -> String {\n        return self ? \"true\" : \"false\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipHTTPResponseTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\npublic import AirshipCore\n\npublic extension AirshipHTTPResponse {\n    \n    static func make(result: T?, statusCode: Int, headers: [String: String]) -> AirshipHTTPResponse<T> {\n        return .init(result: result, statusCode: statusCode, headers: headers)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipIvyVersionMatcherTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AirshipIvyVersionMatcherTest: XCTestCase {\n\n    func testValidVersions() {\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"[1.22.6.189,)\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"[1.22.6.189,)\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"[1.22.6.189,2.2.3.4]\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"[1.22.6.189, 2.2.3.4]\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"[1.22.6.189-junk, 2.2.3.4-junk]\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"1.2.3.4\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"1.2.3.4.+\"))\n        XCTAssertNotNil(try? AirshipIvyVersionMatcher(versionConstraint: \"1.2.3-junk\"))\n    }\n\n    func testRangeLongVersion() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[1.22.6.189,)\")\n\n        XCTAssertTrue(matcher.evaluate(version: \"1.22.6\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.22.6.189\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.22.6.188\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.22.7\"))\n        XCTAssertFalse(matcher.evaluate(version: \"1.22.5\"))\n    }\n\n    func testRangeWithWhiteSpace() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[ 1.2 , 2.0 ]\")\n\n        XCTAssertTrue(matcher.evaluate(version: \"1.2\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.2.0\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.2.1\"))\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"))\n        XCTAssertTrue(matcher.evaluate(version: \"2.0.0\"))\n\n        XCTAssertFalse(matcher.evaluate(version: \"1.1\"))\n        XCTAssertFalse(matcher.evaluate(version: \"1.1.0\"))\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"))\n        XCTAssertFalse(matcher.evaluate(version: \"2.1\"))\n    }\n\n    func testExactVersionMatcher() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0\")\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"))\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.0 \"))\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0 \"))\n\n        XCTAssertFalse(matcher.evaluate(version: \" 0.9\"))\n        XCTAssertFalse(matcher.evaluate(version: \"1.1 \"))\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0\"))\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0 \"))\n\n        let matcher2 = try AirshipIvyVersionMatcher(versionConstraint: \" 1.0\")\n        XCTAssertTrue(matcher2.evaluate(version: \"1.0\"))\n        XCTAssertTrue(matcher2.evaluate(version: \" 1.0\"))\n        XCTAssertTrue(matcher2.evaluate(version: \"1.0 \"))\n        XCTAssertTrue(matcher2.evaluate(version: \" 1.0 \"))\n\n        XCTAssertFalse(matcher2.evaluate(version: \" 0.9\"))\n        XCTAssertFalse(matcher2.evaluate(version: \"1.1 \"))\n        XCTAssertFalse(matcher2.evaluate(version: \" 2.0\"))\n        XCTAssertFalse(matcher2.evaluate(version: \" 2.0 \"))\n\n        let matcher3 = try AirshipIvyVersionMatcher(versionConstraint: \"1.0   \")\n        XCTAssertTrue(matcher3.evaluate(version: \"1.0\"))\n        XCTAssertTrue(matcher3.evaluate(version: \" 1.0\"))\n        XCTAssertTrue(matcher3.evaluate(version: \"1.0 \"))\n        XCTAssertTrue(matcher3.evaluate(version: \" 1.0 \"))\n\n        XCTAssertFalse(matcher3.evaluate(version: \" 0.9\"))\n        XCTAssertFalse(matcher3.evaluate(version: \"1.1 \"))\n        XCTAssertFalse(matcher3.evaluate(version: \" 2.0\"))\n        XCTAssertFalse(matcher3.evaluate(version: \" 2.0 \"))\n\n        let matcher4 = try AirshipIvyVersionMatcher(versionConstraint: \" 1.0 \")\n        XCTAssertTrue(matcher4.evaluate(version: \"1.0\"))\n        XCTAssertTrue(matcher4.evaluate(version: \" 1.0\"))\n        XCTAssertTrue(matcher4.evaluate(version: \"1.0 \"))\n        XCTAssertTrue(matcher4.evaluate(version: \" 1.0 \"))\n\n        XCTAssertFalse(matcher4.evaluate(version: \" 0.9\"))\n        XCTAssertFalse(matcher4.evaluate(version: \"1.1 \"))\n        XCTAssertFalse(matcher4.evaluate(version: \" 2.0\"))\n        XCTAssertFalse(matcher4.evaluate(version: \" 2.0 \"))\n    }\n\n    func testSubVersionMatcher() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0.+\")\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.5\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.a\"))\n\n        XCTAssertFalse(matcher.evaluate(version: \"1.0\"))\n        XCTAssertFalse(matcher.evaluate(version: \"1\"))\n        XCTAssertFalse(matcher.evaluate(version: \"1.01\"))\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"))\n        XCTAssertFalse(matcher.evaluate(version: \"2\"))\n\n        let matcher2 = try AirshipIvyVersionMatcher(versionConstraint: \"1.0+\")\n        XCTAssertNotNil(matcher2)\n        XCTAssertTrue(matcher2.evaluate(version: \"1.0\"))\n        XCTAssertTrue(matcher2.evaluate(version: \"1.0.1\"))\n        XCTAssertTrue(matcher2.evaluate(version: \"1.00\"))\n        XCTAssertTrue(matcher2.evaluate(version: \"1.01\"))\n\n        XCTAssertFalse(matcher2.evaluate(version: \"1\"))\n        XCTAssertFalse(matcher2.evaluate(version: \"1.11\"))\n        XCTAssertFalse(matcher2.evaluate(version: \"2\"))\n    }\n\n    func testVersionRangeMatcher() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[1.0, 2.0]\")\n        XCTAssertNotNil(matcher)\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"))\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"))\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"))\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"))\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"))\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"))\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"))\n    }\n\n    func testSubVersionIgnoresVersionQualifiers() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0-rc1+\")\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.00\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2-SNAPSHOT\"));\n    }\n\n\n    func testExactVersion() throws {\n        var matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-rc\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-rc1\"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0 \"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1\");\n        XCTAssertTrue(matcher.evaluate(version: \"1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1-SNAPSHOT\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \" 0.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.1 \"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0 \"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \" 1.0\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \" 0.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.1 \"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0 \"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0   \");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0-rc01 \"));\n\n        XCTAssertFalse(matcher.evaluate(version: \" 0.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.1 \"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0 \"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \" 1.0 \");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0 \"));\n        XCTAssertTrue(matcher.evaluate(version: \" 1.0-SNAPSHOT\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \" 0.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.1 \"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \" 2.0 \"));\n    }\n\n    func testSubVersion() throws {\n        var matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0.+\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.a\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.0-SNAPSHOT\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.01\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.1-beta\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0+\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.00\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2-SNAPSHOT\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \" 1.0+\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.00\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2-SNAPSHOT\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0+ \");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.00\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2-SNAPSHOT\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \" 1.0+  \");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.00\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.11\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2-SNAPSHOT\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"+\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.00\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.11\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.2.2-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2-SNAPSHOT\"));\n    }\n\n    func testVersionRange() throws  {\n        var matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[1.0, 2.0]\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9-rc1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0-alpha\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[1.0 ,2.0[\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9-rc1\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0-beta\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0-alpha\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"]1.0 , 2.0]\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"] 1.0,2.0[\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1-beta\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0-beta\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0-SNAPSHOT\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[1.0, )\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"3.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"999.999.999\"));\n        XCTAssertTrue(matcher.evaluate(version: \"3.0-SNAPSHOT\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.1-rc3\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"]1.0,) \");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"3.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"999.999.999\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0-alpha01\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0-alpha01\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \" (,2.0]\");\n        XCTAssertTrue(matcher.evaluate(version: \"0.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0-beta3\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"999.999.999\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0-alpha01\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \" ( , 2.0 [ \");\n        XCTAssertTrue(matcher.evaluate(version: \"0.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.1-rc1\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"2.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"999.999.999\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0-beta33\"));\n    }\n\n    func testExactConstraintIgnoresVersionQualifiers() throws {\n        let matcher = try AirshipIvyVersionMatcher(versionConstraint: \"1.0-beta\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-alpha01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-beta01\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-rc\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-rc1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-SNAPSHOT\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-alpha\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-alpha01\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-beta\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-beta01\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-rc\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0-rc1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"1.0.0\"));\n    }\n\n    func testVersionRangeIgnoresVersionQualifiers() throws {\n        var matcher = try AirshipIvyVersionMatcher(versionConstraint: \"[1.0-alpha, 2.0-alpha01]\");\n        XCTAssertTrue(matcher.evaluate(version: \"1.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.5\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9\"));\n        XCTAssertTrue(matcher.evaluate(version: \"1.9.9-rc1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"2.0\"));\n\n        XCTAssertFalse(matcher.evaluate(version: \"0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"0.9.9\"));\n        XCTAssertFalse(matcher.evaluate(version: \"2.0.1\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"3.0-alpha\"));\n\n        matcher = try AirshipIvyVersionMatcher(versionConstraint: \"]17.0.0-beta,)\");\n        XCTAssertFalse(matcher.evaluate(version: \"17.0.0\"));\n        XCTAssertFalse(matcher.evaluate(version: \"17.0.0-SNAPSHOT\"));\n        XCTAssertFalse(matcher.evaluate(version: \"17.0.0-alpha\"));\n        XCTAssertFalse(matcher.evaluate(version: \"17.0.0-beta\"));\n        XCTAssertFalse(matcher.evaluate(version: \"17.0.0-rc\"));\n\n        XCTAssertTrue(matcher.evaluate(version: \"17.0.1\"));\n        XCTAssertTrue(matcher.evaluate(version: \"17.0.1-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"17.0.1-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"17.0.1-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"17.0.1-rc\"));\n        XCTAssertTrue(matcher.evaluate(version: \"18.0.0\"));\n        XCTAssertTrue(matcher.evaluate(version: \"18.0.0-SNAPSHOT\"));\n        XCTAssertTrue(matcher.evaluate(version: \"18.0.0-alpha\"));\n        XCTAssertTrue(matcher.evaluate(version: \"18.0.0-beta\"));\n        XCTAssertTrue(matcher.evaluate(version: \"999.999.999\"));\n        XCTAssertTrue(matcher.evaluate(version: \"999.999.999-rc\"));\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipJSONTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class AirshipJSONTest: XCTestCase {\n    func testWrapPrimitives() throws {\n        XCTAssertEqual(.number(100.0), try AirshipJSON.wrap(100.0))\n        XCTAssertEqual(.number(99.0), try AirshipJSON.wrap(99))\n        XCTAssertEqual(.number(33.0), try AirshipJSON.wrap(UInt(33)))\n        XCTAssertEqual(.number(1), try AirshipJSON.wrap(1))\n        XCTAssertEqual(.number(0), try AirshipJSON.wrap(0))\n\n        XCTAssertEqual(.string(\"hello\"), try AirshipJSON.wrap(\"hello\"))\n        XCTAssertEqual(.bool(true), try AirshipJSON.wrap(true))\n        XCTAssertEqual(.bool(false), try AirshipJSON.wrap(false))\n        XCTAssertEqual(.null, try AirshipJSON.wrap(nil))\n    }\n\n    func testWrapNSNumber() throws {\n        XCTAssertEqual(.number(100.0), try AirshipJSON.wrap(NSNumber(100)))\n        XCTAssertEqual(.number(99.0), try AirshipJSON.wrap(NSNumber(99.0)))\n        XCTAssertEqual(.number(33.0), try AirshipJSON.wrap(NSNumber(33.0)))\n        XCTAssertEqual(.number(1), try AirshipJSON.wrap(NSNumber(1)))\n        XCTAssertEqual(.number(0), try AirshipJSON.wrap(NSNumber(0)))\n        XCTAssertEqual(.bool(true), try AirshipJSON.wrap(NSNumber(true)))\n        XCTAssertEqual(.bool(false), try AirshipJSON.wrap(NSNumber(false)))\n    }\n\n    func testWrapArray() throws {\n        let array: [Any?] = [\n            \"hello\",\n            100,\n            [\n                \"foo\",\n                [\"cool\": \"story\"],\n            ] as [Any],\n            [\"neat\": \"object\"],\n            nil,\n            true,\n        ]\n\n        let expected: [AirshipJSON] = [\n            \"hello\",\n            100.0,\n            [\"foo\", [\"cool\": \"story\"], ],\n            [\"neat\": \"object\"],\n            nil,\n            true,\n        ]\n\n        XCTAssertEqual(.array(expected), try AirshipJSON.wrap(array))\n    }\n\n    func testWrapObject() throws {\n        let object: [String: Any?] = [\n            \"string\": \"hello\",\n            \"number\": 100.0,\n            \"array\": [\"cool\", \"story\"],\n            \"null\": nil,\n            \"boolean\": true,\n            \"object\": [\"neat\": \"object\"],\n        ]\n\n        let expected: [String: AirshipJSON] = [\n            \"string\": \"hello\",\n            \"number\": 100.0,\n            \"array\": [\"cool\", \"story\"],\n            \"null\": nil,\n            \"boolean\": true,\n            \"object\": [\"neat\": \"object\"],\n        ]\n\n        XCTAssertEqual(.object(expected), try AirshipJSON.wrap(object))\n    }\n\n    func testWrapInvalid() throws {\n        XCTAssertThrowsError(try AirshipJSON.wrap(InvalidJSON()))\n    }\n\n    fileprivate struct InvalidJSON {\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipJSONUtilsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nclass JSONUtilsTest: XCTestCase {\n\n    func testInvalidJSON() {\n\n        do {\n            _ = try AirshipJSONUtils.data(NSObject(), options: .prettyPrinted)\n            XCTFail()\n        } catch {}\n\n    }\n\n    func testValidJSON() throws {\n        let _ = try AirshipJSONUtils.data([\"Valid JSON object\": true], options: .prettyPrinted)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipLocaleManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AirshipLocaleManagerTest: XCTestCase {\n\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(\n        notificationCenter: NotificationCenter()\n    )\n\n    private func makeLocaleManager(\n        useUserPreferredLocale: Bool = false\n    ) -> DefaultAirshipLocaleManager {\n        return DefaultAirshipLocaleManager(\n            dataStore: PreferenceDataStore(\n                appKey: UUID().uuidString\n            ),\n            config: .testConfig(useUserPreferredLocale: useUserPreferredLocale),\n            notificationCenter: notificationCenter\n        )\n    }\n\n    func testLocale() throws {\n        let localeManager = makeLocaleManager()\n        XCTAssertEqual(localeManager.currentLocale, Locale.autoupdatingCurrent)\n\n        let french = Locale(identifier: \"fr\")\n        localeManager.currentLocale = french\n        XCTAssertEqual(localeManager.currentLocale, french)\n\n        let english = Locale(identifier: \"en\")\n        localeManager.currentLocale = english\n        XCTAssertEqual(localeManager.currentLocale, english)\n\n        localeManager.clearLocale()\n        XCTAssertEqual(localeManager.currentLocale, Locale.autoupdatingCurrent)\n    }\n    \n    func testLocaleWithUseUserPreferredLocale() throws {\n        let localeManager = makeLocaleManager(useUserPreferredLocale: true)\n        let preferredLocale = Locale(identifier: Locale.preferredLanguages[0])\n        XCTAssertEqual(localeManager.currentLocale, preferredLocale)\n        \n        let french = Locale(identifier: \"fr\")\n        localeManager.currentLocale = french\n        XCTAssertEqual(localeManager.currentLocale, french)\n        \n        localeManager.clearLocale()\n        XCTAssertEqual(localeManager.currentLocale, preferredLocale)\n    }\n\n    func testNotificationWhenOverrideChanges() {\n        let localeManager = makeLocaleManager()\n\n        let expectation = self.expectation(description: \"update called\")\n        self.notificationCenter.addObserver(\n            forName: AirshipNotifications.LocaleUpdated.name\n        ) { _ in\n            expectation.fulfill()\n        }\n\n        localeManager.currentLocale = Locale(identifier: \"fr\")\n\n        self.waitForExpectations(timeout: 10.0)\n    }\n\n    func testNotificationWhenOverrideClears() {\n        let localeManager = makeLocaleManager()\n\n        localeManager.currentLocale = Locale(identifier: \"fr\")\n\n        let expectation = self.expectation(description: \"update called\")\n        \n        self.notificationCenter.addObserver(\n            forName: AirshipNotifications.LocaleUpdated.name\n        ) { _ in\n            expectation.fulfill()\n        }\n\n        localeManager.clearLocale()\n\n        self.waitForExpectations(timeout: 10.0)\n    }\n\n    func testNotificationWhenAutoUpdateChanges() {\n        let localeManager = makeLocaleManager()\n        let expectation = self.expectation(description: \"update called\")\n        self.notificationCenter.addObserver(\n            forName: AirshipNotifications.LocaleUpdated.name\n        ) { _ in\n            expectation.fulfill()\n        }\n\n        self.notificationCenter.post(name: NSLocale.currentLocaleDidChangeNotification)\n\n        self.waitForExpectations(timeout: 10.0)\n        _ = localeManager\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipLocalizationUtilsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nclass AirshipLocalizationUtilsTest: XCTestCase {\n\n    func testLocalization() {\n        let localizedString = AirshipLocalizationUtils.localizedString(\n            \"ua_notification_button_yes\",\n            withTable: \"UrbanAirship\",\n            moduleBundle: AirshipCoreResources.bundle\n        )\n        XCTAssertEqual(localizedString, \"Yes\")\n\n        let badKeyString = AirshipLocalizationUtils.localizedString(\n            \"not_a_key\",\n            withTable: \"UrbanAirship\",\n            moduleBundle: AirshipCoreResources.bundle\n        )\n        XCTAssertNil(badKeyString)\n\n        let badTableString = AirshipLocalizationUtils.localizedString(\n            \"ua_notification_button_yes\",\n            withTable: \"NotATable\",\n            moduleBundle: AirshipCoreResources.bundle\n        )\n        XCTAssertNil(badTableString)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipMeteredUsageTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class AirshipMeteredUsageTest: XCTestCase {\n    \n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let channel: TestChannel = TestChannel()\n    private let contact: TestContact = TestContact()\n\n    private var privacyManager: TestPrivacyManager!\n    private let apiClient: MeteredUsageAPIClientProtocol = MeteredTestApiClient()\n    private let storage = MeteredUsageStore(appKey: \"test.app.key\", inMemory: true)\n    private let workManager = TestWorkManager()\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n    private var target: DefaultAirshipMeteredUsage!\n\n    @MainActor\n    override func setUp() async throws {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config:self.config,\n            defaultEnabledFeatures:[]\n        )\n\n        self.target = DefaultAirshipMeteredUsage(\n            config: config,\n            dataStore: dataStore,\n            channel: channel,\n            contact: contact,\n            privacyManager: privacyManager,\n            client: apiClient,\n            store: storage,\n            workManager: workManager\n        )\n    }\n    \n    func testInit() {\n        let worker = workManager.workers.first\n        XCTAssertNotNil(worker)\n        XCTAssertEqual(\"MeteredUsage.upload\", worker?.workID)\n\n        // should set a default rate limit from the config\n        XCTAssertEqual(1, workManager.rateLimits.count)\n        XCTAssertEqual(0, workManager.workRequests.count)\n    }\n\n    func testUpdateConfig() async {\n        var newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: nil, initialDelayMilliseconds: nil, intervalMilliseconds: nil)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n        XCTAssertEqual(0, workManager.workRequests.count)\n        \n        var limit = workManager.rateLimits[\"MeteredUsage.rateLimit\"]\n        XCTAssertNotNil(limit)\n        \n        XCTAssertEqual(30, limit?.timeInterval)\n        XCTAssertEqual(1, limit?.rate)\n        \n        newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: nil, initialDelayMilliseconds: nil, intervalMilliseconds: 2000)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        XCTAssertEqual(0, workManager.workRequests.count)\n        limit = workManager.rateLimits[\"MeteredUsage.rateLimit\"]\n        XCTAssertNotNil(limit)\n        \n        XCTAssertEqual(2, limit?.timeInterval)\n        XCTAssertEqual(1, limit?.rate)\n        \n        newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: false, initialDelayMilliseconds: 1000, intervalMilliseconds: 2000)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        XCTAssertEqual(0, workManager.workRequests.count)\n        limit = workManager.rateLimits[\"MeteredUsage.rateLimit\"]\n        XCTAssertNotNil(limit)\n        \n        XCTAssertEqual(2, limit?.timeInterval)\n        XCTAssertEqual(1, limit?.rate)\n        \n        newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: true, initialDelayMilliseconds: 1000, intervalMilliseconds: 2000)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        var workRequest = workManager.workRequests.last\n        XCTAssertNotNil(workRequest)\n        XCTAssertEqual(1, workRequest?.initialDelay)\n\n        limit = workManager.rateLimits[\"MeteredUsage.rateLimit\"]\n        XCTAssertNotNil(limit)\n        \n        XCTAssertEqual(2, limit?.timeInterval)\n        XCTAssertEqual(1, limit?.rate)\n        \n        workManager.workRequests.removeAll()\n        \n        newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: false, initialDelayMilliseconds: 1000, intervalMilliseconds: 2000)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        XCTAssertEqual(0, workManager.workRequests.count)\n        limit = workManager.rateLimits[\"MeteredUsage.rateLimit\"]\n        XCTAssertNotNil(limit)\n        \n        XCTAssertEqual(2, limit?.timeInterval)\n        XCTAssertEqual(1, limit?.rate)\n        \n        newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: true, initialDelayMilliseconds: nil, intervalMilliseconds: 2000)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        workRequest = workManager.workRequests.last\n        XCTAssertNotNil(workRequest)\n        XCTAssertEqual(15, workRequest?.initialDelay)\n        \n        limit = workManager.rateLimits[\"MeteredUsage.rateLimit\"]\n        XCTAssertNotNil(limit)\n        \n        XCTAssertEqual(2, limit?.timeInterval)\n        XCTAssertEqual(1, limit?.rate)\n    }\n    \n    func testManagerUploadsDataOnBackground() {\n        XCTAssertEqual(1, workManager.backgroundWorkRequests.count)\n        let work = workManager.backgroundWorkRequests.last\n        XCTAssertNotNil(work)\n        XCTAssertEqual(\"MeteredUsage.upload\", work?.workID)\n        XCTAssertEqual(0, work?.initialDelay)\n    }\n    \n    func testEventStoreTheEventAndSendsData() async throws {\n        privacyManager.enabledFeatures = [.analytics]\n\n        let newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: true, initialDelayMilliseconds: 1, intervalMilliseconds: nil)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        workManager.workRequests.removeAll()\n        \n        let event = AirshipMeteredUsageEvent(\n            eventID: \"test.id\",\n            entityID: \"story.id\",\n            usageType: .inAppExperienceImpression,\n            product: \"Story\",\n            reportingContext: try! AirshipJSON.wrap(\"context\"),\n            timestamp: Date(),\n            contactID: \"test-contact-id\"\n        )\n        \n        XCTAssertEqual(0, workManager.workRequests.count)\n        let storedEvents = try await storage.getEvents()\n        XCTAssertEqual(0, storedEvents.count)\n        let expectation = XCTestExpectation(description: \"adding new event\")\n        workManager.onNewWorkRequestAdded = { _ in\n            expectation.fulfill()\n        }\n        \n        try await self.target.addEvent(event)\n        \n        await fulfillment(of: [expectation], timeout: 30)\n        XCTAssertEqual(1, workManager.workRequests.count)\n        \n        let storedEvent = try await storage.getEvents().first\n        XCTAssertEqual(event, storedEvent)\n    }\n\n    func testAddEventConfigDisabled() async throws {\n        let newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: false, initialDelayMilliseconds: 1, intervalMilliseconds: nil)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        workManager.workRequests.removeAll()\n\n        let event = AirshipMeteredUsageEvent(\n            eventID: \"test.id\",\n            entityID: \"story.id\",\n            usageType: .inAppExperienceImpression,\n            product: \"Story\",\n            reportingContext: try! AirshipJSON.wrap(\"context\"),\n            timestamp: Date(),\n            contactID: \"test-contact-id\"\n        )\n\n        try await self.target.addEvent(event)\n        XCTAssertEqual(0, workManager.workRequests.count)\n\n        let events = try await storage.getEvents()\n        XCTAssertTrue(events.isEmpty)\n    }\n\n    func testEventStoreStripsDataIfAnalyticsDisabled() async throws {\n        let newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: true, initialDelayMilliseconds: 1, intervalMilliseconds: nil)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n        workManager.workRequests.removeAll()\n        \n        let event = AirshipMeteredUsageEvent(\n            eventID: \"test.id\",\n            entityID: \"story.id\",\n            usageType: .inAppExperienceImpression,\n            product: \"Story\",\n            reportingContext: try! AirshipJSON.wrap(\"context\"),\n            timestamp: Date(),\n            contactID: \"test-contact-id\"\n        )\n        \n        XCTAssertEqual(0, workManager.workRequests.count)\n        let storedEvents = try await storage.getEvents()\n        XCTAssertEqual(0, storedEvents.count)\n        let expectation = XCTestExpectation(description: \"adding new event\")\n        workManager.onNewWorkRequestAdded = { _ in\n            expectation.fulfill()\n        }\n        \n        try await self.target.addEvent(event)\n        \n        await fulfillment(of: [expectation], timeout: 30)\n        XCTAssertEqual(1, workManager.workRequests.count)\n        \n        let storedEvent = try await storage.getEvents().first\n        XCTAssertNotNil(storedEvent)\n        XCTAssertNotEqual(storedEvent, event)\n        XCTAssertEqual(storedEvent, event.withDisabledAnalytics())\n    }\n\n    func testContactIDAddedIfNotSet() async throws {\n        self.contact.contactID = \"from-contact\"\n\n        privacyManager.enableFeatures(.analytics)\n        let newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: true, initialDelayMilliseconds: 1, intervalMilliseconds: nil)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n\n        try await self.target.addEvent(\n            AirshipMeteredUsageEvent(\n                eventID: \"test.id\",\n                entityID: \"story.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Story\",\n                reportingContext: try! AirshipJSON.wrap(\"context\"),\n                timestamp: Date(),\n                contactID: \"test-contact-id\"\n            )\n        )\n\n        var fromStore = try await storage.getEvents().first!\n        XCTAssertEqual(fromStore.contactID, \"test-contact-id\")\n\n        try await self.target.addEvent(\n            AirshipMeteredUsageEvent(\n                eventID: \"test.id\",\n                entityID: \"story.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Story\",\n                reportingContext: try! AirshipJSON.wrap(\"context\"),\n                timestamp: Date(),\n                contactID: nil\n            )\n        )\n\n        fromStore = try await storage.getEvents().first!\n        XCTAssertEqual(fromStore.contactID, \"from-contact\")\n\n    }\n\n    func testEventStripDataOnDisabledAnalytics() {\n        let timeStamp = Date()\n        let event = AirshipMeteredUsageEvent(\n            eventID: \"test.id\",\n            entityID: \"story.id\",\n            usageType: .inAppExperienceImpression,\n            product: \"Story\",\n            reportingContext: try! AirshipJSON.wrap(\"context\"),\n            timestamp: timeStamp,\n            contactID: \"test-contact-id\"\n        )\n            .withDisabledAnalytics()\n        \n        XCTAssertEqual(event.eventID, \"test.id\")\n        XCTAssertEqual(event.usageType, .inAppExperienceImpression)\n        XCTAssertEqual(event.product, \"Story\")\n        XCTAssertNil(event.entityID)\n        XCTAssertNil(event.reportingContext)\n        XCTAssertNil(event.timestamp)\n        XCTAssertNil(event.contactID)\n    }\n    \n    func testScheduleWorkRespectsConfig() async {\n        XCTAssertEqual(0, workManager.workRequests.count)\n        target.scheduleWork()\n        XCTAssertEqual(0, workManager.workRequests.count)\n        \n        let newConfig = RemoteConfig.MeteredUsageConfig(isEnabled: true, initialDelayMilliseconds: 1, intervalMilliseconds: 2000)\n        await config.updateRemoteConfig(RemoteConfig(meteredUsageConfig: newConfig))\n        workManager.workRequests.removeAll()\n        target.scheduleWork()\n        \n        var lastWork = workManager.workRequests.last\n        XCTAssertNotNil(lastWork)\n        XCTAssertEqual(\"MeteredUsage.upload\", lastWork?.workID)\n        XCTAssertEqual(0, lastWork?.initialDelay)\n        XCTAssertEqual(true, lastWork?.requiresNetwork)\n        XCTAssertEqual(AirshipWorkRequestConflictPolicy.keepIfNotStarted, lastWork?.conflictPolicy)\n        \n        workManager.workRequests.removeAll()\n        target.scheduleWork(initialDelay: 2)\n\n        lastWork = workManager.workRequests.last\n        XCTAssertNotNil(lastWork)\n        XCTAssertEqual(\"MeteredUsage.upload\", lastWork?.workID)\n        XCTAssertEqual(2, lastWork?.initialDelay)\n        XCTAssertEqual(true, lastWork?.requiresNetwork)\n        XCTAssertEqual(AirshipWorkRequestConflictPolicy.keepIfNotStarted, lastWork?.conflictPolicy)\n        \n        workManager.workRequests.removeAll()\n        target.scheduleWork(initialDelay: 2)\n\n        lastWork = workManager.workRequests.last\n        XCTAssertNotNil(lastWork)\n        XCTAssertEqual(\"MeteredUsage.upload\", lastWork?.workID)\n        XCTAssertEqual(2, lastWork?.initialDelay)\n        XCTAssertEqual(true, lastWork?.requiresNetwork)\n        XCTAssertEqual(AirshipWorkRequestConflictPolicy.keepIfNotStarted, lastWork?.conflictPolicy)\n    }\n}\n\nfinal class MeteredTestApiClient: MeteredUsageAPIClientProtocol {\n    \n    func uploadEvents(_ events: [AirshipCore.AirshipMeteredUsageEvent],\n                      channelID: String?) async throws -> AirshipCore.AirshipHTTPResponse<Void> {\n        \n        \n        return .init(result: nil, statusCode: 200, headers: [:])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipPrivacyManagerTest.swift",
    "content": "import XCTest\n\n@testable\nimport AirshipCore\n\nclass DefaultAirshipPrivacyManagerTest: XCTestCase {\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(notificationCenter: NotificationCenter())\n\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n\n    private var privacyManager: DefaultAirshipPrivacyManager!\n    override func setUp() async throws {\n        self.privacyManager = await DefaultAirshipPrivacyManager(\n            dataStore: dataStore,\n            config: self.config,\n            defaultEnabledFeatures: .all,\n            notificationCenter: notificationCenter\n        )\n    }\n\n    func testDefaultFeatures() async {\n        XCTAssertEqual(self.privacyManager.enabledFeatures, .all)\n\n        self.privacyManager = await DefaultAirshipPrivacyManager(\n            dataStore: dataStore,\n            config: self.config,\n            defaultEnabledFeatures: [],\n            notificationCenter: notificationCenter\n        )\n\n        XCTAssertEqual(self.privacyManager.enabledFeatures, [])\n    }\n\n    func testEnableFeatures() {\n        self.privacyManager.disableFeatures(.all)\n\n        XCTAssertEqual(self.privacyManager.enabledFeatures, [])\n\n        self.privacyManager.enableFeatures(.push)\n        XCTAssertEqual(self.privacyManager.enabledFeatures, [.push])\n\n        self.privacyManager.enableFeatures([.push, .contacts])\n        XCTAssertEqual(self.privacyManager.enabledFeatures, [.push, .contacts])\n    }\n\n    func testDisableFeatures() {\n        XCTAssertEqual(self.privacyManager.enabledFeatures, .all)\n\n        self.privacyManager.disableFeatures(.push)\n        XCTAssertNotEqual(self.privacyManager.enabledFeatures, .all)\n\n        self.privacyManager.disableFeatures([.analytics, .messageCenter, .tagsAndAttributes])\n        XCTAssertEqual(self.privacyManager.enabledFeatures, [.inAppAutomation, .contacts, .featureFlags])\n    }\n\n    func testIsEnabled() {\n        self.privacyManager.disableFeatures(.all)\n\n        XCTAssertFalse(self.privacyManager.isEnabled(.analytics))\n\n        self.privacyManager.enableFeatures(.contacts)\n        XCTAssertTrue(self.privacyManager.isEnabled(.contacts))\n\n        self.privacyManager.enableFeatures(.analytics)\n        XCTAssertTrue(self.privacyManager.isEnabled(.analytics))\n\n        self.privacyManager.enableFeatures(.all)\n        XCTAssertTrue(self.privacyManager.isEnabled(.inAppAutomation))\n    }\n\n    func testIsAnyEnabled() {\n        XCTAssertTrue(self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: false))\n\n        self.privacyManager.disableFeatures([.push, .contacts])\n        XCTAssertTrue(self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: false))\n\n        self.privacyManager.disableFeatures(.all)\n        XCTAssertFalse(self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: false))\n    }\n\n    func testNoneEnabled() {\n        self.privacyManager.enabledFeatures = []\n        XCTAssertFalse(self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: false))\n\n        self.privacyManager.enableFeatures([.push, .tagsAndAttributes])\n        XCTAssertTrue(self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: false))\n\n        self.privacyManager.enabledFeatures = []\n        XCTAssertFalse(self.privacyManager.isAnyFeatureEnabled(ignoringRemoteConfig: false))\n    }\n\n    func testSetEnabled() {\n        self.privacyManager.enabledFeatures = .contacts\n\n        XCTAssertTrue(self.privacyManager.isEnabled(.contacts))\n        XCTAssertFalse(self.privacyManager.isEnabled(.analytics))\n\n        self.privacyManager.enabledFeatures = .analytics\n        XCTAssertTrue(self.privacyManager.isEnabled(.analytics))\n    }\n\n    func testRemoteConfigOverrides() async {\n        XCTAssertEqual(AirshipFeature.all, self.privacyManager.enabledFeatures)\n\n        await self.config.updateRemoteConfig(\n            RemoteConfig(disabledFeatures: .push)\n        )\n\n        XCTAssertEqual(AirshipFeature.all.subtracting(.push), self.privacyManager.enabledFeatures)\n\n        await self.config.updateRemoteConfig(\n            RemoteConfig(disabledFeatures: [])\n        )\n\n        XCTAssertEqual(AirshipFeature.all, self.privacyManager.enabledFeatures)\n\n        await self.config.updateRemoteConfig(\n            RemoteConfig(disabledFeatures: .all)\n        )\n\n        XCTAssertEqual([], self.privacyManager.enabledFeatures)\n    }\n\n\n    @MainActor\n    func testNotifiedOnChange() {\n        let counter = AirshipAtomicValue(0)\n        let observer = notificationCenter.addObserver(forName: AirshipNotifications.PrivacyManagerUpdated.name, object: nil, queue: nil) { @Sendable _ in\n            counter.value += 1\n        }\n\n        self.privacyManager.enabledFeatures = .all\n        self.privacyManager.disableFeatures([])\n        self.privacyManager.enableFeatures(.all)\n        self.privacyManager.enableFeatures(.analytics)\n        XCTAssertEqual(counter.value, 0)\n\n        self.privacyManager.disableFeatures(.analytics)\n        XCTAssertEqual(counter.value, 1)\n\n        self.privacyManager.enableFeatures(.analytics)\n        XCTAssertEqual(counter.value, 2)\n\n        self.config.updateRemoteConfig(\n            RemoteConfig(disabledFeatures: [])\n        )\n        XCTAssertEqual(counter.value, 2)\n\n\n        self.config.updateRemoteConfig(\n            RemoteConfig(disabledFeatures: [.analytics])\n        )\n        XCTAssertEqual(counter.value, 3)\n\n\n        notificationCenter.removeObserver(observer)\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipPushTest.swift",
    "content": "// Copyright Airship and Contributors\n\nimport XCTest\n\n@testable import AirshipCore\nimport Combine\n\nclass AirshipPushTest: XCTestCase {\n\n    private static let validDeviceToken = \"0123456789abcdef0123456789abcdef\"\n\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let channel = TestChannel()\n    private let analtyics = TestAnalytics()\n    private var permissionsManager: DefaultAirshipPermissionsManager!\n\n    private let notificationCenter = AirshipNotificationCenter(notificationCenter: NotificationCenter())\n    private let notificationRegistrar = TestNotificationRegistrar()\n    private var apnsRegistrar: TestAPNSRegistrar!\n    private let badger = TestBadger()\n    private let registrationDelegate = TestRegistraitonDelegate()\n    private var pushDelegate: TestPushNotificationDelegate!\n\n    private var config = AirshipConfig()\n    private var privacyManager: TestPrivacyManager!\n    private var push: DefaultAirshipPush!\n    private var serialQueue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue(priority: .high)\n\n    override func setUp() async throws {\n        self.pushDelegate = await TestPushNotificationDelegate()\n        self.apnsRegistrar = await TestAPNSRegistrar()\n        self.permissionsManager = await DefaultAirshipPermissionsManager()\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: .testConfig(),\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n\n        self.push = await createPush()\n        await self.serialQueue.waitForCurrentOperations()\n        self.channel.updateRegistrationCalled = false\n    }\n\n    override func tearDown() async throws {\n        self.serialQueue.stop()\n    }\n\n    @MainActor\n    func createPush() -> DefaultAirshipPush {\n        return DefaultAirshipPush(\n            config: .testConfig(airshipConfig: self.config),\n            dataStore: dataStore,\n            channel: channel,\n            analytics: analtyics,\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager,\n            notificationCenter: notificationCenter,\n            notificationRegistrar: notificationRegistrar,\n            apnsRegistrar: apnsRegistrar,\n            badger: badger,\n            serialQueue: serialQueue\n        )\n    }\n\n    @MainActor\n    func testBackgroundPushNotificationsEnabled() async throws {\n        XCTAssertTrue(self.push.backgroundPushNotificationsEnabled)\n        XCTAssertFalse(self.channel.updateRegistrationCalled)\n\n        self.push.backgroundPushNotificationsEnabled = false\n        await self.serialQueue.waitForCurrentOperations()\n        XCTAssertTrue(self.channel.updateRegistrationCalled)\n    }\n\n    func testNotificationsPromptedAuthorizedStatus() async throws {\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completed.fulfill()\n\n\n        await self.fulfillment(of: [completed], timeout: 10.0)\n        XCTAssertTrue(self.push.userPromptedForNotifications)\n    }\n\n    func testNotificationsPromptedDeniedStatus() async throws {\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.denied, [])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completed.fulfill()\n\n\n        await self.fulfillment(of: [completed], timeout: 10.0)\n        XCTAssertTrue(self.push.userPromptedForNotifications)\n    }\n\n    func testNotificationsPromptedEphemeralStatus() async throws {\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.ephemeral, [])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completed.fulfill()\n\n        await self.fulfillment(of: [completed], timeout: 10.0)\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n    }\n\n    func testNotificationsPromptedNotDeterminedStatus() async throws {\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.notDetermined, [])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completed.fulfill()\n\n        await self.fulfillment(of: [completed], timeout: 10.0)\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n    }\n\n    @MainActor\n    func testNotificationsStatusPropogation() async throws {\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.badge])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n\n        let cancellable = self.push.notificationStatusPublisher.sink { status in\n            XCTAssertEqual(true, status.areNotificationsAllowed)\n            completed.fulfill()\n        }\n\n        let status = await self.push.notificationStatus\n        XCTAssertEqual(true, status.areNotificationsAllowed)\n\n        await self.fulfillment(of: [completed], timeout: 10.0)\n        XCTAssertTrue(self.push.userPromptedForNotifications)\n\n        cancellable.cancel()\n    }\n\n    /// Test that once prompted always prompted\n    @MainActor\n    func testNotificationsPromptedStaysPrompted() async throws {\n        XCTAssertFalse(self.push.userPromptedForNotifications)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.authorized, [])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completed.fulfill()\n\n        await self.fulfillment(of: [completed], timeout: 10.0)\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.notDetermined, [])\n        }\n\n        let completedAgain = self.expectation(description: \"Completed Again\")\n        _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completedAgain.fulfill()\n\n\n        await self.fulfillment(of: [completedAgain], timeout: 10.0)\n\n        XCTAssertTrue(self.push.userPromptedForNotifications)\n    }\n\n    func testUserPushNotificationsEnabled() async throws {\n        self.push.notificationOptions = [.alert, .badge]\n        self.push.requestExplicitPermissionWhenEphemeral = false\n        await self.serialQueue.waitForCurrentOperations()\n\n        // Make sure updates are called through permissions manager\n        let permissionsManagerCalled = self.expectation(\n            description: \"Permissions manager called\"\n        )\n        self.permissionsManager.addRequestExtender(\n            permission: .displayNotifications\n        ) { _ in\n            permissionsManagerCalled.fulfill()\n        }\n\n        let updated = self.expectation(description: \"Registration updated\")\n        self.notificationRegistrar.onUpdateRegistration = {\n            options,\n            skipIfEphemeral in\n            XCTAssertEqual([.alert, .badge], options)\n            XCTAssertTrue(skipIfEphemeral)\n            updated.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = true\n        await self.serialQueue.waitForCurrentOperations()\n        await self.fulfillment(of: [permissionsManagerCalled, updated], timeout: 20.0)\n    }\n\n    func testUserPushNotificationsDisabled() async throws {\n        let enabled = self.expectation(description: \"Registration updated\")\n        self.notificationRegistrar.onUpdateRegistration = {\n            options,\n            skipIfEphemeral in\n            XCTAssertEqual([.badge, .alert, .sound], options)\n            XCTAssertTrue(skipIfEphemeral)\n            enabled.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = true\n        await self.fulfillment(of: [enabled], timeout: 10.0)\n\n        let disabled = self.expectation(description: \"Registration updated\")\n        self.notificationRegistrar.onUpdateRegistration = {\n            options,\n            skipIfEphemeral in\n            XCTAssertEqual([], options)\n            XCTAssertTrue(skipIfEphemeral)\n            disabled.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = false\n        await self.fulfillment(of: [disabled], timeout: 10.0)\n    }\n\n    /// Test that we always ephemeral when disabling notifications\n    func testUserPushNotificationsSkipEphemeral() async throws {\n        self.push.requestExplicitPermissionWhenEphemeral = false\n        await self.serialQueue.waitForCurrentOperations()\n\n        let enabled = self.expectation(description: \"Registration updated\")\n        self.notificationRegistrar.onUpdateRegistration = { options, skipIfEphemeral in\n            XCTAssertEqual([.badge, .alert, .sound], options)\n            XCTAssertTrue(skipIfEphemeral)\n            enabled.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = true\n        await self.serialQueue.waitForCurrentOperations()\n        await self.fulfillment(of: [enabled], timeout: 10.0)\n\n        let disabled = self.expectation(description: \"Registration updated\")\n        self.notificationRegistrar.onUpdateRegistration = { options, skipIfEphemeral in\n            XCTAssertEqual([], options)\n            XCTAssertTrue(skipIfEphemeral)\n            disabled.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = false\n        await self.serialQueue.waitForCurrentOperations()\n        await self.fulfillment(of: [disabled], timeout: 10.0)\n    }\n\n    func testEnableUserNotificationsAppHandlingAuth() async throws {\n        self.config.requestAuthorizationToUseNotifications = false\n        self.push = await createPush()\n\n        self.permissionsManager.addRequestExtender(\n            permission: .displayNotifications\n        ) { _ in\n            XCTFail(\"Should be skipped\")\n        }\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.authorized, [])\n        }\n\n        let success = await self.push.enableUserPushNotifications()\n        XCTAssertTrue(success)\n    }\n\n    func testEnableUserNotificationsAuthorized() async throws {\n        // Make sure updates are called through permissions manager\n        let permissionsManagerCalled = self.expectation(\n            description: \"Permissions manager called\"\n        )\n        self.permissionsManager.addRequestExtender(\n            permission: .displayNotifications\n        ) { _ in\n            permissionsManagerCalled.fulfill()\n        }\n\n        self.push.notificationOptions = [.alert, .badge]\n        self.push.requestExplicitPermissionWhenEphemeral = false\n        await self.serialQueue.waitForCurrentOperations()\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.authorized, [])\n        }\n\n        let success = await self.push.enableUserPushNotifications()\n        XCTAssertTrue(success)\n\n        await self.fulfillment(of: [permissionsManagerCalled], timeout: 10.0)\n    }\n\n    func testEnableUserNotificationsDenied() async throws {\n        self.notificationRegistrar.onCheckStatus = {\n            return(.denied, [])\n        }\n\n        let enabled = self.expectation(description: \"Enabled\")\n        let success = await self.push.enableUserPushNotifications()\n        enabled.fulfill()\n        XCTAssertFalse(success)\n\n        await self.fulfillment(of: [enabled], timeout: 10.0)\n    }\n\n    func testSkipWhenEphemeralDisabled() async throws {\n        let updated = self.expectation(description: \"Registration updated\")\n\n        self.push.notificationOptions = [.alert, .badge]\n        self.push.requestExplicitPermissionWhenEphemeral = true\n        await self.serialQueue.waitForCurrentOperations()\n\n        self.notificationRegistrar.onUpdateRegistration = {\n            options,\n            skipIfEphemeral in\n            XCTAssertEqual([.alert, .badge], options)\n            XCTAssertFalse(skipIfEphemeral)\n            updated.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = true\n\n        await self.fulfillment(of: [updated], timeout: 10.0)\n    }\n\n    @MainActor\n    func testDeviceToken() throws {\n        push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n        XCTAssertEqual(AirshipPushTest.validDeviceToken, self.push.deviceToken)\n    }\n\n    func testSetQuietTime() throws {\n        self.push.setQuietTimeStartHour(\n            12,\n            startMinute: 30,\n            endHour: 14,\n            endMinute: 58\n        )\n        XCTAssertEqual(\n            \"12:30\",\n            self.push.quietTime?.startString\n        )\n        XCTAssertEqual(\n            \"14:58\",\n            self.push.quietTime?.endString\n        )\n        XCTAssertTrue(self.channel.updateRegistrationCalled)\n        let expected = try QuietTimeSettings(startHour: 12, startMinute: 30, endHour: 14, endMinute: 58)\n        XCTAssertEqual(expected, self.push.quietTime)\n    }\n\n\n    func testSetQuietTimeInvalid() throws {\n        XCTAssertNil(self.push.quietTime)\n\n        self.push.setQuietTimeStartHour(\n            25,\n            startMinute: 30,\n            endHour: 14,\n            endMinute: 58\n        )\n        XCTAssertNil(self.push.quietTime)\n\n        self.push.setQuietTimeStartHour(\n            12,\n            startMinute: 61,\n            endHour: 14,\n            endMinute: 58\n        )\n        XCTAssertNil(self.push.quietTime)\n    }\n\n    func testSetTimeZone() throws {\n        self.push.timeZone = NSTimeZone(abbreviation: \"HST\")\n        XCTAssertEqual(\"HST\", self.push.timeZone?.abbreviation)\n        self.push.timeZone = nil\n        XCTAssertEqual(NSTimeZone.default as NSTimeZone, self.push.timeZone)\n    }\n\n    @MainActor\n    func testChannelPayloadRegistered() async throws {\n        self.push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n\n        let payload = await self.channel.channelPayload\n\n        XCTAssertEqual(AirshipPushTest.validDeviceToken, payload.channel.pushAddress)\n        XCTAssertTrue(\n            payload.channel.iOSChannelSettings?.isTimeSensitive == false\n        )\n        XCTAssertTrue(\n            payload.channel.iOSChannelSettings?.isScheduledSummary == false\n        )\n    }\n\n    func testChannelPayloadNotRegistered() async throws {\n        let payload = await self.channel.channelPayload\n\n        XCTAssertNil(payload.channel.pushAddress)\n        XCTAssertFalse(payload.channel.isOptedIn)\n        XCTAssertFalse(payload.channel.isBackgroundEnabled)\n        XCTAssertNil(payload.channel.iOSChannelSettings?.quietTime)\n        XCTAssertNil(payload.channel.iOSChannelSettings?.quietTimeTimeZone)\n        XCTAssertTrue(\n            payload.channel.iOSChannelSettings?.isTimeSensitive == false\n        )\n        XCTAssertTrue(\n            payload.channel.iOSChannelSettings?.isScheduledSummary == false\n        )\n        XCTAssertNil(payload.channel.iOSChannelSettings?.badge)\n    }\n\n    @MainActor\n    func testChannelPayloadNotificationsEnabled() async throws {\n        self.push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n        apnsRegistrar.isRegisteredForRemoteNotifications = true\n        apnsRegistrar.isRemoteNotificationBackgroundModeEnabled = true\n        apnsRegistrar.isBackgroundRefreshStatusAvailable = true\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(\n                .authorized,\n                [.timeSensitive, .scheduledDelivery, .alert]\n            )\n        }\n\n        let enabled = self.expectation(description: \"Registration updated\")\n        let status = await self.permissionsManager.requestPermission(\n            .displayNotifications,\n            enableAirshipUsageOnGrant: true\n        )\n        enabled.fulfill()\n        XCTAssertEqual(.granted, status)\n\n        await self.fulfillment(of: [enabled], timeout: 10.0)\n\n        let payload = await self.channel.channelPayload\n\n        XCTAssertEqual(AirshipPushTest.validDeviceToken, payload.channel.pushAddress)\n        XCTAssertTrue(payload.channel.isOptedIn)\n        XCTAssertTrue(payload.channel.isBackgroundEnabled)\n        XCTAssertTrue(\n            payload.channel.iOSChannelSettings?.isTimeSensitive == true\n        )\n        XCTAssertTrue(\n            payload.channel.iOSChannelSettings?.isScheduledSummary == true\n        )\n    }\n\n    func testChannelPayloadQuietTime() async throws {\n        self.push.quietTimeEnabled = true\n        self.push.setQuietTimeStartHour(\n            1,\n            startMinute: 30,\n            endHour: 2,\n            endMinute: 30\n        )\n        self.push.timeZone = NSTimeZone(abbreviation: \"EDT\")\n\n        let payload = await self.channel.channelPayload\n\n        XCTAssertEqual(\n            \"01:30\",\n            payload.channel.iOSChannelSettings?.quietTime?.start\n        )\n        XCTAssertEqual(\n            \"02:30\",\n            payload.channel.iOSChannelSettings?.quietTime?.end\n        )\n        XCTAssertEqual(\n            \"America/New_York\",\n            payload.channel.iOSChannelSettings?.quietTimeTimeZone\n        )\n    }\n\n    func testChannelPayloadQuietTimeDisabled() async throws {\n        self.push.quietTimeEnabled = false\n        self.push.setQuietTimeStartHour(\n            1,\n            startMinute: 30,\n            endHour: 2,\n            endMinute: 30\n        )\n        self.push.timeZone = NSTimeZone(abbreviation: \"EDT\")\n\n        let payload = await self.channel.channelPayload\n\n        XCTAssertNil(payload.channel.iOSChannelSettings?.quietTime)\n        XCTAssertNil(payload.channel.iOSChannelSettings?.quietTimeTimeZone)\n    }\n\n    @MainActor\n    func testChannelPayloadAutoBadge() async throws {\n        self.push.autobadgeEnabled = true\n        try await self.push.setBadgeNumber(10)\n\n        let payload = await self.channel.channelPayload\n\n        XCTAssertEqual(10, payload.channel.iOSChannelSettings?.badge)\n    }\n\n    func testAnalyticsHeadersOptedOut() async throws {\n        let expected = [\n            \"X-UA-Channel-Opted-In\": \"false\",\n            \"X-UA-Notification-Prompted\": \"false\",\n            \"X-UA-Channel-Background-Enabled\": \"false\",\n        ]\n        let headers = await self.analtyics.headers\n        XCTAssertEqual(expected, headers)\n    }\n\n    @MainActor\n    func testAnalyticsHeadersOptedIn() async throws {\n        self.push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n        apnsRegistrar.isRegisteredForRemoteNotifications = true\n        apnsRegistrar.isRemoteNotificationBackgroundModeEnabled = true\n        apnsRegistrar.isBackgroundRefreshStatusAvailable = true\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(\n                .authorized,\n                [.timeSensitive, .scheduledDelivery, .alert]\n            )\n        }\n\n        let enabled = self.expectation(description: \"Registration updated\")\n        let status = await self.permissionsManager.requestPermission(\n            .displayNotifications,\n            enableAirshipUsageOnGrant: true\n        )\n        enabled.fulfill()\n        XCTAssertEqual(.granted, status)\n        await self.fulfillment(of: [enabled], timeout: 10.0)\n\n        let expected = [\n            \"X-UA-Channel-Opted-In\": \"true\",\n            \"X-UA-Notification-Prompted\": \"true\",\n            \"X-UA-Channel-Background-Enabled\": \"true\",\n            \"X-UA-Push-Address\": AirshipPushTest.validDeviceToken,\n        ]\n\n        let headers = await self.analtyics.headers\n        XCTAssertEqual(expected, headers)\n    }\n\n    func testAnalyticsHeadersPushDisabled() async throws {\n        await self.push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n        self.privacyManager.disableFeatures(.push)\n        let expected = [\n            \"X-UA-Channel-Opted-In\": \"false\",\n            \"X-UA-Channel-Background-Enabled\": \"false\",\n        ]\n\n        let headers = await self.analtyics.headers\n        XCTAssertEqual(expected, headers)\n    }\n\n    @MainActor\n    func testDefaultNotificationCategories() throws {\n        let defaultCategories = NotificationCategories.defaultCategories()\n        XCTAssertEqual(defaultCategories, notificationRegistrar.categories)\n        XCTAssertEqual(defaultCategories, self.push.combinedCategories)\n    }\n\n    @MainActor\n    func testNotificationCategories() throws {\n        let defaultCategories = NotificationCategories.defaultCategories()\n        let customCategory = UNNotificationCategory(\n            identifier: \"something\",\n            actions: [],\n            intentIdentifiers: [\"intents\"]\n        )\n        let combined = Set(defaultCategories).union([customCategory])\n\n        self.push.customCategories = Set([customCategory])\n        XCTAssertEqual(combined, notificationRegistrar.categories)\n    }\n\n    @MainActor\n    func testRequireAuthorizationForDefaultCategories() throws {\n        self.push.requireAuthorizationForDefaultCategories = true\n        let defaultCategories = NotificationCategories.defaultCategories(\n            withRequireAuth: true\n        )\n        XCTAssertEqual(defaultCategories, notificationRegistrar.categories)\n        XCTAssertEqual(defaultCategories, self.push.combinedCategories)\n    }\n\n    @MainActor\n    func testBadge() async throws {\n        try await self.push.setBadgeNumber(100)\n        XCTAssertEqual(100, self.badger.applicationIconBadgeNumber)\n    }\n\n    @MainActor\n    func testAutoBadge() async throws {\n        self.push.autobadgeEnabled = false\n        try await self.push.setBadgeNumber(10)\n        XCTAssertFalse(self.channel.updateRegistrationCalled)\n\n        self.push.autobadgeEnabled = true\n        XCTAssertTrue(self.channel.updateRegistrationCalled)\n\n        self.channel.updateRegistrationCalled = false\n        try await self.push.setBadgeNumber(1)\n        XCTAssertTrue(self.channel.updateRegistrationCalled)\n\n        self.channel.updateRegistrationCalled = false\n        self.push.autobadgeEnabled = false\n        XCTAssertTrue(self.channel.updateRegistrationCalled)\n    }\n\n    @MainActor\n    func testResetBadge() async throws {\n        try await self.push.setBadgeNumber(1000)\n        XCTAssertEqual(1000, self.push.badgeNumber)\n\n        try await self.push.resetBadge()\n        XCTAssertEqual(0, self.push.badgeNumber)\n        XCTAssertEqual(0, self.badger.applicationIconBadgeNumber)\n    }\n\n    func testActiveChecksRegistration() async  {\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.alert])\n        }\n\n        self.notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n\n        await self.serialQueue.waitForCurrentOperations()\n\n        XCTAssertEqual(.authorized, self.push.authorizationStatus)\n        let settings = self.push.authorizedNotificationSettings\n        XCTAssertEqual([.alert], settings)\n        XCTAssertTrue(self.push.userPromptedForNotifications)\n    }\n\n    func testAuthorizedStatusUpdatesChannelRegistration() async {\n        self.notificationRegistrar.onCheckStatus = {\n            return(.authorized, [.alert])\n        }\n\n        self.notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n\n        await self.serialQueue.waitForCurrentOperations()\n        XCTAssertTrue(self.channel.updateRegistrationCalled)\n    }\n\n    func testDefaultOptions() {\n        XCTAssertEqual([.alert, .badge, .sound], self.push.notificationOptions)\n    }\n\n    func testDefaultOptionsProvisional() async {\n        self.notificationRegistrar.onCheckStatus = {\n            return(.provisional, [])\n        }\n\n        let completed = self.expectation(description: \"Completed\")\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completed.fulfill()\n\n\n        self.push.userPushNotificationsEnabled = true\n        await self.fulfillment(of: [completed], timeout: 10.0)\n\n        XCTAssertEqual(\n            [.alert, .badge, .sound, .provisional],\n            self.push.notificationOptions\n        )\n    }\n\n    @MainActor\n    func testCategoriesWhenAppIsHandlingAuthorization() {\n        self.notificationRegistrar.categories = nil\n        self.config.requestAuthorizationToUseNotifications = false\n        self.push = createPush()\n\n        let customCategory = UNNotificationCategory(\n            identifier: \"something\",\n            actions: [],\n            intentIdentifiers: [\"intents\"]\n        )\n        self.push.customCategories = Set([customCategory])\n\n        XCTAssertNil(self.notificationRegistrar.categories)\n    }\n\n    @MainActor\n    func testPermissionsDelgateWhenAppIsHandlingAuthorization() {\n        XCTAssertTrue(\n            self.permissionsManager.configuredPermissions.contains(\n                .displayNotifications\n            )\n        )\n        self.permissionsManager.setDelegate(\n            nil,\n            permission: .displayNotifications\n        )\n\n        XCTAssertFalse(\n            self.permissionsManager.configuredPermissions.contains(\n                .displayNotifications\n            )\n        )\n        self.config.requestAuthorizationToUseNotifications = false\n        self.push = createPush()\n\n        XCTAssertTrue(\n            self.permissionsManager.configuredPermissions.contains(\n                .displayNotifications\n            )\n        )\n    }\n\n    @MainActor\n    func testForwardNotificationRegistrationFinished() {\n        self.push.registrationDelegate = self.registrationDelegate\n\n        self.notificationRegistrar.onCheckStatus = {\n            return(.provisional, [.badge])\n        }\n\n        let called = self.expectation(description: \"Delegate called\")\n        self.registrationDelegate.onNotificationRegistrationFinished = {\n            settings,\n            categories,\n            status in\n            XCTAssertEqual([.badge], settings)\n            XCTAssertEqual(self.push.combinedCategories, categories)\n            XCTAssertEqual(.provisional, status)\n            called.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = true\n        self.wait(for: [called], timeout: 10.0)\n    }\n\n    @MainActor\n    func testForwardAuthorizedSettingsChanges() {\n        self.push.registrationDelegate = self.registrationDelegate\n        self.notificationRegistrar.onCheckStatus = {\n            return(.provisional, [.alert])\n        }\n\n        let called = self.expectation(description: \"Delegate called\")\n        self.registrationDelegate.onNotificationAuthorizedSettingsDidChange = {\n            settings in\n            XCTAssertEqual([.alert], settings)\n            called.fulfill()\n        }\n\n        self.push.userPushNotificationsEnabled = true\n        self.wait(for: [called], timeout: 10.0)\n    }\n\n    @MainActor\n    func testForwardAuthorizedSettingsChangesForeground() {\n        self.push.registrationDelegate = self.registrationDelegate\n        self.notificationRegistrar.onCheckStatus = {\n            return(.provisional, [.badge])\n        }\n\n        let called = self.expectation(description: \"Delegate called\")\n        self.registrationDelegate.onNotificationAuthorizedSettingsDidChange = {\n            settings in\n            XCTAssertEqual([.badge], settings)\n            called.fulfill()\n        }\n\n        self.notificationCenter.post(\n            name: AppStateTracker.didBecomeActiveNotification,\n            object: nil\n        )\n\n        self.wait(for: [called], timeout: 10.0)\n    }\n\n    @MainActor\n    func testForwardAPNSRegistrationSucceeded() {\n        let expectedToken = AirshipPushTest.validDeviceToken.hexData\n\n        self.push.registrationDelegate = self.registrationDelegate\n\n        let called = self.expectation(description: \"Delegate called\")\n        self.registrationDelegate.onAPNSRegistrationSucceeded = { token in\n            XCTAssertEqual(expectedToken, token)\n            called.fulfill()\n        }\n\n        self.push.didRegisterForRemoteNotifications(expectedToken)\n        self.wait(for: [called], timeout: 10.0)\n    }\n\n    @MainActor\n    func testForwardAPNSRegistrationFailed() {\n        let expectedError = AirshipErrors.error(\"something\")\n\n        self.push.registrationDelegate = self.registrationDelegate\n\n        let called = self.expectation(description: \"Delegate called\")\n        self.registrationDelegate.onAPNSRegistrationFailed = { error in\n            XCTAssertEqual(\n                expectedError.localizedDescription,\n                error.localizedDescription\n            )\n            called.fulfill()\n        }\n\n        self.push.didFailToRegisterForRemoteNotifications(expectedError)\n        self.wait(for: [called], timeout: 10.0)\n    }\n\n    @MainActor\n    func testReceivedForegroundNotification() async {\n        let expected = [\"cool\": \"payload\"]\n\n        let result = await self.push.didReceiveRemoteNotification(\n            expected,\n            isForeground: true\n        )\n\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n    }\n\n    @MainActor\n    func testForwardReceivedForegroundNotification() async {\n        let expected = [\"cool\": \"payload\"]\n        self.push.pushNotificationDelegate = self.pushDelegate\n\n        self.pushDelegate.onReceivedForegroundNotification = {\n            notificaiton in\n            XCTAssertEqual(\n                expected as NSDictionary,\n                notificaiton as NSDictionary\n            )\n        }\n\n        let result = await self.push.didReceiveRemoteNotification(expected, isForeground: true)\n\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n    }\n\n    @MainActor\n    func testReceivedBackgroundNotification() async {\n        let expected = [\"cool\": \"payload\"]\n\n        let result = await self.push.didReceiveRemoteNotification(\n            expected,\n            isForeground: false\n        )\n\n        XCTAssertEqual(UABackgroundFetchResult.noData, result)\n    }\n\n    @MainActor\n    func testForwardReceivedBackgroundNotification() async {\n        let expected = [\"cool\": \"payload\"]\n        self.push.pushNotificationDelegate = self.pushDelegate\n\n        self.pushDelegate.onReceivedBackgroundNotification = {\n            notificaiton in\n            XCTAssertEqual(\n                expected as NSDictionary,\n                notificaiton as NSDictionary\n            )\n            return .newData\n        }\n\n        let result = await self.push.didReceiveRemoteNotification(\n            expected,\n            isForeground: false\n        )\n\n        XCTAssertEqual(UABackgroundFetchResult.newData, result)\n    }\n\n    @MainActor\n    func testOptionsPermissionDelegate() async {\n        self.push.userPushNotificationsEnabled = false\n        self.push.notificationOptions = .alert\n\n        let updated = self.expectation(description: \"Registration updated\")\n        self.notificationRegistrar.onUpdateRegistration = {\n            options,\n            skipIfEphemeral in\n            XCTAssertTrue(skipIfEphemeral)\n            if options == [.alert] {\n                updated.fulfill()\n            }\n        }\n\n        let completionHandlerCalled = self.expectation(\n            description: \"Completion handler called\"\n        )\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        completionHandlerCalled.fulfill()\n\n        await self.fulfillment(of: [updated, completionHandlerCalled], timeout: 10)\n    }\n\n    @MainActor\n    func testNotificationStatus() async {\n        self.push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n        self.push.userPushNotificationsEnabled = true\n\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.alert])\n        }\n\n        self.apnsRegistrar.isRegisteredForRemoteNotifications = true\n\n        self.privacyManager.enabledFeatures = .push\n\n        let status = await self.push.notificationStatus\n        XCTAssertEqual(\n            AirshipNotificationStatus(\n                isUserNotificationsEnabled: true,\n                areNotificationsAllowed: true,\n                isPushPrivacyFeatureEnabled: true,\n                isPushTokenRegistered: true,\n                displayNotificationStatus: .granted\n            ),\n            status\n        )\n    }\n\n    @MainActor\n    func testNotificationStatusNoTokenRegistration() async {\n        self.push.didRegisterForRemoteNotifications(\n            AirshipPushTest.validDeviceToken.hexData\n        )\n\n        var status = await self.push.notificationStatus\n        XCTAssertEqual(\n            AirshipNotificationStatus(\n                isUserNotificationsEnabled: false,\n                areNotificationsAllowed: false,\n                isPushPrivacyFeatureEnabled: true,\n                isPushTokenRegistered: false,\n                displayNotificationStatus: .notDetermined\n            ),\n            status\n        )\n\n        self.apnsRegistrar.isRegisteredForRemoteNotifications = true\n\n        status = await self.push.notificationStatus\n        XCTAssertEqual(\n            AirshipNotificationStatus(\n                isUserNotificationsEnabled: false,\n                areNotificationsAllowed: false,\n                isPushPrivacyFeatureEnabled: true,\n                isPushTokenRegistered: true,\n                displayNotificationStatus: .notDetermined\n            ),\n            status\n        )\n    }\n\n\n    @MainActor\n    func testNotificationStatusAllowed() async {\n        self.notificationRegistrar.onCheckStatus = {\n            return (.notDetermined, [.alert])\n        }\n\n        var status = await self.push.notificationStatus\n        XCTAssertEqual(\n            AirshipNotificationStatus(\n                isUserNotificationsEnabled: false,\n                areNotificationsAllowed: false,\n                isPushPrivacyFeatureEnabled: true,\n                isPushTokenRegistered: false,\n                displayNotificationStatus: .notDetermined\n            ),\n            status\n        )\n\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.alert])\n        }\n\n        status = await self.push.notificationStatus\n        XCTAssertEqual(\n            AirshipNotificationStatus(\n                isUserNotificationsEnabled: false,\n                areNotificationsAllowed: true,\n                isPushPrivacyFeatureEnabled: true,\n                isPushTokenRegistered: false,\n                displayNotificationStatus: .granted\n            ),\n            status\n        )\n    }\n\n    @MainActor\n    func testChannelRegistrationWaitsForToken() async {\n        apnsRegistrar.isRegisteredForRemoteNotifications = true\n\n        let startedCRATask = self.expectation(description: \"Started CRA\")\n\n        Task {\n            await fulfillment(of: [startedCRATask])\n            push.didRegisterForRemoteNotifications(\n                AirshipPushTest.validDeviceToken.hexData\n            )\n        }\n\n        let payload = await Task {\n            Task { @MainActor in\n                try await Task.sleep(for: .milliseconds(100))\n                startedCRATask.fulfill()\n            }\n            return await self.channel.channelPayload\n        }.value\n\n\n        XCTAssertNotNil(payload.channel.pushAddress)\n    }\n\n    @MainActor\n    func testAPNSRegistrationFinishedDelegateFallbackSuccess() async {\n        let expectedToken = \"some-token\"\n        let delegate = TestRegistraitonDelegate()\n        let expectation = self.expectation(description: \"Delegate called\")\n        delegate.onAPNSRegistrationSucceeded = { tokenData in\n            XCTAssertEqual(expectedToken.hexData, tokenData)\n            expectation.fulfill()\n        }\n\n        self.push.registrationDelegate = delegate\n        self.push.onAPNSRegistrationFinished = nil\n\n        self.push.didRegisterForRemoteNotifications(expectedToken.hexData)\n        await self.fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testAPNSRegistrationFinishedDelegateFallbackFailure() async {\n        let expectedError = AirshipErrors.error(\"some error\")\n        let delegate = TestRegistraitonDelegate()\n        let expectation = self.expectation(description: \"Delegate called\")\n        delegate.onAPNSRegistrationFailed = { error in\n            XCTAssertEqual(expectedError.localizedDescription, error.localizedDescription)\n            expectation.fulfill()\n        }\n\n        self.push.registrationDelegate = delegate\n        self.push.onAPNSRegistrationFinished = nil\n\n        self.push.didFailToRegisterForRemoteNotifications(expectedError)\n        await self.fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testNotificationRegistrationFinishedCallback() async {\n        let expectation = self.expectation(description: \"Callback called\")\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.alert])\n        }\n\n        self.push.onNotificationRegistrationFinished = { result in\n            XCTAssertEqual(.authorized, result.status)\n            XCTAssertEqual([.alert], result.authorizedSettings)\n            XCTAssertEqual(self.push.combinedCategories, result.categories)\n            expectation.fulfill()\n        }\n\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        await self.fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testNotificationRegistrationFinishedDelegateFallback() async {\n        let delegate = TestRegistraitonDelegate()\n        let expectation = self.expectation(description: \"Delegate called\")\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.alert])\n        }\n\n        delegate.onNotificationRegistrationFinished = { settings, categories, status in\n            XCTAssertEqual(.authorized, status)\n            XCTAssertEqual([.alert], settings)\n            XCTAssertEqual(self.push.combinedCategories, categories)\n            expectation.fulfill()\n        }\n        self.push.registrationDelegate = delegate\n        self.push.onNotificationRegistrationFinished = nil\n\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        await self.fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testAuthorizedSettingsDidChangeCallback() async {\n        let expectation = self.expectation(description: \"Callback called\")\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.sound])\n        }\n\n        self.push.onNotificationAuthorizedSettingsDidChange = { settings in\n            XCTAssertEqual([.sound], settings)\n            expectation.fulfill()\n        }\n\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        await self.fulfillment(of: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testAuthorizedSettingsDidChangeDelegateFallback() async {\n        let delegate = TestRegistraitonDelegate()\n        let expectation = self.expectation(description: \"Delegate called\")\n        self.notificationRegistrar.onCheckStatus = {\n            return (.authorized, [.sound])\n        }\n\n        delegate.onNotificationAuthorizedSettingsDidChange = { settings in\n            XCTAssertEqual([.sound], settings)\n            expectation.fulfill()\n        }\n        self.push.registrationDelegate = delegate\n        self.push.onNotificationAuthorizedSettingsDidChange = nil\n\n        let _ = await self.permissionsManager.requestPermission(.displayNotifications)\n        await self.fulfillment(of: [expectation], timeout: 10.0)\n    }\n}\n\nextension String {\n    var hexData: Data {\n        let chars = Array(self)\n        let bytes = stride(from: 0, to: chars.count, by: 2)\n            .compactMap { UInt8(\"\\(chars[$0])\\(chars[$0 + 1])\", radix: 16) }\n        return Data(bytes)\n    }\n}\n\n@MainActor\nclass TestPushNotificationDelegate: PushNotificationDelegate {\n    func extendPresentationOptions(_ options: UNNotificationPresentationOptions, notification: UNNotification) async -> UNNotificationPresentationOptions {\n        return self.onExtend?(options, notification) ?? options\n    }\n    \n    var onReceivedForegroundNotification:\n        (([AnyHashable: Any]) -> Void)?\n    var onReceivedBackgroundNotification:\n        (([AnyHashable: Any]) -> UIBackgroundFetchResult)?\n    var onReceivedNotificationResponse: ((UNNotificationResponse) -> Void)?\n    var onExtend:\n        (\n            (UNNotificationPresentationOptions, UNNotification) ->\n                UNNotificationPresentationOptions\n        )?\n\n    func receivedForegroundNotification(_ userInfo: [AnyHashable: Any]) async {\n        guard let block = onReceivedForegroundNotification else {\n            return\n        }\n        \n        block(userInfo)\n    }\n\n    func receivedBackgroundNotification(\n        _ userInfo: [AnyHashable: Any]\n    ) async -> UIBackgroundFetchResult {\n        guard let block = onReceivedBackgroundNotification else {\n            return .noData\n        }\n        return block(userInfo)\n    }\n\n    func receivedNotificationResponse(_ notificationResponse: UNNotificationResponse) async {\n        guard let block = onReceivedNotificationResponse else {\n            return\n        }\n        \n        block(notificationResponse)\n    }\n}\n\nclass TestRegistraitonDelegate: NSObject, RegistrationDelegate {\n    func notificationRegistrationFinished(withAuthorizedSettings authorizedSettings: AirshipAuthorizedNotificationSettings, status: UNAuthorizationStatus) {}\n    \n\n    var onNotificationRegistrationFinished:\n        (\n            (\n                AirshipAuthorizedNotificationSettings, Set<UNNotificationCategory>,\n                UNAuthorizationStatus\n            ) -> Void\n        )?\n    var onNotificationAuthorizedSettingsDidChange:\n        ((AirshipAuthorizedNotificationSettings) -> Void)?\n    var onAPNSRegistrationSucceeded: ((Data) -> Void)?\n    var onAPNSRegistrationFailed: ((Error) -> Void)?\n\n    func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            AirshipAuthorizedNotificationSettings,\n        categories: Set<UNNotificationCategory>,\n        status: UNAuthorizationStatus\n    ) {\n        self.onNotificationRegistrationFinished?(\n            authorizedSettings,\n            categories,\n            status\n        )\n    }\n\n    func notificationAuthorizedSettingsDidChange(\n        _ authorizedSettings: AirshipAuthorizedNotificationSettings\n    ) {\n        self.onNotificationAuthorizedSettingsDidChange?(authorizedSettings)\n    }\n\n    func apnsRegistrationSucceeded(withDeviceToken deviceToken: Data) {\n        self.onAPNSRegistrationSucceeded?(deviceToken)\n    }\n\n    func apnsRegistrationFailedWithError(_ error: Error) {\n        self.onAPNSRegistrationFailed?(error)\n    }\n}\n\nfinal class TestNotificationRegistrar: NotificationRegistrar, @unchecked Sendable {\n    var categories: Set<UNNotificationCategory>?\n    var onCheckStatus:\n        (() -> (UNAuthorizationStatus, AirshipAuthorizedNotificationSettings))?\n    var onUpdateRegistration:\n        ((UNAuthorizationOptions, Bool) -> Void)?\n\n    func setCategories(_ categories: Set<UNNotificationCategory>) {\n        self.categories = categories\n    }\n\n    func checkStatus() async -> (UNAuthorizationStatus, AirshipAuthorizedNotificationSettings) {\n        guard let callback = self.onCheckStatus else {\n            return(.notDetermined, [])\n        }\n        return callback()\n    }\n\n    func updateRegistration(\n        options: UNAuthorizationOptions,\n        skipIfEphemeral: Bool\n    ) async -> Void {\n\n        guard let callback = self.onUpdateRegistration else {\n            return\n        }\n        callback(options, skipIfEphemeral)\n    }\n}\n\nfinal class TestAPNSRegistrar: APNSRegistrar, @unchecked Sendable {\n    var isRegisteredForRemoteNotifications: Bool = false\n    var isBackgroundRefreshStatusAvailable: Bool = false\n    var isRemoteNotificationBackgroundModeEnabled: Bool = false\n    var registerForRemoteNotificationsCalled: Bool?\n\n    func registerForRemoteNotifications() {\n        registerForRemoteNotificationsCalled = true\n    }\n}\n\nfinal class TestBadger: BadgerProtocol, @unchecked Sendable {\n    var applicationIconBadgeNumber: Int = 0\n\n    func setBadgeNumber(_ newBadgeNumber: Int) async throws {\n        applicationIconBadgeNumber = newBadgeNumber\n    }\n\n    var badgeNumber: Int { return applicationIconBadgeNumber }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipTest.swift",
    "content": "import XCTest\n\n@testable\nimport AirshipCore\n\nclass UAirshipTest: XCTestCase {\n    private var airshipInstance: TestAirshipInstance!\n    private let deepLinkHandler: TestDeepLinkDelegateHandler = TestDeepLinkDelegateHandler()\n\n    @MainActor\n    override func setUp() {\n        airshipInstance = TestAirshipInstance()\n        self.airshipInstance.makeShared()\n    }\n\n    override class func tearDown() {\n        TestAirshipInstance.clearShared()\n    }\n\n    @MainActor\n    func testUAirshipDeepLinks() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n        \n        let testOpener = (self.airshipInstance.urlOpener as! TestURLOpener)\n\n        self.airshipInstance.components = [component]\n\n        /// App settings\n        var result = await Airship.processDeepLink(URL(string: \"uairship://app_settings\")!)\n        XCTAssertTrue(result)\n        XCTAssertTrue(testOpener.lastOpenSettingsCalled)\n        \n        testOpener.reset()\n\n        // App Store deeplink\n        result = await Airship.processDeepLink(URL(string: \"uairship://app_store?itunesID=0123456789\")!)\n        XCTAssertTrue(result)\n        XCTAssertEqual(testOpener.lastURL?.absoluteString, \"itms-apps://itunes.apple.com/app/0123456789\")\n    }\n\n    func testUAirshipComponentsDeepLinks() async {\n        let component1 = TestAirshipComponent()\n        component1.onDeepLink = { _ in\n            return false\n        }\n\n        let component2 = TestAirshipComponent()\n        component2.onDeepLink = { _ in\n            return true\n        }\n\n        let component3 = TestAirshipComponent()\n        component3.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n\n        self.airshipInstance.components = [component1, component2, component3]\n        self.airshipInstance.deepLinkDelegate = deepLinkHandler\n\n        let deepLink = URL(string: \"uairship://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n\n        XCTAssertEqual(deepLink, component1.deepLink)\n        XCTAssertEqual(deepLink, component2.deepLink)\n        XCTAssertNil(component3.deepLink)\n        XCTAssertNil(self.deepLinkHandler.deepLink)\n    }\n\n\n    func testUAirshipComponentsDeepLinksFallbackDelegate() async {\n        let component1 = TestAirshipComponent()\n        component1.onDeepLink = { _ in\n            return false\n        }\n\n        let component2 = TestAirshipComponent()\n        component2.onDeepLink = { _ in\n            return false\n        }\n\n        let component3 = TestAirshipComponent()\n        component3.onDeepLink = { _ in\n            return false\n        }\n\n        self.airshipInstance.components = [component1, component2, component3]\n        self.airshipInstance.deepLinkDelegate = deepLinkHandler\n\n        let deepLink = URL(string: \"uairship://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n        XCTAssertEqual(deepLink, self.deepLinkHandler.deepLink)\n        XCTAssertEqual(deepLink, component1.deepLink)\n        XCTAssertEqual(deepLink, component2.deepLink)\n        XCTAssertEqual(deepLink, component3.deepLink)\n    }\n\n    func testUAirshipComponentsDeepLinksAlwaysReturnsTrue() async {\n        let component1 = TestAirshipComponent()\n        component1.onDeepLink = { _ in\n            return false\n        }\n\n        let component2 = TestAirshipComponent()\n        component2.onDeepLink = { _ in\n            return false\n        }\n\n        self.airshipInstance.components = [component1, component2]\n\n        let deepLink = URL(string: \"uairship://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n        XCTAssertEqual(deepLink, component1.deepLink)\n        XCTAssertEqual(deepLink, component2.deepLink)\n    }\n\n\n    func testDeepLink() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n\n        self.airshipInstance.components = [component]\n\n        let deepLink = URL(string: \"some-other://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertFalse(result)\n        XCTAssertNil(component.deepLink)\n    }\n\n    func testDeepLinkDelegate() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n\n        self.airshipInstance.components = [component]\n        self.airshipInstance.deepLinkDelegate = deepLinkHandler\n\n        let deepLink = URL(string: \"some-other://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n        XCTAssertNil(component.deepLink)\n        XCTAssertEqual(deepLink, deepLinkHandler.deepLink)\n    }\n\n    @MainActor\n    func testDeepLinkHandlerReturnsTrue() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n\n        var handlerCalled = false\n        self.airshipInstance.onDeepLink = { url in\n            XCTAssertEqual(url.absoluteString, \"some-other://some-deep-link\")\n            handlerCalled = true\n        }\n\n        self.airshipInstance.deepLinkDelegate = deepLinkHandler\n        self.airshipInstance.components = [component]\n\n        let deepLink = URL(string: \"some-other://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n        XCTAssertTrue(handlerCalled)\n        XCTAssertNil(component.deepLink)\n        XCTAssertNil(deepLinkHandler.deepLink) // Delegate should not be called\n    }\n\n    @MainActor\n    func testDeepLinkHandlerPreventsDelegate() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n\n        var handlerCalled = false\n        self.airshipInstance.onDeepLink = { url in\n            XCTAssertEqual(url.absoluteString, \"some-other://some-deep-link\")\n            handlerCalled = true\n        }\n\n        self.airshipInstance.deepLinkDelegate = deepLinkHandler\n        self.airshipInstance.components = [component]\n\n        let deepLink = URL(string: \"some-other://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n        XCTAssertTrue(handlerCalled)\n        XCTAssertNil(component.deepLink)\n        XCTAssertNil(deepLinkHandler.deepLink) // Delegate should NOT be called when handler is set\n    }\n\n    @MainActor\n    func testDeepLinkHandlerWithNoDelegate() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            XCTFail()\n            return false\n        }\n\n        var handlerCalled = false\n        self.airshipInstance.onDeepLink = { url in\n            XCTAssertEqual(url.absoluteString, \"some-other://some-deep-link\")\n            handlerCalled = true\n        }\n\n        self.airshipInstance.components = [component]\n\n        let deepLink = URL(string: \"some-other://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result) // Should return true since handler is set\n        XCTAssertTrue(handlerCalled)\n        XCTAssertNil(component.deepLink)\n    }\n\n    @MainActor\n    func testUAirshipDeepLinkHandlerIntercepts() async {\n        let component = TestAirshipComponent()\n        component.onDeepLink = { _ in\n            return false\n        }\n\n        var handlerCalled = false\n        self.airshipInstance.onDeepLink = { url in\n            XCTAssertEqual(url.absoluteString, \"uairship://some-deep-link\")\n            handlerCalled = true\n        }\n\n        self.airshipInstance.deepLinkDelegate = deepLinkHandler\n        self.airshipInstance.components = [component]\n\n        let deepLink = URL(string: \"uairship://some-deep-link\")!\n        let result = await Airship.processDeepLink(deepLink)\n        XCTAssertTrue(result)\n        XCTAssertTrue(handlerCalled)\n        XCTAssertEqual(deepLink, component.deepLink) // Component still gets called for uairship:// URLs\n        XCTAssertNil(deepLinkHandler.deepLink) // Delegate should NOT be called when handler is set\n    }\n}\n\n\nfileprivate class TestAirshipComponent: AirshipComponent, @unchecked Sendable {\n    var onDeepLink: ((URL) -> Bool)?\n    var deepLink: URL? = nil\n\n    func deepLink(_ deepLink: URL) -> Bool {\n        self.deepLink = deepLink\n        guard let onDeepLink = onDeepLink else { return false }\n        return onDeepLink(deepLink)\n    }\n}\n\nfileprivate class TestDeepLinkDelegateHandler: DeepLinkDelegate, @unchecked Sendable {\n    var deepLink: URL? = nil\n\n    func receivedDeepLink(_ deepLink: URL) async {\n        self.deepLink = deepLink\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipURLAllowListTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\n@MainActor\nclass AirshipURLAllowListTest: XCTestCase {\n\n    private var allowList: DefaultAirshipURLAllowList = DefaultAirshipURLAllowList()\n    private let scopes: [URLAllowListScope] = [.javaScriptInterface, .openURL, .all]\n\n    func testDefaultURLAllowList() {\n        var airshipConfig = AirshipConfig()\n        airshipConfig.urlAllowListScopeOpenURL = []\n\n        allowList = DefaultAirshipURLAllowList(airshipConfig: airshipConfig)\n\n        for scope in scopes {\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://device-api.urbanairship.com/api/user/\")!, scope: scope))\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://dl.urbanairship.com/aaa/message_id\")!, scope: scope))\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://device-api.asnapieu.com/api/user/\")!, scope: scope))\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://dl.asnapieu.com/aaa/message_id\")!, scope: scope))\n        }\n\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://*.youtube.com\")!, scope: .openURL))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://*.youtube.com\")!, scope: .javaScriptInterface))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://*.youtube.com\")!, scope: .all))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:+18675309?body=Hi%20you\")!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:8675309\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"tel:+18675309\")!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"tel:867-5309\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"mailto:name@example.com?subject=The%20subject%20of%20the%20mail\")!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"mailto:name@example.com\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: UIApplication.openSettingsURLString)!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"app-settings:\")!, scope: .openURL))\n\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://some-random-url.com\")!, scope: .openURL))\n    }\n\n    @MainActor\n    func testDefaultURLAllowListNoOpenScopeSet() {\n        allowList = DefaultAirshipURLAllowList(airshipConfig: .init())\n\n        for scope in scopes {\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://device-api.urbanairship.com/api/user/\")!, scope: scope))\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://dl.urbanairship.com/aaa/message_id\")!, scope: scope))\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://device-api.asnapieu.com/api/user/\")!, scope: scope))\n            XCTAssertTrue(allowList.isAllowed(URL(string: \"https://dl.asnapieu.com/aaa/message_id\")!, scope: scope))\n        }\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://*.youtube.com\")!, scope: .openURL))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://*.youtube.com\")!, scope: .javaScriptInterface))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://*.youtube.com\")!, scope: .all))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:+18675309?body=Hi%20you\")!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:8675309\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"tel:+18675309\")!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"tel:867-5309\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"mailto:name@example.com?subject=The%20subject%20of%20the%20mail\")!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"mailto:name@example.com\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: UIApplication.openSettingsURLString)!, scope: .openURL))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"app-settings:\")!, scope: .openURL))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://some-random-url.com\")!, scope: .openURL))\n    }\n\n    func testInvalidPatterns() {\n        // Not a URL\n        XCTAssertFalse(allowList.addEntry(\"not a url\"))\n\n        // Missing schemes\n        XCTAssertFalse(allowList.addEntry(\"www.urbanairship.com\"))\n        XCTAssertFalse(allowList.addEntry(\"://www.urbanairship.com\"))\n\n        // White space in scheme\n        XCTAssertFalse(allowList.addEntry(\" file://*\"))\n\n        // Invalid hosts\n        XCTAssertFalse(allowList.addEntry(\"*://what*\"))\n        XCTAssertFalse(allowList.addEntry(\"*://*what\"))\n    }\n\n    func testSchemeWildcard() {\n        allowList.addEntry(\"*://www.urbanairship.com\")\n\n        XCTAssertTrue(allowList.addEntry(\"*://www.urbanairship.com\"))\n        XCTAssertTrue(allowList.addEntry(\"cool*story://rad\"))\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"\")))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"urbanairship.com\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"www.urbanairship.com\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"cool://rad\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://www.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://www.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file://www.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"valid://www.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"cool----story://rad\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"coolstory://rad\")!))\n    }\n\n    func testScheme() {\n        allowList.addEntry(\"https://www.urbanairship.com\")\n        allowList.addEntry(\"file:///asset.html\")\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"http://www.urbanairship.com\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://www.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file:///asset.html\")!))\n    }\n\n    func testHost() {\n        XCTAssertTrue(allowList.addEntry(\"http://www.urbanairship.com\"))\n        XCTAssertTrue(allowList.addEntry(\"http://oh.hi.marc\"))\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"http://oh.bye.marc\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"http://www.urbanairship.com.hackers.io\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"http://omg.www.urbanairship.com.hackers.io\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://www.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://oh.hi.marc\")!))\n    }\n\n    func testHostWildcard() {\n        XCTAssertTrue(allowList.addEntry(\"http://*\"))\n        XCTAssertTrue(allowList.addEntry(\"https://*.coolstory\"))\n\n        // * is only available at the beginning\n        XCTAssertFalse(allowList.addEntry(\"https://*.coolstory.*\"))\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"\")))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://cool\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://story\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://what.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http:///android-asset/test.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://www.anything.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://coolstory\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://what.coolstory\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://what.what.coolstory\")!))\n    }\n\n    func testHostWildcardSubdomain() {\n        XCTAssertTrue(allowList.addEntry(\"http://*.urbanairship.com\"))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://what.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://hi.urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://urbanairship.com\")!))\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"http://lololurbanairship.com\")!))\n    }\n\n    func testWildcardMatcher() {\n        XCTAssertTrue(allowList.addEntry(\"*\"))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file:///what/oh/hi\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://hi.urbanairship.com/path\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"http://urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"cool.story://urbanairship.com\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:+18664504185?body=Hi\")!))\n    }\n\n    func testFilePaths() {\n        XCTAssertTrue(allowList.addEntry(\"file:///foo/index.html\"))\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"file:///foo/test.html\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"file:///foo/bar/index.html\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file:///foo/index.html\")!))\n    }\n\n    func testFilePathsWildCard() {\n        XCTAssertTrue(allowList.addEntry(\"file:///foo/bar.html\"))\n        XCTAssertTrue(allowList.addEntry(\"file:///foo/*\"))\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"file:///foooooooo/bar.html\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file:///foo/test.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file:///foo/bar/index.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"file:///foo/bar.html\")!))\n    }\n\n    func testURLPaths() {\n        allowList.addEntry(\"*://*.urbanairship.com/accept.html\")\n        allowList.addEntry(\"*://*.urbanairship.com/anythingHTML/*.html\")\n        allowList.addEntry(\"https://urbanairship.com/what/index.html\")\n        allowList.addEntry(\"wild://cool/*\")\n\n        // Reject\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://what.urbanairship.com/reject.html\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://what.urbanairship.com/anythingHTML/image.png\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://what.urbanairship.com/anythingHTML/image.png\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"wile:///whatever\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"wile:///cool\")!))\n\n        // Accept\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://what.urbanairship.com/anythingHTML/index.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://what.urbanairship.com/anythingHTML/test.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://what.urbanairship.com/anythingHTML/foo/bar/index.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/what/index.html\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"wild://cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"wild://cool/\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"wild://cool/path\")!))\n    }\n\n    func testScope() {\n        allowList.addEntry(\"*://*.urbanairship.com/accept-js.html\", scope: .javaScriptInterface)\n        allowList.addEntry(\"*://*.urbanairship.com/accept-url.html\", scope: .openURL)\n        allowList.addEntry(\"*://*.urbanairship.com/accept-all.html\", scope: .all)\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-js.html\")!, scope: .javaScriptInterface))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-js.html\")!, scope: .openURL))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-js.html\")!, scope: .all))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-url.html\")!, scope: .openURL))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-url.html\")!, scope: .javaScriptInterface))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-url.html\")!, scope: .all))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-all.html\")!, scope: .all))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-all.html\")!, scope: .javaScriptInterface))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/accept-all.html\")!, scope: .openURL))\n    }\n\n    func testDisableOpenURLScopeAllowList() {\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://someurl.com\")!, scope: .openURL))\n\n        allowList.addEntry(\"*\", scope: .openURL)\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://someurl.com\")!, scope: .openURL))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://someurl.com\")!, scope: .javaScriptInterface))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"https://someurl.com\")!, scope: .all))\n    }\n\n    func testAddAllScopesSeparately() {\n        allowList.addEntry(\"*://*.urbanairship.com/all.html\", scope: .openURL)\n        allowList.addEntry(\"*://*.urbanairship.com/all.html\", scope: .javaScriptInterface)\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/all.html\")!, scope: .all))\n    }\n\n    func testAllScopeMatchesInnerScopes() {\n        allowList.addEntry(\"*://*.urbanairship.com/all.html\", scope: .all)\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/all.html\")!, scope: .javaScriptInterface))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"https://urbanairship.com/all.html\")!, scope: .openURL))\n    }\n\n    func testDeepLinks() {\n        // Test any path and undefined host\n        XCTAssertTrue(allowList.addEntry(\"com.urbanairship.one:/*\"))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.one://cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.one:cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.one:/cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.one:///cool\")!))\n\n        // Test any host and undefined path\n        XCTAssertTrue(allowList.addEntry(\"com.urbanairship.two://*\"))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.two:cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.two://cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.two:/cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.two:///cool\")!))\n\n        // Test any host and any path\n        XCTAssertTrue(allowList.addEntry(\"com.urbanairship.three://*/*\"))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.three:cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.three://cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.three:/cool\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.three:///cool\")!))\n\n        // Test specific host and path\n        XCTAssertTrue(allowList.addEntry(\"com.urbanairship.four://*.cool/whatever/*\"))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"com.urbanairship.four:cool\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"com.urbanairship.four://cool\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"com.urbanairship.four:/cool\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"com.urbanairship.four:///cool\")!))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.four://whatever.cool/whatever/\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.four://cool/whatever/indeed\")!))\n    }\n\n\n    func testRootPath() {\n        XCTAssertTrue(allowList.addEntry(\"com.urbanairship.five:/\"))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.five:/\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"com.urbanairship.five:///\")!))\n\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"com.urbanairship.five:/cool\")!))\n    }\n\n    func testDelegate() {\n        // set up a simple URL allow list\n        allowList.addEntry(\"https://*.urbanairship.com\")\n        allowList.addEntry(\"https://*.youtube.com\", scope: .openURL)\n\n        // Matching URL to be checked\n        let matchingURLToReject = URL(string: \"https://www.youtube.com/watch?v=sYd_-pAfbBw\")!\n        let matchingURLToAccept = URL(string: \"https://device-api.urbanairship.com/api/user\")!\n        let nonMatchingURL = URL(string: \"https://maps.google.com\")!\n\n        let scope: URLAllowListScope = .openURL\n\n        // Allow listing when delegate is off\n        XCTAssertTrue(allowList.isAllowed(matchingURLToReject, scope: scope))\n        XCTAssertTrue(allowList.isAllowed(matchingURLToAccept, scope: scope))\n        XCTAssertFalse(allowList.isAllowed(nonMatchingURL, scope: scope))\n\n        // Enable URL allow list delegate\n        let delegate = TestDelegate()\n        delegate.onAllow = { url, scope in\n            if (url == matchingURLToAccept) {\n                return true\n            }\n\n            if (url == matchingURLToReject) {\n                return false\n            }\n\n            XCTFail()\n            return false\n        }\n\n        allowList.delegate = delegate\n\n        // rejected URL should now fail URL allow list test, others should be unchanged\n        XCTAssertFalse(allowList.isAllowed(matchingURLToReject, scope: scope))\n        XCTAssertTrue(allowList.isAllowed(matchingURLToAccept, scope: scope))\n        XCTAssertFalse(allowList.isAllowed(nonMatchingURL, scope: scope))\n\n        // Disable URL allow list delegate\n        allowList.delegate = nil\n\n        // Should go back to original state when delegate was off\n        XCTAssertTrue(allowList.isAllowed(matchingURLToReject, scope: scope))\n        XCTAssertTrue(allowList.isAllowed(matchingURLToAccept, scope: scope))\n        XCTAssertFalse(allowList.isAllowed(nonMatchingURL, scope: scope))\n    }\n\n    func testOnAllowBlock() {\n        // set up a simple URL allow list\n        allowList.addEntry(\"https://*.urbanairship.com\")\n        allowList.addEntry(\"https://*.youtube.com\", scope: .openURL)\n\n        // Matching URL to be checked\n        let matchingURLToReject = URL(string: \"https://www.youtube.com/watch?v=sYd_-pAfbBw\")!\n        let matchingURLToAccept = URL(string: \"https://device-api.urbanairship.com/api/user\")!\n        let nonMatchingURL = URL(string: \"https://maps.google.com\")!\n\n        let scope: URLAllowListScope = .openURL\n\n        // Allow listing when delegate is off\n        XCTAssertTrue(allowList.isAllowed(matchingURLToReject, scope: scope))\n        XCTAssertTrue(allowList.isAllowed(matchingURLToAccept, scope: scope))\n        XCTAssertFalse(allowList.isAllowed(nonMatchingURL, scope: scope))\n\n        // Delegate should be ignored\n        let delegate = TestDelegate()\n        delegate.onAllow = { url, scope in\n            XCTFail()\n            return false\n        }\n\n        allowList.delegate = delegate\n\n        allowList.onAllowURL = { url, scope in\n            if (url == matchingURLToAccept) {\n                return true\n            }\n\n            if (url == matchingURLToReject) {\n                return false\n            }\n\n            XCTFail()\n            return false\n        }\n\n\n        // rejected URL should now fail URL allow list test, others should be unchanged\n        XCTAssertFalse(allowList.isAllowed(matchingURLToReject, scope: scope))\n        XCTAssertTrue(allowList.isAllowed(matchingURLToAccept, scope: scope))\n        XCTAssertFalse(allowList.isAllowed(nonMatchingURL, scope: scope))\n\n        // Disable URL allow list delegate\n        allowList.delegate = nil\n        allowList.onAllowURL = nil\n\n        // Should go back to original state when delegate was off\n        XCTAssertTrue(allowList.isAllowed(matchingURLToReject, scope: scope))\n        XCTAssertTrue(allowList.isAllowed(matchingURLToAccept, scope: scope))\n        XCTAssertFalse(allowList.isAllowed(nonMatchingURL, scope: scope))\n    }\n\n    func testSMSPath() {\n        XCTAssertTrue(allowList.addEntry(\"sms:86753*9*\"))\n\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"sms:86753\")!))\n        XCTAssertFalse(allowList.isAllowed(URL(string: \"sms:867530\")!))\n\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:86753191\")!))\n        XCTAssertTrue(allowList.isAllowed(URL(string: \"sms:8675309\")!))\n    }\n}\n\nfileprivate final class TestDelegate: URLAllowListDelegate, @unchecked Sendable {\n    var allowURLCalled = false\n\n    var onAllow: ((URL, URLAllowListScope) -> Bool)?\n\n    func allowURL(_ url: URL, scope: URLAllowListScope) -> Bool {\n        allowURLCalled = true\n        return onAllow!(url, scope)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AirshipUtilsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AirshipUtilsTest: XCTestCase {\n\n    func testSignedToken() throws {\n\n        XCTAssertEqual(\n            \"VWtkZq18HZM3GWzD/q27qPSVszysSyoQfQ6tDEAcAko=\",\n            try! AirshipUtils.generateSignedToken(secret: \"appSecret\", tokenParams: [\"appKey\", \"some channel\"])\n        )\n\n        XCTAssertEqual(\n            \"Npyqy5OZxMEVv4bt64S3aUE4NwUQVLX50vGrEegohFE=\",\n            try! AirshipUtils.generateSignedToken(secret: \"test-app-secret\", tokenParams: [\"test-app-key\", \"channel ID\"])\n        )\n    }\n\n    func testIsSilentPush() {\n        let emptyNotification: [String: Any] = [\n            \"aps\": [\n                \"content-available\": 1\n            ]\n        ]\n\n        let emptyAlert: [String: Any] = [\n            \"aps\": [\n                \"alert\": \"\"\n            ]\n        ]\n\n        let emptyLocKey: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"loc-key\": \"\"\n                ]\n            ]\n        ]\n\n        let emptyBody: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"body\": \"\"\n                ]\n            ]\n        ]\n\n        XCTAssertTrue(AirshipUtils.isSilentPush(emptyNotification))\n        XCTAssertTrue(AirshipUtils.isSilentPush(emptyAlert))\n        XCTAssertTrue(AirshipUtils.isSilentPush(emptyLocKey))\n        XCTAssertTrue(AirshipUtils.isSilentPush(emptyBody))\n    }\n\n    func testIsSilentPushNo() {\n        let alertNotification: [String: Any] = [\n            \"aps\": [\n                \"alert\": \"hello world\"\n            ]\n        ]\n\n        let badgeNotification: [String: Any] = [\n            \"aps\": [\n                \"badge\": 2\n            ]\n        ]\n\n        let soundNotification: [String: Any] = [\n            \"aps\": [\n                \"sound\": \"cat\"\n            ]\n        ]\n\n        let notification: [String: Any] = [\n            \"aps\": [\n                \"alert\": \"hello world\",\n                \"badge\": 2,\n                \"sound\": \"cat\"\n            ]\n        ]\n\n        let locKeyNotification: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"loc-key\": \"cool\"\n                ]\n            ]\n        ]\n\n        let bodyNotification: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"body\": \"cool\"\n                ]\n            ]\n        ]\n\n        XCTAssertFalse(AirshipUtils.isSilentPush(alertNotification))\n        XCTAssertFalse(AirshipUtils.isSilentPush(badgeNotification))\n        XCTAssertFalse(AirshipUtils.isSilentPush(soundNotification))\n        XCTAssertFalse(AirshipUtils.isSilentPush(notification))\n        XCTAssertFalse(AirshipUtils.isSilentPush(locKeyNotification))\n        XCTAssertFalse(AirshipUtils.isSilentPush(bodyNotification))\n    }\n\n    func testIsAlertingPush() {\n        let alertNotification: [String: Any] = [\n            \"aps\": [\n                \"alert\": \"hello world\"\n            ]\n        ]\n\n        let notification: [String: Any] = [\n            \"aps\": [\n                \"alert\": \"hello world\",\n                \"badge\": 2,\n                \"sound\": \"cat\"\n            ]\n        ]\n\n        let locKeyNotification: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"loc-key\": \"cool\"\n                ]\n            ]\n        ]\n\n        let bodyNotification: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"body\": \"cool\"\n                ]\n            ]\n        ]\n\n        XCTAssertTrue(AirshipUtils.isAlertingPush(alertNotification))\n        XCTAssertTrue(AirshipUtils.isAlertingPush(notification))\n        XCTAssertTrue(AirshipUtils.isAlertingPush(locKeyNotification))\n        XCTAssertTrue(AirshipUtils.isAlertingPush(bodyNotification))\n    }\n\n    func testIsAlertingPushNo() {\n        let emptyNotification: [String: Any] = [\n            \"aps\": [\n                \"content-available\": 1\n            ]\n        ]\n\n        let emptyAlert: [String: Any] = [\n            \"aps\": [\n                \"alert\": \"\"\n            ]\n        ]\n\n        let emptyLocKey: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"loc-key\": \"\"\n                ]\n            ]\n        ]\n\n        let emptyBody: [String: Any] = [\n            \"aps\": [\n                \"alert\": [\n                    \"body\": \"\"\n                ]\n            ]\n        ]\n\n        let badgeNotification: [String: Any] = [\n            \"aps\": [\n                \"badge\": 2\n            ]\n        ]\n\n        let soundNotification: [String: Any] = [\n            \"aps\": [\n                \"sound\": \"cat\"\n            ]\n        ]\n\n        XCTAssertFalse(AirshipUtils.isAlertingPush(emptyNotification))\n        XCTAssertFalse(AirshipUtils.isAlertingPush(emptyAlert))\n        XCTAssertFalse(AirshipUtils.isAlertingPush(emptyLocKey))\n        XCTAssertFalse(AirshipUtils.isAlertingPush(emptyBody))\n        XCTAssertFalse(AirshipUtils.isAlertingPush(badgeNotification))\n        XCTAssertFalse(AirshipUtils.isAlertingPush(soundNotification))\n    }\n\n    func testParseURL() {\n        var originalUrl = \"https://advswift.com/api/v1?page=url+components\"\n        var url = AirshipUtils.parseURL(originalUrl)\n        XCTAssertNotNil(url)\n        XCTAssertEqual(originalUrl, url?.absoluteString)\n\n        originalUrl = \"rtlmost://szakaszó.com/main/típus/v1?page=azonosító\"\n        url = AirshipUtils.parseURL(originalUrl)\n        XCTAssertNotNil(url)\n\n        if #available(iOS 17.0, tvOS 17.0, *) {\n            let encodedUrl = \"rtlmost://xn--szakasz-r0a.com/main/t%C3%ADpus/v1?page=azonos%C3%ADt%C3%B3\"\n            XCTAssertEqual(encodedUrl, url?.absoluteString)\n        } else {\n            let encodedUrl = \"rtlmost://szakasz%C3%B3.com/main/t%C3%ADpus/v1?page=azonos%C3%ADt%C3%B3\"\n            XCTAssertEqual(encodedUrl, url?.absoluteString)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AishipFontTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\nimport SwiftUI\n\n#if canImport(UIKit)\nimport UIKit\n#elseif canImport(AppKit)\nimport AppKit\n#endif\n\n@Suite struct AirshipFontTests {\n    \n    @Test\n    func testResolveFontFamily() {\n        // Serif -> Times New Roman\n#if canImport(UIKit) || canImport(AppKit)\n        let serif = AirshipFont.resolveFontFamily(families: [\"serif\"])\n#if os(macOS)\n        // Times New Roman might vary by OS version but usually present\n        // If not present, it might fail, but let's assume standard env\n        if let serif = serif {\n            #expect(serif == \"Times New Roman\")\n        }\n#else\n        #expect(serif == \"Times New Roman\")\n#endif\n        \n        // Sans-serif -> nil (system)\n        let sans = AirshipFont.resolveFontFamily(families: [\"sans-serif\"])\n        #expect(sans == nil)\n        \n        // Existing font (Helvetica is standard on Apple platforms)\n        let helvetica = AirshipFont.resolveFontFamily(families: [\"Helvetica\"])\n        #expect(helvetica == \"Helvetica\")\n        \n        // Fallback\n        let fallback = AirshipFont.resolveFontFamily(families: [\"NonExistentFont\", \"Helvetica\"])\n        #expect(fallback == \"Helvetica\")\n        \n        // Non-existent\n        let none = AirshipFont.resolveFontFamily(families: [\"NonExistentFontSomething123\"])\n        #expect(none == nil)\n        \n        // Empty/Nil\n        #expect(AirshipFont.resolveFontFamily(families: []) == nil)\n        #expect(AirshipFont.resolveFontFamily(families: nil) == nil)\n#endif\n    }\n    \n    @Test\n    @MainActor\n    func testResolveNativeFont() {\n        #if canImport(UIKit)\n        let size = 20.0\n        let expectedScaledSize = CGFloat(AirshipFont.scaledSize(size))\n        \n        // System Font\n        let systemFont = AirshipFont.resolveNativeFont(size: size)\n        // Note: scaledSize might make it larger or smaller than 20.0 depending on dynamic type settings\n        #expect(abs(systemFont.pointSize - expectedScaledSize) <= 0.5)\n        \n        // Family\n        let helvetica = AirshipFont.resolveNativeFont(size: size, families: [\"Helvetica\"])\n        #expect(helvetica.familyName == \"Helvetica\")\n        \n        // Italic\n        let italic = AirshipFont.resolveNativeFont(size: size, isItalic: true)\n        #expect(italic.fontDescriptor.symbolicTraits.contains(.traitItalic))\n        \n        // Bold\n        let bold = AirshipFont.resolveNativeFont(size: size, isBold: true)\n        // .traitBold check\n        #expect(bold.fontDescriptor.symbolicTraits.contains(.traitBold))\n        \n        // Specific Weight\n        let heavy = AirshipFont.resolveNativeFont(size: size, weight: 800)\n        // Hard to check exact weight value easily cross-version, but we can verify it returns a font\n        #expect(abs(heavy.pointSize - expectedScaledSize) <= 0.5)\n        \n        #elseif canImport(AppKit)\n        let size = 20.0\n        \n        // System Font\n        let systemFont = AirshipFont.resolveNativeFont(size: size)\n        #expect(systemFont.pointSize == size) // macOS usually doesn't scale by default like iOS dynamic type in this context unless specified? \n        // Logic: scaledSize returns size on macOS\n        \n        // Family\n        let helvetica = AirshipFont.resolveNativeFont(size: size, families: [\"Helvetica\"])\n        #expect(helvetica.familyName == \"Helvetica\")\n        \n        // Italic\n        let italic = AirshipFont.resolveNativeFont(size: size, isItalic: true)\n        #expect(italic.fontDescriptor.symbolicTraits.contains(.italic))\n        \n        // Bold\n        // Note: NSFont behavior with traits might vary, but basic system bold should work\n        let bold = AirshipFont.resolveNativeFont(size: size, isBold: true)\n        // Testing exact traits on macOS can be tricky with NSFontDescriptor, but let's try\n        #expect(bold.fontDescriptor.symbolicTraits.contains(.bold)) // .bold is unavailable on some older OS versions? No, .bold is standard in NSFontDescriptor.SymbolicTraits\n        \n#endif\n    }\n    \n    @Test\n    @MainActor\n    func testResolveSwiftUIFont() {\n        // Just verify it doesn't crash and returns a Font\n        let font = AirshipFont.resolveFont(size: 16, families: [\"serif\"], weight: 400, isItalic: true, isBold: false)\n        // Validating SwiftUI Font contents is not possible via public API, so existence is the test.\n        _ = font\n    }\n    \n    @Test\n    func testScaledSize() {\n        let size = 10.0\n        let scaled = AirshipFont.scaledSize(size)\n        \n#if os(macOS)\n        #expect(scaled == size)\n#else\n        // iOS scales based on settings. Assuming default, it might be close to size or larger.\n        // We just ensure it's not zero.\n        #expect(scaled > 0)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutButtonTapEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutButtonTapEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.ButtonTapEvent(\n            identifier: \"button id\",\n            reportingMetadata: \"reporting metadata\"\n        )\n\n        let event = ThomasLayoutButtonTapEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_button_tap\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"reporting_metadata\":\"reporting metadata\",\n           \"button_identifier\":\"button id\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutDisplayEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutDisplayEventTest {\n\n    @Test\n    func testEvent() throws {\n        let event = ThomasLayoutDisplayEvent()\n        #expect(event.name.reportingName == \"in_app_display\")\n        #expect(event.data == nil)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutEventTestUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable import AirshipCore\n\nextension ThomasLayoutEvent {\n    var bodyJSON: AirshipJSON {\n        get throws {\n            guard let data = self.data else {\n                return AirshipJSON.null\n            }\n\n            return try AirshipJSON.wrap(EventBody(data: data))\n        }\n    }\n}\n\nfileprivate struct EventBody: Encodable {\n    var data: (any Encodable&Sendable)?\n\n    func encode(to encoder: Encoder) throws {\n        try data?.encode(to: encoder)\n    }\n}\n\nextension AirshipJSON {\n    func log() {\n        let string = try! self.toString()\n        print(\"\\(string)\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutFormDisplayEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutFormDisplayEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.FormDisplayEvent(\n            identifier: \"form id\",\n            formType: \"nps\",\n            responseType: \"user feedback\"\n        )\n\n        let event = ThomasLayoutFormDisplayEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_form_display\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"form_identifier\":\"form id\",\n           \"form_type\":\"nps\",\n           \"form_response_type\":\"user feedback\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutFormResultEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutFormResultEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.FormResultEvent(\n            forms: \"form result\"\n        )\n\n        let event = ThomasLayoutFormResultEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_form_result\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"forms\": \"form result\"\n        }\n        \"\"\"\n        \n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutGestureEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutGestureEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.GestureEvent(\n            identifier: \"gesture id\",\n            reportingMetadata: \"reporting metadata\"\n        )\n\n        let event = ThomasLayoutGestureEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_gesture\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"reporting_metadata\":\"reporting metadata\",\n           \"gesture_identifier\":\"gesture id\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutPageActionEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutPageActionEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PageActionEvent(\n            identifier: \"action id\",\n            reportingMetadata: \"reporting metadata\"\n        )\n\n        let event = ThomasLayoutPageActionEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_page_action\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"reporting_metadata\":\"reporting metadata\",\n           \"action_identifier\":\"action id\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutPageSwipeEventAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutPageSwipeEventAction {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PageSwipeEvent(\n            identifier: \"pager identifier\",\n            toPageIndex: 4,\n            toPageIdentifier: \"to page identifier\",\n            fromPageIndex: 3,\n            fromPageIdentifier: \"from page identifier\"\n        )\n\n        let event = ThomasLayoutPageSwipeEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_page_swipe\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"pager_identifier\":\"pager identifier\",\n           \"from_page_index\":3,\n           \"to_page_identifier\":\"to page identifier\",\n           \"from_page_identifier\":\"from page identifier\",\n           \"to_page_index\":4\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutPageViewEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutPageViewEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PageViewEvent(\n            identifier: \"pager identifier\",\n            pageIdentifier: \"page identifier\",\n            pageIndex: 3,\n            pageViewCount: 31,\n            pageCount: 12,\n            completed: false\n        )\n\n        let event = ThomasLayoutPageViewEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_page_view\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"page_identifier\":\"page identifier\",\n           \"page_index\":3,\n           \"viewed_count\":31,\n           \"page_count\":12,\n           \"pager_identifier\":\"pager identifier\",\n           \"completed\":false\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutPagerCompletedEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutPagerCompletedEventTest {\n\n    @Test\n    func testEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PagerCompletedEvent(\n            identifier: \"pager identifier\",\n            pageIndex: 3,\n            pageCount: 12,\n            pageIdentifier: \"page identifier\"\n        )\n\n        let event = ThomasLayoutPagerCompletedEvent(data: thomasEvent)\n        #expect(event.name.reportingName == \"in_app_pager_completed\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"page_count\":12,\n           \"pager_identifier\":\"pager identifier\",\n           \"page_index\":3,\n           \"page_identifier\":\"page identifier\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutPagerSummaryEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutPagerSummaryEventTest {\n\n    func testEvent() throws {\n        let event = ThomasLayoutPagerSummaryEvent(\n            data: .init(\n                identifier: \"pager identifier\",\n                viewedPages: [\n                    .init(\n                        identifier: \"page 1\",\n                        index: 0,\n                        displayTime: 10.4\n                    ),\n                    .init(\n                        identifier: \"page 2\",\n                        index: 1,\n                        displayTime: 3.0\n                    ),\n                    .init(\n                        identifier: \"page 3\",\n                        index: 2,\n                        displayTime: 4.0\n                    )\n                ],\n                pageCount: 12,\n                completed: false\n            )\n        )\n        #expect(event.name.reportingName == \"in_app_pager_summary\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"viewed_pages\":[\n              {\n                 \"display_time\":\"10.40\",\n                 \"page_identifier\":\"page 1\",\n                 \"page_index\":0\n              },\n              {\n                 \"page_index\":1,\n                 \"display_time\":\"3.00\",\n                 \"page_identifier\":\"page 2\"\n              },\n              {\n                 \"page_identifier\":\"page 3\",\n                 \"page_index\":2,\n                 \"display_time\":\"4.00\"\n              }\n           ],\n           \"page_count\":12,\n           \"completed\":false,\n           \"pager_identifier\":\"pager identifier\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutPermissionResultEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\n\nstruct ThomasLayoutPermissionResultEventTest {\n\n    @Test\n    func testEvent() throws {\n        let event = ThomasLayoutPermissionResultEvent(\n            permission: .displayNotifications,\n            startingStatus: .denied,\n            endingStatus: .granted\n        )\n        #expect(event.name.reportingName == \"in_app_permission_result\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"permission\":\"display_notifications\",\n           \"starting_permission_status\":\"denied\",\n           \"ending_permission_status\":\"granted\"\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/Events/ThomasLayoutResolutionEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\n\nstruct ThomasLayoutResolutionEventTest {\n\n    @Test\n    func testButtonResolution() throws {\n        let event = ThomasLayoutResolutionEvent.buttonTap(\n            identifier: \"button id\",\n            description: \"button description\",\n            displayTime: 100.0\n        )\n        #expect(event.name.reportingName == \"in_app_resolution\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"resolution\": {\n               \"display_time\":\"100.00\",\n               \"button_description\":\"button description\",\n               \"type\":\"button_click\",\n               \"button_id\":\"button id\"\n            }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testMessageTap() throws {\n        let event = ThomasLayoutResolutionEvent.messageTap(displayTime: 100.0)\n        #expect(event.name.reportingName == \"in_app_resolution\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"resolution\": {\n               \"display_time\":\"100.00\",\n               \"type\":\"message_click\"\n            }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testUserDismissed() throws {\n        let event = ThomasLayoutResolutionEvent.userDismissed(displayTime: 100.0)\n        #expect(event.name.reportingName == \"in_app_resolution\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"resolution\": {\n              \"display_time\":\"100.00\",\n              \"type\":\"user_dismissed\"\n           }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testTimedOut() throws {\n        let event = ThomasLayoutResolutionEvent.timedOut(displayTime: 100.0)\n        #expect(event.name.reportingName == \"in_app_resolution\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"resolution\": {\n              \"display_time\":\"100.00\",\n              \"type\":\"timed_out\"\n           }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testControl() throws {\n        let experimentResult = ExperimentResult(\n            channelID: \"channel id\",\n            contactID: \"contact id\",\n            isMatch: true,\n            reportingMetadata: [AirshipJSON.string(\"reporting\")]\n        )\n\n        let event = ThomasLayoutResolutionEvent.control(experimentResult: experimentResult)\n        #expect(event.name.reportingName == \"in_app_resolution\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"resolution\": {\n              \"display_time\":\"0.00\",\n              \"type\":\"control\"\n           },\n           \"device\": {\n              \"channel_id\": \"channel id\",\n              \"contact_id\": \"contact id\"\n           }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testAudienceExcluded() throws {\n        let event = ThomasLayoutResolutionEvent.audienceExcluded()\n        #expect(event.name.reportingName == \"in_app_resolution\")\n\n        let expectedJSON = \"\"\"\n        {\n           \"resolution\": {\n              \"display_time\":\"0.00\",\n              \"type\":\"audience_check_excluded\"\n           }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try event.bodyJSON\n        #expect(actual == expected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/ThomasDisplayListenerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\n\n\n@MainActor\nstruct ThomasDisplayListenerTest {\n\n    private let analytics = TestThomasLayoutMessageAnalytics()\n    private let listener: ThomasDisplayListener\n    private let result: AirshipMainActorValue<ThomasDisplayListener.DisplayResult?> = AirshipMainActorValue(nil)\n\n    private let layoutContext: ThomasLayoutContext = ThomasLayoutContext(\n        pager: nil,\n        button: .init(identifier: \"button\"),\n        form: nil\n    )\n\n    init() {\n        self.listener = ThomasDisplayListener(\n            analytics: analytics\n        ) { [result] displayResult in\n            result.set(displayResult)\n        }\n    }\n\n    @Test\n    func testDismiss() {\n        listener.onDismissed(cancel: false)\n        #expect(result.value == .finished)\n    }\n\n    @Test\n    func testDismissAndCancel() {\n        listener.onDismissed(cancel: true)\n        #expect(result.value == .cancel)\n    }\n\n    @Test(\"Visibility changes emits display event\")\n    func testVisibilityChangesEmitsDisplayEvent() throws {\n        listener.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        try verifyEvents([(ThomasLayoutDisplayEvent(), nil)])\n\n        listener.onVisibilityChanged(isVisible: false, isForegrounded: false)\n        try verifyEvents([(ThomasLayoutDisplayEvent(), nil)])\n\n        listener.onVisibilityChanged(isVisible: true, isForegrounded: false)\n        try verifyEvents([(ThomasLayoutDisplayEvent(), nil)])\n\n        listener.onVisibilityChanged(isVisible: false, isForegrounded: true)\n        try verifyEvents([(ThomasLayoutDisplayEvent(), nil)])\n\n        listener.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        try verifyEvents([(ThomasLayoutDisplayEvent(), nil), (ThomasLayoutDisplayEvent(), nil)])\n    }\n\n    @Test\n    func testButtonTapEvent() throws {\n        let thomasEvent = ThomasReportingEvent.ButtonTapEvent(\n            identifier: \"button id\",\n            reportingMetadata: \"some metadata\"\n        )\n\n        listener.onReportingEvent(.buttonTap(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutButtonTapEvent(\n                        data: thomasEvent\n                    ),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testFormDisplayedEvent() throws {\n        let thomasEvent = ThomasReportingEvent.FormDisplayEvent(\n            identifier: \"form id\",\n            formType: \"some type\"\n        )\n\n        listener.onReportingEvent(.formDisplay(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutFormDisplayEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testFormResultEvent() throws {\n        let thomasEvent = ThomasReportingEvent.FormResultEvent(\n            forms: try! AirshipJSON.wrap([\"form\": \"result\"])\n        )\n\n        listener.onReportingEvent(.formResult(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutFormResultEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testGestureEvent() throws {\n        let thomasEvent = ThomasReportingEvent.GestureEvent(\n            identifier: \"gesture id\",\n            reportingMetadata: \"some metadata\"\n        )\n\n        listener.onReportingEvent(.gesture(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutGestureEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testPageActionEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PageActionEvent(\n            identifier: \"page id\",\n            reportingMetadata: \"some metadata\"\n        )\n\n        listener.onReportingEvent(.pageAction(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPageActionEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testPagerCompletedEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PagerCompletedEvent(\n            identifier: \"pager id\",\n            pageIndex: 3,\n            pageCount: 3,\n            pageIdentifier: \"page id\"\n        )\n\n        listener.onReportingEvent(.pagerCompleted(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPagerCompletedEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testPageSwipeEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PageSwipeEvent(\n            identifier: \"pager id\",\n            toPageIndex: 4,\n            toPageIdentifier: \"to page id\",\n            fromPageIndex: 3,\n            fromPageIdentifier: \"from page id\"\n        )\n        listener.onReportingEvent(.pageSwipe(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPageSwipeEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testPageViewEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PageViewEvent(\n            identifier: \"pager id\",\n            pageIdentifier: \"page id\",\n            pageIndex: 1,\n            pageViewCount: 1,\n            pageCount: 3,\n            completed: true\n        )\n\n        listener.onReportingEvent(.pageView(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPageViewEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @Test\n    func testPagerSummaryEvent() throws {\n        let thomasEvent = ThomasReportingEvent.PagerSummaryEvent(\n            identifier: \"pager id\",\n            viewedPages: [\n                .init(identifier: \"foo\", index: 1, displayTime: 10),\n                .init(identifier: \"bar\", index: 2, displayTime: 10),\n            ],\n            pageCount: 3,\n            completed: true\n        )\n\n        listener.onReportingEvent(.pagerSummary(thomasEvent, layoutContext))\n\n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutPagerSummaryEvent(data: thomasEvent),\n                    self.layoutContext\n                )\n            ]\n        )\n    }\n\n    @MainActor\n    func testUserDismissedEvent() throws {\n        listener.onReportingEvent(\n            ThomasReportingEvent.dismiss(.userDismissed, 10, layoutContext)\n        )\n\n        try verifyEvents(\n            [(ThomasLayoutResolutionEvent.userDismissed(displayTime: 10), layoutContext)]\n        )\n    }\n\n\n    @MainActor\n    func testTimedOUtEvent() throws {\n        listener.onReportingEvent(\n            ThomasReportingEvent.dismiss(.timedOut, 10, layoutContext)\n        )\n\n        try verifyEvents(\n            [(ThomasLayoutResolutionEvent.timedOut(displayTime: 10), layoutContext)]\n        )\n    }\n\n    @MainActor\n    func testDismissedEvent() throws {\n        listener.onReportingEvent(\n            ThomasReportingEvent.dismiss(\n                .buttonTapped(\n                    identifier: \"button id\",\n                    description: \"button description\"\n                ),\n                10,\n                layoutContext\n            )\n        )\n        \n        try verifyEvents(\n            [\n                (\n                    ThomasLayoutResolutionEvent.buttonTap(\n                        identifier: \"button id\",\n                        description: \"button description\",\n                        displayTime: 10),\n                    layoutContext\n                )\n            ]\n            \n        )\n    }\n\n    private func verifyEvents(\n        _ expected: [(ThomasLayoutEvent, ThomasLayoutContext?)],\n        sourceLocation: SourceLocation = #_sourceLocation\n    ) throws {\n        #expect(expected.count == self.analytics.events.count, sourceLocation: sourceLocation)\n\n        try expected.indices.forEach { index in\n            let expectedEvent = expected[index]\n            let actual = analytics.events[index]\n            #expect(actual.0.name == expectedEvent.0.name, sourceLocation: sourceLocation)\n            let actualData = try AirshipJSON.wrap(actual.0.data)\n            let expectedData = try AirshipJSON.wrap(expectedEvent.0.data)\n            #expect(actualData == expectedData, sourceLocation: sourceLocation)\n            #expect(actual.1 == expectedEvent.1, sourceLocation: sourceLocation)\n        }\n    }\n}\n\nfinal class TestThomasLayoutMessageAnalytics: ThomasLayoutMessageAnalyticsProtocol, @unchecked Sendable {\n    var events: [(ThomasLayoutEvent, ThomasLayoutContext?)] = []\n    var impressionsRecored: UInt = 0\n    func recordEvent(_ event: ThomasLayoutEvent, layoutContext: ThomasLayoutContext?) {\n        events.append((event, layoutContext))\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/ThomasLayoutEventContextTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\nstruct ThomasLayoutEventContextTest {\n\n    private let campaigns = try! AirshipJSON.wrap(\n        [\"campaign1\": \"data1\", \"campaign2\": \"data2\"]\n    )\n\n    private let scheduleID = UUID().uuidString\n\n    @Test\n    func testJSON() async throws {\n        let context = ThomasLayoutEventContext(\n            pager: ThomasLayoutContext.Pager(\n                identifier: \"pager id\",\n                pageIdentifier: \"page id\",\n                pageIndex: 1,\n                completed: true,\n                count: 2,\n                pageHistory: [.init(identifier: \"foo-0\", index: 0, displayTime: 20)]\n            ),\n            button: ThomasLayoutContext.Button(\n                identifier: \"button id\"\n            ),\n            form: ThomasLayoutContext.Form(\n                identifier: \"form id\",\n                submitted: true,\n                type: \"form type\"\n            ),\n            display: .init(\n                triggerSessionID: \"trigger session id\",\n                isFirstDisplay: false,\n                isFirstDisplayTriggerSessionID: true\n            ),\n            reportingContext: \"reporting context\",\n            experimentsReportingData: [\n                \"experiment result 1\",\n                \"experiment result 2\"\n            ]\n        )\n\n        let expectedJSON = \"\"\"\n           {\n              \"reporting_context\":\"reporting context\",\n              \"form\":{\n                 \"type\":\"form type\",\n                 \"identifier\":\"form id\",\n                 \"submitted\":true\n              },\n              \"button\":{\n                 \"identifier\":\"button id\"\n              },\n              \"pager\":{\n                 \"page_identifier\":\"page id\",\n                 \"page_index\":1,\n                 \"identifier\":\"pager id\",\n                 \"completed\":true,\n                 \"count\":2,\n                 \"page_history\": [\n                    {\n                        \"page_identifier\": \"foo-0\",\n                        \"page_index\": 0,\n                        \"display_time\": \"20.00\"\n                    }\n                 ]\n              },\n              \"experiments\":[\n                 \"experiment result 1\",\n                 \"experiment result 2\"\n              ],\n              \"display\":{\n                 \"trigger_session_id\":\"trigger session id\",\n                 \"is_first_display\":false,\n                 \"is_first_display_trigger_session\":true\n              }\n           }\n        \"\"\"\n\n        let string = try AirshipJSON.wrap(context).toString()\n\n        AirshipLogger.error(\"\\(string)\")\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try AirshipJSON.wrap(context)\n        #expect(actual == expected)\n    }\n\n    @Test\n    func testMake() throws {\n        let experimentResult = ExperimentResult(\n            channelID: \"some channel\",\n            contactID: \"some contact\",\n            isMatch: true,\n            reportingMetadata: [AirshipJSON.string(\"some reporting\")]\n        )\n\n        let reportingMetadata = AirshipJSON.string(\"reporting info\")\n\n        let thomasLayoutContext = ThomasLayoutContext(\n            pager: ThomasLayoutContext.Pager(\n                identifier: UUID().uuidString,\n                pageIdentifier: UUID().uuidString,\n                pageIndex: 1,\n                completed: false,\n                count: 2\n            ),\n            button: ThomasLayoutContext.Button(identifier: UUID().uuidString),\n            form: ThomasLayoutContext.Form(\n                identifier: UUID().uuidString,\n                submitted: false,\n                type: UUID().uuidString,\n                responseType: UUID().uuidString\n            )\n        )\n\n        let displayContext = ThomasLayoutEventContext.Display(\n            triggerSessionID: UUID().uuidString,\n            isFirstDisplay: true,\n            isFirstDisplayTriggerSessionID: false\n        )\n\n        let context = ThomasLayoutEventContext.makeContext(\n            reportingContext: reportingMetadata,\n            experimentsResult: experimentResult,\n            layoutContext: thomasLayoutContext,\n            displayContext: displayContext\n        )\n\n        let expected = ThomasLayoutEventContext(\n            pager: thomasLayoutContext.pager,\n            button: thomasLayoutContext.button,\n            form: thomasLayoutContext.form,\n            display: displayContext,\n            reportingContext: reportingMetadata,\n            experimentsReportingData: experimentResult.reportingMetadata\n        )\n\n        #expect(context == expected)\n    }\n\n    @Test\n    func testMakeEmpty() throws {\n        let context = ThomasLayoutEventContext.makeContext(\n            reportingContext: nil,\n            experimentsResult: nil,\n            layoutContext: nil,\n            displayContext: nil\n        )\n\n        #expect(context == nil)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/ThomasLayoutEventMessageIDTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\nimport AirshipCore\n\nclass ThomasLayoutEventMessageIDTest: XCTestCase {\n\n    private let campaigns = try! AirshipJSON.wrap(\n        [\"campaign1\": \"data1\", \"campaign2\": \"data2\"]\n    )\n\n    private let scheduleID = UUID().uuidString\n\n\n    func testLegacy() async throws {\n        let messageID = ThomasLayoutEventMessageID.legacy(identifier: scheduleID)\n\n        let expectedJSON = \"\"\"\n           \"\\(scheduleID)\"\n        \"\"\"\n\n        XCTAssertEqual(try AirshipJSON.wrap(messageID), try AirshipJSON.from(json: expectedJSON))\n    }\n\n    func testAppDefined() async throws {\n        let messageID = ThomasLayoutEventMessageID.appDefined(identifier: scheduleID)\n\n        let expectedJSON = \"\"\"\n           {\n              \"message_id\": \"\\(scheduleID)\"\n           }\n        \"\"\"\n\n        XCTAssertEqual(try AirshipJSON.wrap(messageID), try AirshipJSON.from(json: expectedJSON))\n    }\n\n    func testAirship() async throws {\n        let messageID = ThomasLayoutEventMessageID.airship(identifier: scheduleID, campaigns: self.campaigns)\n\n        let expectedJSON = \"\"\"\n           {\n              \"message_id\": \"\\(scheduleID)\",\n              \"campaigns\": \\(try self.campaigns.toString()),\n           }\n        \"\"\"\n\n        XCTAssertEqual(try AirshipJSON.wrap(messageID), try AirshipJSON.from(json: expectedJSON))\n    }\n\n    func testAirshipNoCampaigns() async throws {\n        let messageID = ThomasLayoutEventMessageID.airship(identifier: scheduleID, campaigns: nil)\n\n        let expectedJSON = \"\"\"\n           {\n              \"message_id\": \"\\(scheduleID)\"\n           }\n        \"\"\"\n\n        XCTAssertEqual(try AirshipJSON.wrap(messageID), try AirshipJSON.from(json: expectedJSON))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Analytics/ThomasLayoutEventRecorderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ThomasLayoutEventRecorderTest: XCTestCase {\n\n    private let airshipAnalytics: TestAnalytics = TestAnalytics()\n    private let meteredUsage: TestMeteredUsage = TestMeteredUsage()\n\n    private var eventRecorder: ThomasLayoutEventRecorder!\n\n    private let campaigns =  try! AirshipJSON.wrap(\n        [\"campaign1\": \"data1\", \"campaign2\": \"data2\"]\n    )\n    private let experimentResult = ExperimentResult(\n        channelID: \"some channel\",\n        contactID: \"some contact\",\n        isMatch: true,\n        reportingMetadata: [AirshipJSON.string(\"some reporting\")]\n    )\n    private let scheduleID = \"5362C754-17A9-48B8-B101-60D9DC5688A2\"\n    private let reportingMetadata = AirshipJSON.string(\"reporting info\")\n    private let renderedLocale = try! AirshipJSON.wrap([\"en-US\"])\n\n    override func setUp() async throws {\n        self.eventRecorder = ThomasLayoutEventRecorder(\n            airshipAnalytics:  airshipAnalytics,\n            meteredUsage: meteredUsage\n        )\n    }\n\n    func testEventData() async throws {\n        let inAppEvent = TestThomasLayoutEvent(\n            name: .appInit,\n            data: TestData(field: \"something\", anotherField: \"something something\")\n        )\n\n        let data = ThomasLayoutEventData(\n            event: inAppEvent,\n            context: ThomasLayoutEventContext(\n                reportingContext: self.reportingMetadata,\n                experimentsReportingData: self.experimentResult.reportingMetadata\n            ),\n            source: .airship,\n            messageID: .airship(identifier: self.scheduleID, campaigns: self.campaigns),\n            renderedLocale: self.renderedLocale\n        )\n\n\n        self.eventRecorder.recordEvent(inAppEventData: data)\n\n        let expectedJSON = \"\"\"\n        {\n           \"context\":{\n              \"reporting_context\":\"reporting info\",\n              \"experiments\":[\n                 \"some reporting\"\n              ]\n           },\n           \"source\":\"urban-airship\",\n           \"rendered_locale\":[\n              \"en-US\"\n           ],\n           \"id\":{\n              \"campaigns\":{\n                 \"campaign1\":\"data1\",\n                 \"campaign2\":\"data2\"\n              },\n              \"message_id\":\"5362C754-17A9-48B8-B101-60D9DC5688A2\"\n           },\n           \"field\":\"something\",\n           \"anotherField\":\"something something\"\n        }\n        \"\"\"\n\n        let event = self.airshipAnalytics.events.first!\n\n        XCTAssertEqual(event.eventType, inAppEvent.name)\n        XCTAssertEqual(event.eventData, try AirshipJSON.from(json: expectedJSON))\n    }\n\n    func testConversionIDs() async throws {\n        let inAppEvent = TestThomasLayoutEvent(\n            name: .featureFlagInteraction,\n            data: TestData(field: \"something\", anotherField: \"something something\")\n        )\n\n        self.airshipAnalytics.conversionSendID = UUID().uuidString\n        self.airshipAnalytics.conversionPushMetadata = UUID().uuidString\n\n        let data = ThomasLayoutEventData(\n            event: inAppEvent,\n            context: ThomasLayoutEventContext(\n                reportingContext: self.reportingMetadata,\n                experimentsReportingData: self.experimentResult.reportingMetadata\n            ),\n            source: .airship,\n            messageID: .airship(identifier: self.scheduleID, campaigns: self.campaigns),\n            renderedLocale: self.renderedLocale\n        )\n\n\n        self.eventRecorder.recordEvent(inAppEventData: data)\n\n\n        let expectedJSON = \"\"\"\n        {\n           \"context\":{\n              \"reporting_context\":\"reporting info\",\n              \"experiments\":[\n                 \"some reporting\"\n              ]\n           },\n           \"source\":\"urban-airship\",\n           \"rendered_locale\":[\n              \"en-US\"\n           ],\n           \"id\":{\n              \"campaigns\":{\n                 \"campaign1\":\"data1\",\n                 \"campaign2\":\"data2\"\n              },\n              \"message_id\":\"5362C754-17A9-48B8-B101-60D9DC5688A2\"\n           },\n           \"field\":\"something\",\n           \"anotherField\":\"something something\",\n           \"conversion_send_id\": \"\\(self.airshipAnalytics.conversionSendID!)\",\n           \"conversion_metadata\": \"\\(self.airshipAnalytics.conversionPushMetadata!)\"\n        }\n        \"\"\"\n\n        let event = self.airshipAnalytics.events.first!\n\n        XCTAssertEqual(event.eventType, inAppEvent.name)\n        XCTAssertEqual(event.eventData, try AirshipJSON.from(json: expectedJSON))\n    }\n\n    func testEventDataError() async throws {\n        let inAppEvent = TestThomasLayoutEvent(\n            name: .appForeground,\n            data: ErrorData(field: \"something\", anotherField: \"something something\")\n        )\n\n        let data = ThomasLayoutEventData(\n            event: inAppEvent,\n            context: ThomasLayoutEventContext(\n                reportingContext: self.reportingMetadata,\n                experimentsReportingData: self.experimentResult.reportingMetadata\n            ),\n            source: .airship,\n            messageID: .airship(identifier: self.scheduleID, campaigns: self.campaigns),\n            renderedLocale: self.renderedLocale\n        )\n\n        self.eventRecorder.recordEvent(inAppEventData: data)\n\n        XCTAssertTrue(self.airshipAnalytics.events.isEmpty)\n    }\n}\n\n\nfileprivate struct TestData: Encodable, Sendable {\n    var field: String\n    var anotherField: String\n}\n\nfileprivate struct ErrorData: Encodable, Sendable {\n    var field: String\n    var anotherField: String\n\n    enum CodingKeys: CodingKey {\n        case field\n        case anotherField\n    }\n\n    func encode(to encoder: Encoder) throws {\n        throw AirshipErrors.error(\"Failed\")\n    }\n}\n\n\nactor TestMeteredUsage: AirshipMeteredUsage {\n    var events: [AirshipMeteredUsageEvent] = []\n    func addEvent(_ event: AirshipCore.AirshipMeteredUsageEvent) async throws {\n        events.append(event)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AnalyticsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\nimport Combine\n\n@testable import AirshipCore\n\nclass AnalyticsTest: XCTestCase {\n\n    private let appStateTracker = TestAppStateTracker()\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let config = RuntimeConfig.testConfig()\n    private let channel = TestChannel()\n    private let locale = TestLocaleManager()\n    private var permissionsManager: DefaultAirshipPermissionsManager!\n    private let notificationCenter = AirshipNotificationCenter(notificationCenter: NotificationCenter())\n    private let date = UATestDate()\n    private let eventManager = TestEventManager()\n    private let sessionEventFactory = TestSessionEventFactory()\n    private let sessionTracker = TestSessionTracker()\n\n    private var privacyManager: TestPrivacyManager!\n    private var analytics: DefaultAirshipAnalytics!\n    private var testAirship: TestAirshipInstance!\n\n\n    @MainActor\n    override func setUp() async throws {\n        testAirship = TestAirshipInstance()\n        self.permissionsManager = DefaultAirshipPermissionsManager()\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: RuntimeConfig.testConfig(),\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n        \n\n        self.analytics = makeAnalytics()\n    }\n\n    @MainActor\n    func makeAnalytics() -> DefaultAirshipAnalytics {\n        return DefaultAirshipAnalytics(\n            config: config,\n            dataStore: dataStore,\n            channel: channel,\n            notificationCenter: notificationCenter,\n            date: date,\n            localeManager: locale,\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager,\n            eventManager: eventManager,\n            sessionTracker: sessionTracker,\n            sessionEventFactory: sessionEventFactory\n        )\n    }\n\n    override class func tearDown() {\n        TestAirshipInstance.clearShared()\n    }\n\n    @MainActor\n    func testScreenTrackingBackground() async throws {\n        let notificationCenter = self.notificationCenter\n        // Foreground\n        notificationCenter.post(name: AppStateTracker.willEnterForegroundNotification)\n\n        self.analytics.trackScreen(\"test_screen\")\n\n        let events = try await self.produceEvents(count: 1) { @MainActor in\n            notificationCenter.post(\n                name: AppStateTracker.didEnterBackgroundNotification,\n                object: nil\n            )\n        }\n\n        XCTAssertEqual(\"screen_tracking\", events[0].type.reportingName)\n    }\n\n    @MainActor\n    func testScreenTrackingTerminate() async throws {\n        let notificationCenter = self.notificationCenter\n\n        // Foreground\n        notificationCenter.post(name: AppStateTracker.willEnterForegroundNotification)\n\n        // Track the screen\n        self.analytics.trackScreen(\"test_screen\")\n\n        self.analytics.trackScreen(\"test_screen\")\n\n        let events = try await self.produceEvents(count: 1) { @MainActor in\n            notificationCenter.post(name: AppStateTracker.didEnterBackgroundNotification)\n        }\n\n        XCTAssertEqual(\"screen_tracking\", events[0].type.reportingName)\n    }\n\n    func testScreenTracking() async throws {\n        let date = self.date\n        let analytics = self.analytics\n        let currentTime = Date().timeIntervalSince1970\n        let timeOffset = 3.0\n\n        let events = try await self.produceEvents(count: 1) { @MainActor in\n            analytics?.trackScreen(\"test_screen\")\n            date.offset = timeOffset\n            analytics?.trackScreen(\"another_screen\")\n        }\n\n        let body: AirshipJSON = events[0].body\n\n        XCTAssertEqual(\"screen_tracking\", events[0].type.reportingName)\n        XCTAssertEqual(\"test_screen\", body.object?[\"screen\"]?.string)\n        XCTAssertEqual(\"3.000\", body.object?[\"duration\"]?.string)\n\n        compareTimestamps(value: body.object?[\"entered_time\"]?.string, expectedValue: currentTime)\n        compareTimestamps(value: body.object?[\"exited_time\"]?.string, expectedValue: currentTime + timeOffset)\n    }\n\n    private func compareTimestamps(value: String?, expectedValue: TimeInterval) {\n        if let value = value, let actualValue = Double(value) {\n            XCTAssertEqual(actualValue, expectedValue, accuracy: 1)\n        } else {\n            XCTFail(\"Not a double\")\n        }\n    }\n\n    @MainActor\n    func testDisablingAnalytics() throws {\n        self.channel.identifier = \"test channel\"\n        self.analytics.airshipReady()\n\n        XCTAssertTrue(self.eventManager.uploadsEnabled)\n\n        let expectation = XCTestExpectation()\n        self.eventManager.deleteEventsCallback = {\n            expectation.fulfill()\n        }\n\n        self.privacyManager.disableFeatures(.analytics)\n        wait(for: [expectation], timeout: 5.0)\n        XCTAssertFalse(self.eventManager.uploadsEnabled)\n\n    }\n\n\n    @MainActor\n    func testEnableAnalytics() throws {\n        self.channel.identifier = \"test channel\"\n        self.analytics.airshipReady()\n\n        XCTAssertTrue(self.eventManager.uploadsEnabled)\n        \n        self.privacyManager.disableFeatures(.analytics)\n        XCTAssertFalse(self.eventManager.uploadsEnabled)\n\n        let expectation = XCTestExpectation()\n        self.eventManager.scheduleUploadCallback = { priority in\n            XCTAssertEqual(AirshipEventPriority.normal, priority)\n            expectation.fulfill()\n        }\n        self.privacyManager.enableFeatures(.analytics)\n        XCTAssertTrue(self.eventManager.uploadsEnabled)\n        wait(for: [expectation], timeout: 5.0)\n    }\n\n    @MainActor\n    func testCurrentScreen() throws {\n        self.analytics.trackScreen(\"foo\")\n        XCTAssertEqual(\"foo\", self.analytics.currentScreen)\n\n        self.analytics.trackScreen(\"bar\")\n        XCTAssertEqual(\"bar\", self.analytics.currentScreen)\n\n        self.analytics.trackScreen(nil)\n        XCTAssertEqual(nil, self.analytics.currentScreen)\n    }\n\n    @MainActor\n    func testScreenUpdates() async throws {\n        let expectation = expectation(description: \"updates received\")\n\n        let screenUpdates = analytics.screenUpdates\n        Task {\n            var updates: [String?] = []\n            for await update in screenUpdates {\n                updates.append(update)\n                if (updates.count == 4) {\n                    break\n                }\n            }\n\n            XCTAssertEqual([nil, \"foo\", \"bar\", nil], updates)\n            expectation.fulfill()\n        }\n\n        self.analytics.trackScreen(\"foo\")\n        XCTAssertEqual(\"foo\", self.analytics.currentScreen)\n\n\n        self.analytics.trackScreen(\"bar\")\n        XCTAssertEqual(\"bar\", self.analytics.currentScreen)\n\n        self.analytics.trackScreen(\"bar\")\n        self.analytics.trackScreen(\"bar\")\n\n\n        self.analytics.trackScreen(nil)\n        XCTAssertEqual(nil, self.analytics.currentScreen)\n\n        await self.fulfillment(of: [expectation])\n    }\n\n    @MainActor\n    func testRegions() async throws {\n        var updates = self.analytics.regionUpdates.makeAsyncIterator()\n        var update = await updates.next()\n        XCTAssertEqual(Set(), update)\n\n        self.analytics.recordRegionEvent(\n            RegionEvent(regionID: \"foo\", source: \"source\", boundaryEvent: .enter)!\n        )\n        \n        update = await updates.next()\n        XCTAssertEqual(Set([\"foo\"]), update)\n        XCTAssertEqual(Set([\"foo\"]), self.analytics.currentRegions)\n\n        self.analytics.recordRegionEvent(\n            RegionEvent(regionID: \"bar\", source: \"source\", boundaryEvent: .enter)!\n        )\n\n        update = await updates.next()\n        XCTAssertEqual(Set([\"foo\", \"bar\"]), update)\n        XCTAssertEqual(Set([\"foo\", \"bar\"]), self.analytics.currentRegions)\n\n        self.analytics.recordRegionEvent(\n            RegionEvent(regionID: \"bar\", source: \"source\", boundaryEvent: .exit)!\n        )\n\n        update = await updates.next()\n        XCTAssertEqual(Set([\"foo\"]), update)\n        XCTAssertEqual(Set([\"foo\"]), self.analytics.currentRegions)\n\n        self.analytics.recordRegionEvent(\n            RegionEvent(regionID: \"baz\", source: \"source\", boundaryEvent: .exit)!\n        )\n\n        update = await updates.next()\n        XCTAssertEqual(Set([\"foo\"]), update)\n        XCTAssertEqual(Set([\"foo\"]), self.analytics.currentRegions)\n\n        self.analytics.recordRegionEvent(\n            RegionEvent(regionID: \"foo\", source: \"source\", boundaryEvent: .exit)!\n        )\n\n        update = await updates.next()\n        XCTAssertEqual(Set(), update)\n        XCTAssertEqual(Set(), self.analytics.currentRegions)\n    }\n\n    func testAddEvent() throws {\n        let expectation = XCTestExpectation()\n        self.eventManager.addEventCallabck = { event in\n            XCTAssertEqual(\"app_background\", event.type.reportingName)\n            expectation.fulfill()\n        }\n\n        self.analytics.recordEvent(AirshipEvent(priority: .normal, eventType: .appBackground, eventData: \"body\"))\n\n        wait(for: [expectation], timeout: 5.0)\n    }\n\n    func testAssociateDeviceIdentifiers() async throws {\n        let analytics = self.analytics\n        let events = try await self.produceEvents(count: 1) {\n            let ids = AssociatedIdentifiers(identifiers: [\"neat\": \"id\"])\n            analytics?.associateDeviceIdentifiers(ids)\n        }\n\n        let expectedData = [\n            \"neat\": \"id\",\n        ]\n\n        XCTAssertEqual(\"associate_identifiers\", events[0].type.reportingName)\n        XCTAssertEqual(\n            try AirshipJSON.wrap(expectedData),\n            events[0].body\n        )\n    }\n\n    @MainActor\n    func testMissingSendID() throws {\n        let notification = [\"aps\": [\"alert\": \"neat\"]]\n        self.analytics.launched(fromNotification: notification)\n        XCTAssertEqual(\"MISSING_SEND_ID\", self.analytics.conversionSendID)\n        XCTAssertNil(self.analytics.conversionPushMetadata)\n    }\n    \n    @MainActor\n    func testConversionSendID() throws {\n        let notification: [String: AnyHashable] = [\n            \"aps\": [\"alert\": \"neat\"],\n            \"_\": \"some conversionSendID\",\n        ]\n        self.analytics.launched(fromNotification: notification)\n        XCTAssertEqual(\"some conversionSendID\", self.analytics.conversionSendID)\n    }\n\n    @MainActor\n    func testConversationMetadata() throws {\n        let notification: [String: AnyHashable] = [\n            \"aps\": [\"alert\": \"neat\"],\n            \"_\": \"some conversionSendID\",\n            \"com.urbanairship.metadata\": \"some metadata\",\n        ]\n\n        self.analytics.launched(fromNotification: notification)\n        XCTAssertEqual(\"some metadata\", self.analytics.conversionPushMetadata)\n    }\n\n    @MainActor\n    func testLaunchedFromSilentPush() throws {\n        let notification: [String: AnyHashable] = [\n            \"aps\": [\"neat\": \"neat\"],\n            \"_\": \"some conversionSendID\",\n            \"com.urbanairship.metadata\": \"some metadata\",\n        ]\n\n        self.analytics.launched(fromNotification: notification)\n        XCTAssertNil(self.analytics.conversionPushMetadata)\n        XCTAssertNil(self.analytics.conversionSendID)\n    }\n\n    func testScreenEventFeed() async throws {\n        var feed = await self.analytics.eventFeed.updates.makeAsyncIterator()\n        await self.analytics.trackScreen(\"some screen\")\n\n        let next = await feed.next()\n        XCTAssertEqual(next, .screen(screen: \"some screen\"))\n    }\n\n    func testRegionEventEventFeed() async throws {\n        let event = RegionEvent(\n            regionID: \"foo\",\n            source: \"test\",\n            boundaryEvent: .enter\n        )!\n\n        var feed = await self.analytics.eventFeed.updates.makeAsyncIterator()\n        self.analytics.recordRegionEvent(event)\n\n        let next = await feed.next()\n        XCTAssertEqual(next, .analytics(eventType: .regionEnter, body: try event.eventBody(stringifyFields: false), value: nil))\n    }\n\n    func testForwardCustomEvents() async throws {\n        let event = CustomEvent(name: \"foo\", value: 10.0)\n\n        var feed = await self.analytics.eventFeed.updates.makeAsyncIterator()\n        self.analytics.recordCustomEvent(event)\n\n        let next = await feed.next()\n        XCTAssertEqual(\n            next,\n            .analytics(\n                eventType: .customEvent,\n                body: event.eventBody(\n                    sendID: nil,\n                    metadata: nil,\n                    formatValue: false\n                ),\n                value: 10.0\n            )\n        )\n    }\n\n    func testForwardCustomEventNoValue() async throws {\n        let event = CustomEvent(name: \"foo\")\n\n        var feed = await self.analytics.eventFeed.updates.makeAsyncIterator()\n        self.analytics.recordCustomEvent(event)\n\n        let next = await feed.next()\n        XCTAssertEqual(\n            next,\n            .analytics(\n                eventType: .customEvent,\n                body: event.eventBody(\n                    sendID: nil,\n                    metadata: nil,\n                    formatValue: false\n                ),\n                value: 1.0\n            )\n        )\n    }\n\n    func testSDKExtensions() async throws {\n        self.analytics.registerSDKExtension(.cordova, version: \"1.2.3\")\n        self.analytics.registerSDKExtension(.unity, version: \"5,.6,.7,,,\")\n\n        let headers = await self.eventManager.headers\n        XCTAssertEqual(\n            \"cordova:1.2.3, unity:5.6.7\",\n            headers[\"X-UA-Frameworks\"]\n        )\n    }\n\n    func testAnalyticsHeaders() async throws {\n        self.channel.identifier = \"someChannelID\"\n        self.locale.currentLocale = Locale(identifier: \"en-US-POSIX\")\n\n        let expected = await [\n            \"X-UA-Channel-ID\": \"someChannelID\",\n            \"X-UA-Timezone\": NSTimeZone.default.identifier,\n            \"X-UA-Locale-Language\": \"en\",\n            \"X-UA-Locale-Country\": \"US\",\n            \"X-UA-Locale-Variant\": \"POSIX\",\n            \"X-UA-Device-Family\": UIDevice.current.systemName,\n            \"X-UA-OS-Version\": UIDevice.current.systemVersion,\n            \"X-UA-Device-Model\": AirshipDevice.modelIdentifier,\n            \"X-UA-Lib-Version\": AirshipVersion.version,\n            \"X-UA-App-Key\": self.config.appCredentials.appKey,\n            \"X-UA-Package-Name\":\n                Bundle.main.infoDictionary?[kCFBundleIdentifierKey as String]\n                as? String,\n            \"X-UA-Package-Version\": AirshipUtils.bundleShortVersionString() ?? \"\",\n        ]\n\n        let headers = await self.eventManager.headers\n        XCTAssertEqual(expected, headers)\n    }\n\n    func testAnalyticsHeaderExtension() async throws {\n        await self.analytics.addHeaderProvider {\n            return [\"neat\": \"story\"]\n        }\n\n        let headers = await self.eventManager.headers\n        XCTAssertEqual(\n            \"story\",\n            headers[\"neat\"]\n        )\n    }\n    \n    @MainActor\n    func testPermissionHeaders() async throws {\n        let testPushDelegate = TestPermissionsDelegate()\n        testPushDelegate.permissionStatus = .denied\n        self.permissionsManager.setDelegate(\n            testPushDelegate,\n            permission: .displayNotifications\n        )\n\n        let testLocationDelegate = TestPermissionsDelegate()\n        testLocationDelegate.permissionStatus = .granted\n        self.permissionsManager.setDelegate(\n            testLocationDelegate,\n            permission: .location\n        )\n\n        let headers = await self.eventManager.headers\n\n        XCTAssertEqual(\n            \"denied\",\n            headers[\"X-UA-Permission-display_notifications\"]\n        )\n        XCTAssertEqual(\"granted\", headers[\"X-UA-Permission-location\"])\n    }\n\n    @MainActor\n    func produceEvents(\n        count: Int,\n        eventProducingAction: @escaping @Sendable () async -> Void\n    ) async throws -> [AirshipEventData] {\n        var subscription: AnyCancellable?\n        defer {\n            subscription?.cancel()\n        }\n\n        let stream = AsyncThrowingStream<AirshipEventData, Error> { continuation in\n            let cancelTask = Task {\n                try await Task.sleep(nanoseconds: 10_000_000_000)\n                continuation.finish(\n                    throwing: AirshipErrors.error(\"Failed to get event\")\n                )\n            }\n\n            var received = 0\n            subscription = self.analytics.eventPublisher\n                .sink { data in\n                    continuation.yield(data)\n                    received += 1\n                    if (received >= count) {\n                        cancelTask.cancel()\n                        continuation.finish()\n                    }\n                }\n        }\n\n        await eventProducingAction()\n\n        var result: [AirshipEventData] = []\n        for try await value in stream {\n            result.append(value)\n        }\n\n        return result\n    }\n\n    func testSessionEvents() async throws {\n        let date = Date()\n        let sessionTracker = self.sessionTracker\n\n        let events = try await self.produceEvents(count: 4) {\n            sessionTracker.eventsContinuation.yield(\n                SessionEvent(type: .background, date: date, sessionState: SessionState())\n            )\n\n            sessionTracker.eventsContinuation.yield(\n                SessionEvent(type: .foreground, date: date, sessionState: SessionState())\n            )\n\n            sessionTracker.eventsContinuation.yield(\n                SessionEvent(type: .foregroundInit, date: date, sessionState: SessionState())\n            )\n            sessionTracker.eventsContinuation.yield(\n                SessionEvent(type: .backgroundInit, date: date, sessionState: SessionState())\n            )\n        }\n\n        XCTAssertEqual(\n            [\n                EventType.appBackground.reportingName,\n                EventType.appForeground.reportingName,\n                EventType.appInit.reportingName,\n                EventType.appInit.reportingName\n            ],\n            events.map { $0.type.reportingName }\n        )\n        XCTAssertEqual(\n            [\n                \"app_background\",\n                \"app_foreground\",\n                \"app_foreground_init\",\n                \"app_background_init\"\n            ],\n            events.map { $0.body }\n        )\n        XCTAssertEqual([date, date, date, date], events.map { $0.date })\n    }\n}\n\n\nfinal class TestEventManager: EventManagerProtocol, @unchecked Sendable {\n    var uploadsEnabled: Bool = false\n\n    var addEventCallabck: ((AirshipEventData) -> Void)?\n\n\n    func addEvent(_ event: AirshipEventData) async throws {\n        addEventCallabck?(event)\n    }\n\n    var deleteEventsCallback: (() -> Void)?\n    func deleteEvents() async throws {\n        self.deleteEventsCallback?()\n    }\n\n    var scheduleUploadCallback: ((AirshipEventPriority) -> Void)?\n\n    func scheduleUpload(eventPriority: AirshipEventPriority) async {\n        scheduleUploadCallback?(eventPriority)\n    }\n\n    var headerProviders: [() async -> [String : String]] = []\n    func addHeaderProvider(\n        _ headerProvider: @escaping () async -> [String : String]\n    ) {\n        headerProviders.append(headerProvider)\n    }\n\n    public var headers: [String: String] {\n        get async {\n            var allHeaders: [String: String] = [:]\n            for provider in self.headerProviders {\n                let headers = await provider()\n                allHeaders.merge(headers) { (_, new) in\n                    return new\n                }\n            }\n            return allHeaders\n        }\n    }\n}\n\n\nfinal class TestSessionEventFactory: SessionEventFactoryProtocol, @unchecked Sendable {\n    func make(event: SessionEvent) -> AirshipEvent {\n        let eventType: EventType = switch(event.type) {\n        case .backgroundInit, .foregroundInit: .appInit\n        case .background: .appBackground\n        case .foreground: .appForeground\n        }\n\n        let name: String = switch(event.type) {\n        case .backgroundInit: \"app_background_init\"\n        case .foregroundInit: \"app_foreground_init\"\n        case .background: \"app_background\"\n        case .foreground: \"app_foreground\"\n        }\n\n        return AirshipEvent(eventType: eventType, eventData: AirshipJSON.string(name))\n    }\n}\n\nfinal class TestSessionTracker: SessionTrackerProtocol {\n\n    let eventsContinuation: AsyncStream<SessionEvent>.Continuation\n    public let events: AsyncStream<SessionEvent>\n\n    private let _sessionState: AirshipAtomicValue<SessionState> = AirshipAtomicValue(SessionState())\n\n    var sessionState: SessionState {\n        return _sessionState.value\n    }\n\n    init() {\n        (self.events, self.eventsContinuation) = AsyncStream<SessionEvent>.airshipMakeStreamWithContinuation()\n    }\n\n    func airshipReady() {\n\n    }\n    \n    func launchedFromPush(sendID: String?, metadata: String?) {\n        self._sessionState.update { state in\n            var state = state\n            state.conversionMetadata = metadata\n            state.conversionSendID = sendID\n            return state\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AppIntegrationTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n@testable import AirshipBasement\n\nclass AppIntegrationTests: XCTestCase {\n    private var testDelegate: TestIntegrationDelegate!\n\n    @MainActor\n    override func setUpWithError() throws {\n        self.testDelegate = TestIntegrationDelegate()\n        AppIntegration.integrationDelegate = self.testDelegate\n    }\n\n    @MainActor\n    override func tearDownWithError() throws {\n        AppIntegration.integrationDelegate = nil\n    }\n\n    @MainActor\n    func testPerformFetchWithCompletionHandler() throws {\n        let appCallbackCalled = expectation(description: \"Callback called\")\n        AppIntegration.application(\n            UIApplication.shared,\n            performFetchWithCompletionHandler: { result in\n                XCTAssertEqual(result, .noData)\n                appCallbackCalled.fulfill()\n            }\n        )\n        wait(for: [appCallbackCalled], timeout: 10)\n        XCTAssertTrue(self.testDelegate.onBackgroundAppRefreshCalled!)\n    }\n\n    @MainActor\n    func testDidRegisterForRemoteNotificationsWithDeviceToken() throws {\n        let token = Data(\"some token\".utf8)\n        AppIntegration.application(\n            UIApplication.shared,\n            didRegisterForRemoteNotificationsWithDeviceToken: token\n        )\n        XCTAssertEqual(token, self.testDelegate.deviceToken)\n    }\n\n    @MainActor\n    func testDidFailToRegisterForRemoteNotificationsWithError() throws {\n        let error = AirshipErrors.error(\"some error\") as NSError\n        AppIntegration.application(\n            UIApplication.shared,\n            didFailToRegisterForRemoteNotificationsWithError: error\n        )\n        XCTAssertEqual(error, self.testDelegate.registrationError as NSError?)\n    }\n\n    @MainActor\n    func testDidReceiveRemoteNotifications() async throws {\n        let notification = [\"some\": \"alert\"]\n\n        let testHookCalled = expectation(description: \"Callback called\")\n        self.testDelegate.didReceiveRemoteNotificationCallback = { userInfo, isForeground in\n            XCTAssertEqual(\n                notification as NSDictionary,\n                userInfo as NSDictionary\n            )\n            testHookCalled.fulfill()\n            return .newData\n        }\n\n        let result = await AppIntegration.application(\n            UIApplication.shared,\n            didReceiveRemoteNotification: notification\n        )\n\n        XCTAssertEqual(result, .newData)\n\n        await fulfillment(of: [testHookCalled], timeout: 10)\n    }\n}\n\n@MainActor\nfinal class TestIntegrationDelegate: NSObject, AppIntegrationDelegate {\n    var onBackgroundAppRefreshCalled: Bool?\n    var deviceToken: Data?\n    var registrationError: Error?\n    var didReceiveRemoteNotificationCallback: (@MainActor ([AnyHashable: Any], Bool) async -> UIBackgroundFetchResult)?\n\n    func onBackgroundAppRefresh() {\n        self.onBackgroundAppRefreshCalled = true\n    }\n\n    func didRegisterForRemoteNotifications(deviceToken: Data) {\n        self.deviceToken = deviceToken\n    }\n\n    func didFailToRegisterForRemoteNotifications(error: Error) {\n        self.registrationError = error\n    }\n\n    func didReceiveRemoteNotification(\n        userInfo: [AnyHashable: Any],\n        isForeground: Bool,\n        completionHandler: @escaping (UIBackgroundFetchResult) -> Void\n    ) {\n        Task {\n            let result = await self.didReceiveRemoteNotificationCallback?(userInfo, isForeground) ?? .noData\n            completionHandler(result)\n        }\n    }\n\n    func willPresentNotification(\n        notification: UNNotification,\n        presentationOptions: UNNotificationPresentationOptions,\n        completionHandler: @escaping () -> Void\n    ) {\n        completionHandler()\n    }\n\n    func didReceiveNotificationResponse(\n        response: UNNotificationResponse,\n        completionHandler: @escaping () -> Void\n    ) {\n        completionHandler()\n    }\n    \n    func presentationOptions(\n        for notification: UNNotification,\n        completionHandler: @escaping (UNNotificationPresentationOptions) -> Void\n    ) {\n        completionHandler([])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AppRemoteDataProviderDelegateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipCore\n\nfinal class AppRemoteDataProviderDelegateTest: XCTestCase {\n\n    private let client: TestRemoteDataAPIClient = TestRemoteDataAPIClient()\n    private let config: RuntimeConfig = RuntimeConfig.testConfig()\n\n    private var delegate: AppRemoteDataProviderDelegate!\n\n    override func setUpWithError() throws {\n        delegate = AppRemoteDataProviderDelegate(config: config, apiClient: client)\n    }\n\n    func testIsRemoteDataInfoUpToDate() async throws {\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data/app/\\(config.appCredentials.appKey)/ios\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .app\n        )\n\n        var isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue\n        )\n        XCTAssertTrue(isUpToDate)\n\n        // Different locale\n        isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: Locale(identifier: \"en\"),\n            randomValue: randomValue\n        )\n        XCTAssertFalse(isUpToDate)\n\n        // Different randomValue\n        isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue + 1\n        )\n        XCTAssertFalse(isUpToDate)\n    }\n\n    func testIsRemoteDataInfoUpToDateDifferentURL() async throws {\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data/app/\\(config.appCredentials.appKey)/ios/something-else\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .app\n        )\n\n        let isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue\n        )\n\n        XCTAssertFalse(isUpToDate)\n    }\n\n    func testFetch() async throws {\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data/app/\\(config.appCredentials.appKey)/ios\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .app\n        )\n\n        client.lastModified = \"some other time\"\n        client.fetchData = { url, auth, lastModified, info in\n            XCTAssertEqual(remoteDatInfo.url, url)\n            XCTAssertEqual(AirshipRequestAuth.generatedAppToken, auth)\n            XCTAssertEqual(\"some time\", lastModified)\n\n            XCTAssertEqual(\n                RemoteDataInfo(\n                    url: try RemoteDataURLFactory.makeURL(\n                        config: self.config,\n                        path: \"/api/remote-data/app/\\(self.config.appCredentials.appKey)/ios\",\n                        locale: locale,\n                        randomValue: randomValue\n                    ),\n                    lastModifiedTime: \"some other time\",\n                    source: .app\n                ),\n                info\n            )\n\n            return AirshipHTTPResponse(\n                result: RemoteDataResult(\n                    payloads: [],\n                    remoteDataInfo: remoteDatInfo\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.delegate.fetchRemoteData(\n            locale: locale,\n            randomValue: randomValue,\n            lastRemoteDataInfo: remoteDatInfo\n        )\n\n        XCTAssertEqual(result.statusCode, 200)\n    }\n\n    func testFetchLastModifiedOutOfDate() async throws {\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data/app/\\(config.appCredentials.appKey)/ios\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .app\n        )\n\n        client.fetchData = { _, _, lastModified, _ in\n            XCTAssertNil(lastModified)\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.delegate.fetchRemoteData(\n            locale: locale,\n            randomValue: randomValue + 1,\n            lastRemoteDataInfo: remoteDatInfo\n        )\n\n        XCTAssertEqual(result.statusCode, 400)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AppStateTrackerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class AppStateTrackerTest: XCTestCase {\n\n    private let adapter = TestAppStateAdapter()\n    private let notificationCenter = NotificationCenter()\n    private var tracker: AppStateTracker!\n\n    @MainActor\n    override func setUp() async throws {\n        self.tracker = AppStateTracker(\n            adapter: adapter,\n            notificationCenter: self.notificationCenter\n        )\n    }\n\n    @MainActor\n    func testDidBecomeActive() async throws {\n        let expectations = [\n            expectNotification(name: AppStateTracker.didBecomeActiveNotification)\n        ]\n\n        adapter.dispatchEvent(event: .didBecomeActive)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n    @MainActor\n    func testWillEnterForeground() async throws {\n        let expectations = [\n            expectNotification(name: AppStateTracker.willEnterForegroundNotification)\n        ]\n\n        adapter.dispatchEvent(event: .willEnterForeground)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n    @MainActor\n    func testDidEnterBackground() async throws {\n        let expectations = [\n            expectNotification(name: AppStateTracker.didEnterBackgroundNotification)\n        ]\n\n        adapter.dispatchEvent(event: .didEnterBackground)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n    @MainActor\n    func testWillResignActive() async throws {\n        let expectations = [\n            expectNotification(name: AppStateTracker.willResignActiveNotification)\n        ]\n\n        adapter.dispatchEvent(event: .willResignActive)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n\n    @MainActor\n    func testWillTerminate() async throws {\n        let expectations = [\n            expectNotification(name: AppStateTracker.willTerminateNotification)\n        ]\n\n        adapter.dispatchEvent(event: .willTerminate)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n\n    @MainActor\n    func testTransitionToForeground() async throws {\n        adapter.dispatchEvent(event: .didBecomeActive)\n\n        let expectations = [\n            expectNotification(name: AppStateTracker.didTransitionToForeground)\n        ]\n\n        adapter.dispatchEvent(event: .didEnterBackground)\n        adapter.dispatchEvent(event: .didBecomeActive)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n    @MainActor\n    func testTransitionToBackground() async throws {\n        adapter.dispatchEvent(event: .didEnterBackground)\n\n        let expectations = [\n            expectNotification(name: AppStateTracker.didTransitionToForeground)\n        ]\n\n        adapter.dispatchEvent(event: .didBecomeActive)\n        adapter.dispatchEvent(event: .didEnterBackground)\n\n        await self.fulfillment(of: expectations, timeout: 1.0)\n    }\n\n    private func expectNotification(name: Notification.Name) -> XCTestExpectation {\n        let expectation = self.expectation(description: \"Notification received\")\n        self.notificationCenter.addObserver(\n            forName: name,\n            object: nil,\n            queue: nil\n        ) { _ in\n            expectation.fulfill()\n        }\n        return expectation\n    }\n\n}\n\n\nfinal class TestAppStateAdapter: AppStateTrackerAdapter {\n    @MainActor\n    var state: AirshipCore.ApplicationState = .inactive\n    @MainActor\n    var eventHandlers: [@MainActor @Sendable (AppLifeCycleEvent) -> Void] = []\n\n    @MainActor\n    func watchAppLifeCycleEvents(\n        eventHandler: @escaping @MainActor @Sendable (AirshipCore.AppLifeCycleEvent) -> Void) {\n            eventHandlers.append(eventHandler)\n    }\n\n    @MainActor\n    public func dispatchEvent(event: AppLifeCycleEvent) {\n        self.eventHandlers.forEach { $0(event) }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AssociatedIdentifiersTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class AssociatedIdentifiersTest: XCTestCase {\n\n    func testIDs() {\n        let identifiers = AssociatedIdentifiers(identifiers: [\"custom key\": \"custom value\"])\n        identifiers.vendorID = \"vendor ID\"\n        identifiers.advertisingID = \"advertising ID\"\n        identifiers.advertisingTrackingEnabled = false\n        identifiers.set(identifier: \"another custom value\", key: \"another custom key\")\n\n        XCTAssertEqual(\"vendor ID\", identifiers.allIDs[\"com.urbanairship.vendor\"])\n        XCTAssertEqual(\"advertising ID\", identifiers.allIDs[\"com.urbanairship.idfa\"])\n        XCTAssertFalse(identifiers.advertisingTrackingEnabled)\n        XCTAssertEqual(\"true\", identifiers.allIDs[\"com.urbanairship.limited_ad_tracking_enabled\"])\n        XCTAssertEqual(\"another custom value\", identifiers.allIDs[\"another custom key\"])\n\n        identifiers.advertisingTrackingEnabled = true\n        XCTAssertTrue(identifiers.advertisingTrackingEnabled)\n        XCTAssertEqual(\"false\", identifiers.allIDs[\"com.urbanairship.limited_ad_tracking_enabled\"])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AttributeEditorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass AttributeEditorTest: XCTestCase {\n\n    var date: UATestDate!\n\n    override func setUp() {\n        self.date = UATestDate()\n    }\n\n    func testEditor() throws {\n        var out: [AttributeUpdate]?\n\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n\n        editor.remove(\"bar\")\n        editor.set(string: \"neat\", attribute: \"bar\")\n\n        editor.set(int: 10, attribute: \"foo\")\n        editor.remove(\"foo\")\n\n        let applyDate = Date(timeIntervalSince1970: 1)\n        self.date.dateOverride = applyDate\n        editor.apply()\n\n        XCTAssertEqual(2, out?.count)\n\n        let foo = out?.first { $0.attribute == \"foo\" }\n        let bar = out?.first { $0.attribute == \"bar\" }\n\n        XCTAssertEqual(AttributeUpdateType.remove, foo?.type)\n        XCTAssertEqual(applyDate, foo?.date)\n        XCTAssertNil(foo?.jsonValue?.unWrap())\n\n        XCTAssertEqual(AttributeUpdateType.set, bar?.type)\n        XCTAssertEqual(\"neat\", bar?.jsonValue?.unWrap() as? String)\n        XCTAssertEqual(applyDate, foo?.date)\n    }\n\n    func testDateAttribute() throws {\n        var out: [AttributeUpdate]?\n\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n\n        editor.set(date: Date(timeIntervalSince1970: 10000), attribute: \"date\")\n        let applyDate = Date(timeIntervalSince1970: 1)\n        self.date.dateOverride = applyDate\n        editor.apply()\n\n        let attribute = out?.first\n\n        XCTAssertEqual(AttributeUpdateType.set, attribute?.type)\n        XCTAssertEqual(applyDate, attribute?.date)\n        XCTAssertEqual(\n            \"1970-01-01T02:46:40\",\n            attribute?.jsonValue?.unWrap() as! String\n        )\n    }\n\n    func testEditorNoAttributes() throws {\n        var out: [AttributeUpdate]?\n\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n\n        editor.apply()\n\n        XCTAssertEqual(0, out?.count)\n    }\n\n    func testEditorEmptyString() throws {\n        var out: [AttributeUpdate]?\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n        editor.set(string: \"\", attribute: \"cool\")\n        editor.set(string: \"cool\", attribute: \"\")\n        editor.apply()\n\n        XCTAssertEqual(0, out?.count)\n    }\n\n    func testSetJSONAttributeNoExpiration() throws {\n        var out: [AttributeUpdate]?\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n\n        let payload: [String: AirshipJSON] = [\n            \"flavor\": \"vanilla\",\n            \"rating\": 5.0,\n            \"available\": true,\n        ]\n\n        try editor.set(\n            json: payload,\n            attribute: \"icecream\",\n            instanceID: \"store-123\"\n        )\n\n        let now = Date(timeIntervalSince1970: 10)\n        self.date.dateOverride = now\n        editor.apply()\n\n        XCTAssertEqual(1, out?.count)\n        guard let first = out?.first else {\n            XCTFail(\"missing update\")\n            return\n        }\n\n        XCTAssertEqual(AttributeUpdateType.set, first.type)\n        XCTAssertEqual(\"icecream#store-123\", first.attribute)\n        XCTAssertEqual(now, first.date)\n\n        let unwrapped = first.jsonValue?.unWrap() as? [String: AnyHashable]\n        XCTAssertEqual(3, unwrapped?.count)\n        XCTAssertEqual(\"vanilla\", unwrapped?[\"flavor\"] as? String)\n        XCTAssertEqual(5.0, unwrapped?[\"rating\"] as? Double)\n        XCTAssertEqual(true, unwrapped?[\"available\"] as? Bool)\n        XCTAssertNil(unwrapped?[\"exp\"], \"Unexpected expiry key present\")\n    }\n\n    func testSetJSONAttributeWithExpiration() throws {\n        var out: [AttributeUpdate]? = nil\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n\n        let payload: [String: AirshipJSON] = [\n            \"size\": .string(\"large\"),\n        ]\n\n        let expiration = Date(timeIntervalSince1970: 1000)\n\n        try editor.set(\n            json: payload,\n            attribute: \"coffee\",\n            instanceID: \"order-123\",\n            expiration: expiration\n        )\n\n        self.date.dateOverride = Date(timeIntervalSince1970: 20)\n        editor.apply()\n\n        guard let update = out?.first else {\n            XCTFail(\"Missing update\")\n            return\n        }\n\n        XCTAssertEqual(\"coffee#order-123\", update.attribute)\n        XCTAssertEqual(AttributeUpdateType.set, update.type)\n\n        let dict = update.jsonValue?.unWrap() as? [String: AnyHashable]\n        XCTAssertEqual(\"large\", dict?[\"size\"] as? String)\n\n        if let exp = dict?[\"exp\"] as? Double {\n            XCTAssertEqual(expiration.timeIntervalSince1970, exp, accuracy: 0.001)\n        } else {\n            XCTFail(\"Missing expiration key in payload\")\n        }\n    }\n\n    func testRemoveJSONAttribute() throws {\n        var out: [AttributeUpdate]?\n        let editor = AttributesEditor(date: self.date) { updates in\n            out = updates\n        }\n\n        try editor.remove(attribute: \"coffee\", instanceID: \"order-123\")\n\n        self.date.dateOverride = Date(timeIntervalSince1970: 30)\n        editor.apply()\n\n        XCTAssertEqual(1, out?.count)\n        XCTAssertEqual(AttributeUpdateType.remove, out?.first?.type)\n        XCTAssertEqual(\"coffee#order-123\", out?.first?.attribute)\n    }\n\n    func testJSONAttributeValidation() throws {\n        let editor = AttributesEditor(date: self.date) { _ in }\n\n        // Empty JSON\n        XCTAssertThrowsError(try editor.set(\n            json: [:],\n            attribute: \"test\",\n            instanceID: \"id\"\n        ))\n\n        // JSON contains reserved key\n        let badPayload: [String: AirshipJSON] = [\n            \"exp\": 100\n        ]\n        XCTAssertThrowsError(try editor.set(\n            json: badPayload,\n            attribute: \"test\",\n            instanceID: \"id\"\n        ))\n\n        // Attribute or instanceID validation\n        let payload: [String: AirshipJSON] = [\"k\": .string(\"v\")]\n        XCTAssertThrowsError(try editor.set(\n            json: payload,\n            attribute: \"has#pound\",\n            instanceID: \"id\"\n        ))\n\n        XCTAssertThrowsError(try editor.set(\n            json: payload,\n            attribute: \"\",\n            instanceID: \"id\"\n        ))\n\n        XCTAssertThrowsError(try editor.set(\n            json: payload,\n            attribute: \"valid\",\n            instanceID: \"bad#id\"\n        ))\n\n        XCTAssertThrowsError(try editor.set(\n            json: payload,\n            attribute: \"valid\",\n            instanceID: \"\"\n        ))\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AttributeUpdateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass AttributeUpdateTest: XCTestCase {\n\n    func testNumberCoding() throws {\n        let original = AttributeUpdate(\n            attribute: \"some attribute\",\n            type: .set,\n            jsonValue: 42,\n            date: Date()\n        )\n\n        let encoded = try JSONEncoder().encode(original)\n        let decoded = try JSONDecoder()\n            .decode(AttributeUpdate.self, from: encoded)\n\n        XCTAssertEqual(original.attribute, decoded.attribute)\n        XCTAssertEqual(\n            original.jsonValue!,\n            decoded.jsonValue!\n        )\n        XCTAssertEqual(original.date, decoded.date)\n\n    }\n\n    func testStringCoding() throws {\n        let original = AttributeUpdate(\n            attribute: \"some attribute\",\n            type: .set,\n            jsonValue: \"neat\",\n            date: Date()\n        )\n\n        let encoded = try JSONEncoder().encode(original)\n        let decoded = try JSONDecoder()\n            .decode(AttributeUpdate.self, from: encoded)\n\n        XCTAssertEqual(original.attribute, decoded.attribute)\n        XCTAssertEqual(\n            original.jsonValue!,\n            decoded.jsonValue!\n        )\n        XCTAssertEqual(original.date, decoded.date)\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AudienceHashSelectorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\nfinal class AudienceHashSelectorTest: XCTestCase {\n\n    func testCodable() throws {\n        let json: String = \"\"\"\n        {\n            \"audience_hash\": {\n                \"hash_prefix\": \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                \"num_hash_buckets\": 16384,\n                \"hash_identifier\": \"contact\",\n                \"hash_algorithm\": \"farm_hash\",\n                \"hash_seed\": 100,\n                \"hash_identifier_overrides\": {\n                    \"foo\": \"bar\"\n                }\n            },\n            \"audience_subset\": {\n                \"min_hash_bucket\": 10,\n                \"max_hash_bucket\": 100\n            }\n        }\n        \"\"\"\n\n        let decoded: AudienceHashSelector = try JSONDecoder().decode(\n            AudienceHashSelector.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: [ \"foo\": \"bar\" ]\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 10, max: 100)\n        )\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n    \n    func testCodableWithSticky() throws {\n        let json: String = \"\"\"\n        {\n          \"audience_hash\": {\n            \"hash_prefix\": \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n            \"num_hash_buckets\": 16384,\n            \"hash_identifier\": \"contact\",\n            \"hash_algorithm\": \"farm_hash\",\n            \"hash_seed\": 100,\n            \"hash_identifier_overrides\": {\n              \"foo\": \"bar\"\n            }\n          },\n          \"audience_subset\": {\n            \"min_hash_bucket\": 10,\n            \"max_hash_bucket\": 100\n          },\n          \"sticky\": {\n            \"id\": \"test-id\",\n            \"reporting_metadata\": \"test\",\n            \"last_access_ttl\": 123\n          }\n        }\n        \"\"\"\n\n        let decoded: AudienceHashSelector = try JSONDecoder().decode(\n            AudienceHashSelector.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: [ \"foo\": \"bar\" ]\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 10, max: 100),\n            sticky: AudienceHashSelector.Sticky(\n                id: \"test-id\",\n                reportingMetadata: \"test\",\n                lastAccessTTL: 0.123\n            )\n        )\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n\n    func testBoundaries() throws {\n        let selectorGenerator: (UInt64, UInt64) throws -> AudienceHashSelector = { min, max in\n            let json = \"\"\"\n                {\n                    \"audience_hash\":{\n                       \"hash_prefix\":\"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                       \"num_hash_buckets\":16384,\n                       \"hash_identifier\":\"contact\",\n                       \"hash_algorithm\":\"farm_hash\"\n                    },\n                    \"audience_subset\":{\n                       \"min_hash_bucket\":\\(min),\n                       \"max_hash_bucket\":\\(max)\n                    }\n                 }\n            \"\"\"\n\n            return try JSONDecoder().decode(\n                AudienceHashSelector.self,\n                from: json.data(using: .utf8)!\n            )\n        }\n\n\n        // contactId = 9908\n        XCTAssertTrue(\n            try selectorGenerator(9908, 9908)\n                .evaluate(\n                    channelID: \"\",\n                    contactID: \"contactId\"\n                )\n        )\n\n        XCTAssertTrue(\n            try selectorGenerator(9907, 9908)\n                .evaluate(\n                    channelID: \"\",\n                    contactID: \"contactId\"\n                )\n        )\n\n        XCTAssertTrue(\n            try selectorGenerator(9908, 9909)\n                .evaluate(\n                    channelID: \"\",\n                    contactID: \"contactId\"\n                )\n        )\n\n        XCTAssertFalse(\n            try selectorGenerator(9907, 9907)\n                .evaluate(\n                    channelID: \"\",\n                    contactID: \"contactId\"\n                )\n        )\n\n        XCTAssertFalse(\n            try selectorGenerator(9909, 9909)\n                .evaluate(\n                    channelID: \"\",\n                    contactID: \"contactId\"\n                )\n        )\n    }\n\n    func testEvaluateChannel() throws {\n        let experiment = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .channel,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000)\n        )\n\n        // match = 12443\n        XCTAssertTrue(experiment.evaluate(channelID: \"match\", contactID: \"not used\"))\n        // not a match = 11599\n        XCTAssertFalse(experiment.evaluate(channelID: \"not a match\", contactID: \"not used\"))\n    }\n\n    func testEvaluateContact() throws {\n        let experiment = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000)\n        )\n\n\n        // match = 12443\n        XCTAssertTrue(experiment.evaluate(channelID: \"not used\", contactID: \"match\"))\n        // not a match = 11599\n        XCTAssertFalse(experiment.evaluate(channelID: \"not used\", contactID: \"not a match\"))\n    }\n\n    func testEvaluateOverrides() throws {\n        let experiment = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: [\n                    \"not a match\" : \"match\"\n                ]\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000)\n        )\n\n        // match = 12443\n        XCTAssertTrue(experiment.evaluate(channelID: \"not used\", contactID: \"match\"))\n        // not a match = 11599\n        XCTAssertTrue(experiment.evaluate(channelID: \"not used\", contactID: \"not a match\"))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/AudienceUtilsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass AudienceUtilsTest: XCTestCase {\n\n    func testCollapseTagGroupUpdates() throws {\n        let updates = [\n            TagGroupUpdate(\n                group: \"some-group\",\n                tags: [\"1\", \"2\", \"3\"],\n                type: .remove\n            ),\n            TagGroupUpdate(group: \"some-group\", tags: [\"1\", \"2\"], type: .add),\n            TagGroupUpdate(group: \"some-group\", tags: [\"4\"], type: .set),\n            TagGroupUpdate(group: \"some-group\", tags: [\"5\", \"6\"], type: .add),\n            TagGroupUpdate(group: \"some-group\", tags: [\"5\"], type: .remove),\n            TagGroupUpdate(\n                group: \"some-other-group\",\n                tags: [\"10\", \"11\"],\n                type: .remove\n            ),\n            TagGroupUpdate(group: \"some-other-group\", tags: [\"12\"], type: .add),\n            TagGroupUpdate(group: \"some-other-group\", tags: [\"10\"], type: .add),\n        ]\n\n        let collapsed = AudienceUtils.collapse(updates)\n\n        XCTAssertEqual(3, collapsed.count)\n        XCTAssertEqual(\"some-group\", collapsed[0].group)\n        XCTAssertEqual(Set([\"6\", \"4\"]), Set(collapsed[0].tags))\n        XCTAssertEqual(.set, collapsed[0].type)\n\n        XCTAssertEqual(\"some-other-group\", collapsed[1].group)\n        XCTAssertEqual(Set([\"12\", \"10\"]), Set(collapsed[1].tags))\n        XCTAssertEqual(.add, collapsed[1].type)\n\n        XCTAssertEqual(\"some-other-group\", collapsed[2].group)\n        XCTAssertEqual(Set([\"11\"]), Set(collapsed[2].tags))\n        XCTAssertEqual(.remove, collapsed[2].type)\n    }\n\n    func testCollapseTagGroupUpdatesEmptyTags() throws {\n        let updates = [\n            TagGroupUpdate(group: \"set-group\", tags: [], type: .set),\n            TagGroupUpdate(group: \"add-group\", tags: [], type: .add),\n            TagGroupUpdate(group: \"remove-group\", tags: [], type: .remove),\n\n        ]\n\n        let collapsed = AudienceUtils.collapse(updates)\n\n        XCTAssertEqual(1, collapsed.count)\n        XCTAssertEqual(\"set-group\", collapsed[0].group)\n        XCTAssertEqual(Set([]), Set(collapsed[0].tags))\n        XCTAssertEqual(.set, collapsed[0].type)\n    }\n\n    func testCollapseAttributeUpdates() throws {\n        let date = Date()\n        let updates = [\n            AttributeUpdate.remove(attribute: \"some-attribute\", date: date),\n            AttributeUpdate.set(\n                attribute: \"some-attribute\",\n                value: \"neat\",\n                date: date\n            ),\n            AttributeUpdate.set(\n                attribute: \"some-other-attribute\",\n                value: 12,\n                date: date\n            ),\n            AttributeUpdate.remove(\n                attribute: \"some-other-attribute\",\n                date: date\n            ),\n        ]\n\n        let collapsed = AudienceUtils.collapse(updates)\n\n        XCTAssertEqual(2, collapsed.count)\n        XCTAssertEqual(\"some-attribute\", collapsed[0].attribute)\n        XCTAssertEqual(\"neat\", collapsed[0].jsonValue?.unWrap() as! String)\n        XCTAssertEqual(.set, collapsed[0].type)\n        XCTAssertEqual(date, collapsed[0].date)\n\n        XCTAssertEqual(\"some-other-attribute\", collapsed[1].attribute)\n        XCTAssertNil(collapsed[1].jsonValue?.unWrap())\n        XCTAssertEqual(.remove, collapsed[1].type)\n        XCTAssertEqual(.set, collapsed[0].type)\n    }\n\n    func testCollapseSubscriptionListUpdates() throws {\n        let updates = [\n            SubscriptionListUpdate(listId: \"coffee\", type: .unsubscribe),\n            SubscriptionListUpdate(listId: \"pizza\", type: .subscribe),\n            SubscriptionListUpdate(listId: \"coffee\", type: .subscribe),\n            SubscriptionListUpdate(listId: \"pizza\", type: .unsubscribe),\n        ]\n\n        let expected = [\n            SubscriptionListUpdate(listId: \"coffee\", type: .subscribe),\n            SubscriptionListUpdate(listId: \"pizza\", type: .unsubscribe),\n        ]\n\n        let collapsed = AudienceUtils.collapse(updates)\n\n        XCTAssertEqual(expected, collapsed)\n    }\n\n    func testCollapseScopedSubscriptionListUpdates() throws {\n        let now = Date()\n\n        let updates = [\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .sms,\n                date: now.advanced(by: 1)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .app,\n                date: now.advanced(by: 2)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .subscribe,\n                scope: .web,\n                date: now.advanced(by: 3)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .sms,\n                date: now.advanced(by: 4)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .app,\n                date: now.advanced(by: 5)\n            ),\n\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .subscribe,\n                scope: .app,\n                date: now.advanced(by: 6)\n            ),\n\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .unsubscribe,\n                scope: .app,\n                date: now.advanced(by: 7)\n            ),\n        ]\n\n        let expected = [\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .subscribe,\n                scope: .web,\n                date: now.advanced(by: 3)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .sms,\n                date: now.advanced(by: 4)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .app,\n                date: now.advanced(by: 5)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .unsubscribe,\n                scope: .app,\n                date: now.advanced(by: 7)\n            ),\n        ]\n\n        let collapsed = AudienceUtils.collapse(updates)\n\n        XCTAssertEqual(expected, collapsed)\n    }\n\n    func testApplyScopedSubscriptionListsEmptyPayload() throws {\n        let now = Date()\n\n        let updates = [\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .subscribe,\n                scope: .web,\n                date: now.advanced(by: 3)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .sms,\n                date: now.advanced(by: 4)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .app,\n                date: now.advanced(by: 5)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .unsubscribe,\n                scope: .app,\n                date: now.advanced(by: 7)\n            ),\n        ]\n\n        let expected: [String: [ChannelScope]] = [\n            \"foo\": [.sms, .app],\n            \"bar\": [.web],\n        ]\n\n        XCTAssertEqual(\n            expected,\n            AudienceUtils.applySubscriptionListsUpdates(nil, updates: updates)\n        )\n        XCTAssertEqual(\n            expected,\n            AudienceUtils.applySubscriptionListsUpdates([:], updates: updates)\n        )\n    }\n\n    func testApplyScopedSubscriptionLists() throws {\n        let now = Date()\n\n        let updates = [\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .subscribe,\n                scope: .web,\n                date: now.advanced(by: 3)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .sms,\n                date: now.advanced(by: 4)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .subscribe,\n                scope: .app,\n                date: now.advanced(by: 5)\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .unsubscribe,\n                scope: .app,\n                date: now.advanced(by: 7)\n            ),\n        ]\n\n        let expected: [String: [ChannelScope]] = [\n            \"baz\": [.email],\n            \"foo\": [.app, .web, .sms],\n            \"bar\": [.web],\n        ]\n\n        let payload: [String: [ChannelScope]] = [\n            \"baz\": [.email],\n            \"bar\": [.app],\n            \"foo\": [.app, .web],\n        ]\n\n        XCTAssertEqual(\n            expected,\n            AudienceUtils.applySubscriptionListsUpdates(\n                payload,\n                updates: updates\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/CachedListTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass CachedListTest: XCTestCase {\n\n    let date = UATestDate(offset: 0, dateOverride: Date())\n\n    func testValue() throws {\n        let cachedList = CachedList<String>(date: date)\n\n        cachedList.append(\"foo\", expiresIn: 100)\n        XCTAssertEqual([\"foo\"], cachedList.values)\n\n        date.offset += 99\n\n        cachedList.append(\"bar\", expiresIn: 2)\n        XCTAssertEqual([\"foo\", \"bar\"], cachedList.values)\n\n        date.offset += 1\n        XCTAssertEqual([\"bar\"], cachedList.values)\n\n        date.offset += 1\n        XCTAssertEqual([], cachedList.values)\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/CachedValueTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass CachedValueTest: XCTestCase {\n\n    let date = UATestDate(offset: 0, dateOverride: Date())\n\n    func testValue() throws {\n        let cachedValue = CachedValue<String>(date: date)\n        cachedValue.set(value: \"Hello!\", expiresIn: 100)\n\n        XCTAssertEqual(100.0, cachedValue.timeRemaining)\n        XCTAssertEqual(\"Hello!\", cachedValue.value)\n\n        date.offset += 99\n\n        XCTAssertEqual(1.0, cachedValue.timeRemaining)\n        XCTAssertEqual(\"Hello!\", cachedValue.value)\n\n        date.offset += 1\n        XCTAssertEqual(0, cachedValue.timeRemaining)\n        XCTAssertNil(cachedValue.value)\n    }\n\n    func testValueExpiration() throws {\n        let cachedValue = CachedValue<String>(date: date)\n        cachedValue.set(value: \"Hello!\", expiration: date.now.advanced(by: 1.0))\n\n        XCTAssertEqual(1.0, cachedValue.timeRemaining)\n        XCTAssertEqual(\"Hello!\", cachedValue.value)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChallengeResolverTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class ChallengeResolverTest: XCTestCase {\n    \n    @MainActor\n    override func tearDown() async throws {\n        ChallengeResolver.shared.resolver = nil\n    }\n    \n    func testResolverReturnsDefaultIfNotConfigured() async {\n        await assertResolve(disposition: .performDefaultHandling, credentials: nil)\n    }\n    \n    @MainActor\n    func testResolverClosure() async {\n        let credentials = URLCredential()\n        ChallengeResolver.shared.resolver = { _ in\n            return (URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, credentials)\n        }\n        \n        let challenge = URLAuthenticationChallenge(\n            protectionSpace: AirshipProtectionSpace(\n                host: \"urbanairship.com\",\n                port: 443,\n                protocol: \"https\",\n                realm: nil,\n                authenticationMethod: NSURLAuthenticationMethodServerTrust),\n            proposedCredential: nil,\n            previousFailureCount: 0,\n            failureResponse: nil,\n            error: nil,\n            sender: ChallengeSender())\n        \n        await assertResolve(\n            disposition: .cancelAuthenticationChallenge,\n            credentials: credentials,\n            challenge: challenge\n        )\n    }\n    \n    @MainActor\n    func testResolverClosureNotCalledOnNonServerTrust() async {\n        let credentials = URLCredential()\n        ChallengeResolver.shared.resolver = { _ in\n            return (URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, credentials)\n        }\n        \n        let challenge = URLAuthenticationChallenge(\n            protectionSpace: AirshipProtectionSpace(\n                host: \"urbanairship.com\",\n                port: 443,\n                protocol: \"https\",\n                realm: nil,\n                authenticationMethod: NSURLAuthenticationMethodClientCertificate),\n            proposedCredential: nil,\n            previousFailureCount: 0,\n            failureResponse: nil,\n            error: nil,\n            sender: ChallengeSender())\n        \n        await assertResolve(\n            disposition: .performDefaultHandling,\n            credentials: nil,\n            challenge: challenge\n        )\n    }\n    \n    @MainActor\n    func testResolverClosureNotCalledOnNoPublicKey() async {\n        let credentials = URLCredential()\n        ChallengeResolver.shared.resolver = { _ in\n            return (URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, credentials)\n        }\n        \n        let protectionSpace = AirshipProtectionSpace(\n            host: \"urbanairship.com\",\n            port: 443,\n            protocol: \"https\",\n            realm: nil,\n            authenticationMethod: NSURLAuthenticationMethodServerTrust)\n        protectionSpace.useAirshipCert = false\n        \n        let challenge = URLAuthenticationChallenge(\n            protectionSpace: protectionSpace,\n            proposedCredential: nil,\n            previousFailureCount: 0,\n            failureResponse: nil,\n            error: nil,\n            sender: ChallengeSender())\n        \n        await assertResolve(\n            disposition: .performDefaultHandling,\n            credentials: nil,\n            challenge: challenge\n        )\n    }\n    \n    private func assertResolve(\n        disposition: URLSession.AuthChallengeDisposition,\n        credentials: URLCredential? = nil,\n        challenge: URLAuthenticationChallenge? = nil\n    ) async {\n        let actual = await ChallengeResolver.shared.resolve(challenge ?? URLAuthenticationChallenge())\n        \n        XCTAssertEqual(disposition, actual.0)\n        XCTAssertEqual(credentials, actual.1)\n    }\n}\n\nprivate class ChallengeSender: NSObject, URLAuthenticationChallengeSender {\n    func use(_ credential: URLCredential, for challenge: URLAuthenticationChallenge) { }\n    \n    func continueWithoutCredential(for challenge: URLAuthenticationChallenge) { }\n    \n    func cancel(_ challenge: URLAuthenticationChallenge) { }\n}\n\nprivate class AirshipProtectionSpace: URLProtectionSpace, @unchecked Sendable {\n    var useAirshipCert: Bool = true\n    \n    private func airshipCert() -> SecTrust? {\n        guard\n            let certFilePath = Bundle(for: type(of: self)).path(forResource: \"airship\", ofType: \"der\"),\n            let data = NSData(contentsOfFile: certFilePath),\n            let cert = SecCertificateCreateWithData(nil, data)\n        else {\n            return nil\n        }\n        \n        var trust: SecTrust?\n        SecTrustCreateWithCertificates(cert, SecPolicyCreateBasicX509(), &trust)\n        return trust\n    }\n    \n    override var serverTrust: SecTrust? {\n        return useAirshipCert ? airshipCert() : nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelAPIClientTest.swift",
    "content": "\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ChannelAPIClientTest: XCTestCase {\n    private var config: RuntimeConfig = .testConfig()\n    private let session = TestAirshipRequestSession()\n    private var client: ChannelAPIClient!\n    private let encoder = JSONEncoder()\n\n    override func setUpWithError() throws {\n        self.client = ChannelAPIClient(\n            config: self.config,\n            session: self.session\n        )\n    }\n\n    func testCreate() async throws {\n        let payload = ChannelRegistrationPayload()\n\n        self.session.data = try AirshipJSONUtils.data([\n            \"channel_id\": \"some-channel-id\"\n        ])\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        let response = try await self.client.createChannel(payload: payload)\n        XCTAssertEqual(\"some-channel-id\", response.result!.channelID)\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/channels/some-channel-id\",\n            response.result!.location.absoluteString\n        )\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\"POST\", request.method)\n        XCTAssertEqual(AirshipRequestAuth.generatedAppToken, request.auth)\n        XCTAssertEqual(\"https://device-api.urbanairship.com/api/channels/\", request.url?.absoluteString)\n        XCTAssertEqual(try! AirshipJSON.wrap(payload), try! AirshipJSON.from(data: request.body))\n    }\n\n    func testCreateInvalidResponse() async throws {\n        let payload = ChannelRegistrationPayload()\n\n        self.session.data = try AirshipJSONUtils.data([\n            \"not-right\": \"some-channel-id\"\n        ])\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        do {\n            _ = try await self.client.createChannel(payload: payload)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testCreateError() async throws {\n        let payload = ChannelRegistrationPayload()\n        self.session.error = AirshipErrors.error(\"Error!\")\n        do {\n            _ = try await self.client.createChannel(payload: payload)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testCreateFailed() async throws {\n        let payload = ChannelRegistrationPayload()\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 400,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        let response = try await self.client.createChannel(payload: payload)\n        XCTAssertEqual(400, response.statusCode)\n    }\n\n    func testUpdate() async throws {\n        let payload = ChannelRegistrationPayload()\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        let response = try await self.client.updateChannel(\n            \"some-channel-id\",\n            payload: payload\n        )\n\n        XCTAssertEqual(\"some-channel-id\", response.result!.channelID)\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/channels/some-channel-id\",\n            response.result!.location.absoluteString\n        )\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\"PUT\", request.method)\n\n        XCTAssertEqual(AirshipRequestAuth.channelAuthToken(identifier: \"some-channel-id\"), request.auth)\n        XCTAssertEqual(try! AirshipJSON.wrap(payload), try! AirshipJSON.from(data: request.body))\n        XCTAssertEqual(\"https://device-api.urbanairship.com/api/channels/some-channel-id\", request.url?.absoluteString)\n\n    }\n    func testUpdateError() async throws {\n        let payload = ChannelRegistrationPayload()\n        self.session.error = AirshipErrors.error(\"Error!\")\n        do {\n            _ = try await self.client.updateChannel(\n                \"some-channel-id\",\n                payload: payload\n            )\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testUpdateFailed() async throws {\n        let payload = ChannelRegistrationPayload()\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 400,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        let response = try await self.client.updateChannel(\n            \"some-channel-id\",\n            payload: payload\n        )\n        XCTAssertEqual(400, response.statusCode)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelAudienceManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\nimport Combine\n\nclass ChannelAudienceManagerTest: XCTestCase {\n\n    private let workManager = TestWorkManager()\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(\n        notificationCenter: NotificationCenter()\n    )\n    private let date: UATestDate = UATestDate()\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let subscriptionListClient: TestSubscriptionListAPIClient = TestSubscriptionListAPIClient()\n    private let updateClient: TestChannelBulkUpdateAPIClient = TestChannelBulkUpdateAPIClient()\n    private let audienceOverridesProvider: DefaultAudienceOverridesProvider = DefaultAudienceOverridesProvider()\n    private var privacyManager: TestPrivacyManager!\n    private var audienceManager: ChannelAudienceManager!\n\n    @MainActor\n    override func setUp() async throws {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: RuntimeConfig.testConfig(),\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n\n        self.audienceManager = ChannelAudienceManager(\n            dataStore: self.dataStore,\n            workManager: self.workManager,\n            subscriptionListProvider: ChannelSubscriptionListProvider(\n                audienceOverrides: self.audienceOverridesProvider,\n                apiClient: self.subscriptionListClient,\n                date: self.date\n            ),\n            updateClient: self.updateClient,\n            privacyManager: self.privacyManager,\n            notificationCenter: self.notificationCenter,\n            date: self.date,\n            audienceOverridesProvider: self.audienceOverridesProvider\n        )\n\n        self.audienceManager.enabled = true\n        self.audienceManager.channelID = \"some-channel\"\n\n        self.workManager.workRequests.removeAll()\n    }\n\n    func testBackgroundWorkRequest() async throws {\n        XCTAssertEqual(1, self.workManager.backgroundWorkRequests.count)\n\n        let expected = AirshipWorkRequest(\n            workID: ChannelAudienceManager.updateTaskID\n        )\n        XCTAssertEqual(expected, self.workManager.backgroundWorkRequests.first)\n    }\n\n    func testUpdates() async throws {\n        let subscriptionListEditor = self.audienceManager\n            .editSubscriptionLists()\n        subscriptionListEditor.subscribe(\"pizza\")\n        subscriptionListEditor.unsubscribe(\"coffee\")\n        subscriptionListEditor.apply()\n\n        subscriptionListEditor.subscribe(\"hotdogs\")\n        subscriptionListEditor.apply()\n\n        let tagEditor = self.audienceManager.editTagGroups(\n            allowDeviceGroup: true\n        )\n        tagEditor.add([\"tag\"], group: \"some-group\")\n        tagEditor.apply()\n\n        let attributeEditor = self.audienceManager.editAttributes()\n        attributeEditor.set(string: \"hello\", attribute: \"some-attribute\")\n        attributeEditor.apply()\n\n        let activityUpdate = LiveActivityUpdate(\n            action: .set,\n            source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n            actionTimeMS: 10\n        )\n\n        self.audienceManager.addLiveActivityUpdate(activityUpdate)\n\n        XCTAssertEqual(5, self.workManager.workRequests.count)\n\n        let expectation = XCTestExpectation(description: \"callback called\")\n\n        self.updateClient.updateCallback = { identifier, update in\n            expectation.fulfill()\n            XCTAssertEqual(\"some-channel\", identifier)\n            XCTAssertEqual(3, update.subscriptionListUpdates.count)\n            XCTAssertEqual(1, update.tagGroupUpdates.count)\n            XCTAssertEqual(1, update.attributeUpdates.count)\n            XCTAssertEqual([activityUpdate], update.liveActivityUpdates)\n            return AirshipHTTPResponse(result: nil, statusCode: 200, headers: [:])\n        }\n\n        var result = try? await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n        XCTAssertEqual(result, .success)\n        await fulfillment(of: [expectation])\n\n        result = try? await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n        XCTAssertEqual(result, .success)\n    }\n\n    func testGet() async throws {\n        let expectedLists = [\"cool\", \"story\"]\n        self.subscriptionListClient.getCallback = { identifier in\n            XCTAssertEqual(\"some-channel\", identifier)\n            return AirshipHTTPResponse(\n                result: expectedLists,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n        let result = try await self.audienceManager.fetchSubscriptionLists()\n        XCTAssertEqual(expectedLists, result)\n    }\n\n    func testGetCache() async throws {\n        self.date.dateOverride = Date()\n\n        var apiResult = [\"cool\", \"story\"]\n\n        self.subscriptionListClient.getCallback = { identifier in\n            XCTAssertEqual(\"some-channel\", identifier)\n            return AirshipHTTPResponse(\n                result: apiResult,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Populate cache\n        var result = try await self.audienceManager.fetchSubscriptionLists()\n        XCTAssertEqual([\"cool\", \"story\"], result)\n\n        apiResult = [\"some-other-result\"]\n\n        // From cache\n        result = try await self.audienceManager.fetchSubscriptionLists()\n        XCTAssertEqual([\"cool\", \"story\"], result)\n        self.date.offset += 599  // 1 second before cache should invalidate\n\n        // From cache\n        result = try await self.audienceManager.fetchSubscriptionLists()\n        XCTAssertEqual([\"cool\", \"story\"], result)\n\n        self.date.offset += 1\n\n        // From api\n        result = try await self.audienceManager.fetchSubscriptionLists()\n        XCTAssertEqual([\"some-other-result\"], result)\n    }\n\n    func testNoPendingOperations() async throws {\n        let result = try? await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n        XCTAssertEqual(result, .success)\n        XCTAssertEqual(0, self.workManager.workRequests.count)\n    }\n\n    func testEnableEnqueuesTask() throws {\n        self.audienceManager.enabled = false\n        XCTAssertEqual(0, self.workManager.workRequests.count)\n\n        self.audienceManager.enabled = true\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n    }\n\n    func testSetChannelIDEnqueuesTask() throws {\n        self.audienceManager.channelID = nil\n        XCTAssertEqual(0, self.workManager.workRequests.count)\n\n        self.audienceManager.channelID = \"sweet\"\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n    }\n\n    func testPrivacyManagerDisabledIgnoresUpdates() async throws {\n        self.privacyManager.disableFeatures(.tagsAndAttributes)\n\n        let editor = self.audienceManager.editSubscriptionLists()\n        editor.subscribe(\"pizza\")\n        editor.unsubscribe(\"coffee\")\n        editor.apply()\n\n        self.updateClient.updateCallback = { identifier, update in\n            return AirshipHTTPResponse(result: nil, statusCode: 200, headers: [:])\n        }\n\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n        _ = try? await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n    }\n\n    func testMigrateMutations() async throws {\n        let testDate = UATestDate()\n        testDate.dateOverride = Date()\n\n        let attributePayload = [\n            \"action\": \"remove\",\n            \"key\": \"some-attribute\",\n            \"timestamp\": AirshipDateFormatter.string(fromDate: testDate.now, format: .isoDelimitter)\n        ]\n\n        let attributeMutation = AttributePendingMutations(mutationsPayload: [\n            attributePayload\n        ])\n        let attributeData = try! NSKeyedArchiver.archivedData(\n            withRootObject: [attributeMutation],\n            requiringSecureCoding: true\n        )\n        dataStore.setObject(\n            attributeData,\n            forKey: ChannelAudienceManager.legacyPendingAttributesKey\n        )\n\n        let tagMutation = TagGroupsMutation(\n            adds: [\"some-group\": Set([\"tag\"])],\n            removes: nil,\n            sets: nil\n        )\n        let tagData = try! NSKeyedArchiver.archivedData(\n            withRootObject: [tagMutation],\n            requiringSecureCoding: true\n        )\n        dataStore.setObject(\n            tagData,\n            forKey: ChannelAudienceManager.legacyPendingTagGroupsKey\n        )\n\n        self.audienceManager.migrateMutations()\n\n        let pending = await self.audienceOverridesProvider.pendingOverrides(channelID: \"some-channel\")\n        XCTAssertEqual(\n            [\n                TagGroupUpdate(group: \"some-group\", tags: [\"tag\"], type: .add)\n            ],\n            pending?.tags\n        )\n\n        XCTAssertEqual(\n            [\n                AttributeUpdate.remove(\n                    attribute: \"some-attribute\",\n                    date: AirshipDateFormatter.date(fromISOString: attributePayload[\"timestamp\"]!)!\n                )\n            ],\n            pending?.attributes\n        )\n    }\n\n    func testGetSubscriptionListOverrides() async throws {\n        await self.audienceOverridesProvider.setStableContactIDProvider {\n            \"some contact ID\"\n        }\n\n        await self.audienceOverridesProvider.contactUpdated(\n            contactID: \"some contact ID\",\n            tags: nil,\n            attributes: nil,\n            subscriptionLists: [\n                ScopedSubscriptionListUpdate(listId: \"bar\", type: .subscribe, scope: .app, date: Date()),\n                ScopedSubscriptionListUpdate(listId: \"baz\", type: .unsubscribe, scope: .app, date: Date())\n            ], channels: []\n        )\n\n\n        self.subscriptionListClient.getCallback = { identifier in\n            return AirshipHTTPResponse(\n                result: [\"cool\", \"baz\"],\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.audienceManager.fetchSubscriptionLists()\n        XCTAssertEqual([\"cool\", \"bar\"], result)\n    }\n\n    func testSubscriptionListEdits() throws {\n        var edits = [SubscriptionListEdit]()\n\n        let expectation = self.expectation(description: \"Publisher\")\n        expectation.expectedFulfillmentCount = 3\n        let cancellable = self.audienceManager.subscriptionListEdits.sink {\n            edits.append($0)\n            expectation.fulfill()\n        }\n\n        let editor = self.audienceManager.editSubscriptionLists()\n        editor.unsubscribe(\"apple\")\n        editor.unsubscribe(\"pen\")\n        editor.subscribe(\"apple pen\")\n        editor.apply()\n\n        self.waitForExpectations(timeout: 10.0)\n\n        let expected: [SubscriptionListEdit] = [\n            .unsubscribe(\"apple\"),\n            .unsubscribe(\"pen\"),\n            .subscribe(\"apple pen\"),\n        ]\n\n        XCTAssertEqual(expected, edits)\n        cancellable.cancel()\n    }\n\n    func testLiveActivityUpdates() async throws {\n        let activityUpdate = LiveActivityUpdate(\n            action: .set,\n            source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n            actionTimeMS: 10\n        )\n\n        self.audienceManager.addLiveActivityUpdate(activityUpdate)\n        let expectation = XCTestExpectation(description: \"callback called\")\n        self.updateClient.updateCallback = { identifier, update in\n            expectation.fulfill()\n            XCTAssertEqual(\"some-channel\", identifier)\n            XCTAssertEqual([activityUpdate], update.liveActivityUpdates)\n            return AirshipHTTPResponse(result: nil, statusCode: 200, headers: [:])\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n        XCTAssertEqual(result, .success)\n        await fulfillment(of: [expectation])\n    }\n\n    func testLiveActivityUpdateAdjustTimestamps() async throws {\n        let activityUpdates = [\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 10\n            ),\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 10\n            ),\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"some other foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 10\n            ),\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"something else\", name: \"something else\", startTimeMS: 10),\n                actionTimeMS: 10\n            ),\n        ]\n\n        let expected = [\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 10\n            ),\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 11\n            ),\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"some other foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 12\n            ),\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"something else\", name: \"something else\", startTimeMS: 10),\n                actionTimeMS: 13\n            ),\n        ]\n\n        activityUpdates.forEach { update in\n            self.audienceManager.addLiveActivityUpdate(update)\n        }\n\n        let expectation = XCTestExpectation(description: \"callback called\")\n        self.updateClient.updateCallback = { identifier, update in\n            expectation.fulfill()\n            XCTAssertEqual(\"some-channel\", identifier)\n            XCTAssertEqual(expected, update.liveActivityUpdates)\n            return AirshipHTTPResponse(result: nil, statusCode: 200, headers: [:])\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n        XCTAssertEqual(result, .success)\n        await fulfillment(of: [expectation])\n    }\n\n    func testLiveActivityUpdatesStream() async throws {\n        let updates = [\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 10\n            ),\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 11\n            ),\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"some other foo\", name: \"bar\", startTimeMS: 10),\n                actionTimeMS: 12\n            ),\n        ]\n\n        updates.forEach { update in\n            self.audienceManager.addLiveActivityUpdate(update)\n        }\n\n        let expectation = XCTestExpectation(description: \"callback called\")\n        self.updateClient.updateCallback = { identifier, update in\n            expectation.fulfill()\n            XCTAssertEqual(updates, update.liveActivityUpdates)\n            return AirshipHTTPResponse(result: nil, statusCode: 200, headers: [:])\n        }\n\n        let result = try? await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ChannelAudienceManager.updateTaskID\n            )\n        )\n        XCTAssertEqual(result, .success)\n        await fulfillment(of: [expectation])\n\n        var iterator = self.audienceManager.liveActivityUpdates.makeAsyncIterator()\n        let actualUpdates = await iterator.next()\n\n        XCTAssertEqual(actualUpdates, updates)\n\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelAuthTokenAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class ChannelAuthTokenAPIClientTest: AirshipBaseTest {\n    \n    private var client: ChannelAuthTokenAPIClient!\n    private let session = TestAirshipRequestSession()\n\n    override func setUpWithError() throws {\n        self.client = ChannelAuthTokenAPIClient(\n            config: self.config,\n            session: self.session\n        )\n    }\n    \n    func testTokenWithChannelID() async throws {\n        self.session.data = try AirshipJSONUtils.data([\n            \"token\": \"abc123\",\n            \"expires_in\": 12345\n        ] as [String : Any])\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://www.linkedin.com/\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: nil)\n\n        let token = try await self.client.fetchToken(channelID: \"channel ID\")\n        XCTAssertNotNil(token)\n        \n        let request = try XCTUnwrap(self.session.lastRequest)\n        \n        XCTAssertEqual(request.method, \"GET\")\n        XCTAssertEqual(request.url!.absoluteString, \"\\(self.config.deviceAPIURL!)/api/auth/device\")\n        XCTAssertEqual(\n            AirshipRequestAuth.generatedChannelToken(identifier: \"channel ID\"),\n            request.auth\n        )\n        XCTAssertEqual(request.headers[\"Accept\"], \"application/vnd.urbanairship+json; version=3;\")\n    }\n    \n    func testTokenWithChannelIDMalformedPayload() async throws {\n        self.session.data = try AirshipJSONUtils.data([\n            \"not a token\": \"abc123\",\n            \"expires_in_3_2_1\": 12345\n        ] as [String : Any])\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://www.linkedin.com/\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: nil)\n        \n        do {\n            let _ = try await self.client.fetchToken(channelID: \"channel ID\")\n            XCTFail(\"Should throw\")\n        } catch {\n            XCTAssertNotNil(error)\n        }\n        \n    }\n    \n    func testTokenWithChannelIDClientError() async throws {\n        self.session.data = try AirshipJSONUtils.data([\n            \"too\": \"bad\"\n        ])\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://www.linkedin.com/\")!,\n            statusCode: 400,\n            httpVersion: nil,\n            headerFields: nil)\n        \n        let response = try await self.client.fetchToken(channelID: \"channel ID\")\n        let unwrapResponse = try XCTUnwrap(response)\n        XCTAssertNil(unwrapResponse.result?.token)\n        XCTAssertTrue(unwrapResponse.isClientError)\n        XCTAssertFalse(unwrapResponse.isSuccess)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelAuthTokenProviderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class ChannelAuthTokenProviderTest: XCTestCase {\n    \n    var client = TestChannelAuthTokenAPIClient()\n    var channel = TestChannel()\n    var channelID = \"channel ID\"\n    var testDate = UATestDate(offset: 0, dateOverride: Date())\n    var provider: ChannelAuthTokenProvider!\n    \n    override func setUpWithError() throws {\n        self.channel.identifier = \"channel ID\"\n        self.provider = ChannelAuthTokenProvider(\n            channel: channel,\n            apiClient: client,\n            date: testDate\n        )\n    }\n    \n    func testFetchToken() async throws {\n        self.client.handler = { channelId in\n            let response = ChannelAuthTokenResponse(\n                token: \"my token\",\n                expiresInMillseconds: 100000\n            )\n            return AirshipHTTPResponse(\n                result: response,\n                statusCode: 200,\n                headers: [:])\n        }\n\n        try await verifyToken(expected: \"my token\")\n    }\n\n    func testTokenCached() async throws {\n        self.client.handler = { channelId in\n            let response = ChannelAuthTokenResponse(\n                token: \"my token\",\n                expiresInMillseconds: 100000\n            )\n            return AirshipHTTPResponse(\n                result: response,\n                statusCode: 200,\n                headers: [:])\n        }\n        \n        let _ = try await self.provider.resolveAuth(identifier: \"channel ID\")\n        self.client.handler = { channelId in\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        // Should be cached\n        try await verifyToken(expected: \"my token\")\n    }\n\n    func testTokenCachedExpired() async throws {\n        self.client.handler = { channelId in\n            let response = ChannelAuthTokenResponse(\n                token: \"my token\",\n                expiresInMillseconds: 100000\n            )\n            return AirshipHTTPResponse(\n                result: response,\n                statusCode: 200,\n                headers: [:])\n        }\n\n        let _ = try await self.provider.resolveAuth(identifier: \"channel ID\")\n        self.client.handler = { channelId in\n            let response = ChannelAuthTokenResponse(\n                token: \"some other token\",\n                expiresInMillseconds: 100000\n            )\n            return AirshipHTTPResponse(\n                result: response,\n                statusCode: 200,\n                headers: [:])\n        }\n\n        // Should be cached\n        try await verifyToken(expected: \"my token\")\n        testDate.offset += 70.0\n\n        // 30 second buffer\n        try await verifyToken(expected: \"my token\")\n        testDate.offset += 1.0\n\n        try await verifyToken(expected: \"some other token\")\n    }\n\n\n    private func verifyToken(expected: String, file: StaticString = #filePath, line: UInt = #line) async throws {\n        let token = try await self.provider.resolveAuth(identifier: \"channel ID\")\n        XCTAssertEqual(expected, token, file: file, line: line)\n    }\n\n\n    func testTokenWithNilChannelID() async {\n        self.channel.identifier = nil\n        do {\n            let _ = try await self.provider.resolveAuth(identifier: \"channel ID\")\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n\n    func testTokenMismatchChannelID() async {\n        do {\n            let _ = try await self.provider.resolveAuth(identifier: \"some other channel ID\")\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testClientError() async {\n        self.client.handler = { channelId in\n            throw AirshipErrors.error(\"some error\")\n        }\n\n        do {\n            let _ = try await self.provider.resolveAuth(identifier: \"some other channel ID\")\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelBulkUpdateAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ChannelBulkUpdateAPIClientTest: XCTestCase {\n\n    private var config: RuntimeConfig = .testConfig()\n    private let session = TestAirshipRequestSession()\n    var client: ChannelBulkUpdateAPIClient!\n\n    override func setUpWithError() throws {\n        self.client = ChannelBulkUpdateAPIClient(\n            config: self.config,\n            session: self.session\n        )\n    }\n\n    func testUpdate() async throws {\n        let date = Date()\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n\n        let update = AudienceUpdate(\n            subscriptionListUpdates: [\n                SubscriptionListUpdate(\n                    listId: \"coffee\",\n                    type: .unsubscribe\n                ),\n                SubscriptionListUpdate(\n                    listId: \"pizza\",\n                    type: .subscribe\n                ),\n            ],\n            tagGroupUpdates: [\n                TagGroupUpdate(\n                    group: \"some-group\",\n                    tags: [\"tag-1\", \"tag-2\"],\n                    type: .add\n                ),\n                TagGroupUpdate(\n                    group: \"some-other-group\",\n                    tags: [\"tag-3\", \"tag-4\"],\n                    type: .set\n                ),\n            ],\n            attributeUpdates: [\n                AttributeUpdate(\n                    attribute: \"some-attribute\",\n                    type: .set,\n                    jsonValue: \"hello\",\n                    date: date\n                )\n            ]\n        )\n\n        let response = try await self.client.update(\n            update,\n            channelID: \"some-channel\"\n        )\n\n        XCTAssertEqual(response.statusCode, 200)\n\n        let expectedBody =\n            [\n                \"subscription_lists\": [\n                    [\n                        \"action\": \"unsubscribe\",\n                        \"list_id\": \"coffee\",\n                    ],\n                    [\n                        \"action\": \"subscribe\",\n                        \"list_id\": \"pizza\",\n                    ],\n                ],\n                \"tags\": [\n                    \"add\": [\n                        \"some-group\": [\"tag-1\", \"tag-2\"]\n                    ],\n                    \"set\": [\n                        \"some-other-group\": [\"tag-3\", \"tag-4\"]\n                    ],\n                ],\n                \"attributes\": [\n                    [\n                        \"action\": \"set\",\n                        \"key\": \"some-attribute\",\n                        \"timestamp\": AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter),\n                        \"value\": \"hello\",\n                    ]\n                ],\n            ] as NSDictionary\n\n        let lastRequest = self.session.lastRequest!\n        let body =\n            AirshipJSONUtils.object(String(data: lastRequest.body!, encoding: .utf8)!)\n            as? NSDictionary\n        XCTAssertEqual(\"PUT\", lastRequest.method)\n        XCTAssertEqual(expectedBody, body)\n\n        let url = lastRequest.url\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/channels/sdk/batch/some-channel?platform=ios\",\n            url?.absoluteString\n        )\n    }\n\n    func testUpdateError() async throws {\n        let sessionError = AirshipErrors.error(\"error!\")\n        self.session.error = sessionError\n\n\n        let update = AudienceUpdate(\n            subscriptionListUpdates: [\n                SubscriptionListUpdate(\n                    listId: \"coffee\",\n                    type: .unsubscribe\n                ),\n                SubscriptionListUpdate(\n                    listId: \"pizza\",\n                    type: .subscribe\n                ),\n            ]\n        )\n\n        do {\n            _ = try await self.client.update(\n                update,\n                channelID: \"some-channel\"\n            )\n        } catch {\n            XCTAssertEqual(sessionError as NSError, error as NSError)\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelCaptureTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ChannelCaptureTest: XCTestCase {\n\n    private var config: AirshipConfig = AirshipConfig()\n    private let channel: TestChannel = TestChannel()\n    private let pasteboard: TestPasteboard = TestPasteboard()\n    private let notificationCenter: NotificationCenter = NotificationCenter()\n    private let date: UATestDate = UATestDate()\n    private var channelCapture: (any AirshipChannelCapture)!\n\n    @MainActor\n    override func setUpWithError() throws {\n        self.date.dateOverride = Date()\n        self.config.isChannelCaptureEnabled = true\n        self.channel.identifier = UUID().uuidString\n\n        self.channelCapture = DefaultAirshipChannelCapture(\n            config: .testConfig(),\n            channel: channel,\n            notificationCenter: notificationCenter,\n            date: date,\n            pasteboard: pasteboard\n        )\n    }\n\n    func testCapture() throws {\n        knock(times: 6)\n\n        let (text, expiry) = self.pasteboard.lastCopy!\n\n        XCTAssertEqual(\"ua:\\(self.channel.identifier!)\", text)\n        XCTAssertEqual(expiry, 60)\n    }\n\n    func testCaptureNilIdentifier() throws {\n        self.channel.identifier = nil\n        knock(times: 6)\n\n        let (text, expiry) = self.pasteboard.lastCopy!\n\n        XCTAssertEqual(\"ua:\", text)\n        XCTAssertEqual(expiry, 60)\n    }\n\n    func testKnock() throws {\n        knock(times: 5)\n        XCTAssertNil(self.pasteboard.lastCopy)\n        self.date.offset += 30\n        XCTAssertNil(self.pasteboard.lastCopy)\n        knock(times: 1)\n        XCTAssertNotNil(self.pasteboard.lastCopy)\n    }\n\n    func testKnockTooSlow() throws {\n        knock(times: 5)\n        XCTAssertNil(self.pasteboard.lastCopy)\n        self.date.offset += 31\n        XCTAssertNil(self.pasteboard.lastCopy)\n        knock(times: 1)\n        XCTAssertNil(self.pasteboard.lastCopy)\n    }\n\n    private func knock(times: UInt = 1) {\n        for _ in 1...times {\n            self.notificationCenter.post(\n                name: AppStateTracker.didTransitionToForeground,\n                object: nil\n            )\n        }\n    }\n}\n\nfileprivate final class TestPasteboard: AirshipPasteboardProtocol, @unchecked Sendable {\n    var lastCopy: (String, TimeInterval)?\n\n    func copy(value: String) {\n        lastCopy = (value, -1)\n    }\n\n    func copy(value: String, expiry: TimeInterval) {\n        lastCopy = (value, expiry)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelRegistrarTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Combine\n\n@testable import AirshipCore\nimport Foundation\n\n@Suite(.timeLimit(.minutes(1)))\nstruct ChannelRegistrarTest {\n\n    let dataStore: PreferenceDataStore\n    let client: TestChannelRegistrationClient\n    let date: UATestDate\n    let workManager: TestWorkManager\n    let appStateTracker: TestAppStateTracker\n    let payloadProvider: ChannelRegistrationPayloadProvider\n    let workID: String\n    let channelRegistrar: ChannelRegistrar\n\n    init() async throws {\n        self.dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        self.client = TestChannelRegistrationClient()\n        self.date = UATestDate()\n        self.workManager = TestWorkManager()\n        self.appStateTracker = TestAppStateTracker()\n        self.payloadProvider = ChannelRegistrationPayloadProvider()\n        self.workID = \"UAChannelRegistrar.registration\"\n\n        let dataStore = self.dataStore\n        let client = self.client\n        let date = self.date\n        let workManager = self.workManager\n        let appStateTracker = self.appStateTracker\n        let payloadProvider = self.payloadProvider\n\n        self.channelRegistrar = await MainActor.run {\n            let registrar = ChannelRegistrar(\n                dataStore: dataStore,\n                channelAPIClient: client,\n                date: date,\n                workManager: workManager,\n                appStateTracker: appStateTracker,\n                channelCreateMethod: { return .automatic },\n                privacyManager: TestPrivacyManager(\n                    dataStore: dataStore,\n                    config: RuntimeConfig.testConfig(),\n                    defaultEnabledFeatures: AirshipFeature.all\n                )\n            )\n            registrar.payloadCreateBlock = { await payloadProvider.getPayload() }\n            return registrar\n        }\n    }\n\n    actor ChannelRegistrationPayloadProvider {\n        private var payload: ChannelRegistrationPayload\n\n        init(deviceModel: String? = nil, appVersion: String? = nil) {\n            var payload = ChannelRegistrationPayload()\n            payload.channel.deviceModel = deviceModel ?? UUID().uuidString\n            payload.channel.appVersion = appVersion ?? \"test\"\n\n            self.payload = payload\n        }\n\n        func updatePayload(update: @Sendable @escaping(inout ChannelRegistrationPayload) -> Void ) {\n            update(&payload)\n        }\n\n        func getPayload() -> ChannelRegistrationPayload {\n            payload\n        }\n    }\n\n    @Test(\"Register\")\n    func register() async throws {\n        #expect(self.workManager.workRequests.count == 0)\n\n        self.channelRegistrar.register(forcefully: false)\n\n        #expect(self.workManager.workRequests.count == 1)\n\n        let extras = [\"forcefully\": \"false\"]\n\n        let request = self.workManager.workRequests[0]\n        #expect(request.workID == workID)\n        #expect(request.conflictPolicy == .keepIfNotStarted)\n        #expect(request.extras == extras)\n        #expect(request.initialDelay == 0)\n    }\n\n    @Test(\"Register forcefully\")\n    func registerForcefully() async throws {\n        #expect(self.workManager.workRequests.count == 0)\n\n        self.channelRegistrar.register(forcefully: true)\n\n        #expect(self.workManager.workRequests.count == 1)\n\n        let extras = [\"forcefully\": \"true\"]\n\n        let request = self.workManager.workRequests[0]\n        #expect(request.workID == workID)\n        #expect(request.conflictPolicy == .replace)\n        #expect(request.extras == extras)\n        #expect(request.initialDelay == 0)\n    }\n\n    @Test(\"Create channel\")\n    func createChannel() async throws {\n        var stream = await self.channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let payload = await payloadProvider.getPayload()\n\n        await MainActor.run {\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        self.client.createCallback =  { channelPayload in\n            #expect(channelPayload == payload)\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: \"some-channel-id\",\n                    location: try self.client.makeChannelLocation(\n                        channelID: \"some-channel-id\"\n                    )\n                ),\n                statusCode: 201,\n                headers: [:])\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .success)\n\n        let update = await stream.next()\n        #expect(update == .created(channelID: \"some-channel-id\", isExisting: false))\n    }\n\n    @Test(\"Create channel restores\")\n    func createChannelRestores() async throws {\n        let restoredUUID = UUID().uuidString\n\n        let channelRegistrar = await MainActor.run {\n            ChannelRegistrar(\n                dataStore: self.dataStore,\n                channelAPIClient: self.client,\n                date: self.date,\n                workManager: self.workManager,\n                appStateTracker: self.appStateTracker,\n                channelCreateMethod: { return .restore(channelID: restoredUUID) },\n                privacyManager: TestPrivacyManager(\n                    dataStore: self.dataStore,\n                    config: RuntimeConfig.testConfig(),\n                    defaultEnabledFeatures: AirshipFeature.all\n                )\n            )\n        }\n\n        var stream = await channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let payload = await payloadProvider.getPayload()\n\n        await MainActor.run {\n            channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        self.client.createCallback =  { channelPayload in\n            Issue.record(\"Should not create\")\n            throw AirshipErrors.error(\"\")\n        }\n\n        self.client.updateCallback = { channelID, channelPayload in\n            #expect(restoredUUID == channelID)\n            #expect(channelPayload.channel.deviceModel == nil) // minimized\n\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: restoredUUID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: restoredUUID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        var update = await stream.next()\n        #expect(update == .created(channelID: restoredUUID, isExisting: true))\n\n        update = await stream.next()\n        #expect(update == .updated(channelID: restoredUUID))\n\n        #expect(result == .success)\n    }\n\n    @Test(\"Restore fall back to create on invalid ID\")\n    func restoreFallBackToCreateOnInvalidID() async throws {\n        let channelRegistrar = await MainActor.run {\n            ChannelRegistrar(\n                dataStore: self.dataStore,\n                channelAPIClient: self.client,\n                date: self.date,\n                workManager: self.workManager,\n                appStateTracker: self.appStateTracker,\n                channelCreateMethod: { return .restore(channelID: \"invalid-uuid\") },\n                privacyManager: TestPrivacyManager(\n                    dataStore: self.dataStore,\n                    config: RuntimeConfig.testConfig(),\n                    defaultEnabledFeatures: AirshipFeature.all\n                )\n            )\n        }\n\n        let payload = await payloadProvider.getPayload()\n\n        await MainActor.run {\n            channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        try await confirmation { confirm in\n            self.client.createCallback =  { channelPayload in\n                #expect(channelPayload == payload)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: ChannelAPIResponse(\n                        channelID: \"some-channel-id\",\n                        location: try self.client.makeChannelLocation(\n                            channelID: \"some-channel-id\"\n                        )\n                    ),\n                    statusCode: 201,\n                    headers: [:])\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(workID: workID)\n            )\n\n            #expect(result == .success)\n        }\n    }\n\n    @Test(\"Create channel existing\")\n    func createChannelExisting() async throws {\n        var stream = await self.channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let payload = await payloadProvider.getPayload()\n\n        self.client.createCallback =  { channelPayload in\n            #expect(channelPayload == payload)\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: \"some-channel-id\",\n                    location: try self.client.makeChannelLocation(\n                        channelID: \"some-channel-id\"\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n        #expect(result == .success)\n\n        let update = await stream.next()\n        #expect(update == .created(channelID: \"some-channel-id\", isExisting: true))\n    }\n\n    @Test(\"Create channel error\")\n    func createChannelError() async throws {\n        self.client.createCallback =  { channelPayload in\n            throw AirshipErrors.error(\"Some error\")\n        }\n\n        do {\n            _ = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(workID: workID)\n            )\n            Issue.record(\"Should throw\")\n        } catch {\n\n        }\n    }\n\n    @Test(\"Create channel server error\")\n    func createChannelServerError() async throws {\n        self.client.createCallback =  { channelPayload in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 500,\n                headers: [:])\n        }\n\n\n        let payload = await payloadProvider.getPayload()\n        await MainActor.run {\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Create channel client error\")\n    func createChannelClientError() async throws {\n        self.client.createCallback =  { channelPayload in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let _ = await payloadProvider.getPayload()\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .success)\n    }\n\n    @Test(\"Update not configured\")\n    func updateNotConfigured() async {\n        self.client.isURLConfigured = false\n        self.channelRegistrar.register(forcefully: true)\n        self.channelRegistrar.register(forcefully: false)\n        #expect(self.workManager.workRequests.count == 0)\n\n        self.client.isURLConfigured = true\n\n        self.channelRegistrar.register(forcefully: true)\n        self.channelRegistrar.register(forcefully: false)\n        #expect(self.workManager.workRequests.count == 2)\n    }\n\n    @Test(\"Create channel 429 error\")\n    func createChannel429Error() async throws {\n        self.client.createCallback =  { channelPayload in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 429,\n                headers: [:])\n        }\n\n        let payload = await payloadProvider.getPayload()\n        await MainActor.run {\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Update channel\")\n    func updateChannel() async throws {\n        var stream = await self.channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let someChannelID = UUID().uuidString\n\n        try await createChannel(channelID: someChannelID)\n\n        await payloadProvider.updatePayload { payload in\n            payload.channel.deviceModel = UUID().uuidString\n        }\n\n        let payload = await payloadProvider.getPayload()\n\n        self.client.updateCallback = { channelID, channelPayload in\n            #expect(someChannelID == channelID)\n            #expect(\n                channelPayload.channel.deviceModel ==\n                payload.channel.deviceModel\n            )\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n        #expect(result == .success)\n\n        var update = await stream.next()\n        #expect(update == .created(channelID: someChannelID, isExisting: true))\n        update = await stream.next()\n        #expect(update == .updated(channelID: someChannelID))\n    }\n\n    @Test(\"Update channel error\")\n    func updateChannelError() async throws {\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        await payloadProvider.updatePayload { payload in\n            payload.channel.deviceModel = UUID().uuidString\n        }\n\n        self.client.updateCallback = { channelID, channelPayload in\n            throw AirshipErrors.error(\"Error!\")\n        }\n\n        do {\n            _ = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(workID: workID)\n            )\n            Issue.record(\"Should throw\")\n        } catch {}\n    }\n\n    @Test(\"Update channel server error\")\n    func updateChannelServerError() async throws {\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        await payloadProvider.updatePayload { payload in\n            payload.channel.deviceModel = UUID().uuidString\n        }\n\n        self.client.updateCallback = { channelID, channelPayload in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 500,\n                headers: [:])\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Update channel client error\")\n    func updateChannelClientError() async throws {\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        let payload = await payloadProvider.getPayload()\n\n        await MainActor.run {\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        self.client.updateCallback = { channelID, channelPayload in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:])\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .success)\n    }\n\n\n    @Test(\"Update channel 429 error\")\n    @MainActor\n    func updateChannel429Error() async throws {\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        await payloadProvider.updatePayload { payload in\n            payload.channel.deviceModel = UUID().uuidString\n        }\n\n        self.client.updateCallback = { channelID, channelPayload in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 429,\n                headers: [:])\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Skip update channel up to date\")\n    func skipUpdateChannelUpToDate() async throws {\n        let payload = await payloadProvider.getPayload()\n\n        await MainActor.run {\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return payload\n            }\n        }\n\n        let someChannelID = UUID().uuidString\n\n        // Create the channel\n        try await createChannel(channelID: someChannelID)\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .success)\n    }\n\n    @Test(\"Update forcefully\")\n    func updateForcefully() async throws {\n        var stream = await self.channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        self.client.updateCallback = { channelID, channelPayload in\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID,\n                extras: [\"forcefully\": \"true\"]\n            )\n        )\n\n        #expect(result == .success)\n\n        var update = await stream.next()\n        #expect(update == .created(channelID: someChannelID, isExisting: true))\n        update = await stream.next()\n        #expect(update == .updated(channelID: someChannelID))\n    }\n\n    @Test(\"Update location changed\")\n    func updateLocationChanged() async throws {\n        var stream = await self.channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let someChannelID = UUID().uuidString\n\n        let payload = await payloadProvider.getPayload()\n\n        try await createChannel(channelID: someChannelID)\n\n        self.client.channelLocation = { _ in\n            return URL(string: \"some:otherlocation\")!\n        }\n\n        self.client.updateCallback = { channelID, channelPayload in\n            #expect(payload == channelPayload)\n            #expect(payload.minimizePayload(previous: payload) != channelPayload)\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n        #expect(result == .success)\n\n        var update = await stream.next()\n        #expect(update == .created(channelID: someChannelID, isExisting: true))\n        update = await stream.next()\n        #expect(update == .updated(channelID: someChannelID))\n    }\n\n    @Test(\"Update min payload\")\n    func updateMinPayload() async throws {\n        var stream = await self.channelRegistrar.registrationUpdates.makeStream().makeAsyncIterator()\n\n        let someChannelID = UUID().uuidString\n\n        let firstPayload = await self.payloadProvider.getPayload()\n\n        try await createChannel(channelID: someChannelID)\n\n        await self.payloadProvider.updatePayload { payload in\n            payload.channel.deviceModel = UUID().uuidString\n        }\n\n        let secondPayload = await payloadProvider.getPayload()\n\n        self.client.updateCallback = { channelID, channelPayload in\n            #expect(\n                secondPayload.minimizePayload(\n                    previous: firstPayload\n                ) ==\n                channelPayload\n            )\n            #expect(secondPayload != channelPayload)\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        #expect(result == .success)\n\n        var update = await stream.next()\n        #expect(update == .created(channelID: someChannelID, isExisting: true))\n        update = await stream.next()\n        #expect(update == .updated(channelID: someChannelID))\n    }\n\n    @Test(\"Update after 24 hours\")\n    @MainActor\n    func updateAfter24Hours() async throws {\n        self.appStateTracker.currentState = .active\n        self.date.dateOverride = Date()\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        var updateCount = 0\n        self.client.updateCallback = { channelID, channelPayload in\n            updateCount += 1\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n        #expect(updateCount == 0)\n\n        // Forward to almost 1 second before 24 hours\n        self.date.offset = 24 * 60 * 60 - 1\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n        #expect(updateCount == 0)\n\n\n        // 24 hours\n        self.date.offset += 1\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        #expect(updateCount == 1)\n    }\n\n    @Test(\"Full payload upload after 24 hours\")\n    @MainActor\n    func fullPayloadUploadAfter24Hours() async throws {\n        self.appStateTracker.currentState = .active\n        self.date.dateOverride = Date()\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        let payload = await payloadProvider.getPayload()\n\n        var updatePayload: ChannelRegistrationPayload? = nil\n        self.client.updateCallback = { channelID, channelPayload in\n            updatePayload = channelPayload\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        #expect(updatePayload == nil)\n\n        self.date.offset = 24 * 60 * 60 - 1\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        #expect(updatePayload == nil)\n\n        // 24 hours\n        self.date.offset += 2\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        #expect(payload == updatePayload)\n    }\n\n    @Test(\"Empty last full registration\")\n    @MainActor\n    func emptyLastFullRegistration() async throws {\n        self.appStateTracker.currentState = .active\n        self.date.dateOverride = Date()\n        let someChannelID = UUID().uuidString\n        try await createChannel(channelID: someChannelID)\n\n        var registrationInfo: LastRegistrationInfo = self.dataStore.safeCodable(forKey: \"ChannelRegistrar.lastRegistrationInfo\")!\n        registrationInfo.lastFullPayloadSent = nil\n        self.dataStore.setSafeCodable(registrationInfo, forKey: \"ChannelRegistrar.lastRegistrationInfo\")\n\n        let payload = await payloadProvider.getPayload()\n\n        var updatePayload: ChannelRegistrationPayload? = nil\n        self.client.updateCallback = { channelID, channelPayload in\n            updatePayload = channelPayload\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: someChannelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: someChannelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        #expect(payload == updatePayload)\n\n       updatePayload = nil\n        _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: workID\n            )\n        )\n\n        // No update\n        #expect(updatePayload == nil)\n    }\n\n    private func createChannel(channelID: String) async throws {\n        // Set a payload since the create flow now requires one\n        let _ = await payloadProvider.getPayload()\n\n        self.client.createCallback = { _ in\n            return AirshipHTTPResponse(\n                result: ChannelAPIResponse(\n                    channelID: channelID,\n                    location: try self.client.makeChannelLocation(\n                        channelID: channelID\n                    )\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(workID: workID)\n        )\n\n        #expect(result == .success)\n    }\n}\n\nfileprivate struct LastRegistrationInfo: Codable {\n    var date: Date\n    var payload: ChannelRegistrationPayload\n    var lastFullPayloadSent: Date?\n    var location: URL\n}\n\ninternal class TestChannelRegistrationClient: ChannelAPIClientProtocol, @unchecked Sendable {\n    var isURLConfigured: Bool = true\n\n    var createCallback:((ChannelRegistrationPayload) async throws -> AirshipHTTPResponse<ChannelAPIResponse>)?\n    var updateCallback:\n    ((String, ChannelRegistrationPayload) async throws -> AirshipHTTPResponse<ChannelAPIResponse>)?\n\n    var channelLocation: ((String) throws -> URL)?\n\n    func makeChannelLocation(channelID: String) throws -> URL {\n        guard let channelLocation = channelLocation else {\n            return URL(string: \"channel:\\(channelID)\")!\n        }\n\n        return try channelLocation(channelID)\n    }\n\n    func createChannel(\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipHTTPResponse<ChannelAPIResponse> {\n        return try await createCallback!(payload)\n    }\n\n    func updateChannel(\n        _ channelID: String,\n        payload: ChannelRegistrationPayload\n    ) async throws -> AirshipHTTPResponse<ChannelAPIResponse> {\n        return try await updateCallback!(channelID, payload)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelRegistrationPayloadTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ChannelRegistrationPayloadTest: XCTestCase {\n\n    private lazy var fullPayload: ChannelRegistrationPayload = {\n        let quietTime = ChannelRegistrationPayload.QuietTime(\n            start: \"16:00\",\n            end: \"16:01\"\n        )\n        var fullPayload = ChannelRegistrationPayload()\n        fullPayload.identityHints = ChannelRegistrationPayload.IdentityHints()\n        fullPayload.channel.iOSChannelSettings = ChannelRegistrationPayload.iOSChannelSettings()\n\n        // set up the full payload\n        fullPayload.channel.isOptedIn = true\n        fullPayload.channel.isBackgroundEnabled = true\n        fullPayload.channel.pushAddress = \"FAKEADDRESS\"\n        fullPayload.identityHints?.userID = \"fakeUser\"\n        fullPayload.channel.contactID = \"some-contact-id\"\n        fullPayload.channel.iOSChannelSettings?.badge = 1\n        fullPayload.channel.iOSChannelSettings?.quietTime = quietTime\n        fullPayload.channel.iOSChannelSettings?.quietTimeTimeZone = \"quietTimeTimeZone\"\n        fullPayload.channel.timeZone = \"timezone\"\n        fullPayload.channel.language = \"language\"\n        fullPayload.channel.country = \"country\"\n        fullPayload.channel.tags = [\"tagOne\", \"tagTwo\"]\n        fullPayload.channel.setTags = true\n        fullPayload.channel.sdkVersion = \"SDKVersion\"\n        fullPayload.channel.appVersion = \"appVersion\"\n        fullPayload.channel.deviceModel = \"deviceModel\"\n        fullPayload.channel.deviceOS = \"deviceOS\"\n\n        return fullPayload\n    }()\n\n    func testMinimalUpdatePayloadSameValues() {\n        let minPayload = self.fullPayload.minimizePayload(\n            previous: self.fullPayload\n        )\n\n        var expected = self.fullPayload\n        expected.channel.appVersion = nil\n        expected.channel.deviceModel = nil\n        expected.channel.setTags = false\n        expected.channel.tags = nil\n        expected.channel.country = nil\n        expected.channel.language = nil\n        expected.channel.deviceOS = nil\n        expected.channel.timeZone = nil\n        expected.channel.sdkVersion = nil\n        expected.identityHints = nil\n        expected.channel.iOSChannelSettings?.isTimeSensitive = nil\n        expected.channel.iOSChannelSettings?.isScheduledSummary = nil\n\n        XCTAssertEqual(expected, minPayload)\n    }\n\n    func testMinimalUpdateDifferentValues() {\n        var otherPayload = self.fullPayload\n        otherPayload.channel.appVersion = UUID().uuidString\n        otherPayload.channel.deviceModel = UUID().uuidString\n        otherPayload.channel.tags = [\"some other tag\"]\n        otherPayload.channel.country = UUID().uuidString\n        otherPayload.channel.language = UUID().uuidString\n        otherPayload.channel.deviceOS = UUID().uuidString\n        otherPayload.channel.timeZone = UUID().uuidString\n        otherPayload.channel.sdkVersion = UUID().uuidString\n        otherPayload.identityHints?.userID = UUID().uuidString\n        otherPayload.channel.iOSChannelSettings?.isTimeSensitive = true\n        otherPayload.channel.iOSChannelSettings?.isScheduledSummary = true\n\n        let minPayload = otherPayload.minimizePayload(\n            previous: self.fullPayload\n        )\n\n        var expected = otherPayload\n        expected.identityHints = nil\n        expected.channel.tagChanges = ChannelRegistrationPayload.TagChanges(\n            adds: otherPayload.channel.tags!,\n            removes: fullPayload.channel.tags!\n        )\n\n        XCTAssertEqual(expected, minPayload)\n    }\n\n    func testMinimalUpdateDifferentContact() {\n        var otherPayload = self.fullPayload\n        otherPayload.channel.contactID = UUID().uuidString\n\n        let minPayload = otherPayload.minimizePayload(\n            previous: self.fullPayload\n        )\n\n        var expected = otherPayload\n        expected.channel.setTags = false\n        expected.channel.tags = nil\n        expected.identityHints = nil\n        expected.channel.iOSChannelSettings?.isTimeSensitive = nil\n        expected.channel.iOSChannelSettings?.isScheduledSummary = nil\n\n        XCTAssertEqual(expected, minPayload)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ChannelTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable import AirshipCore\nimport UIKit\n\n@Suite(.timeLimit(.minutes(1)))\nstruct ChannelTest {\n    let channelRegistrar: TestChannelRegistrar\n    let localeManager: TestLocaleManager\n    let audienceManager: TestChannelAudienceManager\n    let appStateTracker: TestAppStateTracker\n    let notificationCenter: AirshipNotificationCenter\n    let dataStore: PreferenceDataStore\n    let config: AirshipConfig\n    let privacyManager: TestPrivacyManager\n    let permissionsManager: DefaultAirshipPermissionsManager\n    let channel: DefaultAirshipChannel\n\n    // Helper to wait for async conditions with timeout\n    private func waitForCondition(\n        timeout: Duration = .seconds(2),\n        pollingInterval: Duration = .milliseconds(10),\n        condition: @escaping () async -> Bool\n    ) async throws {\n        let deadline = ContinuousClock.now + timeout\n        while ContinuousClock.now < deadline {\n            if await condition() { return }\n            try await Task.sleep(for: pollingInterval)\n        }\n        throw NSError(domain: \"TestTimeout\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"Condition not met within timeout\"])\n    }\n\n    init() async throws {\n        self.channelRegistrar = TestChannelRegistrar()\n        self.localeManager = TestLocaleManager()\n        self.audienceManager = TestChannelAudienceManager()\n        self.appStateTracker = TestAppStateTracker()\n        self.notificationCenter = AirshipNotificationCenter(notificationCenter: NotificationCenter())\n        self.dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        self.config = AirshipConfig()\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: RuntimeConfig.testConfig(),\n            defaultEnabledFeatures: [],\n            notificationCenter: self.notificationCenter\n        )\n        self.permissionsManager = await DefaultAirshipPermissionsManager()\n        self.channel = await Self.createChannel(\n            dataStore: self.dataStore,\n            config: self.config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceManager: self.audienceManager,\n            channelRegistrar: self.channelRegistrar,\n            notificationCenter: self.notificationCenter,\n            appStateTracker: self.appStateTracker\n        )\n    }\n\n    @MainActor\n    private static func createChannel(\n        dataStore: PreferenceDataStore,\n        config: AirshipConfig,\n        privacyManager: TestPrivacyManager,\n        permissionsManager: DefaultAirshipPermissionsManager,\n        localeManager: TestLocaleManager,\n        audienceManager: TestChannelAudienceManager,\n        channelRegistrar: TestChannelRegistrar,\n        notificationCenter: AirshipNotificationCenter,\n        appStateTracker: TestAppStateTracker\n    ) -> DefaultAirshipChannel {\n        return DefaultAirshipChannel(\n            dataStore: dataStore,\n            config: RuntimeConfig.testConfig(airshipConfig: config),\n            privacyManager: privacyManager,\n            permissionsManager: permissionsManager,\n            localeManager: localeManager,\n            audienceManager: audienceManager,\n            channelRegistrar: channelRegistrar,\n            notificationCenter: notificationCenter,\n            appStateTracker: appStateTracker\n        )\n    }\n\n    @Test(\"Registration feature enabled\")\n    @MainActor\n    func registrationFeatureEnabled() async throws {\n        #expect(!self.channelRegistrar.registerCalled)\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n        // Allow notification to propagate to observers\n        try await Task.sleep(for: .milliseconds(100))\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Tags\")\n    func tags() throws {\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n\n        self.channelRegistrar.registerCalled = false\n        self.channel.tags = [\"foo\", \"bar\"]\n\n        #expect(self.channel.tags == [\"foo\", \"bar\"])\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Edit tags\")\n    func editTags() throws {\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n\n        self.channelRegistrar.registerCalled = false\n\n        self.channel.editTags { editor in\n            editor.add([\"foo\", \"bar\"])\n            editor.remove([\"foo\"])\n            editor.add([\"baz\"])\n        }\n\n        #expect(self.channel.tags == [\"bar\", \"baz\"])\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Tags disabled\")\n    func tagsDisabled() throws {\n        self.privacyManager.disableFeatures(.tagsAndAttributes)\n        self.channelRegistrar.registerCalled = false\n\n        self.channel.tags = [\"neat\"]\n        self.channel.editTags { editor in\n            editor.add([\"foo\", \"bar\"])\n        }\n\n        #expect(self.channel.tags == [])\n        #expect(!self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Clear tags privacy manager disabled\")\n    func clearTagsPrivacyManagerDisabled() throws {\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n        self.channel.tags = [\"neat\"]\n        self.privacyManager.disableFeatures(.tagsAndAttributes)\n        #expect(self.channel.tags == [])\n    }\n\n    @Test(\"Normalize tags\")\n    func normalizeTags() throws {\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n\n        self.channel.tags = [\n            \"함\",\n            \"함수 목록\",\n            \" neat \",\n            \"1\",\n            \"  \",\n            \"\",\n            String(repeating: \"함\", count: 128),\n            String(repeating: \"g\", count: 128),\n            String(repeating: \"b\", count: 129),\n        ]\n\n        let expected = [\n            \"함\",\n            \"함수 목록\",\n            \"neat\",\n            \"1\",\n            String(repeating: \"함\", count: 128),\n            String(repeating: \"g\", count: 128),\n        ]\n\n        #expect(self.channel.tags == expected)\n    }\n\n    @Test(\"Channel creation flag disabled\")\n    @MainActor\n    func channelCreationFlagDisabled() async throws {\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n        var config = self.config\n        config.isChannelCreationDelayEnabled = true\n        self.channelRegistrar.registerCalled = false\n\n        _ = Self.createChannel(\n            dataStore: self.dataStore,\n            config: config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceManager: self.audienceManager,\n            channelRegistrar: self.channelRegistrar,\n            notificationCenter: self.notificationCenter,\n            appStateTracker: self.appStateTracker\n        )\n        #expect(!self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Enable channel creation\")\n    func enableChannelCreation() async throws {\n        self.privacyManager.enableFeatures(.tagsAndAttributes)\n        var config = self.config\n        config.isChannelCreationDelayEnabled = true\n        self.channelRegistrar.registerCalled = false\n\n        let channel = await Self.createChannel(\n            dataStore: self.dataStore,\n            config: config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceManager: self.audienceManager,\n            channelRegistrar: self.channelRegistrar,\n            notificationCenter: self.notificationCenter,\n            appStateTracker: self.appStateTracker\n        )\n        channel.enableChannelCreation()\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"CRA payload\")\n    @MainActor\n    func craPayload() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        let locationPermission = TestPermissionsDelegate()\n        locationPermission.permissionStatus = .granted\n\n        let notificationPermission = TestPermissionsDelegate()\n        notificationPermission.permissionStatus = .denied\n\n        self.permissionsManager.setDelegate(locationPermission, permission: .location)\n        self.permissionsManager.setDelegate(notificationPermission, permission: .displayNotifications)\n\n        self.channel.tags = [\"foo\", \"bar\"]\n        var expectedPayload = ChannelRegistrationPayload()\n        expectedPayload.channel.language =\n        Locale.autoupdatingCurrent.getLanguageCode()\n        expectedPayload.channel.country = Locale.autoupdatingCurrent.region?.identifier\n        expectedPayload.channel.timeZone =\n        TimeZone.autoupdatingCurrent.identifier\n        expectedPayload.channel.tags = [\"foo\", \"bar\"]\n        expectedPayload.channel.appVersion = AirshipUtils.bundleShortVersionString()\n        expectedPayload.channel.sdkVersion = AirshipVersion.version\n        expectedPayload.channel.deviceOS = UIDevice.current.systemVersion\n        expectedPayload.channel.deviceModel = AirshipDevice.modelIdentifier\n        expectedPayload.channel.setTags = true\n        expectedPayload.channel.permissions = [\n            \"location\": \"granted\",\n            \"display_notifications\": \"denied\"\n        ]\n\n        await MainActor.run { [expectedPayload] in\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return expectedPayload\n            }\n        }\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(expectedPayload == payload)\n    }\n\n    @Test(\"CRA payload permission on no feature\")\n    @MainActor\n    func craPayloadPermissionOnNoFeature() async throws {\n        self.privacyManager.enableFeatures(.all)\n        self.privacyManager.disableFeatures(.tagsAndAttributes)\n\n        let locationPermission = TestPermissionsDelegate()\n        locationPermission.permissionStatus = .granted\n\n        let notificationPermission = TestPermissionsDelegate()\n        notificationPermission.permissionStatus = .denied\n\n        self.permissionsManager.setDelegate(locationPermission, permission: .location)\n        self.permissionsManager.setDelegate(notificationPermission, permission: .displayNotifications)\n\n        self.channel.tags = [\"foo\", \"bar\"]\n        var expectedPayload = ChannelRegistrationPayload()\n        expectedPayload.channel.language =\n        Locale.autoupdatingCurrent.getLanguageCode()\n        expectedPayload.channel.country = Locale.autoupdatingCurrent.region?.identifier\n        expectedPayload.channel.timeZone =\n        TimeZone.autoupdatingCurrent.identifier\n        expectedPayload.channel.tags = []\n        expectedPayload.channel.appVersion = AirshipUtils.bundleShortVersionString()\n        expectedPayload.channel.sdkVersion = AirshipVersion.version\n        expectedPayload.channel.deviceOS = UIDevice.current.systemVersion\n        expectedPayload.channel.deviceModel = AirshipDevice.modelIdentifier\n        expectedPayload.channel.setTags = true\n        expectedPayload.channel.permissions = nil\n\n        await MainActor.run { [expectedPayload] in\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return expectedPayload\n            }\n        }\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(expectedPayload == payload)\n    }\n\n    @Test(\"CRA payload minify\")\n    @MainActor\n    func craPayloadMinify() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        let locationPermission = TestPermissionsDelegate()\n        locationPermission.permissionStatus = .granted\n\n        let notificationPermission = TestPermissionsDelegate()\n        notificationPermission.permissionStatus = .denied\n\n        self.permissionsManager.setDelegate(locationPermission, permission: .location)\n        self.permissionsManager.setDelegate(notificationPermission, permission: .displayNotifications)\n\n        self.channel.tags = [\"foo\", \"bar\"]\n        var expectedPayload = ChannelRegistrationPayload()\n        expectedPayload.channel.language =\n        Locale.autoupdatingCurrent.getLanguageCode()\n        expectedPayload.channel.country = Locale.autoupdatingCurrent.region?.identifier\n        expectedPayload.channel.timeZone =\n        TimeZone.autoupdatingCurrent.identifier\n        expectedPayload.channel.tags = [\"foo\", \"bar\"]\n        expectedPayload.channel.appVersion = AirshipUtils.bundleShortVersionString()\n        expectedPayload.channel.sdkVersion = AirshipVersion.version\n        expectedPayload.channel.deviceOS = UIDevice.current.systemVersion\n        expectedPayload.channel.deviceModel = AirshipDevice.modelIdentifier\n        expectedPayload.channel.setTags = true\n        expectedPayload.channel.permissions = [\n            \"location\": \"granted\",\n            \"display_notifications\": \"denied\"\n        ]\n\n        await MainActor.run { [expectedPayload] in\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return expectedPayload\n            }\n        }\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(expectedPayload == payload)\n\n        notificationPermission.permissionStatus = .granted\n\n        var expectedMinimized = ChannelRegistrationPayload()\n        expectedMinimized.channel.permissions = [\n            \"display_notifications\": \"granted\",\n            \"location\": \"granted\",\n        ]\n\n        await MainActor.run { [expectedMinimized] in\n            self.channelRegistrar.payloadCreateBlock = { @Sendable () async -> ChannelRegistrationPayload? in\n                return expectedMinimized\n            }\n        }\n\n        let minimized = await self.channelRegistrar.channelPayload.minimizePayload(previous: payload)\n\n        await MainActor.run { [expectedMinimized] in\n            #expect(expectedMinimized == minimized)\n        }\n    }\n\n    @Test(\"CRA payload disabled device tags\")\n    func craPayloadDisabledDeviceTags() async throws {\n        self.privacyManager.enableFeatures(.all)\n        self.channel.isChannelTagRegistrationEnabled = false\n        self.channel.tags = [\"foo\", \"bar\"]\n\n        var expectedPayload = ChannelRegistrationPayload()\n        expectedPayload.channel.language =\n        Locale.autoupdatingCurrent.getLanguageCode()\n        expectedPayload.channel.country = Locale.autoupdatingCurrent.region?.identifier\n        expectedPayload.channel.timeZone =\n        TimeZone.autoupdatingCurrent.identifier\n        expectedPayload.channel.appVersion = AirshipUtils.bundleShortVersionString()\n        expectedPayload.channel.sdkVersion = AirshipVersion.version\n        expectedPayload.channel.deviceOS = await AirshipDevice.osVersion\n        expectedPayload.channel.deviceModel = AirshipDevice.modelIdentifier\n        expectedPayload.channel.setTags = false\n        expectedPayload.channel.permissions = [:]\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(expectedPayload == payload)\n    }\n\n    @Test(\"CRA payload privacy manager disabled\")\n    func craPayloadPrivacyManagerDisabled() async throws {\n        var expectedPayload = ChannelRegistrationPayload()\n        expectedPayload.channel.setTags = true\n        expectedPayload.channel.tags = []\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(expectedPayload == payload)\n    }\n\n    @Test(\"Extending CRA payload\")\n    func extendingCRAPayload() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        await self.channel.addRegistrationExtender { payload in\n            payload.channel.pushAddress = \"WHAT\"\n        }\n\n        await self.channel.addRegistrationExtender { payload in\n            payload.channel.pushAddress = \"OK\"\n        }\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(payload.channel.pushAddress == \"OK\")\n    }\n\n    @Test(\"Application did transition to foreground\")\n    func applicationDidTransitionToForeground() throws {\n        self.privacyManager.enableFeatures(.all)\n        self.channelRegistrar.registerCalled = false\n\n        self.notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground,\n            object: nil\n        )\n\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Existing channel created notification\")\n    @MainActor\n    func existingChannelCreatedNotification() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        // Send the registration update\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someChannelID\",\n                isExisting: true\n            )\n        )\n\n        // Wait for the async Task to process the update and update audience manager\n        try await waitForCondition {\n            self.audienceManager.channelID == \"someChannelID\"\n        }\n\n        // Verify the channel ID was set correctly\n        #expect(self.audienceManager.channelID == \"someChannelID\")\n    }\n\n    @Test(\"New channel created notification\")\n    @MainActor\n    func newChannelCreatedNotification() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        // Send the registration update for a new channel\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someChannelID\",\n                isExisting: false\n            )\n        )\n\n        // Wait for the async Task to process the update and update audience manager\n        try await waitForCondition {\n            self.audienceManager.channelID == \"someChannelID\"\n        }\n\n        // Verify the channel ID was set correctly\n        #expect(self.audienceManager.channelID == \"someChannelID\")\n    }\n\n    @Test(\"Identifier updates\")\n    @MainActor\n    func identifierUpdates() async throws {\n        var updates = self.channel.identifierUpdates.makeAsyncIterator()\n\n        self.privacyManager.enableFeatures(.all)\n\n        // Yield to ensure async stream is set up\n        await Task.yield()\n\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someChannelID\",\n                isExisting: false\n            )\n        )\n\n        // Yield between sends to ensure ordering\n        await Task.yield()\n\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someOtherChannelID\",\n                isExisting: false\n            )\n        )\n\n        var value = await updates.next()\n        #expect(value == \"someChannelID\")\n        value = await updates.next()\n        #expect(value == \"someOtherChannelID\")\n    }\n\n    @Test(\"Identifier updates deduping\")\n    @MainActor\n    func identifierUpdatesDeduping() async throws {\n        self.channelRegistrar.channelID = \"someChannelID\"\n\n        var updates = self.channel.identifierUpdates.makeAsyncIterator()\n\n        self.privacyManager.enableFeatures(.all)\n\n\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someChannelID\",\n                isExisting: false\n            )\n        )\n\n\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someChannelID\",\n                isExisting: false\n            )\n        )\n\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someChannelID\",\n                isExisting: false\n            )\n        )\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someOtherChannelID\",\n                isExisting: false\n            )\n        )\n\n        var value = await updates.next()\n        #expect(value == \"someChannelID\")\n        value = await updates.next()\n        #expect(value == \"someOtherChannelID\")\n    }\n\n    @Test(\"Identifier update already created\")\n    func identifierUpdateAlreadyCreated() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        self.channelRegistrar.channelID = \"someChannelID\"\n        var updates = self.channel.identifierUpdates.makeAsyncIterator()\n\n\n        var value = await updates.next()\n        #expect(value == \"someChannelID\")\n\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"someOtherChannelID\",\n                isExisting: false\n            )\n        )\n        value = await updates.next()\n        #expect(value == \"someOtherChannelID\")\n    }\n\n\n    @Test(\"Created identifier passed to audience manager\")\n    @MainActor\n    func createdIdentifierPassedToAudienceManager() async throws {\n        // Send the registration update\n        await self.channelRegistrar.registrationUpdates.send(\n            .created(\n                channelID: \"foo\",\n                isExisting: true\n            )\n        )\n\n        // Wait for the async Task to process the update and pass ID to audience manager\n        try await waitForCondition {\n            self.audienceManager.channelID == \"foo\"\n        }\n\n        // Verify the audience manager received the channel ID\n        #expect(self.audienceManager.channelID == \"foo\")\n    }\n\n    @Test(\"Initial identifier passed to audience manager\")\n    func initialIdentifierPassedToAudienceManager() async throws {\n        self.channelRegistrar.channelID = \"foo\"\n        _ = await Self.createChannel(\n            dataStore: self.dataStore,\n            config: self.config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceManager: self.audienceManager,\n            channelRegistrar: self.channelRegistrar,\n            notificationCenter: self.notificationCenter,\n            appStateTracker: self.appStateTracker\n        )\n        #expect(self.audienceManager.channelID == \"foo\")\n    }\n\n    @Test(\"Locale updated\")\n    func localeUpdated() throws {\n        self.privacyManager.enableFeatures(.all)\n        self.channelRegistrar.registerCalled = false\n\n        self.notificationCenter.post(\n            name: AirshipNotifications.LocaleUpdated.name,\n            object: nil\n        )\n\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Config update\")\n    func configUpdate() throws {\n        self.channelRegistrar.channelID = \"foo\"\n        self.privacyManager.enableFeatures(.all)\n        self.channelRegistrar.registerCalled = false\n\n        self.notificationCenter.post(\n            name: RuntimeConfig.configUpdatedEvent,\n            object: nil\n        )\n\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Config update no channel ID\")\n    func configUpdateNoChannelID() throws {\n        self.channelRegistrar.channelID = nil\n        self.privacyManager.enableFeatures(.all)\n        self.channelRegistrar.registerCalled = false\n\n        self.notificationCenter.post(\n            name: RuntimeConfig.configUpdatedEvent,\n            object: nil\n        )\n\n        #expect(self.channelRegistrar.registerCalled)\n    }\n\n    @Test(\"Migrate push tags to channel tags\")\n    func migratePushTagsToChannelTags() async throws {\n        self.privacyManager.enableFeatures(.all)\n\n        self.dataStore.setObject([\"cool\", \"rad\"], forKey: \"UAPushTags\")\n        let channel = await Self.createChannel(\n            dataStore: self.dataStore,\n            config: self.config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceManager: self.audienceManager,\n            channelRegistrar: self.channelRegistrar,\n            notificationCenter: self.notificationCenter,\n            appStateTracker: self.appStateTracker\n        )\n\n        #expect(channel.tags == [\"cool\", \"rad\"])\n    }\n\n    @Test(\"Migrate push tags to channel tags already migrated\")\n    func migratePushTagsToChannelTagsAlreadyMigrated() async throws {\n        self.privacyManager.enableFeatures(.all)\n        self.channel.tags = [\"some-random-value\"]\n\n        let channel = await Self.createChannel(\n            dataStore: self.dataStore,\n            config: self.config,\n            privacyManager: self.privacyManager,\n            permissionsManager: self.permissionsManager,\n            localeManager: self.localeManager,\n            audienceManager: self.audienceManager,\n            channelRegistrar: self.channelRegistrar,\n            notificationCenter: self.notificationCenter,\n            appStateTracker: self.appStateTracker\n        )\n        #expect(channel.tags == [\"some-random-value\"])\n    }\n\n    @Test(\"CRA payload is active flag in foreground\")\n    @MainActor\n    func craPayloadIsActiveFlagInForeground() async throws {\n        self.privacyManager.enableFeatures(.all)\n        self.appStateTracker.currentState = .active\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(payload.channel.isActive)\n    }\n\n    @Test(\"CRA payload is active flag in background\")\n    @MainActor\n    func craPayloadIsActiveFlagInBackground() async throws {\n        self.privacyManager.enableFeatures(.all)\n        self.appStateTracker.currentState = .background\n\n\n        let payload = await self.channelRegistrar.channelPayload\n        #expect(!payload.channel.isActive)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/CircularRegionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class CircularRegionTest: XCTestCase {\n    private var coordinates: (latitude: Double, longitude: Double) = (45.5200, 122.6819)\n    \n    /**\n     * Test creating a circular region with a valid radius\n     */\n    func testSetValidRadius() {\n        let region = CircularRegion(radius: 10, latitude: coordinates.latitude, longitude: coordinates.longitude)\n        XCTAssertNotNil(region)\n    }\n    \n    /**\n     * Test creating a circular region and adding an invalid radius\n     */\n    func testSetInvalidRadius() {\n        // test radius greater than max\n        var region = CircularRegion(radius: 100001, latitude: coordinates.latitude, longitude: coordinates.longitude)\n        XCTAssertNil(region, \"Circular region should be nil if radius fails to set.\")\n        \n        // test radius less than min\n        region = CircularRegion(radius: 0, latitude: coordinates.latitude, longitude: coordinates.longitude)\n        XCTAssertNil(region, \"Circular region should be nil if radius fails to set.\")\n    }\n    \n    /**\n     * Test creating a circular region and adding a valid latitude\n     */\n    func testSetValidLatitude() {\n        // test Portland's latitude\n        var circularRegion = CircularRegion(radius: 10, latitude: coordinates.latitude, longitude: coordinates.longitude)\n        XCTAssertNotNil(circularRegion)\n\n        // test latitude of 0 degrees\n        circularRegion = CircularRegion(radius: 10, latitude: 0, longitude: coordinates.longitude)\n        XCTAssertNotNil(circularRegion)\n    }\n    \n    /**\n     * Test creating a circular region and adding invalid latitudes\n     */\n    func testSetInvalidLatitude() {\n        // test latitude greater than max\n        var circularRegion = CircularRegion(radius: 10, latitude: 91, longitude: coordinates.longitude)\n        XCTAssertNil(circularRegion, \"Circular region should be nil if latitude fails to set.\")\n\n        // test latitude less than min\n        circularRegion = CircularRegion(radius: 10, latitude: -91, longitude: coordinates.longitude)\n        XCTAssertNil(circularRegion, \"Circular region should be nil if latitude fails to set.\")\n    }\n    \n    /**\n     * Test creating a circular region and adding a valid longitude\n     */\n    func testSetValidLongitude() {\n        // test Portland's longitude\n        var circularRegion = CircularRegion(radius: 10, latitude: coordinates.latitude, longitude: coordinates.longitude)\n        XCTAssertNotNil(circularRegion)\n\n        // test longitude of 0 degrees\n        circularRegion = CircularRegion(radius: 10, latitude: coordinates.latitude, longitude: 0)\n        XCTAssertNotNil(circularRegion)\n    }\n    \n    /**\n     * Test creating a circular region and adding invalid longitudes\n     */\n    func testSetInvalidLongitude() {\n        // test longitude greater than max\n        var circularRegion = CircularRegion(radius: 10, latitude: coordinates.latitude, longitude: 181)\n        XCTAssertNil(circularRegion)\n\n        // test longitude less than min\n        circularRegion = CircularRegion(radius: 10, latitude: coordinates.latitude, longitude: -181)\n        XCTAssertNil(circularRegion)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/CompoundDeviceAudienceSelectorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class CompoundDeviceAudienceSelectorTest: XCTestCase, @unchecked Sendable {\n\n    func testCombing() {\n        let selector = DeviceAudienceSelector(newUser: true)\n        let compound = CompoundDeviceAudienceSelector.atomic(DeviceAudienceSelector(newUser: false))\n        let combined = CompoundDeviceAudienceSelector.combine(compoundSelector: compound, deviceSelector: selector)\n\n        let expected = CompoundDeviceAudienceSelector.and(\n            [\n                .atomic(DeviceAudienceSelector(newUser: true)),\n                .atomic(DeviceAudienceSelector(newUser: false))\n            ]\n        )\n        XCTAssertEqual(expected, combined)\n    }\n    \n    func testParsing() throws {\n        [\n            (\n                CompoundDeviceAudienceSelector.atomic(DeviceAudienceSelector(newUser: true)),\n                \"{\\\"type\\\":\\\"atomic\\\", \\\"audience\\\":{\\\"new_user\\\":true}}\"\n            ),\n            (\n                CompoundDeviceAudienceSelector.not(.atomic(DeviceAudienceSelector(newUser: true))),\n                \"{\\\"type\\\":\\\"not\\\", \\\"selector\\\": {\\\"type\\\":\\\"atomic\\\", \\\"audience\\\":{\\\"new_user\\\":true}}}\"\n            ),\n            (\n                CompoundDeviceAudienceSelector.and([\n                    .atomic(DeviceAudienceSelector(newUser: true)),\n                    .atomic(DeviceAudienceSelector(newUser: false))]\n                ),\n                \"{\\\"type\\\":\\\"and\\\", \\\"selectors\\\": [{\\\"type\\\":\\\"atomic\\\", \\\"audience\\\":{\\\"new_user\\\":true}},{\\\"type\\\":\\\"atomic\\\", \\\"audience\\\":{\\\"new_user\\\":false}}]}\"\n            ),\n            (\n                CompoundDeviceAudienceSelector.and([]),\n                \"{\\\"type\\\":\\\"and\\\", \\\"selectors\\\": []}\"\n            ),\n            (\n                CompoundDeviceAudienceSelector.or([\n                    .atomic(DeviceAudienceSelector(newUser: true)),\n                    .atomic(DeviceAudienceSelector(newUser: false))]\n                ),\n                \"{\\\"type\\\":\\\"or\\\", \\\"selectors\\\": [{\\\"type\\\":\\\"atomic\\\", \\\"audience\\\":{\\\"new_user\\\":true}},{\\\"type\\\":\\\"atomic\\\", \\\"audience\\\":{\\\"new_user\\\":false}}]}\"\n            ),\n            (\n                CompoundDeviceAudienceSelector.or([]),\n                \"{\\\"type\\\":\\\"or\\\", \\\"selectors\\\": []}\"\n            ),\n            \n        ].forEach { (key, value) in\n            checkEqualRoundTrip(original: key, json: value)\n        }\n    }\n\n    private func checkEqualRoundTrip(original: CompoundDeviceAudienceSelector, json: String) {\n        let decoder = JSONDecoder()\n        let fromSource = try! decoder.decode(CompoundDeviceAudienceSelector.self, from: json.data(using: .utf8)!)\n\n        XCTAssertEqual(original, fromSource)\n        \n        let roundTrip = try! decoder.decode(CompoundDeviceAudienceSelector.self, from: try JSONEncoder().encode(fromSource))\n        XCTAssertEqual(original, roundTrip)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ContactAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ContactAPIClientTest: XCTestCase {\n\n    private let session: TestAirshipRequestSession = TestAirshipRequestSession()\n    private var contactAPIClient: ContactAPIClient!\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n    private let currentLocale = Locale(identifier: \"fr-CA\")\n\n    override func setUpWithError() throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://contacts_test\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        self.contactAPIClient = ContactAPIClient(\n            config: self.config,\n            session: self.session\n        )\n    }\n\n    func testIdentify() async throws {\n        self.session.data = \"\"\"\n            {\n              \"ok\": true,\n              \"contact\": {\n                \"contact_id\": \"1a32e8c7-5a73-47c0-9716-99fd3d41924b\",\n                \"is_anonymous\": true,\n                \"channel_association_timestamp\": \"2022-12-29T10:15:30.00\"\n              },\n              \"token\": \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJLSHVNTE15RmVmYjdoeXR3WkV5VTF4IiwiaWF0IjoxNjAyMDY4NDIxLCJleHAiOjE2MDIwNjg0MjEsInN1YiI6InVMa2hSaktBYzVXQW1SdTFPTFZSVncifQ.kJPu3enbLJMX10xEtzlxxeum66R2ZWLs02OSVPhjomQ\",\n              \"token_expires_in\": 3600000\n            }\n            \"\"\"\n            .data(using: .utf8)\n\n        let response = try await contactAPIClient.identify(\n            channelID: \"test_channel\",\n            namedUserID: \"my-named-user\",\n            contactID: nil,\n            possiblyOrphanedContactID: \"1a32e8c7-5a73-47c0-9716-99fd3d41924c\"\n        )\n\n        let expected = ContactIdentifyResult(\n            contact: ContactIdentifyResult.ContactInfo(\n                channelAssociatedDate: AirshipDateFormatter.date(fromISOString: \"2022-12-29T10:15:30.00\")!,\n                contactID: \"1a32e8c7-5a73-47c0-9716-99fd3d41924b\",\n                isAnonymous: true\n            ),\n            token: \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJLSHVNTE15RmVmYjdoeXR3WkV5VTF4IiwiaWF0IjoxNjAyMDY4NDIxLCJleHAiOjE2MDIwNjg0MjEsInN1YiI6InVMa2hSaktBYzVXQW1SdTFPTFZSVncifQ.kJPu3enbLJMX10xEtzlxxeum66R2ZWLs02OSVPhjomQ\",\n            tokenExpiresInMilliseconds: 3600000\n        )\n\n        XCTAssertTrue(response.isSuccess)\n        XCTAssertEqual(response.result!, expected)\n\n        let request = session.lastRequest!\n\n        let requestBody = try AirshipJSON.from(data: request.body).unWrap() as! [String: AnyHashable]\n        let expectedBody = [\n            \"device_info\": [\n                \"device_type\": \"ios\"\n            ],\n            \"action\": [\n                \"type\": \"identify\",\n                \"named_user_id\": \"my-named-user\",\n                \"possibly_orphaned_contact_id\": \"1a32e8c7-5a73-47c0-9716-99fd3d41924c\"\n              ]\n            ]\n\n        XCTAssertEqual(expectedBody, requestBody)\n        XCTAssertEqual(request.url?.absoluteString, \"\\(config.deviceAPIURL!)/api/contacts/identify/v2\")\n        XCTAssertEqual(request.method, \"POST\")\n        XCTAssertEqual(request.auth, .generatedChannelToken(identifier: \"test_channel\"))\n        XCTAssertEqual(\n            request.headers,\n            [\n                \"Content-Type\": \"application/json\",\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            ]\n        )\n    }\n\n    func testResolve() async throws {\n        self.session.data = \"\"\"\n            {\n              \"ok\": true,\n              \"contact\": {\n                \"contact_id\": \"1a32e8c7-5a73-47c0-9716-99fd3d41924b\",\n                \"is_anonymous\": true,\n                \"channel_association_timestamp\": \"2022-12-29T10:15:30.00\"\n              },\n              \"token\": \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJLSHVNTE15RmVmYjdoeXR3WkV5VTF4IiwiaWF0IjoxNjAyMDY4NDIxLCJleHAiOjE2MDIwNjg0MjEsInN1YiI6InVMa2hSaktBYzVXQW1SdTFPTFZSVncifQ.kJPu3enbLJMX10xEtzlxxeum66R2ZWLs02OSVPhjomQ\",\n              \"token_expires_in\": 3600000\n            }\n            \"\"\"\n            .data(using: .utf8)\n\n        let response = try await contactAPIClient.resolve(\n            channelID: \"test_channel\",\n            contactID: \"some contact id\",\n            possiblyOrphanedContactID: \"1a32e8c7-5a73-47c0-9716-99fd3d41924c\"\n        )\n\n        let expected = ContactIdentifyResult(\n            contact: ContactIdentifyResult.ContactInfo(\n                channelAssociatedDate: AirshipDateFormatter.date(fromISOString: \"2022-12-29T10:15:30.00\")!,\n                contactID: \"1a32e8c7-5a73-47c0-9716-99fd3d41924b\",\n                isAnonymous: true\n            ),\n            token: \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJLSHVNTE15RmVmYjdoeXR3WkV5VTF4IiwiaWF0IjoxNjAyMDY4NDIxLCJleHAiOjE2MDIwNjg0MjEsInN1YiI6InVMa2hSaktBYzVXQW1SdTFPTFZSVncifQ.kJPu3enbLJMX10xEtzlxxeum66R2ZWLs02OSVPhjomQ\",\n            tokenExpiresInMilliseconds: 3600000\n        )\n\n        XCTAssertTrue(response.isSuccess)\n        XCTAssertEqual(response.result!, expected)\n\n        let request = session.lastRequest!\n\n        let requestBody = try AirshipJSON.from(data: request.body).unWrap() as! [String: AnyHashable]\n        let expectedBody = [\n            \"device_info\": [\n                \"device_type\": \"ios\"\n            ],\n            \"action\": [\n                \"type\": \"resolve\",\n                \"contact_id\": \"some contact id\",\n                \"possibly_orphaned_contact_id\": \"1a32e8c7-5a73-47c0-9716-99fd3d41924c\"\n              ]\n            ]\n\n        XCTAssertEqual(expectedBody, requestBody)\n        XCTAssertEqual(request.url?.absoluteString, \"https://device-api.urbanairship.com/api/contacts/identify/v2\")\n        XCTAssertEqual(request.method, \"POST\")\n        XCTAssertEqual(request.auth, .generatedChannelToken(identifier: \"test_channel\"))\n        XCTAssertEqual(\n            request.headers,\n            [\n                \"Content-Type\": \"application/json\",\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            ]\n        )\n    }\n\n    func testReset() async throws {\n        self.session.data = \"\"\"\n            {\n              \"ok\": true,\n              \"contact\": {\n                \"contact_id\": \"1a32e8c7-5a73-47c0-9716-99fd3d41924b\",\n                \"is_anonymous\": true,\n                \"channel_association_timestamp\": \"2022-12-29T10:15:30.00\"\n              },\n              \"token\": \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJLSHVNTE15RmVmYjdoeXR3WkV5VTF4IiwiaWF0IjoxNjAyMDY4NDIxLCJleHAiOjE2MDIwNjg0MjEsInN1YiI6InVMa2hSaktBYzVXQW1SdTFPTFZSVncifQ.kJPu3enbLJMX10xEtzlxxeum66R2ZWLs02OSVPhjomQ\",\n              \"token_expires_in\": 3600000\n            }\n            \"\"\"\n            .data(using: .utf8)\n\n        let response = try await contactAPIClient.reset(\n            channelID: \"test_channel\",\n            possiblyOrphanedContactID: \"1a32e8c7-5a73-47c0-9716-99fd3d41924c\"\n        )\n\n        let expected = ContactIdentifyResult(\n            contact: ContactIdentifyResult.ContactInfo(\n                channelAssociatedDate: AirshipDateFormatter.date(fromISOString: \"2022-12-29T10:15:30.00\")!,\n                contactID: \"1a32e8c7-5a73-47c0-9716-99fd3d41924b\",\n                isAnonymous: true\n            ),\n            token: \"eyJhbGciOiJIUzI1NiJ9.eyJhdWQiOiJLSHVNTE15RmVmYjdoeXR3WkV5VTF4IiwiaWF0IjoxNjAyMDY4NDIxLCJleHAiOjE2MDIwNjg0MjEsInN1YiI6InVMa2hSaktBYzVXQW1SdTFPTFZSVncifQ.kJPu3enbLJMX10xEtzlxxeum66R2ZWLs02OSVPhjomQ\",\n            tokenExpiresInMilliseconds: 3600000\n        )\n\n        XCTAssertTrue(response.isSuccess)\n        XCTAssertEqual(response.result!, expected)\n\n        let request = session.lastRequest!\n\n        let requestBody = try AirshipJSON.from(data: request.body).unWrap() as! [String: AnyHashable]\n        let expectedBody = [\n            \"device_info\": [\n                \"device_type\": \"ios\"\n            ],\n            \"action\": [\n                \"type\": \"reset\",\n                \"possibly_orphaned_contact_id\": \"1a32e8c7-5a73-47c0-9716-99fd3d41924c\"\n              ]\n            ]\n\n        XCTAssertEqual(expectedBody, requestBody)\n        XCTAssertEqual(request.url?.absoluteString, \"\\(config.deviceAPIURL!)/api/contacts/identify/v2\")\n        XCTAssertEqual(request.method, \"POST\")\n        XCTAssertEqual(request.auth, .generatedChannelToken(identifier: \"test_channel\"))\n        XCTAssertEqual(\n            request.headers,\n            [\n                \"Content-Type\": \"application/json\",\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            ]\n        )\n    }\n\n    func testRegisterEmail() async throws {\n        self.session.data = \"\"\"\n            {\n                \"channel_id\": \"some-channel\",\n            }\n            \"\"\"\n            .data(using: .utf8)\n        let date = Date()\n        let response  = try await contactAPIClient.registerEmail(\n            contactID: \"some-contact-id\",\n            address: \"ua@airship.com\",\n            options: EmailRegistrationOptions.options(\n                transactionalOptedIn: date,\n                properties: [\"interests\": \"newsletter\"],\n                doubleOptIn: true\n            ),\n            locale: currentLocale\n        )\n\n        XCTAssertTrue(response.isSuccess)\n        if let associatedChannel = response.result, case .email = associatedChannel.channelType {\n            XCTAssertEqual(\"some-channel\", associatedChannel.channelID)\n            let previousRequest = self.session.previousRequest!\n            XCTAssertNotNil(previousRequest)\n            XCTAssertEqual(\n                \"\\(config.deviceAPIURL!)/api/channels/restricted/email\",\n                previousRequest.url!.absoluteString\n            )\n\n            let previousBody = try JSONSerialization.jsonObject(\n                with: previousRequest.body!,\n                options: []\n            ) as! [String : AnyHashable]\n\n            let previousExpectedBody: [String : AnyHashable] = [\n                \"channel\": [\n                    \"type\": \"email\",\n                    \"address\": \"ua@airship.com\",\n                    \"timezone\": TimeZone.current.identifier,\n                    \"locale_country\": \"CA\",\n                    \"locale_language\": \"fr\",\n                    \"transactional_opted_in\": AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter),\n                ],\n                \"opt_in_mode\": \"double\",\n                \"properties\": [\n                    \"interests\": \"newsletter\"\n                ],\n            ]\n\n            XCTAssertEqual(\n                previousBody,\n                previousExpectedBody\n            )\n\n            let lastRequest = self.session.lastRequest!\n            XCTAssertEqual(\n                \"\\(config.deviceAPIURL!)/api/contacts/some-contact-id\",\n                lastRequest.url!.absoluteString\n            )\n\n            let lastBody = try JSONSerialization.jsonObject(\n                with: lastRequest.body!,\n                options: []\n            ) as! [String : AnyHashable]\n\n            let lastExpectedBody:[String : AnyHashable] = [\n                \"associate\": [\n                    [\n                        \"device_type\": \"email\",\n                        \"channel_id\": \"some-channel\",\n                    ]\n                ]\n            ]\n            XCTAssertEqual(\n                lastBody,\n                lastExpectedBody\n            )\n        } else {\n            XCTAssertThrowsError(\"Error: Invalid associated channel type\")\n        }\n        \n\n    }\n\n    func testRegisterSMS() async throws {\n        self.session.data = \"\"\"\n            {\n                \"channel_id\": \"some-channel\",\n            }\n            \"\"\"\n            .data(using: .utf8)\n\n        let response = try await contactAPIClient.registerSMS(\n            contactID: \"some-contact-id\",\n            msisdn: \"15035556789\",\n            options: SMSRegistrationOptions.optIn(senderID: \"28855\"),\n            locale: currentLocale\n        )\n\n        XCTAssertTrue(response.isSuccess)\n\n        if let associatedChannel = response.result, case .sms = associatedChannel.channelType {\n            XCTAssertEqual(\"some-channel\", associatedChannel.channelID)\n            \n            let previousRequest = self.session.previousRequest!\n            XCTAssertNotNil(previousRequest)\n            XCTAssertEqual(\n                \"https://device-api.urbanairship.com/api/channels/restricted/sms\",\n                previousRequest.url!.absoluteString\n            )\n            \n            let previousBody = try JSONSerialization.jsonObject(\n                with: previousRequest.body!,\n                options: []\n            )\n            let previousExpectedBody: Any = [\n                \"msisdn\": \"15035556789\",\n                \"sender\": \"28855\",\n                \"timezone\": TimeZone.current.identifier,\n                \"locale_country\": currentLocale.getRegionCode(),\n                \"locale_language\": currentLocale.getLanguageCode(),\n            ]\n            XCTAssertEqual(\n                previousBody as! NSDictionary,\n                previousExpectedBody as! NSDictionary\n            )\n            \n            let lastRequest = self.session.lastRequest!\n            XCTAssertEqual(\n                \"https://device-api.urbanairship.com/api/contacts/some-contact-id\",\n                lastRequest.url!.absoluteString\n            )\n            \n            let lastBody = try JSONSerialization.jsonObject(\n                with: lastRequest.body!,\n                options: []\n            )\n            let lastExpectedBody: Any = [\n                \"associate\": [\n                    [\n                        \"device_type\": \"sms\",\n                        \"channel_id\": \"some-channel\",\n                    ]\n                ]\n            ]\n            XCTAssertEqual(\n                lastBody as! NSDictionary,\n                lastExpectedBody as! NSDictionary\n            )\n        } else {\n            XCTAssertThrowsError(\"Error: Invalid associated channel type\")\n        }\n    }\n\n    func testRegisterOpen() async throws {\n        self.session.data = \"\"\"\n            {\n                \"channel_id\": \"some-channel\",\n            }\n            \"\"\"\n            .data(using: .utf8)\n\n        let response = try await contactAPIClient.registerOpen(\n            contactID: \"some-contact-id\",\n            address: \"open_address\",\n            options: OpenRegistrationOptions.optIn(\n                platformName: \"my_platform\",\n                identifiers: [\"model\": \"4\", \"category\": \"1\"]\n            ),\n            locale: currentLocale\n        )\n\n        XCTAssertTrue(response.isSuccess)\n        if let associatedChannel = response.result, case .open = associatedChannel.channelType {\n            XCTAssertEqual(\"some-channel\", associatedChannel.channelID)\n            \n            let previousRequest = self.session.previousRequest!\n            XCTAssertNotNil(previousRequest)\n            XCTAssertEqual(\n                \"https://device-api.urbanairship.com/api/channels/restricted/open\",\n                previousRequest.url!.absoluteString\n            )\n            \n            let previousBody = try JSONSerialization.jsonObject(\n                with: previousRequest.body!,\n                options: []\n            )\n            let previousExpectedBody: [String: Any] = [\n                \"channel\": [\n                    \"type\": \"open\",\n                    \"address\": \"open_address\",\n                    \"timezone\": TimeZone.current.identifier,\n                    \"locale_country\": currentLocale.getRegionCode(),\n                    \"locale_language\": currentLocale.getLanguageCode(),\n                    \"opt_in\": true,\n                    \"open\": [\n                        \"open_platform_name\": \"my_platform\",\n                        \"identifiers\": [\n                            \"model\": \"4\",\n                            \"category\": \"1\",\n                        ],\n                    ] as [String : Any],\n                ] as [String : Any]\n            ]\n            XCTAssertEqual(\n                previousBody as! NSDictionary,\n                previousExpectedBody as NSDictionary\n            )\n            \n            let lastRequest = self.session.lastRequest!\n            XCTAssertEqual(\n                \"https://device-api.urbanairship.com/api/contacts/some-contact-id\",\n                lastRequest.url!.absoluteString\n            )\n            \n            let lastBody = try JSONSerialization.jsonObject(\n                with: lastRequest.body!,\n                options: []\n            )\n            let lastExpectedBody: Any = [\n                \"associate\": [\n                    [\n                        \"device_type\": \"open\",\n                        \"channel_id\": \"some-channel\",\n                    ]\n                ]\n            ]\n            XCTAssertEqual(\n                lastBody as! NSDictionary,\n                lastExpectedBody as! NSDictionary\n            )\n        } else {\n            XCTAssertThrowsError(\"Error: Invalid associated channel type\")\n        }\n    }\n\n    func testAssociateChannel() async throws {\n        let response = try await contactAPIClient.associateChannel(\n            contactID: \"some-contact-id\",\n            channelID: \"some-channel\",\n            channelType: .sms\n        )\n\n        XCTAssertTrue(response.isSuccess)\n\n        if let associatedChannel = response.result, case .sms = associatedChannel.channelType {\n            XCTAssertEqual(\"some-channel\", associatedChannel.channelID)\n            \n            let request = self.session.lastRequest!\n            XCTAssertEqual(\n                \"https://device-api.urbanairship.com/api/contacts/some-contact-id\",\n                request.url!.absoluteString\n            )\n            \n            let body = try JSONSerialization.jsonObject(\n                with: request.body!,\n                options: []\n            )\n            let expectedBody: Any = [\n                \"associate\": [\n                    [\n                        \"device_type\": \"sms\",\n                        \"channel_id\": \"some-channel\",\n                    ]\n                ]\n            ]\n            XCTAssertEqual(body as! NSDictionary, expectedBody as! NSDictionary)\n        } else {\n            XCTAssertThrowsError(\"Error: Invalid associated channel type\")\n        }\n    }\n\n    func testDisassociateRegistered() async throws {\n        let expectedChannelType: ChannelType = .email\n        let expectedChannelID: String = \"some channel\"\n        let expectedContactID: String = \"contact\"\n\n        let response = try await contactAPIClient.disassociateChannel(\n            contactID: expectedContactID,\n            disassociateOptions: DisassociateOptions(\n                channelID: expectedChannelID,\n                channelType: expectedChannelType,\n                optOut: true\n            )\n        )\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/contacts/disassociate/\\(expectedContactID)\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let expectedBody = [\n            \"channel_type\": expectedChannelType.rawValue,\n            \"channel_id\": expectedChannelID,\n            \"opt_out\": true\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n\n    func testDisassociatePendingEmail() async throws {\n        let expectedChannelType: ChannelType = .email\n        let expectedEmailAddress: String = \"some@email.com\"\n        let expectedContactID: String = \"contact\"\n\n        let response = try await contactAPIClient.disassociateChannel(\n            contactID: expectedContactID,\n            disassociateOptions: DisassociateOptions(\n                emailAddress: expectedEmailAddress,\n                optOut: false\n            )\n        )\n\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/contacts/disassociate/\\(expectedContactID)\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let expectedBody = [\n            \"channel_type\": expectedChannelType.rawValue,\n            \"email_address\": expectedEmailAddress,\n            \"opt_out\": false\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n\n    func testDisassociatePendingSMS() async throws {\n        let expectedChannelType: ChannelType = .sms\n        let expectedMSISDN: String = \"12345\"\n        let expectedSender: String = \"56789\"\n\n        let expectedContactID: String = \"contact\"\n\n        let response = try await contactAPIClient.disassociateChannel(contactID: expectedContactID, disassociateOptions: DisassociateOptions(msisdn: expectedMSISDN, senderID: expectedSender, optOut: false))\n\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/contacts/disassociate/\\(expectedContactID)\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let expectedBody = [\n            \"channel_type\": expectedChannelType.rawValue,\n            \"msisdn\": expectedMSISDN,\n            \"sender\": expectedSender,\n            \"opt_out\": false\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n\n    func testResendEmail() async throws {\n        let expectedChannelType: ChannelType = .email\n        let expectedEmail: String = \"test@email.com\"\n\n        let expectedResendOptions = ResendOptions(emailAddress: expectedEmail)\n\n        let response = try await contactAPIClient.resend(resendOptions: expectedResendOptions)\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/channels/resend\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let expectedBody = [\n            \"channel_type\": expectedChannelType.rawValue,\n            \"email_address\": expectedEmail\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n\n    func testResendSMS() async throws {\n        let expectedChannelType: ChannelType = .sms\n        let expectedMSISDN: String = \"1234\"\n        let expectedSenderID: String = \"1234\"\n\n        let expectedResendOptions = ResendOptions(msisdn: expectedMSISDN, senderID: expectedSenderID)\n\n        let response = try await contactAPIClient.resend(resendOptions: expectedResendOptions)\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/channels/resend\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let expectedBody = [\n            \"channel_type\": expectedChannelType.rawValue,\n            \"sender\": expectedSenderID,\n            \"msisdn\": expectedMSISDN\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n\n    func testResendChannel() async throws {\n        let expectedChannelType: ChannelType = .email\n        let expectedChannelID: String = \"some channel\"\n        let expectedResendOptions = ResendOptions(channelID: expectedChannelID, channelType: expectedChannelType)\n\n        let response = try await contactAPIClient.resend(resendOptions: expectedResendOptions)\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/channels/resend\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let expectedBody = [\n            \"channel_type\": expectedChannelType.rawValue,\n            \"channel_id\": expectedChannelID\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n\n    func testUpdate() async throws {\n        let tagUpdates = [\n            TagGroupUpdate(group: \"tag-set\", tags: [], type: .set),\n            TagGroupUpdate(group: \"tag-add\", tags: [\"add tag\"], type: .add),\n            TagGroupUpdate(\n                group: \"tag-other-add\",\n                tags: [\"other tag\"],\n                type: .add\n            ),\n            TagGroupUpdate(\n                group: \"tag-remove\",\n                tags: [\"remove tag\"],\n                type: .remove\n            ),\n        ]\n\n        let date = Date()\n        let attributeUpdates = [\n            AttributeUpdate.set(\n                attribute: \"some-string\",\n                value: \"Hello\",\n                date: date\n            ),\n            AttributeUpdate.set(\n                attribute: \"some-number\",\n                value: 32.0,\n                date: date\n            ),\n            AttributeUpdate.remove(attribute: \"some-remove\", date: date),\n        ]\n\n        let listUpdates = [\n            ScopedSubscriptionListUpdate(\n                listId: \"bar\",\n                type: .subscribe,\n                scope: .web,\n                date: date\n            ),\n            ScopedSubscriptionListUpdate(\n                listId: \"foo\",\n                type: .unsubscribe,\n                scope: .app,\n                date: date\n            ),\n        ]\n\n        let response = try await contactAPIClient.update(\n            contactID: \"some-contact-id\",\n            tagGroupUpdates: tagUpdates,\n            attributeUpdates: attributeUpdates,\n            subscriptionListUpdates: listUpdates\n        )\n\n        XCTAssertTrue(response.isSuccess)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/contacts/some-contact-id\",\n            request.url!.absoluteString\n        )\n\n        let body = try JSONSerialization.jsonObject(\n            with: request.body!,\n            options: []\n        ) as! [String: Any]\n\n        let formattedDate = AirshipDateFormatter.string(fromDate: date, format: .isoDelimitter)\n        \n        let expectedBody = [\n            \"attributes\": [\n                [\n                    \"action\": \"set\",\n                    \"key\": \"some-string\",\n                    \"timestamp\": formattedDate,\n                    \"value\": \"Hello\",\n                ] as [String : Any],\n                [\n                    \"action\": \"set\",\n                    \"key\": \"some-number\",\n                    \"timestamp\": formattedDate,\n                    \"value\": 32,\n                ],\n                [\n                    \"action\": \"remove\",\n                    \"key\": \"some-remove\",\n                    \"timestamp\": formattedDate,\n                ],\n            ],\n            \"tags\": [\n                \"add\": [\n                    \"tag-add\": [\n                        \"add tag\"\n                    ],\n                    \"tag-other-add\": [\n                        \"other tag\"\n                    ],\n                ],\n                \"remove\": [\n                    \"tag-remove\": [\n                        \"remove tag\"\n                    ]\n                ],\n                \"set\": [\n                    \"tag-set\": []\n                ],\n            ],\n            \"subscription_lists\": [\n                [\n                    \"action\": \"subscribe\",\n                    \"list_id\": \"bar\",\n                    \"scope\": \"web\",\n                    \"timestamp\": formattedDate,\n                ],\n                [\n                    \"action\": \"unsubscribe\",\n                    \"list_id\": \"foo\",\n                    \"scope\": \"app\",\n                    \"timestamp\": formattedDate,\n                ],\n            ],\n        ] as [String : Any]\n\n        XCTAssertEqual(body as NSDictionary, expectedBody as NSDictionary)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ContactChannelsProviderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\nclass ContactChannelsProviderTest: XCTestCase {\n    private var audienceOverridesProvider: DefaultAudienceOverridesProvider!\n    private var provider: ContactChannelsProvider!\n    var apiClient: TestContactChannelsAPIClient!\n    private var privacyManager: TestPrivacyManager!\n    private var dataStore: PreferenceDataStore!\n    private var notificationCenter: AirshipNotificationCenter!\n    private var taskSleeper: TestSleeper!\n    private var date: UATestDate = UATestDate(dateOverride: Date())\n\n    private let testChannels1: [ContactChannel] = [\n        .email(\n            .registered(\n                ContactChannel.Email.Registered(\n                    channelID: UUID().uuidString,\n                    maskedAddress: \"****@email.com\"\n                )\n            )\n        ),\n        .sms(\n            .registered(\n                ContactChannel.Sms.Registered(\n                    channelID: UUID().uuidString,\n                    maskedAddress: \"****@email.com\",\n                    isOptIn: true,\n                    senderID: \"123\"\n                )\n            )\n        )\n    ]\n\n\n    private let testChannels2: [ContactChannel] = [\n        .email(\n            .registered(\n                ContactChannel.Email.Registered(\n                    channelID: UUID().uuidString,\n                    maskedAddress: \"****@email.com\"\n                )\n            )\n        ),\n        .email(\n            .registered(\n                ContactChannel.Email.Registered(\n                    channelID: UUID().uuidString,\n                    maskedAddress: \"****@email.com\"\n                )\n            )\n        )\n    ]\n\n    private let testChannels3: [ContactChannel] = [\n        .sms(\n            .registered(\n                ContactChannel.Sms.Registered(\n                    channelID: UUID().uuidString,\n                    maskedAddress: \"****@email.com\",\n                    isOptIn: false,\n                    senderID: \"123\"\n                )\n            )\n        )\n    ]\n\n    override func setUp() async throws {\n        try await super.setUp()\n\n        self.audienceOverridesProvider = DefaultAudienceOverridesProvider()\n        self.apiClient = TestContactChannelsAPIClient()\n        self.dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        self.taskSleeper = TestSleeper()\n        self.notificationCenter = AirshipNotificationCenter(notificationCenter: NotificationCenter())\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: .testConfig(),\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n\n        self.provider = ContactChannelsProvider(\n            audienceOverrides: self.audienceOverridesProvider,\n            apiClient: self.apiClient,\n            date: self.date,\n            taskSleeper: self.taskSleeper,\n            privacyManager: self.privacyManager\n        )\n    }\n\n    override func tearDown() async throws {\n        try await super.tearDown()\n\n        self.audienceOverridesProvider = nil\n        self.apiClient = nil\n        self.dataStore = nil\n        self.taskSleeper = nil\n        self.notificationCenter = nil\n        self.privacyManager = nil\n        self.provider = nil\n    }\n\n    func testPrivacyManagerDisabled() async {\n        self.privacyManager.disableFeatures(.contacts)\n\n        let contactIDStream = AsyncStream<String> { continuation in\n            continuation.yield(\"test-contact-id-1\")\n            continuation.finish()\n        }\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(\n            result: self.testChannels2,\n            statusCode: 200,\n            headers: [:]\n        )\n\n        var resultStream = provider.contactChannels(stableContactIDUpdates: contactIDStream).makeAsyncIterator()\n        let result = await resultStream.next()\n        XCTAssertEqual(result, .error(.contactsDisabled))\n    }\n\n    func testContactChannelsSuccess() async {\n        let contactIDChannel = AirshipAsyncChannel<String>()\n\n        var resultStream = provider.contactChannels(\n            stableContactIDUpdates: await contactIDChannel.makeStream()\n        ).makeAsyncIterator()\n\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(\n            result: self.testChannels1,\n            statusCode: 200,\n            headers: [:]\n        )\n        await contactIDChannel.send(\"test-contact-id-1\")\n        var result = await resultStream.next()\n        XCTAssertEqual(result, .success(self.testChannels1))\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(\n            result: self.testChannels2,\n            statusCode: 200,\n            headers: [:]\n        )\n        await contactIDChannel.send(\"test-contact-id-2\")\n        result = await resultStream.next()\n        XCTAssertEqual(result, .success(self.testChannels2))\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(\n            result: self.testChannels3,\n            statusCode: 200,\n            headers: [:]\n        )\n        await contactIDChannel.send(\"test-contact-id-3\")\n        result = await resultStream.next()\n        XCTAssertEqual(result, .success(self.testChannels3))\n\n        XCTAssertEqual(self.apiClient.fetchAssociatedChannelsCallCount, 3)\n    }\n    \n    func testContactChannelsRefresh() async {\n        let contactIDChannel = AirshipAsyncChannel<String>()\n\n        var resultStream = provider.contactChannels(\n            stableContactIDUpdates: await contactIDChannel.makeStream()\n        ).makeAsyncIterator()\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(\n            result: self.testChannels1,\n            statusCode: 200,\n            headers: [:]\n        )\n        await contactIDChannel.send(\"test-contact-id-1\")\n        var result = await resultStream.next()\n        XCTAssertEqual(result, .success(self.testChannels1))\n        XCTAssertEqual(1, self.apiClient.fetchAssociatedChannelsCallCount)\n\n        //from cache\n        await contactIDChannel.send(\"test-contact-id-1\")\n        result = await resultStream.next()\n        XCTAssertEqual(result, .success(self.testChannels1))\n        XCTAssertEqual(1, self.apiClient.fetchAssociatedChannelsCallCount)\n        \n        await provider.refresh()\n        \n        await contactIDChannel.send(\"test-contact-id-1\")\n        result = await resultStream.next()\n        XCTAssertEqual(result, .success(self.testChannels1))\n        XCTAssertEqual(2, self.apiClient.fetchAssociatedChannelsCallCount)\n    }\n\n    func testContactChannelsFailure() async {\n        let contactIDStream = AsyncStream<String> { continuation in\n            continuation.yield(\"test-contact-id\")\n            continuation.finish()\n        }\n\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(result: [], statusCode: 500, headers: [:])\n\n        var resultStream = provider.contactChannels(stableContactIDUpdates: contactIDStream).makeAsyncIterator()\n        let result = await resultStream.next()\n        XCTAssertEqual(result, .error(.failedToFetchContacts))\n    }\n\n    func testEmptyContactChannelUpdates() async {\n        let contactIDStream = AsyncStream<String> { continuation in\n            continuation.yield(\"test-contact-id-1\")\n            continuation.finish()\n        }\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(result: [], statusCode: 200, headers: [:])\n\n        var resultStream = provider.contactChannels(stableContactIDUpdates: contactIDStream).makeAsyncIterator()\n        let result = await resultStream.next()\n        XCTAssertEqual(result, .success([]))\n    }\n\n    func testBackoffOnFailure() async {\n        let contactIDStream = AsyncStream<String> { continuation in\n            continuation.yield(\"test-contact-id-1\")\n            continuation.finish()\n        }\n\n        self.apiClient.fetchResponse = AirshipHTTPResponse(result: [], statusCode: 500, headers: [:])\n        var sleepUpdates = await self.taskSleeper.sleepUpdates.makeAsyncIterator()\n\n        var results = provider.contactChannels(stableContactIDUpdates: contactIDStream).makeAsyncIterator()\n        _ = await results.next()\n\n\n        for backoff in [8.0, 16.0, 32.0, 64.0, 64.0] {\n            let next = await sleepUpdates.next()\n            XCTAssertEqual(next, backoff)\n            await self.taskSleeper.advance()\n        }\n\n    }\n\n    func testRefreshRateOnSuccess() async {\n        let contactIDStream = AsyncStream<String> { continuation in\n            continuation.yield(\"test-contact-id-1\")\n            continuation.finish()\n        }\n\n        var results = provider.contactChannels(stableContactIDUpdates: contactIDStream).makeAsyncIterator()\n        self.apiClient.fetchResponse = AirshipHTTPResponse(result: [], statusCode: 200, headers: [:])\n\n        _ = await results.next()\n        await self.taskSleeper.advance()\n\n        let sleeps = await self.taskSleeper.sleeps\n        XCTAssertEqual(sleeps, [600])\n    }\n}\n\n\nclass TestContactChannelsAPIClient: ContactChannelsAPIClientProtocol, @unchecked Sendable {\n    internal init(\n        fetchAssociatedChannelsCallCount: Int = 0,\n        fetchedContactIDs: [String] = [],\n        fetchResponse: AirshipHTTPResponse<[ContactChannel]>? = nil\n    ) {\n        self.fetchAssociatedChannelsCallCount = fetchAssociatedChannelsCallCount\n        self.fetchedContactIDs = fetchedContactIDs\n        self.fetchResponse = fetchResponse\n    }\n\n    var fetchAssociatedChannelsCallCount = 0\n    var fetchedContactIDs: [String] = []\n    var fetchResponse: AirshipHTTPResponse<[ContactChannel]>?\n\n    func fetchAssociatedChannelsList(contactID: String) async throws -> AirshipHTTPResponse<[ContactChannel]> {\n        fetchAssociatedChannelsCallCount += 1\n        fetchedContactIDs.append(contactID)\n\n        return fetchResponse!\n    }\n}\n\nprivate actor TestSleeper: AirshipTaskSleeper, @unchecked Sendable {\n\n    private let channel = AirshipAsyncChannel<TimeInterval>()\n    var sleepUpdates: AsyncStream<TimeInterval> {\n        get async {\n            await channel.makeStream()\n        }\n    }\n\n\n    func advance() {\n        continuations.forEach {\n            $0.resume()\n        }\n        continuations.removeAll()\n    }\n\n\n    var sleeps: [TimeInterval] = []\n    var continuations: [CheckedContinuation<Void, Never>] = []\n\n    func sleep(timeInterval: TimeInterval) async throws {\n        sleeps.append(timeInterval)\n        await channel.send(timeInterval)\n        await withCheckedContinuation { continuation in\n            continuations.append(continuation)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ContactManagerTest.swift",
    "content": "import Testing\n\n@testable\nimport AirshipCore\nimport Foundation\n\n@Suite(.timeLimit(.minutes(1)))\nstruct ContactManagerTest {\n\n    let date: UATestDate\n    let channel: TestChannel\n    let localeManager: TestLocaleManager\n    let workManager: TestWorkManager\n    let dataStore: PreferenceDataStore\n    let apiClient: TestContactAPIClient\n    let contactManager: ContactManager\n\n    let anonIdentifyResponse: ContactIdentifyResult\n    let nonAnonIdentifyResponse: ContactIdentifyResult\n\n    // Helper to wait for async conditions with timeout\n    private func waitForCondition(\n        timeout: Duration = .seconds(2),\n        pollingInterval: Duration = .milliseconds(10),\n        condition: @escaping () async -> Bool\n    ) async throws {\n        let deadline = ContinuousClock.now + timeout\n        while ContinuousClock.now < deadline {\n            if await condition() { return }\n            try await Task.sleep(for: pollingInterval)\n        }\n        throw NSError(domain: \"TestTimeout\", code: 1, userInfo: [NSLocalizedDescriptionKey: \"Condition not met within timeout\"])\n    }\n\n    init() async throws {\n        self.date = UATestDate(offset: 0, dateOverride: Date())\n        self.channel = TestChannel()\n        self.localeManager = TestLocaleManager()\n        self.workManager = TestWorkManager()\n        self.dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        self.apiClient = TestContactAPIClient()\n\n        self.anonIdentifyResponse = ContactIdentifyResult(\n            contact: ContactIdentifyResult.ContactInfo(\n                channelAssociatedDate: AirshipDateFormatter.date(fromISOString: \"2022-12-29T10:15:30.00\")!,\n                contactID: \"some contact\",\n                isAnonymous: true\n            ),\n            token: \"some token\",\n            tokenExpiresInMilliseconds: 3600000\n        )\n\n        self.nonAnonIdentifyResponse = ContactIdentifyResult(\n            contact: ContactIdentifyResult.ContactInfo(\n                channelAssociatedDate: AirshipDateFormatter.date(fromISOString: \"2022-12-29T10:15:30.00\")!,\n                contactID: \"some other contact\",\n                isAnonymous: false\n            ),\n            token: \"some other token\",\n            tokenExpiresInMilliseconds: 3600000\n        )\n\n        self.localeManager.currentLocale = Locale(identifier: \"fr-CA\")\n\n        self.contactManager = ContactManager(\n            dataStore: self.dataStore,\n            channel: self.channel,\n            localeManager: self.localeManager,\n            apiClient: self.apiClient,\n            date: self.date,\n            workManager: self.workManager,\n            internalIdentifyRateLimit: 0.0\n        )\n\n        await self.contactManager.setEnabled(enabled: true)\n        self.channel.identifier = \"some channel\"\n    }\n\n    @Test(\"Enable enqueues work\")\n    func enableEnqueuesWork() async throws {\n        await self.contactManager.setEnabled(enabled: false)\n        #expect(self.workManager.workRequests.isEmpty)\n\n        await self.contactManager.addOperation(.resolve)\n\n        await self.contactManager.setEnabled(enabled: false)\n        #expect(self.workManager.workRequests.isEmpty)\n\n        await self.contactManager.setEnabled(enabled: true)\n        #expect(!self.workManager.workRequests.isEmpty)\n    }\n\n    @Test(\"Channel creation enqueues work\")\n    func channelCreationEnqueuesWork() async throws {\n        await self.contactManager.setEnabled(enabled: true)\n\n        // Clear the channel identifier to simulate no channel\n        self.channel.identifier = nil\n\n        // Wait a moment for that to process\n        try await Task.sleep(for: .milliseconds(100))\n\n        // Track initial work count\n        let initialWorkCount = self.workManager.workRequests.count\n\n        // Simulate channel creation by setting identifier\n        self.channel.identifier = \"newly-created-channel-id\"\n\n        // Wait for new work to be enqueued\n        try await waitForCondition(timeout: .seconds(5)) {\n            self.workManager.workRequests.count > initialWorkCount\n        }\n\n        // Verify new work was enqueued\n        #expect(self.workManager.workRequests.count > initialWorkCount)\n    }\n\n    @Test(\"Add operation enqueues work\")\n    func addOperationEnqueuesWork() async throws {\n        await self.contactManager.setEnabled(enabled: true)\n        #expect(self.workManager.workRequests.isEmpty)\n\n        await self.contactManager.addOperation(.resolve)\n        #expect(!self.workManager.workRequests.isEmpty)\n    }\n\n    @Test(\"Add skippable operation enqueues work\")\n    func addSkippableOperationEnqueuesWork() async throws {\n        await self.contactManager.setEnabled(enabled: true)\n        await self.contactManager.addOperation(.resolve)\n\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n        #expect(result == .success)\n        self.workManager.workRequests.removeAll()\n\n        await self.contactManager.addOperation(.reset)\n        #expect(!self.workManager.workRequests.isEmpty)\n    }\n\n    @Test(\"Rate limit config\")\n    func rateLimitConfig() async throws {\n        let rateLimits = self.workManager.rateLimits\n        #expect(rateLimits.count == 2)\n\n        let updateRule = rateLimits[ContactManager.updateRateLimitID]!\n        #expect(updateRule.rate == 1)\n        #expect(abs(updateRule.timeInterval - 0.5) < 0.01)\n\n\n        let identityRule = rateLimits[ContactManager.identityRateLimitID]!\n        #expect(identityRule.rate == 1)\n        #expect(abs(identityRule.timeInterval - 5.0) < 0.01)\n    }\n\n    @Test(\"Resolve\")\n    func resolve() async throws {\n        await self.contactManager.addOperation(.resolve)\n\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                #expect(contactID == nil)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        let contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(anonIdentifyResponse.contact.contactID == contactInfo?.contactID)\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: self.anonIdentifyResponse.contact.contactID,\n                    isStable: true,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n    }\n\n    @Test(\"Resolve with contact ID\")\n    func resolveWithContactID() async throws {\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n        await self.contactManager.addOperation(.resolve)\n\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                #expect(contactID != nil)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        let contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(anonIdentifyResponse.contact.contactID == contactInfo?.contactID)\n    }\n\n    @Test(\"Resolved failed\")\n    func resolvedFailed() async throws {\n        await self.contactManager.addOperation(.resolve)\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 500,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Verify\")\n    func verify() async throws {\n        await self.contactManager.addOperation(.verify(self.date.now))\n\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                #expect(contactID == nil)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        let contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(anonIdentifyResponse.contact.contactID == contactInfo?.contactID)\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: self.anonIdentifyResponse.contact.contactID,\n                    isStable: true,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n    }\n\n    @Test(\"Required verify\")\n    func requiredVerify() async throws {\n        // Resolve is called first if we do not have a valid token\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                #expect(contactID == nil)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            await self.contactManager.addOperation(.resolve)\n            _ = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n        }\n\n        await self.contactManager.addOperation(.verify(self.date.now + 1, required: true))\n\n        await self.verifyUpdates(\n            [\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.anonIdentifyResponse.contact.contactID,\n                        isStable: true,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                ),\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.anonIdentifyResponse.contact.contactID,\n                        isStable: false,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                )\n            ]\n        )\n    }\n\n    @Test(\"Verify failed\")\n    func verifyFailed() async throws {\n        await self.contactManager.addOperation(.verify(self.date.now))\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 500,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Resolved failed client error\")\n    func resolvedFailedClientError() async throws {\n        await self.contactManager.addOperation(.resolve)\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        #expect(result == .success)\n    }\n\n    @Test(\"Identify\")\n    func identify() async throws {\n        await self.contactManager.addOperation(.identify(\"some named user\"))\n        await self.verifyUpdates([.namedUserUpdate(\"some named user\")])\n\n        // Resolve is called first if we do not have a valid token\n        let resolveExpectation = expectation(description: \"resolve contact\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            #expect(self.channel.identifier == channelID)\n            #expect(contactID == nil)\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        try await confirmation { confirm in\n            self.apiClient.identifyCallback = { channelID, namedUserID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                #expect(\"some named user\" == namedUserID)\n                #expect(self.anonIdentifyResponse.contact.contactID == contactID)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.nonAnonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n\n        let contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(nonAnonIdentifyResponse.contact.contactID == contactInfo?.contactID)\n\n        await self.verifyUpdates(\n            [\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.anonIdentifyResponse.contact.contactID,\n                        isStable: false,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                ),\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.nonAnonIdentifyResponse.contact.contactID,\n                        isStable: true,\n                        namedUserID: \"some named user\",\n                        resolveDate: self.date.now\n                    )\n                ),\n            ]\n        )\n    }\n\n    @Test(\"Identify failed\")\n    func identifyFailed() async throws {\n        await self.contactManager.addOperation(.identify(\"some named user\"))\n\n        // Resolve is called first if we do not have a valid token\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            #expect(self.channel.identifier == channelID)\n            #expect(contactID == nil)\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.apiClient.identifyCallback = { channelID, namedUserID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.nonAnonIdentifyResponse,\n                statusCode: 500,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        #expect(result == .failure)\n    }\n\n    @Test(\"Identify failed client error\")\n    func identifyFailedClientError() async throws {\n        await self.contactManager.addOperation(.identify(\"some named user\"))\n\n        // Resolve is called first if we do not have a valid token\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            #expect(self.channel.identifier == channelID)\n            #expect(contactID == nil)\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.apiClient.identifyCallback = { channelID, namedUserID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.nonAnonIdentifyResponse,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        #expect(result == .success)\n    }\n\n    @Test(\"Reset\")\n    func reset() async throws {\n        await self.contactManager.addOperation(.reset)\n\n        // Resolve is called first if we do not have a valid token\n        let resolveExpectation = expectation(description: \"resolve contact\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            #expect(self.channel.identifier == channelID)\n            #expect(contactID == nil)\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.nonAnonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        try await confirmation { confirm in\n            self.apiClient.resetCallback = { channelID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n\n        await self.verifyUpdates(\n            [\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.nonAnonIdentifyResponse.contact.contactID,\n                        isStable: false,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                ),\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.anonIdentifyResponse.contact.contactID,\n                        isStable: true,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                )\n            ]\n        )\n    }\n\n    @Test(\"Reset if needed\")\n    func resetIfNeeded() async throws {\n        let info = await self.contactManager.currentContactIDInfo()\n        #expect(info == nil)\n\n        await self.contactManager.resetIfNeeded()\n\n        // Resolve is called first if we do not have a valid token\n        let resolveExpectation = expectation(description: \"resolve contact\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            #expect(self.channel.identifier == channelID)\n            #expect(contactID == nil)\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.nonAnonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        try await confirmation { confirm in\n            self.apiClient.resetCallback = { channelID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n\n        await self.verifyUpdates(\n            [\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.nonAnonIdentifyResponse.contact.contactID,\n                        isStable: false,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                ),\n                .contactIDUpdate(\n                    ContactIDInfo(\n                        contactID: self.anonIdentifyResponse.contact.contactID,\n                        isStable: true,\n                        namedUserID: nil,\n                        resolveDate: self.date.now\n                    )\n                )\n            ]\n        )\n    }\n\n    @Test(\"Auth token no contact info\")\n    func authTokenNoContactInfo() async throws {\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let authToken = try await self.contactManager.resolveAuth(\n                identifier: self.anonIdentifyResponse.contact.contactID\n            )\n            #expect(authToken == self.anonIdentifyResponse.token)\n        }\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: self.anonIdentifyResponse.contact.contactID,\n                    isStable: true,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n    }\n\n    @Test(\"Auth token valid token mismatch contact ID\")\n    func authTokenValidTokenMismatchContactID() async throws {\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        await self.contactManager.addOperation(.resolve)\n        let _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        do {\n            let _ = try await self.contactManager.resolveAuth(\n                identifier: \"some other contactID\"\n            )\n            Issue.record(\"Should throw\")\n        } catch {}\n\n    }\n\n    @Test(\"Auth token resolve mismatch\")\n    func authTokenResolveMismatch() async {\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: self.anonIdentifyResponse,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            do {\n                let _ = try await self.contactManager.resolveAuth(\n                    identifier: \"some other contactID\"\n                )\n                Issue.record(\"Should throw\")\n            } catch {}\n        }\n    }\n\n    @Test(\"Expire auth token\")\n    func expireAuthToken() async throws {\n        let resolveExpectation = expectation(description: \"resolve contact\", expectedFulfillmentCount: 2)\n\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            #expect(self.channel.identifier == channelID)\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n\n        var authToken = try await self.contactManager.resolveAuth(\n            identifier: self.anonIdentifyResponse.contact.contactID\n        )\n\n        await self.contactManager.authTokenExpired(token: authToken)\n\n        authToken = try await self.contactManager.resolveAuth(\n            identifier: self.anonIdentifyResponse.contact.contactID\n        )\n\n        #expect(authToken == self.anonIdentifyResponse.token)\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Auth token failed\")\n    func authTokenFailed() async  {\n        try await confirmation { confirm in\n            self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n                #expect(self.channel.identifier == channelID)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: nil,\n                    statusCode: 400,\n                    headers: [:]\n                )\n            }\n\n            do {\n                let _ = try await self.contactManager.resolveAuth(\n                    identifier: \"some contact id\"\n                )\n                Issue.record(\"Should throw\")\n            } catch {}\n        }\n    }\n\n    @Test(\"Generate default contact info\")\n    func generateDefaultContactInfo() async {\n        var contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(contactInfo == nil)\n\n\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n        contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(contactInfo != nil)\n\n\n        #expect(contactInfo!.contactID.lowercased() == contactInfo!.contactID)\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: contactInfo!.contactID,\n                    isStable: true,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n    }\n\n    @Test(\"Generate default contact info lowercased ID\")\n    func generateDefaultContactInfoLowercasedID() async {\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n        let contactInfo = await self.contactManager.currentContactIDInfo()\n        #expect(contactInfo != nil)\n        #expect(contactInfo!.contactID.lowercased() == contactInfo!.contactID)\n    }\n\n    @Test(\"Generate default contact info already set\")\n    func generateDefaultContactInfoAlreadySet() async throws {\n        await self.contactManager.addOperation(.resolve)\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n        let _ = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        let contactInfo = await self.contactManager.currentContactIDInfo()!\n\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n\n        let afterGenerate = await self.contactManager.currentContactIDInfo()\n        #expect(contactInfo == afterGenerate)\n    }\n\n    @Test(\"Contact unstable pending reset\")\n    func contactUnstablePendingReset() async throws {\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n        let contactInfo = await self.contactManager.currentContactIDInfo()!\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: contactInfo.contactID,\n                    isStable: true,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n\n        await self.contactManager.addOperation(.reset)\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: contactInfo.contactID,\n                    isStable: false,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n    }\n\n    @Test(\"Contact unstable pending identify\")\n    func contactUnstablePendingIdentify() async throws {\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n        let contactInfo = await self.contactManager.currentContactIDInfo()!\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: contactInfo.contactID,\n                    isStable: true,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n\n        await self.contactManager.addOperation(.identify(\"something something\"))\n\n        await self.verifyUpdates([\n            .contactIDUpdate(\n                ContactIDInfo(\n                    contactID: contactInfo.contactID,\n                    isStable: false,\n                    namedUserID: nil,\n                    resolveDate: self.date.now\n                )\n            )\n        ])\n    }\n\n    @Test(\"Pending updates combine operations\")\n    func pendingUpdatesCombineOperations() async throws {\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n\n        let tags = [\n            TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .add)\n        ]\n\n        let attributes = [\n            AttributeUpdate(attribute: \"some attribute\", type: .set, jsonValue: \"cool\", date: self.date.now)\n        ]\n\n        let subscriptions = [\n            ScopedSubscriptionListUpdate(listId: \"some list\", type: .unsubscribe, scope: .app, date: self.date.now)\n        ]\n\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: tags,\n                attributeUpdates: nil,\n                subscriptionListsUpdates: nil\n            )\n        )\n\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: nil,\n                attributeUpdates: attributes,\n                subscriptionListsUpdates: nil\n            )\n        )\n\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: nil,\n                attributeUpdates: nil,\n                subscriptionListsUpdates: subscriptions\n            )\n        )\n\n        let contactID = await self.contactManager.currentContactIDInfo()!.contactID\n        let pendingOverrides = await self.contactManager.pendingAudienceOverrides(\n            contactID: contactID\n        )\n\n        #expect(tags == pendingOverrides.tags)\n        #expect(attributes == pendingOverrides.attributes)\n        #expect(subscriptions == pendingOverrides.subscriptionLists)\n    }\n\n    @Test(\"Pending updates\")\n    func pendingUpdates() async throws {\n        let tags = [\n            TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .add)\n        ]\n\n        let attributes = [\n            AttributeUpdate(attribute: \"some attribute\", type: .set, jsonValue: \"cool\", date: self.date.now)\n        ]\n\n        let subscriptions = [\n            ScopedSubscriptionListUpdate(listId: \"some list\", type: .unsubscribe, scope: .app, date: self.date.now)\n        ]\n\n        await self.contactManager.generateDefaultContactIDIfNotSet()\n        let contactID = await self.contactManager.currentContactIDInfo()!.contactID\n\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: tags,\n                attributeUpdates: nil,\n                subscriptionListsUpdates: nil\n            )\n        )\n\n        await self.contactManager.addOperation(.identify(\"some user\"))\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: nil,\n                attributeUpdates: attributes,\n                subscriptionListsUpdates: nil\n            )\n        )\n\n        await self.contactManager.addOperation(.identify(\"some other user\"))\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: nil,\n                attributeUpdates: nil,\n                subscriptionListsUpdates: subscriptions\n            )\n        )\n\n\n        // Since are an anon user ID, we should get the tags,\n        // assume the identify will keep the same contact id,\n        // get the attributes, then skip the subscriptions\n        // because it will for sure be a different contact ID\n\n        let anonUserOverrides = await self.contactManager.pendingAudienceOverrides(contactID: contactID)\n        #expect(tags == anonUserOverrides.tags)\n        #expect(attributes == anonUserOverrides.attributes)\n        #expect([] == anonUserOverrides.subscriptionLists)\n\n\n        // If we request a stale contact ID, it should return empty overrides\n        let staleOverrides = await self.contactManager.pendingAudienceOverrides(contactID: \"not the current contact id\")\n        #expect([] == staleOverrides.tags)\n        #expect([] == staleOverrides.attributes)\n        #expect([] == staleOverrides.subscriptionLists)\n    }\n\n    @Test(\"Register email\")\n    func registerEmail() async throws {\n        let expectedAddress = \"ua@airship.com\"\n        let expectedOptions = EmailRegistrationOptions.options(\n            transactionalOptedIn: Date(),\n            properties: [\"interests\": \"newsletter\"],\n            doubleOptIn: true\n        )\n\n        await self.contactManager.addOperation(\n            .registerEmail(address: expectedAddress, options: expectedOptions)\n        )\n\n        // Should resolve contact first\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Then register the channel\n        try await confirmation { confirm in\n            self.apiClient.registerEmailCallback = { contactID, address, options, locale in\n                #expect(contactID == self.anonIdentifyResponse.contact.contactID)\n                #expect(address == expectedAddress)\n                #expect(options == options)\n                #expect(locale == self.localeManager.currentLocale)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: .init(channelType: .email, channelID: \"some channel\"),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Register open\")\n    func registerOpen() async throws {\n        let expectedAddress = \"ua@airship.com\"\n        let expectedOptions = OpenRegistrationOptions.optIn(\n            platformName: \"my_platform\",\n            identifiers: [\"model\": \"4\"]\n        )\n\n        await self.contactManager.addOperation(\n            .registerOpen(address: expectedAddress, options: expectedOptions)\n        )\n\n        // Should resolve contact first\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Then register the channel\n        try await confirmation { confirm in\n            self.apiClient.registerOpenCallback = { contactID, address, options, locale in\n                #expect(contactID == self.anonIdentifyResponse.contact.contactID)\n                #expect(address == expectedAddress)\n                #expect(options == options)\n                #expect(locale == self.localeManager.currentLocale)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: .init(channelType: .open, channelID: \"some channel\"),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Register SMS\")\n    func registerSMS() async throws {\n        let expectedAddress = \"15035556789\"\n        let expectedOptions = SMSRegistrationOptions.optIn(senderID: \"28855\")\n\n        await self.contactManager.addOperation(\n            .registerSMS(msisdn: expectedAddress, options: expectedOptions)\n        )\n\n        // Should resolve contact first\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Then register the channel\n        try await confirmation { confirm in\n            self.apiClient.registerSMSCallback = {contactID, address, options, locale in\n                #expect(address == expectedAddress)\n                #expect(options == options)\n                #expect(locale == self.localeManager.currentLocale)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: .init(channelType: .sms, channelID: \"some channel\"),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Resend email\")\n    func resendEmail() async throws {\n        let expectedAddress: String = \"example@email.com\"\n\n        let expectedResendOptions = ResendOptions(emailAddress: expectedAddress)\n\n        // Should resolve contact first after checking the token\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let pendingChannel = makePendingEmailContactChannel(address: expectedAddress)\n\n        await self.contactManager.addOperation(\n            .resend(channel: pendingChannel)\n        )\n\n        try await confirmation { confirm in\n            self.apiClient.resendCallback = { resendOptions in\n                #expect(resendOptions == expectedResendOptions)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: true,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Resend SMS\")\n    func resendSMS() async throws {\n        let expectedMSISDN: String = \"12345\"\n        let expectedSenderID: String = \"1111\"\n\n        let expectedResendOptions = ResendOptions(msisdn: expectedMSISDN, senderID: expectedSenderID)\n\n        // Should resolve contact first after checking the token\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let pendingChannel = makePendingSMSContactChannel(msisdn: expectedMSISDN, sender: expectedSenderID)\n\n        await self.contactManager.addOperation(\n            .resend(channel: pendingChannel)\n        )\n\n        try await confirmation { confirm in\n            self.apiClient.resendCallback = { resendOptions in\n                #expect(resendOptions == expectedResendOptions)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: true,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Resend channel\")\n    func resendChannel() async throws {\n        let expectedChannelID = \"12345\"\n        let expectedChannelType: ChannelType = ChannelType.email\n\n        let expectedResendOptions = ResendOptions(channelID: expectedChannelID, channelType: expectedChannelType)\n\n        // Should resolve contact first after checking the token\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let registeredChannel = makeRegisteredContactChannel(from: expectedChannelID)\n\n        await self.contactManager.addOperation(\n            .resend(channel: registeredChannel)\n        )\n\n        try await confirmation { confirm in\n            self.apiClient.resendCallback = { resendOptions in\n                #expect(resendOptions == expectedResendOptions)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: true,\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Disassociate\")\n    func disassociate() async throws {\n        let expectedChannelID = \"12345\"\n        let registeredChannel = makeRegisteredContactChannel(from: expectedChannelID)\n\n        await self.contactManager.addOperation(\n            .disassociateChannel(channel: registeredChannel)\n        )\n\n        // Should resolve contact first\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Then disassociate the channel\n        try await confirmation { confirm in\n            self.apiClient.disassociateChannelCallback = { contactID, channelID, type in\n                #expect(channelID == expectedChannelID)\n                #expect(type == ChannelType.email)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: ContactDisassociateChannelResult(channelID: channelID),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Associate channel\")\n    func associateChannel() async throws {\n        await self.contactManager.addOperation(\n            .associateChannel(\n                channelID: \"some channel\",\n                channelType: .open\n            )\n        )\n\n        // Should resolve contact first\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Then register the channel\n        try await confirmation { confirm in\n            self.apiClient.associateChannelCallback = { contactID, channelID, type in\n                #expect(contactID == \"some contact\")\n                #expect(channelID == \"some channel\")\n                #expect(type == .open)\n                confirm()\n                return AirshipHTTPResponse(\n                    result: .init(channelType: type, channelID: \"some channel\"),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await self.workManager.launchTask(\n                request: AirshipWorkRequest(\n                    workID: ContactManager.updateTaskID\n                )\n            )\n            #expect(result == .success)\n        }\n\n        await resolveExpectation.fulfillment\n    }\n\n    @Test(\"Update\")\n    func update() async throws {\n        let tags = [\n            TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .add),\n            TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .remove),\n            TagGroupUpdate(group: \"some group\", tags: [\"some other tag\"], type: .remove)\n        ]\n\n        let attributes = [\n            AttributeUpdate(attribute: \"some other attribute\", type: .set, jsonValue: \"cool\", date: self.date.now),\n            AttributeUpdate(attribute: \"some attribute\", type: .set, jsonValue: \"cool\", date: self.date.now),\n            AttributeUpdate(attribute: \"some attribute\", type: .remove, jsonValue: \"cool\", date: self.date.now)\n        ]\n\n        let subscriptions = [\n            ScopedSubscriptionListUpdate(listId: \"some other list\", type: .subscribe, scope: .app, date: self.date.now),\n            ScopedSubscriptionListUpdate(listId: \"some list\", type: .unsubscribe, scope: .app, date: self.date.now),\n            ScopedSubscriptionListUpdate(listId: \"some list\", type: .subscribe, scope: .app, date: self.date.now)\n        ]\n\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: tags,\n                attributeUpdates: attributes,\n                subscriptionListsUpdates: subscriptions\n            )\n        )\n\n        // Should resolve contact first\n        let resolveExpectation = expectation(description: \"resolve\")\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            resolveExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Then register the channel\n        let updateExpectation = expectation(description: \"update\")\n        self.apiClient.updateCallback = { contactID, tagUpdates, attributeUpdates, subscriptionUpdates in\n            #expect(contactID == self.anonIdentifyResponse.contact.contactID)\n            #expect(tagUpdates == AudienceUtils.collapse(tags))\n            #expect(attributeUpdates == AudienceUtils.collapse(attributes))\n            #expect(subscriptionUpdates == AudienceUtils.collapse(subscriptions))\n            updateExpectation.fulfill()\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let audienceCallbackExpectation = expectation(description: \"audience callback\")\n        await self.contactManager.onAudienceUpdated { update in\n            #expect(update.tags == AudienceUtils.collapse(tags))\n            #expect(update.attributes == AudienceUtils.collapse(attributes))\n            #expect(update.subscriptionLists == AudienceUtils.collapse(subscriptions))\n            audienceCallbackExpectation.fulfill()\n        }\n\n        let result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n        #expect(result == .success)\n\n        await resolveExpectation.fulfillment\n        await updateExpectation.fulfillment\n        await audienceCallbackExpectation.fulfillment\n    }\n\n    @Test(\"Conflict\")\n    func conflict() async throws {\n        let tags = [\n            TagGroupUpdate(group: \"some group\", tags: [\"tag\"], type: .add),\n        ]\n\n        let attributes = [\n            AttributeUpdate(attribute: \"some attribute\", type: .set, jsonValue: \"cool\", date: self.date.now),\n        ]\n\n        let subscriptions = [\n            ScopedSubscriptionListUpdate(listId: \"some list\", type: .subscribe, scope: .app, date: self.date.now),\n        ]\n\n        // Adds some anon data\n        await self.contactManager.addOperation(\n            .update(\n                tagUpdates: tags,\n                attributeUpdates: attributes,\n                subscriptionListsUpdates: subscriptions\n            )\n        )\n\n        // resolve\n        self.apiClient.resolveCallback = { channelID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.anonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // update\n        self.apiClient.updateCallback = { contactID, tagUpdates, attributeUpdates, subscriptionUpdates in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // identify\n        self.apiClient.identifyCallback = { channelID, namedUserID, contactID, possiblyOrphanedContactID in\n            return AirshipHTTPResponse(\n                result: self.nonAnonIdentifyResponse,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        var result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n\n        await self.contactManager.addOperation(.identify(\"some named user\"))\n\n        result = try await self.workManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: ContactManager.updateTaskID\n            )\n        )\n        #expect(result == .success)\n\n\n        let expctedConflictEvent =  ContactConflictEvent(\n            tags: [\"some group\": [\"tag\"]],\n            attributes: [\"some attribute\": \"cool\"],\n            associatedChannels: [],\n            subscriptionLists: [\"some list\": [.app]],\n            conflictingNamedUserID: \"some named user\"\n        )\n        // resolve, update, resolve, conflict\n        let conflict = await self.collectUpdates(count: 4).last\n        #expect(conflict == .conflict(expctedConflictEvent))\n    }\n\n    private func collectUpdates(count: Int) async -> [ContactUpdate] {\n        guard count > 0 else { return [] }\n\n        var collected: [ContactUpdate] = []\n        for await contactUpdate in await self.contactManager.contactUpdates {\n            collected.append(contactUpdate)\n            if (collected.count == count) {\n                break\n            }\n        }\n\n        return collected\n    }\n\n\n    private func makePendingEmailContactChannel(address: String) -> ContactChannel {\n        return .email(\n            .pending(\n                ContactChannel.Email.Pending(\n                    address: address,\n                    registrationOptions: .options(properties: nil, doubleOptIn: true)\n                )\n            )\n        )\n    }\n\n    private func makePendingSMSContactChannel(msisdn: String, sender: String) -> ContactChannel {\n        return .sms(\n            .pending(\n                ContactChannel.Sms.Pending(\n                    address: msisdn,\n                    registrationOptions: .optIn(senderID: sender)\n                )\n            )\n        )\n    }\n\n    private func makeRegisteredContactChannel(from channelID: String) -> ContactChannel {\n        return .email(\n            .registered(\n                ContactChannel.Email.Registered(\n                    channelID: channelID,\n                    maskedAddress: \"****@email.com\"\n                )\n            )\n        )\n    }\n\n    private func verifyUpdates(_ expected: [ContactUpdate], sourceLocation: SourceLocation = #_sourceLocation) async {\n        let collected = await self.collectUpdates(count: expected.count)\n        #expect(collected == expected, sourceLocation: sourceLocation)\n    }\n\n    private func expectation(description: String, expectedFulfillmentCount: Int = 1) -> Expectation {\n        return Expectation(description: description, expectedFulfillmentCount: expectedFulfillmentCount)\n    }\n}\n\nactor Expectation {\n    private var count: Int = 0\n    private let expectedCount: Int\n    private let description: String\n\n    init(description: String, expectedFulfillmentCount: Int = 1) {\n        self.description = description\n        self.expectedCount = expectedFulfillmentCount\n    }\n\n    nonisolated func fulfill() {\n        Task {\n            await self.incrementCount()\n        }\n    }\n\n    private func incrementCount() {\n        count += 1\n    }\n\n    var fulfillment: Void {\n        get async {\n            while count < expectedCount {\n                await Task.yield()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ContactOperationTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ContactOperationTests: XCTestCase {\n\n    // SDK 16 payload\n    private let legacyPayload = \"\"\"\n[{\\\"type\\\":\\\"update\\\",\\\"payload\\\":{\\\"tagUpdates\\\":[{\\\"group\\\":\\\"group\\\",\\\"tags\\\":[\\\"tags\\\"],\\\"type\\\":2}]}},{\\\"type\\\":\\\"resolve\\\",\\\"payload\\\":null},{\\\"type\\\":\\\"identify\\\",\\\"payload\\\":{\\\"identifier\\\":\\\"some-user\\\"}},{\\\"type\\\":\\\"reset\\\",\\\"payload\\\":null},{\\\"type\\\":\\\"registerEmail\\\",\\\"payload\\\":{\\\"address\\\":\\\"ua@airship.com\\\",\\\"options\\\":{\\\"doubleOptIn\\\":true,\\\"transactionalOptedIn\\\":700424522.44925797,\\\"properties\\\":{\\\"jsonEncodedValue\\\":\\\"{\\\\\\\"interests\\\\\\\":\\\\\\\"newsletter\\\\\\\"}\\\"}}}},{\\\"type\\\":\\\"registerSMS\\\",\\\"payload\\\":{\\\"options\\\":{\\\"senderID\\\":\\\"28855\\\"},\\\"msisdn\\\":\\\"15035556789\\\"}},{\\\"type\\\":\\\"registerOpen\\\",\\\"payload\\\":{\\\"address\\\":\\\"open_address\\\",\\\"options\\\":{\\\"identifiers\\\":{\\\"model\\\":\\\"4\\\"},\\\"platformName\\\":\\\"my_platform\\\"}}}]\n\"\"\"\n\n    // SDK 17 payload\n    private let updatedPayload = \"\"\"\n[{\\\"type\\\":\\\"update\\\",\\\"payload\\\":{\\\"tagUpdates\\\":[{\\\"group\\\":\\\"group\\\",\\\"tags\\\":[\\\"tags\\\"],\\\"type\\\":2}]}},{\\\"type\\\":\\\"resolve\\\",\\\"payload\\\":null},{\\\"type\\\":\\\"identify\\\",\\\"payload\\\":{\\\"identifier\\\":\\\"some-user\\\"}},{\\\"type\\\":\\\"reset\\\",\\\"payload\\\":null},{\\\"type\\\":\\\"registerEmail\\\",\\\"payload\\\":{\\\"address\\\":\\\"ua@airship.com\\\",\\\"options\\\":{\\\"doubleOptIn\\\":true,\\\"transactionalOptedIn\\\":700424522.44925797,\\\"properties\\\":{\\\"interests\\\":\\\"newsletter\\\"}}}},{\\\"type\\\":\\\"registerSMS\\\",\\\"payload\\\":{\\\"options\\\":{\\\"senderID\\\":\\\"28855\\\"},\\\"msisdn\\\":\\\"15035556789\\\"}},{\\\"type\\\":\\\"registerOpen\\\",\\\"payload\\\":{\\\"address\\\":\\\"open_address\\\",\\\"options\\\":{\\\"identifiers\\\":{\\\"model\\\":\\\"4\\\"},\\\"platformName\\\":\\\"my_platform\\\"}}},\n    {\\\"type\\\":\\\"verify\\\",\\\"payload\\\":{\\\"date\\\":500.0}}]\n\"\"\"\n\n    func testLegacyDecode() throws {\n        let fromJSON = try JSONDecoder().decode([ContactOperation].self, from: legacyPayload.data(using: .utf8)!)\n        let toJSON = try JSONEncoder().encode(fromJSON)\n\n        XCTAssertEqual(\n            try AirshipJSON.from(json: String(data: toJSON, encoding: .utf8)),\n            try AirshipJSON.from(json: legacyPayload)\n        )\n    }\n\n    func testDecode() throws {\n        let fromJSON = try JSONDecoder().decode([ContactOperation].self, from: updatedPayload.data(using: .utf8)!)\n        let toJSON = try JSONEncoder().encode(fromJSON)\n\n        XCTAssertEqual(\n            try AirshipJSON.from(json: String(data: toJSON, encoding: .utf8)),\n            try AirshipJSON.from(json: updatedPayload)\n        )\n    }\n\n    func testEncode() throws {\n        let expected = [\n            ContactOperation.update(tagUpdates: [\n                TagGroupUpdate.init(group: \"group\", tags: [\"tags\"], type: .set)\n            ]),\n            ContactOperation.resolve,\n            ContactOperation.identify(\"some-user\"),\n            ContactOperation.reset,\n            ContactOperation.registerEmail(\n                address: \"ua@airship.com\",\n                options: EmailRegistrationOptions.options(\n                    transactionalOptedIn: Date(timeIntervalSinceReferenceDate: 700424522.44925797),\n                    properties: [\"interests\": \"newsletter\"],\n                    doubleOptIn: true\n                )\n            ),\n            ContactOperation.registerSMS(\n                msisdn: \"15035556789\",\n                options: SMSRegistrationOptions.optIn(senderID: \"28855\")\n            ),\n            ContactOperation.registerOpen(\n                address: \"open_address\",\n                options: OpenRegistrationOptions.optIn(\n                    platformName: \"my_platform\",\n                    identifiers: [\"model\": \"4\"]\n                )\n            ),\n            ContactOperation.verify(Date(timeIntervalSinceReferenceDate: 500))\n        ]\n\n        let fromExpected = try JSONEncoder().encode(expected)\n\n        XCTAssertEqual(\n            try AirshipJSON.from(json: String(data: fromExpected, encoding: .utf8)),\n            try AirshipJSON.from(json: updatedPayload)\n        )\n    }\n\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ContactRemoteDataProviderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipCore\n\nfinal class ContactRemoteDataProviderDelegateTest: XCTestCase {\n\n    private let contact: TestContact = TestContact()\n    private let client: TestRemoteDataAPIClient = TestRemoteDataAPIClient()\n    private let config: RuntimeConfig = RuntimeConfig.testConfig()\n\n    private var delegate: ContactRemoteDataProviderDelegate!\n\n    override func setUpWithError() throws {\n        delegate = ContactRemoteDataProviderDelegate(\n            config: config,\n            apiClient: client,\n            contact: contact\n        )\n    }\n\n    func testIsRemoteDataInfoUpToDate() async throws {\n        contact.contactIDInfo = ContactIDInfo(contactID: \"some-contact-id\", isStable: true, namedUserID: nil)\n\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data-contact/ios/some-contact-id\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .contact,\n            contactID: \"some-contact-id\"\n        )\n\n        var isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue\n        )\n        XCTAssertTrue(isUpToDate)\n\n        // Different locale\n        isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: Locale(identifier: \"en\"),\n            randomValue: randomValue\n        )\n        XCTAssertFalse(isUpToDate)\n\n        // Different randomValue\n        isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue + 1\n        )\n        XCTAssertFalse(isUpToDate)\n\n        // Different contact ID\n        contact.contactIDInfo = ContactIDInfo(contactID: \"some-other-contact-id\", isStable: true, namedUserID: nil)\n        isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue\n        )\n        XCTAssertFalse(isUpToDate)\n\n        // Unstable contact ID\n        contact.contactIDInfo = ContactIDInfo(contactID: \"some-contact-id\", isStable: false, namedUserID: nil)\n        isUpToDate = await self.delegate.isRemoteDataInfoUpToDate(\n            remoteDatInfo,\n            locale: locale,\n            randomValue: randomValue\n        )\n        XCTAssertFalse(isUpToDate)\n    }\n\n    func testFetch() async throws {\n        contact.contactID = \"some-contact-id\"\n\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data-contact/ios/some-contact-id\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .contact,\n            contactID: \"some-contact-id\"\n        )\n\n        client.lastModified = \"some other time\"\n        client.fetchData = { url, auth, lastModified, info in\n            XCTAssertEqual(remoteDatInfo.url, url)\n            XCTAssertEqual(AirshipRequestAuth.contactAuthToken(identifier: \"some-contact-id\"), auth)\n            XCTAssertEqual(\"some time\", lastModified)\n\n            XCTAssertEqual(\n                RemoteDataInfo(\n                    url: try RemoteDataURLFactory.makeURL(\n                        config: self.config,\n                        path: \"/api/remote-data-contact/ios/some-contact-id\",\n                        locale: locale,\n                        randomValue: randomValue\n                    ),\n                    lastModifiedTime: \"some other time\",\n                    source: .contact,\n                    contactID: \"some-contact-id\"\n                ),\n                info\n            )\n\n            return AirshipHTTPResponse(\n                result: RemoteDataResult(\n                    payloads: [],\n                    remoteDataInfo: remoteDatInfo\n                ),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.delegate.fetchRemoteData(\n            locale: locale,\n            randomValue: randomValue,\n            lastRemoteDataInfo: remoteDatInfo\n        )\n\n        XCTAssertEqual(result.statusCode, 200)\n    }\n\n    func testFetchLastModifiedOutOfDate() async throws {\n        contact.contactID = \"some-other-contact-id\"\n\n        let locale = Locale(identifier: \"br\")\n        let randomValue = 1003\n\n        let remoteDatInfo = RemoteDataInfo(\n            url: try RemoteDataURLFactory.makeURL(\n                config: config,\n                path: \"/api/remote-data-contact/ios/some-contact-id\",\n                locale: locale,\n                randomValue: randomValue\n            ),\n            lastModifiedTime: \"some time\",\n            source: .contact,\n            contactID: \"some-contact-id\"\n        )\n\n        client.fetchData = { _, _, lastModified, _ in\n            XCTAssertNil(lastModified)\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.delegate.fetchRemoteData(\n            locale: locale,\n            randomValue: randomValue + 1,\n            lastRemoteDataInfo: remoteDatInfo\n        )\n\n        XCTAssertEqual(result.statusCode, 400)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ContactSubscriptionListAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ContactSubscriptionListAPIClientTest: XCTestCase {\n\n    private let session: TestAirshipRequestSession = TestAirshipRequestSession()\n    private var contactAPIClient: ContactSubscriptionListAPIClient!\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n\n    override func setUpWithError() throws {\n        self.contactAPIClient = ContactSubscriptionListAPIClient(\n            config: self.config,\n            session: self.session\n        )\n    }\n\n    func testGetContactLists() async throws {\n        let responseBody = \"\"\"\n            {\n               \"ok\" : true,\n               \"subscription_lists\": [\n                  {\n                     \"list_ids\": [\"example_listId-1\", \"example_listId-3\"],\n                      \"scope\": \"email\"\n                  },\n                  {\n                     \"list_ids\": [\"example_listId-2\", \"example_listId-4\"],\n                     \"scope\": \"app\"\n                  },\n                  {\n                     \"list_ids\": [\"example_listId-2\"],\n                     \"scope\": \"web\"\n                  }\n               ],\n            }\n            \"\"\"\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n        self.session.data = responseBody.data(using: .utf8)\n\n        let expected: [String: [ChannelScope]] = [\n            \"example_listId-1\": [.email],\n            \"example_listId-2\": [.app, .web],\n            \"example_listId-3\": [.email],\n            \"example_listId-4\": [.app],\n        ]\n\n        let response = try await self.contactAPIClient.fetchSubscriptionLists(\n            contactID: \"some-contact\"\n        )\n        XCTAssertTrue(response.isSuccess)\n\n        XCTAssertEqual(expected, response.result!)\n\n        XCTAssertEqual(\"GET\", self.session.lastRequest?.method)\n        XCTAssertEqual(\n            \"\\(self.config.deviceAPIURL!)/api/subscription_lists/contacts/some-contact\",\n            self.session.lastRequest?.url?.absoluteString\n        )\n    }\n\n    func testGetContactListParseError() async throws {\n        let responseBody = \"What?\"\n\n        self.session.data = responseBody.data(using: .utf8)\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        do {\n            _ = try await self.contactAPIClient.fetchSubscriptionLists(\n                contactID: \"some-contact\"\n            )\n            XCTFail()\n        }\n        catch {\n\n        }\n    }\n\n    func testGetContactListError() async throws {\n        let sessionError = AirshipErrors.error(\"error!\")\n        self.session.error = sessionError\n\n        do {\n            _ = try await self.contactAPIClient.fetchSubscriptionLists(\n                contactID: \"some-contact\"\n            )\n            XCTFail()\n        }\n        catch {\n\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/CustomEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class CustomEventTest: XCTestCase {\n\n    /**\n     * Test creating a custom event.\n     */\n    func testCustomEvent() {\n        let eventName = \"\".padding(toLength: 255, withPad: \"EVENT_NAME\", startingAt: 0)\n        let transactionId = \"\".padding(toLength: 255, withPad: \"TRANSACTION_ID\", startingAt: 0)\n        let interactionId = \"\".padding(toLength: 255, withPad: \"INTERACTION_ID\", startingAt: 0)\n        let interactionType = \"\".padding(toLength: 255, withPad: \"INTERACTION_TYPE\", startingAt: 0)\n        let templateType = \"\".padding(toLength: 255, withPad: \"TEMPLATE_TYPE\", startingAt: 0)\n        \n        var event = CustomEvent(name: eventName, value: Double(Int32.min))\n        event.transactionID = transactionId\n        event.interactionID = interactionId\n        event.interactionType = interactionType\n        event.templateType = templateType\n        \n        XCTAssertEqual(eventName, event.data[\"event_name\"] as? String, \"Unexpected event name.\")\n        XCTAssertEqual(transactionId, event.data[\"transaction_id\"] as? String, \"Unexpected transaction ID.\")\n        XCTAssertEqual(interactionId, event.data[\"interaction_id\"] as? String, \"Unexpected interaction ID.\")\n        XCTAssertEqual(interactionType, event.data[\"interaction_type\"] as? String, \"Unexpected interaction type.\")\n        XCTAssertEqual(templateType, event.data[\"template_type\"] as? String, \"Unexpected template type.\")\n        XCTAssertEqual(NSNumber(value: -2147483648000000), event.data[\"event_value\"] as? NSNumber, \"Unexpected event value.\")\n    }\n    \n    /**\n     * Test setting an event name.\n     */\n    func testSetCustomEventName() {\n        var event = CustomEvent(name: \"event name\")\n        XCTAssert(event.isValid())\n        \n        let largeName = \"\".padding(toLength: 255, withPad: \"event-name\", startingAt: 0)\n        event.eventName = largeName\n        XCTAssert(event.isValid())\n    }\n    \n    /**\n     * Test setting the interaction ID.\n     */\n    func testSetInteractionID() {\n        var event = CustomEvent(name: \"event name\")\n        XCTAssertNil(event.interactionID, \"Interaction ID should default to nil\")\n        \n        let longInteractionId = \"\".padding(toLength: 255, withPad: \"INTERACTION_ID\", startingAt: 0)\n        event.interactionID = longInteractionId\n        XCTAssert(event.isValid())\n        \n        event.interactionID = nil\n        XCTAssert(event.isValid())\n    }\n    \n    /**\n     * Test setting the interaction type.\n     */\n    func testSetInteractionType() {\n        var event = CustomEvent(name: \"event name\")\n        XCTAssertNil(event.interactionType, \"Interaction type should default to nil\")\n        \n        let longInteractionType = \"\".padding(toLength: 255, withPad: \"INTERACTION_TYPE\", startingAt: 0)\n        event.interactionType = longInteractionType\n        XCTAssert(event.isValid())\n        \n        event.interactionType = nil\n        XCTAssert(event.isValid())\n    }\n    \n    /**\n     * Test setting the transaction ID\n     */\n    func testSetTransactionID() {\n        var event = CustomEvent(name: \"event name\")\n        XCTAssertNil(event.transactionID, \"Transaction ID should default to nil\")\n\n        let longTransactionID = \"\".padding(toLength: 255, withPad: \"TRANSACTION_ID\", startingAt: 0)\n\n        event.transactionID = longTransactionID\n        XCTAssertTrue(event.isValid())\n\n        event.transactionID = nil\n        XCTAssertTrue(event.isValid())\n    }\n    \n    /**\n     * Test set template type\n     */\n    func testSetTemplateType() {\n        var event = CustomEvent(name: \"event name\")\n        XCTAssertNil(event.templateType, \"Template type should default to nil\")\n\n        let longTemplateType = \"\".padding(toLength: 255, withPad: \"TEMPLATE_TYPE\", startingAt: 0)\n\n        event.templateType = longTemplateType\n        XCTAssertTrue(event.isValid())\n\n        event.templateType = nil\n        XCTAssertTrue(event.isValid())\n    }\n\n    func testEventValue() {\n        var event = CustomEvent(name: \"event name\", value: 100)\n        XCTAssertEqual(100, event.eventValue)\n        XCTAssert(event.isValid())\n        \n        // Max value\n        let maxValue = Double(Int32.max)\n        event = CustomEvent(name: \"event name\", value: maxValue)\n        XCTAssertEqual(NSNumber(value: 2147483647000000), event.data[\"event_value\"] as? NSNumber)\n        XCTAssertTrue(event.isValid())\n\n        // Above Max\n        let aboveMax = Decimal(maxValue).advanced(by: 0.0001).doubleValue\n        event = CustomEvent(name: \"event name\", value: aboveMax)\n        XCTAssertFalse(event.isValid())\n\n        // Min value\n        let minValue = Double(Int32.min)\n        event = CustomEvent(name: \"event name\", value: minValue)\n        XCTAssertEqual(NSNumber(value: -2147483648000000), event.data[\"event_value\"] as? NSNumber)\n        XCTAssertTrue(event.isValid())\n\n        // Below min\n        let belowMin = Decimal(minValue).advanced(by: -0.000001).doubleValue\n        event = CustomEvent(name: \"event name\", value: belowMin)\n        XCTAssertFalse(event.isValid())\n\n        // 0\n        event = CustomEvent(name: \"event name\", value: 0)\n        XCTAssertEqual(NSNumber(value: 0), event.data[\"event_value\"] as? NSNumber)\n        XCTAssertTrue(event.isValid())\n\n        // NaN\n        event = CustomEvent(name: \"event name\", value: Double.nan)\n        XCTAssertEqual(event.eventValue, Decimal(1.0))\n        XCTAssertTrue(event.isValid())\n\n        // Infinity\n        event = CustomEvent(name: \"event name\", value: Double.infinity)\n        XCTAssertEqual(event.eventValue, Decimal(1.0))\n        XCTAssertTrue(event.isValid())\n    }\n    \n    /**\n     * Test event value to data conversion.  The value should be a decimal multiplied by\n     * 10^6 and cast to a long.\n     */\n    func testEventValueToData() {\n        let eventValues: [Decimal: Int64] = [\n            123.123456789: 123123456,\n            9.999999999: 9999999,\n            99.999999999: 99999999,\n            999.999999999: 999999999,\n            9999.999999999: 9999999999,\n            99999.999999999: 99999999999,\n            999999.999999999: 999999999999,\n            9999999.999999999: 9999999999999\n        ]\n\n        eventValues.forEach { value, expected in\n            let event = CustomEvent(name: \"event name\", decimalValue: value)\n            XCTAssertTrue(event.isValid())\n            XCTAssertEqual(NSNumber(value: expected), event.data[\"event_value\"] as? NSNumber)\n        }\n    }\n\n    func testConversionSendID() {\n        let data = CustomEvent(name: \"event name\")\n            .eventBody(sendID: \"send id\", metadata: \"metadata\", formatValue: false)\n        XCTAssertEqual(\"send id\", data.object?[\"conversion_send_id\"]?.string)\n        XCTAssertEqual(\"metadata\", data.object?[\"conversion_metadata\"]?.string)\n    }\n\n    func testConversionSendIDSet() {\n        var event = CustomEvent(name: \"event name\")\n        event.conversionSendID = \"some other send id\"\n        event.conversionPushMetadata = \"some other metadata\"\n\n        let data = event.eventBody(sendID: \"send id\", metadata: \"metadata\", formatValue: false)\n        XCTAssertEqual(\"some other send id\", data.object?[\"conversion_send_id\"]?.string)\n        XCTAssertEqual(\"some other metadata\", data.object?[\"conversion_metadata\"]?.string)\n    }\n\n    func testMaxTotalPropertySize() throws {\n        var event = CustomEvent(name: \"event name\")\n\n        var properties: [String: NSNumber] = [:]\n        (0...5000).forEach({ properties[\"\\($0)\"] = 324 })\n        try event.setProperties(properties)\n\n        XCTAssertTrue(event.isValid())\n        \n        (0...2000).forEach({ properties[\"\\(5000 + $0)\"] = 324 })\n        try event.setProperties(properties)\n\n        XCTAssertFalse(event.isValid())\n    }\n\n    func testInApp() {\n        var event = CustomEvent(name: \"event name\")\n\n        // Defined in automation, just make sure it passes it through\n        event.inApp = AirshipJSON.makeObject { builder in\n            builder.set(string: \"foo\", key: \"bar\")\n        }\n\n        let result = try! AirshipJSON.wrap(event.data[\"in_app\"])\n\n        XCTAssertEqual(event.inApp, result)\n    }\n\n    func testCodableProperties() throws {\n        var event = CustomEvent(name: \"event name\")\n\n        try event.setProperties([\n            \"some-codable\": TestCodable(string: \"foo\", bool: false)\n        ])\n        let properties = event.data[\"properties\"] as! [String: Any]\n        let someCodable = properties[\"some-codable\"] as! [String: Any]\n\n        XCTAssertEqual(\"foo\", someCodable[\"string\"] as! String)\n        XCTAssertEqual(false, someCodable[\"bool\"] as! Bool)\n    }\n\n    func testDateProperties() throws {\n        var event = CustomEvent(name: \"event name\")\n        try event.setProperties([\n            \"some-date\": Date(timeIntervalSince1970: 10000.0)\n        ])\n\n        let properties = event.data[\"properties\"] as! [String: Any]\n        XCTAssertEqual(\"1970-01-01T02:46:40Z\", properties[\"some-date\"] as! String)\n    }\n}\n\nfileprivate struct TestCodable: Encodable {\n    let string: String\n    let bool: Bool\n}\n\nextension CustomEvent {\n    var data: [AnyHashable: Any] {\n        return self.eventBody(\n            sendID: nil,\n            metadata: nil,\n            formatValue: true\n        ).unWrap() as? [AnyHashable : Any] ?? [:]\n    }\n}\n\nextension Decimal {\n    var doubleValue: Double {\n        return NSDecimalNumber(decimal:self).doubleValue\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/CustomNotificationCategories.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>share_category</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>authorizationRequired</key>\n\t\t\t<false/>\n\t\t\t<key>destructive</key>\n\t\t\t<false/>\n\t\t\t<key>title</key>\n\t\t\t<string>Share</string>\n\t\t\t<key>foreground</key>\n\t\t\t<true/>\n\t\t\t<key>identifier</key>\n\t\t\t<string>share_button</string>\n\t\t</dict>\n\t</array>\n\t<key>follow_category</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>authorizationRequired</key>\n\t\t\t<false/>\n\t\t\t<key>destructive</key>\n\t\t\t<false/>\n\t\t\t<key>title_resource</key>\n\t\t\t<string>ua_follow_title_resource</string>\n\t\t\t<key>title</key>\n\t\t\t<string>FollowMe</string>\n\t\t\t<key>foreground</key>\n\t\t\t<true/>\n\t\t\t<key>identifier</key>\n\t\t\t<string>follow_button</string>\n\t\t</dict>\n\t</array>\n\t<key>yes_no_category</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>authenticationRequired</key>\n\t\t\t<false/>\n\t\t\t<key>destructive</key>\n\t\t\t<false/>\n\t\t\t<key>title</key>\n\t\t\t<string>Yes</string>\n\t\t\t<key>foreground</key>\n\t\t\t<true/>\n\t\t\t<key>identifier</key>\n\t\t\t<string>yes_button</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>authenticationRequired</key>\n\t\t\t<true/>\n\t\t\t<key>destructive</key>\n\t\t\t<true/>\n\t\t\t<key>title</key>\n\t\t\t<string>No</string>\n\t\t\t<key>foreground</key>\n\t\t\t<false/>\n\t\t\t<key>identifier</key>\n\t\t\t<string>no_button</string>\n\t\t</dict>\n\t</array>\n\t<key>text_input_category</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>text_input_button_title</key>\n\t\t\t<string>text_input_button</string>\n\t\t\t<key>text_input_placeholder</key>\n\t\t\t<string>placeholder_text</string>\n\t\t\t<key>action_type</key>\n\t\t\t<string>text_input</string>\n\t\t\t<key>authorizationRequired</key>\n\t\t\t<false/>\n\t\t\t<key>destructive</key>\n\t\t\t<false/>\n\t\t\t<key>title_resource</key>\n\t\t\t<string>ua_text_input_title_resource</string>\n\t\t\t<key>title</key>\n\t\t\t<string>TextInput</string>\n\t\t\t<key>foreground</key>\n\t\t\t<true/>\n\t\t\t<key>identifier</key>\n\t\t\t<string>text_input</string>\n\t\t</dict>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DeepLinkActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass DeepLinkActionTest: XCTestCase {\n\n    private let testURLOpener: TestURLOpener = TestURLOpener()\n    private let urlAllowList: TestURLAllowList = TestURLAllowList()\n    private var airship: TestAirshipInstance!\n\n    private var action: DeepLinkAction!\n\n    @MainActor\n    override func setUp() {\n        airship = TestAirshipInstance()\n        self.action = DeepLinkAction(urlOpener: self.testURLOpener)\n        self.airship.urlAllowList = self.urlAllowList\n        self.airship.makeShared()\n    }\n\n    override func tearDown() async throws {\n        TestAirshipInstance.clearShared()\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush,\n            ActionSituation.backgroundInteractiveButton\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    @MainActor\n    func testPerformDeepLinkDelegate() async throws {\n        let deepLinkDelegate = TestDeepLinkDelegate()\n        self.urlAllowList.isAllowedReturnValue = false\n        self.testURLOpener.returnValue = false\n\n        self.airship.deepLinkDelegate = deepLinkDelegate\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        _ = try await action.perform(arguments: args)\n\n        XCTAssertEqual(\"http://some-valid-url\", deepLinkDelegate.lastDeepLink?.absoluteString)\n        XCTAssertNil(self.testURLOpener.lastURL)\n    }\n\n    @MainActor\n    func testPerformFallback() async throws {\n        self.urlAllowList.isAllowedReturnValue = true\n        self.testURLOpener.returnValue = true\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        _ = try await action.perform(arguments: args)\n        XCTAssertEqual(\"http://some-valid-url\", self.testURLOpener.lastURL?.absoluteString)\n    }\n\n    @MainActor\n    func testPerformFallbackRejectsURL() async throws {\n        self.urlAllowList.isAllowedReturnValue = false\n        self.testURLOpener.returnValue = true\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"Should throw\")\n        } catch {}\n\n        XCTAssertNil(self.testURLOpener.lastURL)\n    }\n\n    @MainActor\n    func testPerformFallbackUnableToOpenURL() async throws {\n        self.urlAllowList.isAllowedReturnValue = true\n        self.testURLOpener.returnValue = false\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"Should throw\")\n        } catch {}\n\n        XCTAssertEqual(\"http://some-valid-url\", self.testURLOpener.lastURL?.absoluteString)\n    }\n\n    @MainActor\n    func testPerformInvalidURL() async throws {\n        self.urlAllowList.isAllowedReturnValue = true\n        self.testURLOpener.returnValue = true\n\n        let args = ActionArguments(\n            double: 10.0,\n            situation: .manualInvocation\n        )\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"Should throw\")\n        } catch {}\n\n        XCTAssertNil(self.testURLOpener.lastURL)\n    }\n}\n\n\nfileprivate final class TestDeepLinkDelegate: DeepLinkDelegate, @unchecked Sendable {\n    var lastDeepLink: URL?\n    func receivedDeepLink(_ deepLink: URL) async {\n        self.lastDeepLink = deepLink\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DeepLinkHandlerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\n@Suite(\"Deep Link Handler Tests\")\nstruct DeepLinkHandlerTest {\n\n    // MARK: - Test Helpers\n\n    actor TestDeepLinkDelegate: DeepLinkDelegate {\n        private(set) var receivedDeepLink: URL?\n\n        func receivedDeepLink(_ deepLink: URL) async {\n            self.receivedDeepLink = deepLink\n        }\n\n        func reset() {\n            self.receivedDeepLink = nil\n        }\n    }\n\n    final class TestComponent: AirshipComponent, @unchecked Sendable {\n        var onDeepLink: ((URL) -> Bool)?\n        private(set) var deepLink: URL?\n\n        func deepLink(_ deepLink: URL) -> Bool {\n            self.deepLink = deepLink\n            return onDeepLink?(deepLink) ?? false\n        }\n\n        func reset() {\n            self.deepLink = nil\n        }\n    }\n\n    // MARK: - Tests\n\n    @Test(\"Handler set - delegate not called\")\n    @MainActor\n    func testHandlerPreventsDelegate() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        let delegate = TestDeepLinkDelegate()\n        let component = TestComponent()\n        component.onDeepLink = { _ in\n            Issue.record(\"Component should not be called for non-uairship URLs\")\n            return false\n        }\n\n        var handlerCalled = false\n        let testURL = URL(string: \"myapp://deep-link/test\")!\n\n        airshipInstance.deepLinkHandler = { url in\n            #expect(url == testURL)\n            handlerCalled = true\n        }\n\n        airshipInstance.deepLinkDelegate = delegate\n        airshipInstance.components = [component]\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        #expect(result == true)\n        #expect(handlerCalled == true)\n        #expect(component.deepLink == nil)\n        await #expect(delegate.receivedDeepLink == nil)\n    }\n\n    @Test(\"No handler - uses delegate\")\n    @MainActor\n    func testNoHandlerUsesDelegate() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        let delegate = TestDeepLinkDelegate()\n        let testURL = URL(string: \"myapp://deep-link/direct\")!\n\n        airshipInstance.deepLinkDelegate = delegate\n        airshipInstance.components = []\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        #expect(result == true)\n        await #expect(delegate.receivedDeepLink == testURL)\n    }\n\n    @Test(\"Handler without delegate\")\n    @MainActor\n    func testHandlerWithoutDelegate() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        var handlerCalled = false\n        let testURL = URL(string: \"myapp://deep-link/no-delegate\")!\n\n        airshipInstance.deepLinkHandler = { url in\n            #expect(url == testURL)\n            handlerCalled = true\n        }\n\n        airshipInstance.components = []\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        #expect(result == true)\n        #expect(handlerCalled == true)\n    }\n\n    @Test(\"No handler and no delegate\")\n    @MainActor\n    func testNoHandlerNoDelegate() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        let testURL = URL(string: \"myapp://deep-link/unhandled\")!\n        airshipInstance.components = []\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        #expect(result == false)\n    }\n\n    @Test(\"UAirship scheme - handler intercepts\")\n    @MainActor\n    func testUAirshipSchemeHandler() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        let delegate = TestDeepLinkDelegate()\n        let component = TestComponent()\n        component.onDeepLink = { _ in false }\n\n        var handlerCalled = false\n        let testURL = URL(string: \"uairship://custom-action\")!\n\n        airshipInstance.deepLinkHandler = { url in\n            #expect(url == testURL)\n            handlerCalled = true\n        }\n\n        airshipInstance.deepLinkDelegate = delegate\n        airshipInstance.components = [component]\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        #expect(result == true)\n        #expect(handlerCalled == true)\n        #expect(component.deepLink == testURL)\n        await #expect(delegate.receivedDeepLink == nil) // Handler prevents delegate\n    }\n\n    @Test(\n        \"Different URL schemes\",\n        arguments: [\n            \"https://example.com/deep\",\n            \"myapp://home\",\n            \"custom://action/test\",\n            \"uairship://test\"\n        ]\n    )\n    @MainActor\n    func testDifferentURLSchemes(urlString: String) async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        let testURL = URL(string: urlString)!\n        var handlerCalled = false\n\n        airshipInstance.deepLinkHandler = { url in\n            #expect(url == testURL)\n            handlerCalled = true\n        }\n\n        airshipInstance.components = []\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        // Handler always returns true when set\n        let isUAirshipScheme = testURL.scheme == \"uairship\"\n        #expect(result == true)\n        #expect(handlerCalled == true || isUAirshipScheme)\n    }\n\n    @Test(\"Handler priority over delegate\")\n    @MainActor\n    func testHandlerPriorityOverDelegate() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        let delegate = TestDeepLinkDelegate()\n        var handlerCalled = false\n        let testURL = URL(string: \"myapp://test/priority\")!\n\n        // Both handler and delegate are set\n        airshipInstance.deepLinkHandler = { url in\n            #expect(url == testURL)\n            handlerCalled = true\n        }\n\n        airshipInstance.deepLinkDelegate = delegate\n        airshipInstance.components = []\n\n        let result = await Airship.shared.deepLink(testURL)\n\n        // Handler takes priority, delegate not called\n        #expect(result == true)\n        #expect(handlerCalled == true)\n        await #expect(delegate.receivedDeepLink == nil)\n    }\n\n    @Test(\"Thread safety - concurrent deep link handling\")\n    @MainActor\n    func testConcurrentDeepLinkHandling() async throws {\n        let airshipInstance = TestAirshipInstance()\n        airshipInstance.makeShared()\n        defer { TestAirshipInstance.clearShared() }\n\n        var processedURLs: Set<String> = []\n        let lock = NSLock()\n\n        airshipInstance.deepLinkHandler = { url in\n            lock.lock()\n            processedURLs.insert(url.absoluteString)\n            lock.unlock()\n            // Simulate some processing time\n            try? await Task.sleep(nanoseconds: 10_000_000) // 10ms\n        }\n\n        airshipInstance.components = []\n\n        // Create multiple URLs to process concurrently\n        let urls = (1...10).map { URL(string: \"myapp://test/\\($0)\")! }\n\n        // Process all URLs concurrently\n        await withTaskGroup(of: Bool.self) { group in\n            for url in urls {\n                group.addTask {\n                    await Airship.shared.deepLink(url)\n                }\n            }\n\n            for await result in group {\n                #expect(result == true)\n            }\n        }\n\n        // Verify all URLs were processed\n        lock.lock()\n        let finalCount = processedURLs.count\n        lock.unlock()\n\n        #expect(finalCount == urls.count)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DefaultAirshipRequestSessionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class DefaultAirshipRequestSessionTest: AirshipBaseTest {\n\n    private let testURLSession = TestURLRequestSession()\n    private var airshipSession: DefaultAirshipRequestSession!\n    private var nonce: String = UUID().uuidString\n\n    private var date: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n\n    override func setUpWithError() throws {\n        self.airshipSession = DefaultAirshipRequestSession(\n            appKey: \"testAppKey\",\n            appSecret: \"testAppSecret\",\n            session: self.testURLSession,\n            date: date\n        ) {\n            return self.nonce\n        }\n    }\n\n    func testDefaultHeaders() async throws {\n        let request = AirshipRequest(url: URL(string: \"http://neat.com\"))\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let headers = testURLSession.requests.last?.allHTTPHeaderFields\n        let expected = [\n            \"Accept-Encoding\": \"gzip;q=1.0, compress;q=0.5\",\n            \"User-Agent\": \"(UALib \\(AirshipVersion.version); testAppKey)\",\n            \"X-UA-App-Key\": \"testAppKey\",\n        ]\n\n        XCTAssertEqual(expected, headers)\n    }\n\n    func testCombinedHeaders() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            headers: [\n                \"foo\": \"bar\",\n                \"User-Agent\": \"Something else\",\n            ]\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let headers = testURLSession.requests.last?.allHTTPHeaderFields\n        let expected = [\n            \"foo\": \"bar\",\n            \"Accept-Encoding\": \"gzip;q=1.0, compress;q=0.5\",\n            \"User-Agent\": \"Something else\",\n            \"X-UA-App-Key\": \"testAppKey\"\n        ]\n\n        XCTAssertEqual(expected, headers)\n    }\n\n    func testBasicAuth() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .basic(username: \"name\", password: \"password\")\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let auth = testURLSession.requests.last?.allHTTPHeaderFields?[\n            \"Authorization\"\n        ]\n        XCTAssertEqual(\"Basic bmFtZTpwYXNzd29yZA==\", auth)\n    }\n\n    func testAppAuth() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .basicAppAuth\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let auth = testURLSession.requests.last?.allHTTPHeaderFields?[\n            \"Authorization\"\n        ]\n        XCTAssertEqual(\"Basic dGVzdEFwcEtleTp0ZXN0QXBwU2VjcmV0\", auth)\n    }\n\n    @MainActor\n    func testChannelAuthToken() async throws {\n        let authProvider = TestAuthTokenProvider() { identifier in\n            XCTAssertEqual(\"some identifier\", identifier)\n            return \"some auth token\"\n        }\n        airshipSession.channelAuthTokenProvider = authProvider\n\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .channelAuthToken(identifier: \"some identifier\")\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let authHeaders = [\n            \"Authorization\": \"Bearer some auth token\",\n            \"X-UA-Appkey\": \"testAppKey\",\n            \"X-UA-Auth-Type\": \"SDK-JWT\"\n        ]\n\n        let headers = testURLSession.requests.last?.allHTTPHeaderFields?.filter({ (key: String, value: String) in\n            authHeaders[key] != nil\n        })\n\n        XCTAssertEqual(authHeaders, headers)\n    }\n\n    @MainActor\n    func testContactAuthToken() async throws {\n        let authProvider = TestAuthTokenProvider() { identifier in\n            XCTAssertEqual(\"some contact\", identifier)\n            return \"some auth token\"\n        }\n        airshipSession.contactAuthTokenProvider = authProvider\n\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .contactAuthToken(identifier: \"some contact\")\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let authHeaders = [\n            \"Authorization\": \"Bearer some auth token\",\n            \"X-UA-Appkey\": \"testAppKey\",\n            \"X-UA-Auth-Type\": \"SDK-JWT\"\n        ]\n\n        let headers = testURLSession.requests.last?.allHTTPHeaderFields?.filter({ (key: String, value: String) in\n            authHeaders[key] != nil\n        })\n\n        XCTAssertEqual(authHeaders, headers)\n    }\n\n    func testGeneratedAppToken() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .generatedAppToken\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n        let timeStamp = AirshipDateFormatter.string(fromDate: self.date.now, format: .iso)\n\n        let token = try AirshipUtils.generateSignedToken(\n            secret: \"testAppSecret\",\n            tokenParams: [\"testAppKey\", nonce, timeStamp]\n        )\n\n        let authHeaders = [\n            \"Authorization\": \"Bearer \\(token)\",\n            \"X-UA-Appkey\": \"testAppKey\",\n            \"X-UA-Nonce\": nonce,\n            \"X-UA-Timestamp\": timeStamp\n        ]\n\n        let headers = testURLSession.requests.last?.allHTTPHeaderFields?.filter({ (key: String, value: String) in\n            authHeaders[key] != nil\n        })\n\n        XCTAssertEqual(authHeaders, headers)\n    }\n\n    func testGeneratedChannelToken() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .generatedChannelToken(identifier: \"some channel\")\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n        let timeStamp = AirshipDateFormatter.string(fromDate: self.date.now, format: .iso)\n\n        let token = try AirshipUtils.generateSignedToken(\n            secret: \"testAppSecret\",\n            tokenParams: [\"testAppKey\", \"some channel\", nonce, timeStamp]\n        )\n\n        let authHeaders = [\n            \"Authorization\": \"Bearer \\(token)\",\n            \"X-UA-Appkey\": \"testAppKey\",\n            \"X-UA-Channel-ID\": \"some channel\",\n            \"X-UA-Nonce\": nonce,\n            \"X-UA-Timestamp\": timeStamp\n        ]\n\n        let headers = testURLSession.requests.last?.allHTTPHeaderFields?.filter({ (key: String, value: String) in\n            authHeaders[key] != nil\n        })\n\n        XCTAssertEqual(authHeaders, headers)\n    }\n\n    @MainActor\n    func testExpiredChannelAuth() async throws {\n        let authProvider = TestAuthTokenProvider() { identifier in\n            XCTAssertEqual(\"some identifier\", identifier)\n            return \"some auth token\"\n        }\n\n        airshipSession.channelAuthTokenProvider = authProvider\n\n        let request = AirshipRequest(\n            url: URL(string: \"https://airship.com/something\"),\n            auth: .channelAuthToken(identifier: \"some identifier\")\n        )\n\n        self.testURLSession.responses = [\n            Response.makeResponse(status: 401),\n            Response.makeResponse(status: 401)\n        ]\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        XCTAssertEqual(2, authProvider.resolveAuthCount)\n        XCTAssertEqual([\"some auth token\", \"some auth token\"], authProvider.expiredTokens)\n    }\n\n    @MainActor\n    func testResolveAuthSequentially() async throws {\n\n        // Using a stream to send a result later on\n        var escapee: AsyncStream<String>.Continuation!\n        let stream = AsyncStream<String>() { continuation in\n            escapee = continuation\n        }\n\n        let authProvider = TestAuthTokenProvider() { identifier in\n            for await token in stream {\n                return token\n            }\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        airshipSession.channelAuthTokenProvider = authProvider\n\n        let request = AirshipRequest(\n            url: URL(string: \"https://airship.com/something\"),\n            auth: .channelAuthToken(identifier: \"some identifier\")\n        )\n\n        let airshipSession = self.airshipSession\n        await withTaskGroup(of: Void.self) { [escapee] group in\n            for _ in 1...4 {\n                group.addTask {\n                    let _ = try? await airshipSession?.performHTTPRequest(request)\n                }\n            }\n\n            group.addTask {\n                try? await Task.sleep(nanoseconds: 100)\n                escapee?.yield(\"token\")\n            }\n        }\n\n        XCTAssertEqual(1, authProvider.resolveAuthCount)\n    }\n\n    @MainActor\n    func testNilChannelAuthProviderThrows() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .channelAuthToken(identifier: \"some identifier\")\n        )\n\n        do {\n            let _ = try await self.airshipSession.performHTTPRequest(request)\n            XCTFail()\n        } catch {}\n    }\n\n    @MainActor\n    func testNilContactAuthProviderThrows() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .contactAuthToken(identifier: \"some contact\")\n        )\n\n        do {\n            let _ = try await self.airshipSession.performHTTPRequest(request)\n            XCTFail()\n        } catch {}\n    }\n\n    func testBearerAuth() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            auth: .bearer(token: \"some token\")\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let auth = testURLSession.requests.last?.allHTTPHeaderFields?[\n            \"Authorization\"\n        ]\n        XCTAssertEqual(\"Bearer some token\", auth)\n    }\n\n    func testBody() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            body: \"body\".data(using: .utf8)\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let body = testURLSession.requests.last?.httpBody\n        XCTAssertEqual(request.body, body)\n    }\n\n    func testMethod() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            method: \"HEAD\"\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let method = testURLSession.requests.last?.httpMethod\n        XCTAssertEqual(\"HEAD\", method)\n    }\n\n    func testDeflateBody() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"http://neat.com\"),\n            body: \"body\".data(using: .utf8),\n            contentEncoding: .deflate\n        )\n\n        let _ = try? await self.airshipSession.performHTTPRequest(request)\n\n        let body = testURLSession.requests.last?.httpBody\n        XCTAssertEqual(\n            \"S8pPqQQA\",\n            body?.base64EncodedString()\n        )\n\n        let contentEncoding = testURLSession.requests.last?.allHTTPHeaderFields?[\"Content-Encoding\"]\n        XCTAssertEqual(\"deflate\", contentEncoding)\n    }\n\n    func testDeflateRoundTrip() throws {\n        let testInputs: [Data] = [\n            \"body\".data(using: .utf8)!,\n            \"Hello, World! This is a test of deflate compression.\".data(using: .utf8)!,\n            String(repeating: \"ABCDEFGHIJ\", count: 1000).data(using: .utf8)!,\n            Data((0..<256).map { UInt8($0) }),\n            \"a\".data(using: .utf8)!,\n        ]\n\n        for (index, input) in testInputs.enumerated() {\n            let compressed = try (input as NSData).compressed(using: .zlib) as Data\n            let decompressed = try (compressed as NSData).decompressed(using: .zlib) as Data\n\n            XCTAssertEqual(\n                input,\n                decompressed,\n                \"Deflate round-trip failed for input \\(index) (size \\(input.count) bytes)\"\n            )\n        }\n    }\n\n    func testRequest() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"https://airship.com\")\n        )\n\n        self.testURLSession.responses = [\n            Response.makeResponse(status: 301, responseBody: \"Neat\")\n        ]\n\n        let response = try! await self.airshipSession.performHTTPRequest(\n            request\n        ) {\n            data,\n            response in\n            return String(data: data!, encoding: .utf8)\n        }\n\n        XCTAssertEqual(\"Neat\", response.result)\n        XCTAssertEqual(301, response.statusCode)\n    }\n\n    func testNilURL() async throws {\n        let request = AirshipRequest(\n            url: nil,\n            body: \"body\".data(using: .utf8),\n            contentEncoding: .deflate\n        )\n\n        do {\n            let _ = try await self.airshipSession.performHTTPRequest(request)\n            XCTFail()\n        } catch {\n\n        }\n    }\n\n    func testParseError() async throws {\n        let request = AirshipRequest(\n            url: URL(string: \"https://airship.com/something\")!\n        )\n\n        self.testURLSession.responses = [\n            Response.makeResponse(status: 301, responseBody: \"Neat\")\n        ]\n\n\n        do {\n            let _ = try await self.airshipSession.performHTTPRequest(request) {\n                _,\n                _ in\n                throw AirshipErrors.error(\"NEAT!\")\n            }\n            XCTFail()\n        } catch {\n\n        }\n    }\n}\n\n\nfinal class TestAuthTokenProvider: AuthTokenProvider, @unchecked Sendable {\n\n    public var resolveAuthCount: Int = 0\n    public var expiredTokens: [String] = []\n    private let onResolve: (String) async throws -> String\n    init(onResolve: @escaping (String) async throws -> String) {\n        self.onResolve = onResolve\n    }\n\n    func resolveAuth(identifier: String) async throws -> String {\n        resolveAuthCount += 1\n        return try await self.onResolve(identifier)\n    }\n\n    func authTokenExpired(token: String) async {\n        expiredTokens.append(token)\n    }\n}\n\n\nfileprivate final class TestURLRequestSession: URLRequestSessionProtocol, @unchecked Sendable {\n\n    private let lock = AirshipLock()\n    private var _requests: [URLRequest] = []\n    var requests: [URLRequest] {\n        var result: [URLRequest]!\n        lock.sync {\n            result = _requests\n        }\n        return result\n    }\n\n    var responses: [Response] = []\n\n    func dataTask(\n        request: URLRequest,\n        completionHandler: @escaping (Data?, URLResponse?, Error?) -> Void\n    ) -> AirshipCancellable {\n        lock.sync {\n            self._requests.append(request)\n        }\n        let response = responses.isEmpty ? nil :  responses.removeFirst()\n        completionHandler(\n            response?.responseBody?.data(using: .utf8),\n            response?.httpResponse,\n            response?.error\n        )\n\n        return CancellableValueHolder<String>() { _ in}\n    }\n    \n}\n\nfileprivate struct Response {\n    let httpResponse: HTTPURLResponse?\n    let error: Error?\n    let responseBody: String?\n\n    init(\n        httpResponse: HTTPURLResponse? = nil,\n        responseBody: String? = nil,\n        error: Error? = nil\n    ) {\n        self.httpResponse = httpResponse\n        self.error = error\n        self.responseBody = responseBody\n    }\n\n    static func makeError(_ error: Error) -> Response {\n        return Response(error: error)\n    }\n\n    static func makeResponse(\n        status: Int,\n        responseHeaders: [String: String]? = nil,\n        responseBody: String? = nil\n    ) -> Response {\n        return Response(\n            httpResponse: HTTPURLResponse(\n                url: URL(string: \"https://example.com\")!,\n                statusCode: status,\n                httpVersion: nil,\n                headerFields: responseHeaders ?? [:]\n            )!,\n            responseBody: responseBody\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DefaultAppIntegrationDelegateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\nimport Combine\n\n@testable import AirshipCore\n\nclass DefaultAppIntegrationdelegateTest: XCTestCase {\n\n    private var delegate: DefaultAppIntegrationDelegate!\n    private let push = TestPush()\n    private let analytics = TestAnalytics()\n    private let pushableComponent = TestPushableComponent()\n    private var airshipInstance: TestAirshipInstance!\n\n    @MainActor\n    override func setUp() async throws {\n        airshipInstance = TestAirshipInstance()\n        self.airshipInstance.actionRegistry = DefaultAirshipActionRegistry()\n        self.airshipInstance.makeShared()\n\n        self.delegate = DefaultAppIntegrationDelegate(\n            push: self.push,\n            analytics: self.analytics,\n            pushableComponents: [pushableComponent]\n        )\n    }\n\n    @MainActor\n    func testOnBackgroundAppRefresh() throws {\n        delegate.onBackgroundAppRefresh()\n        XCTAssertTrue(push.updateAuthorizedNotificationTypesCalled)\n    }\n\n    @MainActor\n    func testDidRegisterForRemoteNotifications() async throws {\n        let data = Data()\n        delegate.didRegisterForRemoteNotifications(deviceToken: data)\n        let token = push.deviceToken?.data(using: .utf8)\n        XCTAssertEqual(data, token)\n    }\n\n    @MainActor\n    func testDidFailToRegisterForRemoteNotifications() throws {\n        let error = AirshipErrors.error(\"some error\")\n        delegate.didFailToRegisterForRemoteNotifications(error: error)\n        XCTAssertEqual(\"some error\", error.localizedDescription)\n    }\n\n    @MainActor\n    func testDidReceiveRemoteNotification() async throws {\n        let expectedUserInfo = [\"neat\": \"story\"]\n\n        self.push.didReceiveRemoteNotificationCallback = {\n            userInfo,\n            isForeground in\n            XCTAssertEqual(\n                expectedUserInfo as NSDictionary,\n                userInfo as NSDictionary\n            )\n            XCTAssertTrue(isForeground)\n            return .noData\n        }\n\n        self.pushableComponent.didReceiveRemoteNotificationCallback = {\n            userInfo in\n            XCTAssertEqual(\n                expectedUserInfo as NSDictionary,\n                userInfo as NSDictionary\n            )\n            return .newData\n        }\n\n        let result = await withCheckedContinuation { continuation in\n            delegate.didReceiveRemoteNotification(\n                userInfo: expectedUserInfo,\n                isForeground: true\n            ) { result in\n                continuation.resume(returning: result)\n            }\n        }\n        \n        XCTAssertEqual(result, .newData)\n    }\n}\n\n\nclass TestPushableComponent: AirshipPushableComponent, @unchecked Sendable {\n    \n    var didReceiveRemoteNotificationCallback:(\n        ([AnyHashable: Any]) -> UABackgroundFetchResult\n    )?\n\n    public func receivedRemoteNotification(\n        _ notification: AirshipJSON\n    ) async -> UABackgroundFetchResult {\n        let unwrapped = notification.unWrap() as? [AnyHashable: Any] ?? [:]\n        return self.didReceiveRemoteNotificationCallback!(unwrapped)\n    }\n\n    public func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        assertionFailure(\"Unable to create UNNotificationResponse in tests.\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DefaultTaskSleeperTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class DefaultTaskSleeperTest: XCTestCase {\n    private let date: UATestDate = UATestDate(dateOverride: Date())\n    private let sleeps: AirshipActorValue<[TimeInterval]> = AirshipActorValue([])\n    private var sleeper: AirshipTaskSleeper!\n\n    override func setUp() async throws {\n        sleeper = DefaultAirshipTaskSleeper(date: date) { [sleeps, date] interval in\n            date.offset += interval\n            await sleeps.update { current in\n                current.append(interval)\n            }\n        }\n    }\n\n    func testIntervalSleep() async throws {\n        try await sleeper.sleep(timeInterval: 85.0)\n        let sleeps = await sleeps.value\n        XCTAssertEqual(sleeps, [30.0, 30.0, 25.0])\n    }\n\n    func testBelowIntervalSleep() async throws {\n        try await sleeper.sleep(timeInterval: 30.0)\n        let sleeps = await sleeps.value\n        XCTAssertEqual(sleeps, [30.0])\n    }\n\n    func testNegativeSleep() async throws {\n        try await sleeper.sleep(timeInterval: -1.0)\n        let sleeps = await sleeps.value\n        XCTAssertEqual(sleeps, [])\n    }\n\n    func testNoSleep() async throws {\n        try await sleeper.sleep(timeInterval: 0.0)\n        let sleeps = await sleeps.value\n        XCTAssertEqual(sleeps, [])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DeferredAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class DeferredAPIClientTest: AirshipBaseTest {\n    var apiClient: DeferredAPIClient!\n    private let testSession: TestAirshipRequestSession = TestAirshipRequestSession()\n    private let exampleURL: URL = URL(string: \"exampleurl://\")!\n\n    let date = AirshipDateFormatter.date(fromISOString: \"2023-10-27T21:18:15\")!\n\n    override func setUpWithError() throws {\n        self.apiClient = DeferredAPIClient(\n            config: self.config,\n            session: self.testSession\n        )\n    }\n\n    func testResolve() async throws {\n        self.testSession.response = HTTPURLResponse(\n            url: exampleURL,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [:]\n        )\n\n        let responseBody = \"some response\".data(using: .utf8)\n        self.testSession.data = responseBody\n\n        let audienceOverrides = ChannelAudienceOverrides(\n            tags: [\n                TagGroupUpdate(\n                    group: \"some-group\",\n                    tags: [\"tag-1\", \"tag-2\"],\n                    type: .add\n                ),\n                TagGroupUpdate(\n                    group: \"some-other-group\",\n                    tags: [\"tag-3\", \"tag-4\"],\n                    type: .set\n                )\n            ],\n            attributes: [\n                AttributeUpdate(\n                    attribute: \"some-attribute\",\n                    type: .set,\n                    jsonValue: \"hello\",\n                    date: date\n                )\n            ]\n        )\n\n        let response = try await self.apiClient.resolve(\n            url: exampleURL,\n            channelID: \"some channel id\",\n            contactID: \"some contact id\",\n            stateOverrides: AirshipStateOverrides(\n                appVersion: \"1.0.0\",\n                sdkVersion: \"2.0.0\",\n                notificationOptIn: true,\n                localeLangauge: \"en\",\n                localeCountry: \"US\"\n            ),\n            audienceOverrides: audienceOverrides,\n            triggerContext: AirshipTriggerContext(\n                type: \"some trigger type\",\n                goal: 10.0,\n                event: \"event body\"\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n           \"state_overrides\":{\n              \"app_version\":\"1.0.0\",\n              \"locale_language\":\"en\",\n              \"sdk_version\":\"2.0.0\",\n              \"locale_country\":\"US\",\n              \"notification_opt_in\":true\n           },\n           \"tag_overrides\":{\n              \"set\":{\n                 \"some-other-group\":[\n                    \"tag-3\",\n                    \"tag-4\"\n                 ]\n              },\n              \"add\":{\n                 \"some-group\":[\n                    \"tag-1\",\n                    \"tag-2\"\n                 ]\n              }\n           },\n           \"channel_id\":\"some channel id\",\n           \"platform\":\"ios\",\n           \"trigger\":{\n              \"event\":\"event body\",\n              \"type\":\"some trigger type\",\n              \"goal\":10\n           },\n           \"contact_id\":\"some contact id\",\n           \"attribute_overrides\":[\n              {\n                 \"value\":\"hello\",\n                 \"timestamp\":\"2023-10-27T21:18:15\",\n                 \"key\":\"some-attribute\",\n                 \"action\":\"set\"\n              }\n           ]\n        }\n        \"\"\"\n\n        XCTAssertEqual(200, response.statusCode)\n        XCTAssertEqual(responseBody, response.result)\n        XCTAssertEqual(\"POST\", self.testSession.lastRequest?.method)\n        XCTAssertEqual(self.exampleURL, self.testSession.lastRequest?.url)\n        XCTAssertEqual([\"Accept\": \"application/vnd.urbanairship+json; version=3;\"], self.testSession.lastRequest?.headers)\n        XCTAssertEqual(AirshipRequestAuth.channelAuthToken(identifier: \"some channel id\"), self.testSession.lastRequest?.auth)\n        XCTAssertEqual(\n            try AirshipJSON.from(json: expectedBody),\n            try AirshipJSON.from(data:self.testSession.lastRequest?.body)\n        )\n    }\n\n    func testResolveMinimal() async throws {\n        self.testSession.response = HTTPURLResponse(\n            url: exampleURL,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [:]\n        )\n        self.testSession.data = \"some response\".data(using: .utf8)\n\n        _ = try await self.apiClient.resolve(\n            url: exampleURL,\n            channelID: \"some channel id\",\n            contactID: nil,\n            stateOverrides: AirshipStateOverrides(\n                appVersion: \"1.0.0\",\n                sdkVersion: \"2.0.0\",\n                notificationOptIn: true,\n                localeLangauge: nil,\n                localeCountry: nil\n            ),\n            audienceOverrides: ChannelAudienceOverrides(),\n            triggerContext: nil\n        )\n\n        let expectedBody = \"\"\"\n        {\n           \"state_overrides\":{\n              \"app_version\":\"1.0.0\",\n              \"sdk_version\":\"2.0.0\",\n              \"notification_opt_in\":true\n           },\n           \"channel_id\":\"some channel id\",\n           \"platform\":\"ios\"\n        }\n        \"\"\"\n\n        XCTAssertEqual(\n            try AirshipJSON.from(json: expectedBody),\n            try AirshipJSON.from(data:self.testSession.lastRequest?.body)\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DeferredResolverTest.swift",
    "content": "import XCTest\n\n@testable\nimport AirshipCore\n\nfinal class DeferredResolverTest: XCTestCase {\n\n    private var resolver: AirshipDeferredResolver!\n    private let audienceOverridesProvider: DefaultAudienceOverridesProvider = DefaultAudienceOverridesProvider()\n    private let client: TestDeferredAPIClient = TestDeferredAPIClient()\n    private let exampleURL: URL = URL(string: \"exampleurl://\")!\n    private let altExampleURL: URL = URL(string: \"altexampleurl://\")!\n\n    override func setUp() {\n        self.resolver = AirshipDeferredResolver(\n            client: client,\n            audienceOverrides: audienceOverridesProvider\n        )\n    }\n\n    func testResolve() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            contactID: \"some contact ID\",\n            triggerContext: AirshipTriggerContext(\n                type: \"some type\",\n                goal: 10.0,\n                event: \"event\"\n            ),\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let tags = [\n            TagGroupUpdate(\n                group: \"some-group\",\n                tags: [\"tag-1\", \"tag-2\"],\n                type: .add\n            ),\n            TagGroupUpdate(\n                group: \"some-other-group\",\n                tags: [\"tag-3\", \"tag-4\"],\n                type: .set\n            )\n        ]\n\n        let attributes =  [\n            AttributeUpdate(\n                attribute: \"some-attribute\",\n                type: .set,\n                jsonValue: \"hello\",\n                date: Date()\n            )\n        ]\n\n        /// Local history\n        await self.audienceOverridesProvider.channelUpdated(\n            channelID: \"some channel ID\",\n            tags: tags,\n            attributes: attributes,\n            subscriptionLists: nil\n        )\n\n\n        let body = \"some body\".data(using: .utf8)!\n        self.client.onResolve = { url, channel, contact, stateOverrides, audienceOverrides, trigger in\n            let expectedStateOverrides = AirshipStateOverrides(\n                appVersion: request.appVersion,\n                sdkVersion: request.sdkVersion,\n                notificationOptIn: request.notificationOptIn,\n                localeLangauge: request.locale.getLanguageCode(),\n                localeCountry: request.locale.getRegionCode()\n            )\n\n            let expectedAudienceOverrides = ChannelAudienceOverrides(\n                tags: tags,\n                attributes: attributes,\n                subscriptionLists: []\n            )\n\n            XCTAssertEqual(url, request.url)\n            XCTAssertEqual(channel, request.channelID)\n            XCTAssertEqual(contact, request.contactID)\n            XCTAssertEqual(trigger, request.triggerContext)\n            XCTAssertEqual(trigger, request.triggerContext)\n            XCTAssertEqual(stateOverrides, expectedStateOverrides)\n            XCTAssertEqual(audienceOverrides, expectedAudienceOverrides)\n            return AirshipHTTPResponse(result: body, statusCode: 200, headers: [:])\n        }\n\n        let result = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .success(body))\n    }\n\n    func testResolveNoAudienceOverrides() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)\n        self.client.onResolve = { _, _, _, _, audienceOverrides, _ in\n            let expectedAudienceOverrides = ChannelAudienceOverrides(\n                tags: [],\n                attributes: [],\n                subscriptionLists: []\n            )\n\n            XCTAssertEqual(audienceOverrides, expectedAudienceOverrides)\n            return AirshipHTTPResponse(result: body, statusCode: 200, headers: [:])\n        }\n\n        _ = await resolver.resolve(request: request) { data in\n            return data\n        }\n    }\n\n    func testResolveParseError() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let statusCode: Int = 200\n\n        let body = \"some body\".data(using: .utf8)\n        self.client.onResolve = { _, _, _, _, _, _ in\n            return AirshipHTTPResponse(result: body, statusCode: statusCode, headers: [:])\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            throw AirshipErrors.error(\"parse error\")\n        }\n\n        XCTAssertEqual(result, .retriableError(statusCode: statusCode))\n    }\n\n    func testResolveTimedOut() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        self.client.onResolve = { _, _, _, _, _, _ in\n            throw AirshipErrors.error(\"timed out\")\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .timedOut)\n    }\n\n    func testResolve404() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)\n        self.client.onResolve = { _, _, _, _, _, _ in\n            return AirshipHTTPResponse(result: body, statusCode: 404, headers: [:])\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .notFound)\n    }\n\n    func testResolve409() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)\n        self.client.onResolve = { _, _, _, _, _, _ in\n            return AirshipHTTPResponse(result: body, statusCode: 409, headers: [:])\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .outOfDate)\n    }\n\n    func testResolveOutOfDateURL() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)\n        self.client.onResolve = { _, _, _, _, _, _ in\n            return AirshipHTTPResponse(result: body, statusCode: 409, headers: [:])\n        }\n\n        var result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n        XCTAssertEqual(result, .outOfDate)\n\n        self.client.onResolve = { _, _, _, _, _, _ in\n            XCTFail()\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        result = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .outOfDate)\n    }\n\n    func testResolve429() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let statusCode: Int = 429\n        let body = \"some body\".data(using: .utf8)!\n        self.client.onResolve = { _, _, _, _, _, _ in\n            return AirshipHTTPResponse(result: body, statusCode: statusCode, headers: [\"Location\": self.altExampleURL.absoluteString])\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .retriableError(statusCode: statusCode))\n\n        self.client.onResolve = { url, _, _, _, _, _ in\n            XCTAssertEqual(url, self.altExampleURL)\n            return AirshipHTTPResponse(result: body, statusCode: 200, headers: [:])\n        }\n\n        let anotherResult: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(anotherResult, .success(body))\n\n    }\n\n    func testResolve429RetryAfter() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let statusCode: Int = 429\n        let body = \"some body\".data(using: .utf8)!\n        self.client.onResolve = { _, _, _, _, _, _ in\n            return AirshipHTTPResponse(result: body, statusCode: statusCode, headers: [\"Retry-After\": \"100.0\"])\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .retriableError(retryAfter: 100.0,  statusCode: statusCode))\n    }\n\n    func testResolve307() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)!\n        self.client.onResolve = { url, _, _, _, _, _ in\n            if (url == self.exampleURL) {\n                return AirshipHTTPResponse(result: nil, statusCode: 307, headers: [\"Location\": self.altExampleURL.absoluteString])\n            } else {\n                return AirshipHTTPResponse(result: body, statusCode: 200, headers: [:])\n            }\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .success(body))\n    }\n    \n    func testRedirectTwice() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)!\n        self.client.onResolve = { url, _, _, _, _, _ in\n            if (url == self.exampleURL) {\n                return AirshipHTTPResponse(result: nil, statusCode: 307, headers: [\"Location\": \"altexampleurl://1\"])\n            } else {\n                return AirshipHTTPResponse(result: body, statusCode: 307, headers:  [\"Location\": \"altexampleurl://2\"])\n            }\n        }\n\n        var result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n        \n        XCTAssertEqual(result, .retriableError(retryAfter: nil, statusCode: 307))\n\n        self.client.onResolve = { url, _, _, _, _, _ in\n            XCTAssertEqual(url.absoluteString, \"altexampleurl://2\")\n            return AirshipHTTPResponse(result: body, statusCode: 200, headers: [:])\n        }\n        \n        result = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .success(body))\n    }\n\n    func testResolve307RetryAfter() async throws {\n        let request = DeferredRequest(\n            url: exampleURL,\n            channelID: \"some channel ID\",\n            locale: Locale(identifier: \"de-DE\"),\n            notificationOptIn: true\n        )\n\n        let body = \"some body\".data(using: .utf8)!\n\n        let statusCode: Int = 307\n        self.client.onResolve = { url, _, _, _, _, _ in\n            if (url == self.exampleURL) {\n                return AirshipHTTPResponse(\n                    result: nil,\n                    statusCode: statusCode,\n                    headers: [\n                        \"Location\": self.altExampleURL.absoluteString,\n                        \"Retry-After\": \"20.0\"\n                    ]\n                )\n            } else {\n                return AirshipHTTPResponse(result: body, statusCode: 200, headers: [:])\n            }\n        }\n\n        let result: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(result, .retriableError(retryAfter: 20.0, statusCode: statusCode))\n\n        let anotherResult: AirshipDeferredResult<Data> = await resolver.resolve(request: request) { data in\n            return data\n        }\n\n        XCTAssertEqual(anotherResult, .success(body))\n    }\n}\n\n\nfileprivate class TestDeferredAPIClient: DeferredAPIClientProtocol, @unchecked Sendable {\n    var onResolve: ((URL, String, String?, AirshipStateOverrides, ChannelAudienceOverrides, AirshipTriggerContext?) throws -> AirshipHTTPResponse<Data>)?\n\n    func resolve(\n        url: URL,\n        channelID: String,\n        contactID: String?,\n        stateOverrides: AirshipStateOverrides,\n        audienceOverrides: ChannelAudienceOverrides,\n        triggerContext: AirshipTriggerContext?\n    ) async throws -> AirshipHTTPResponse<Data> {\n        try onResolve!(url, channelID, contactID, stateOverrides, audienceOverrides, triggerContext)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DeviceAudienceSelectorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class DefaultDeviceAudienceCheckerTest: XCTestCase, @unchecked Sendable {\n\n    private let testDeviceInfo: TestAudienceDeviceInfoProvider = TestAudienceDeviceInfoProvider()\n    private let audienceChecker = DefaultDeviceAudienceChecker(cache: TestCache())\n\n    private let stickyHash = AudienceHashSelector(\n        hash: AudienceHashSelector.Hash(\n            prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n            property: .contact,\n            algorithm: .farm,\n            seed: 100,\n            numberOfBuckets: 16384,\n            overrides: nil\n        ),\n        bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000),\n        sticky: AudienceHashSelector.Sticky(\n            id: \"sticky ID\",\n            reportingMetadata: \"sticky reporting\",\n            lastAccessTTL: 100.0\n        )\n    )\n\n    private let stickyHashInverse = AudienceHashSelector(\n        hash: AudienceHashSelector.Hash(\n            prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n            property: .contact,\n            algorithm: .farm,\n            seed: 100,\n            numberOfBuckets: 16384,\n            overrides: nil\n        ),\n        bucket: AudienceHashSelector.Bucket(min: 0, max: 11600),\n        sticky: AudienceHashSelector.Sticky(\n            id: \"sticky ID\",\n            reportingMetadata: \"inverse sticky reporting\",\n            lastAccessTTL: 100.0\n        )\n    )\n\n    func testAirshipNotReadyThrows() async throws {\n        testDeviceInfo.isAirshipReady = false\n        do {\n            _ = try await self.audienceChecker.evaluate(\n                audienceSelector: .atomic(DeviceAudienceSelector()),\n                newUserEvaluationDate: .now,\n                deviceInfoProvider: self.testDeviceInfo\n            )\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testEmptyAudience() async throws {\n        try await self.assert(\n            audienceSelector: DeviceAudienceSelector(),\n            isMatch: true\n        )\n    }\n\n    func testNewUserCondition() async throws {\n        let now = Date()\n        testDeviceInfo.installDate = now\n        let audience = DeviceAudienceSelector(newUser: true)\n\n        try await self.assert(\n            audienceSelector: audience,\n            newUserEvaluationDate: now,\n            isMatch: true\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            newUserEvaluationDate: now.advanced(by: -1.0),\n            isMatch: true\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            newUserEvaluationDate: now.advanced(by: 1.0),\n            isMatch: false\n        )\n    }\n\n    func testNotifiicationOptIn() async throws {\n        self.testDeviceInfo.isUserOptedInPushNotifications = false\n        let audience = DeviceAudienceSelector(notificationOptIn: true)\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n\n        self.testDeviceInfo.isUserOptedInPushNotifications = true\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testNotifiicationOptOut() async throws {\n        self.testDeviceInfo.isUserOptedInPushNotifications = true\n\n        let audience = DeviceAudienceSelector(notificationOptIn: false)\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.isUserOptedInPushNotifications = false\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testRequireAnalyticsTrue() async throws {\n        self.testDeviceInfo.analyticsEnabled = true\n\n        let audience = DeviceAudienceSelector(requiresAnalytics: true)\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n\n        self.testDeviceInfo.analyticsEnabled = false\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n    }\n\n    func testRequireAnalyticsFalse() async throws {\n        self.testDeviceInfo.analyticsEnabled = true\n\n        let audience = DeviceAudienceSelector(requiresAnalytics: false)\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n\n        self.testDeviceInfo.analyticsEnabled = false\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testLocale() async throws {\n        self.testDeviceInfo.locale = Locale(identifier: \"de\")\n        let audience = DeviceAudienceSelector(\n            languageIDs: [ \"fr\", \"en-CA\"]\n        )\n        \n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.locale = Locale(identifier: \"en-GB\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.locale = Locale(identifier: \"en\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.locale = Locale(identifier: \"fr-FR\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n\n        self.testDeviceInfo.locale = Locale(identifier: \"en-CA\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n\n        self.testDeviceInfo.locale = Locale(identifier: \"en-CA-POSIX\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testTags() async throws {\n        let audience = DeviceAudienceSelector(\n            tagSelector: .and([.tag(\"bar\"), .tag(\"foo\")])\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.tags = Set([\"foo\"])\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.tags = Set([\"foo\", \"bar\"])\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testTestDevices() async throws {\n        let audience = DeviceAudienceSelector(\n            testDevices: [\"obIvSbh47TjjqfCrPatbXQ==\\n\"] // test channel\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.channelID = \"wrong channnel\"\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.channelID = \"test channel\"\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testVersion() async throws {\n        let audience = DeviceAudienceSelector(\n            versionPredicate: JSONPredicate(\n                jsonMatcher: JSONMatcher(\n                    valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"1.1.1\"),\n                    scope: [\"ios\", \"version\"]\n                )\n            )\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.appVersion = \"1.0.0\"\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.appVersion = \"1.1.1\"\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testPermissions() async throws {\n        let audience = DeviceAudienceSelector(\n            permissionPredicate: JSONPredicate(\n                jsonMatcher: JSONMatcher(\n                    valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"granted\"),\n                    scope: [\"display_notifications\"]\n                )\n            )\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.permissions = [.displayNotifications: .denied]\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.permissions = [.displayNotifications: .granted]\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testLocationOptIn() async throws {\n        let audience = DeviceAudienceSelector(\n            locationOptIn: true\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.permissions = [.location: .denied]\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.permissions = [.location: .granted]\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testLocationOptOut() async throws {\n        let audience = DeviceAudienceSelector(\n            locationOptIn: false\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n\n        self.testDeviceInfo.permissions = [.location: .denied]\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n\n        self.testDeviceInfo.permissions = [.location: .granted]\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n    }\n\n    func testContactHash() async throws {\n        let hash = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000)\n        )\n\n        let audience = DeviceAudienceSelector(\n            hashSelector: hash\n        )\n\n        self.testDeviceInfo.channelID = \"not a match\"\n\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"not a match\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testChannelHash() async throws {\n        let hash = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .channel,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000)\n        )\n\n        let audience = DeviceAudienceSelector(\n            hashSelector: hash\n        )\n\n        self.testDeviceInfo.channelID = \"not a match\"\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"not a match\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n\n        self.testDeviceInfo.channelID = \"match\"\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testDeviceTypes() async throws {\n        let audience = DeviceAudienceSelector(\n            deviceTypes: [\"android\", \"ios\"]\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testDeviceTypesNoIOS() async throws {\n        let audience = DeviceAudienceSelector(\n            deviceTypes: [\"android\", \"web\"]\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n    }\n\n    func testEmtpyDeviceTypes() async throws {\n        let audience = DeviceAudienceSelector(\n            deviceTypes: []\n        )\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false\n        )\n    }\n\n    func testStickyHash() async throws {\n        self.testDeviceInfo.channelID = UUID().uuidString\n\n        let audience = DeviceAudienceSelector(\n            hashSelector: stickyHash\n        )\n\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"not a match\")\n\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: false,\n            reportingMetadata: [stickyHash.sticky!.reportingMetadata!]\n        )\n\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n        try await self.assert(\n            audienceSelector: audience,\n            isMatch: true,\n            reportingMetadata: [stickyHash.sticky!.reportingMetadata!]\n        )\n\n        // Update sticky hash to swap matches\n        let updatedAudience = DeviceAudienceSelector(\n            hashSelector: stickyHashInverse\n        )\n\n        // Should be the same results\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"not a match\")\n        try await self.assert(\n            audienceSelector: updatedAudience,\n            isMatch: false,\n            reportingMetadata: [stickyHash.sticky!.reportingMetadata!]\n        )\n\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n        try await self.assert(\n            audienceSelector: updatedAudience,\n            isMatch: true,\n            reportingMetadata: [stickyHash.sticky!.reportingMetadata!]\n        )\n\n        // New contacts should reevaluate\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"also is a match\")\n        try await self.assert(\n            audienceSelector: updatedAudience,\n            isMatch: true,\n            reportingMetadata: [stickyHashInverse.sticky!.reportingMetadata!]\n        )\n    }\n\n    func testORMatch() async throws {\n        self.testDeviceInfo.analyticsEnabled = false\n        let audience = CompoundDeviceAudienceSelector.or(\n            [\n                .atomic(DeviceAudienceSelector(requiresAnalytics: false)),\n                .atomic(DeviceAudienceSelector(requiresAnalytics: true)),\n            ]\n        )\n\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testORMatchFirstNoMatch() async throws {\n        self.testDeviceInfo.analyticsEnabled = false\n        let audience = CompoundDeviceAudienceSelector.or(\n            [\n                .atomic(DeviceAudienceSelector(requiresAnalytics: true)),\n                .atomic(DeviceAudienceSelector(requiresAnalytics: false)),\n            ]\n        )\n\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testORMiss() async throws {\n        self.testDeviceInfo.analyticsEnabled = false\n        self.testDeviceInfo.isUserOptedInPushNotifications = false\n\n        let audience = CompoundDeviceAudienceSelector.or(\n            [\n                .atomic(DeviceAudienceSelector(requiresAnalytics: true)),\n                .atomic(DeviceAudienceSelector(notificationOptIn: true)),\n            ]\n        )\n\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: false\n        )\n    }\n\n\n    func testEmptyOR() async throws {\n        let audience = CompoundDeviceAudienceSelector.or([])\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: false\n        )\n    }\n\n    func testANDMatch() async throws {\n        self.testDeviceInfo.analyticsEnabled = true\n        self.testDeviceInfo.isUserOptedInPushNotifications = true\n\n        let audience = CompoundDeviceAudienceSelector.or(\n            [\n                .atomic(DeviceAudienceSelector(requiresAnalytics: true)),\n                .atomic(DeviceAudienceSelector(notificationOptIn: true)),\n            ]\n        )\n\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testANDMiss() async throws {\n        self.testDeviceInfo.analyticsEnabled = false\n        self.testDeviceInfo.isUserOptedInPushNotifications = true\n\n        let audience = CompoundDeviceAudienceSelector.and(\n            [\n                .atomic(DeviceAudienceSelector(requiresAnalytics: true)),\n                .atomic(DeviceAudienceSelector(notificationOptIn: true)),\n            ]\n        )\n\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: false\n        )\n    }\n\n    func testEmptyAND() async throws {\n        let audience = CompoundDeviceAudienceSelector.and([])\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testNOT() async throws {\n        self.testDeviceInfo.analyticsEnabled = false\n        self.testDeviceInfo.isUserOptedInPushNotifications = true\n\n        let audience = CompoundDeviceAudienceSelector.not(\n            .and(\n                [\n                    .atomic(DeviceAudienceSelector(requiresAnalytics: true)),\n                    .atomic(DeviceAudienceSelector(notificationOptIn: true)),\n                ]\n            )\n        )\n\n        try await self.assert(\n            compoundSelector: audience,\n            isMatch: true\n        )\n    }\n\n    func testStickyHashShortCircuitOR() async throws {\n        var stickyHashDiffID = stickyHash\n        stickyHashDiffID.sticky = AudienceHashSelector.Sticky(\n            id: UUID().uuidString,\n            reportingMetadata: .string(UUID().uuidString),\n            lastAccessTTL: 100.0\n        )\n\n        self.testDeviceInfo.channelID = UUID().uuidString\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n\n        // short circuits, only get the first one\n        try await self.assert(\n            compoundSelector: .or(\n                [\n                    .atomic(DeviceAudienceSelector(hashSelector: stickyHash)),\n                    .atomic(DeviceAudienceSelector(hashSelector: stickyHashDiffID)),\n                ]\n            ),\n            isMatch: true,\n            reportingMetadata: [stickyHash.sticky!.reportingMetadata!]\n        )\n    }\n\n    func testStickyHashShortCircuitAND() async throws {\n        var stickyHashDiffID = stickyHash\n        stickyHashDiffID.sticky = AudienceHashSelector.Sticky(\n            id: UUID().uuidString,\n            reportingMetadata: .string(UUID().uuidString),\n            lastAccessTTL: 100.0\n        )\n\n        self.testDeviceInfo.channelID = UUID().uuidString\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n\n        // short circuits, only get the first one\n        try await self.assert(\n            compoundSelector: .and(\n                [\n                    .atomic(DeviceAudienceSelector(hashSelector: stickyHashInverse)),\n                    .atomic(DeviceAudienceSelector(hashSelector: stickyHashDiffID)),\n                ]\n            ),\n            isMatch: false,\n            reportingMetadata: [stickyHashInverse.sticky!.reportingMetadata!]\n        )\n    }\n\n    func testStickyHashMultiple() async throws {\n        var stickyHashDiffID = stickyHash\n        stickyHashDiffID.sticky = AudienceHashSelector.Sticky(\n            id: UUID().uuidString,\n            reportingMetadata: .string(UUID().uuidString),\n            lastAccessTTL: 100.0\n        )\n\n        self.testDeviceInfo.channelID = UUID().uuidString\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n\n        // short circuits, only get the first one\n        try await self.assert(\n            compoundSelector: .and(\n                [\n                    .atomic(DeviceAudienceSelector(hashSelector: stickyHash)),\n                    .atomic(DeviceAudienceSelector(hashSelector: stickyHashDiffID)),\n                ]\n            ),\n            isMatch: true,\n            reportingMetadata: [\n                stickyHash.sticky!.reportingMetadata!,\n                stickyHashDiffID.sticky!.reportingMetadata!\n            ]\n        )\n    }\n\n    func assert(\n        audienceSelector: DeviceAudienceSelector,\n        newUserEvaluationDate: Date = Date.distantPast,\n        isMatch: Bool,\n        reportingMetadata: [AirshipJSON]? = nil,\n        file: StaticString = #filePath,\n        line: UInt = #line\n    ) async throws {\n        try await self.assert(\n            compoundSelector: .atomic(audienceSelector),\n            newUserEvaluationDate: newUserEvaluationDate,\n            isMatch: isMatch,\n            reportingMetadata: reportingMetadata,\n            file: file,\n            line: line\n        )\n    }\n\n    func assert(\n        compoundSelector: CompoundDeviceAudienceSelector,\n        newUserEvaluationDate: Date = Date.distantPast,\n        isMatch: Bool,\n        reportingMetadata: [AirshipJSON]? = nil,\n        file: StaticString = #filePath,\n        line: UInt = #line\n    ) async throws {\n        let result = try await self.audienceChecker.evaluate(\n            audienceSelector: compoundSelector,\n            newUserEvaluationDate: newUserEvaluationDate,\n            deviceInfoProvider: self.testDeviceInfo\n        )\n\n        XCTAssertEqual(result.isMatch, isMatch, file: file, line: line)\n        XCTAssertEqual(result.reportingMetadata, reportingMetadata, file: file, line: line)\n    }\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/DeviceTagSelectorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class DeviceTagSelectorTest: XCTestCase {\n\n    func testCodable() throws {\n        let json: String = \"\"\"\n        {\n           \"or\":[\n              {\n                 \"and\":[\n                    {\n                       \"tag\":\"some-tag\"\n                    },\n                    {\n                       \"not\":{\n                          \"tag\":\"not-tag\"\n                       }\n                    }\n                 ]\n              },\n              {\n                 \"tag\":\"some-other-tag\"\n              }\n           ]\n        }\n        \"\"\"\n\n        let decoded: DeviceTagSelector = try JSONDecoder().decode(\n            DeviceTagSelector.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = DeviceTagSelector.or(\n            [\n                .and([.tag(\"some-tag\"), .not(.tag(\"not-tag\"))]),\n                .tag(\"some-other-tag\")\n            ]\n        )\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n\n    func testEvaluate() {\n        let selector = DeviceTagSelector.or(\n            [\n                .and([.tag(\"some-tag\"), .not(.tag(\"not-tag\"))]),\n                .tag(\"some-other-tag\")\n            ]\n        )\n\n        XCTAssertFalse(selector.evaluate(tags: Set()))\n        XCTAssertTrue(selector.evaluate(tags: Set<String>([\"some-tag\"])))\n        XCTAssertTrue(selector.evaluate(tags: Set<String>([\"some-other-tag\"])))\n        XCTAssertTrue(selector.evaluate(tags: Set<String>([\"some-other-tag\", \"not-tag\"])))\n        XCTAssertFalse(selector.evaluate(tags: Set<String>([\"some-tag\", \"not-tag\"])))\n        XCTAssertFalse(selector.evaluate(tags: Set<String>([\"not-tag\"])))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/EnableFeatureActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass EnableFeatureActionTest: XCTestCase {\n\n    let testPrompter = TestPermissionPrompter()\n    var action: EnableFeatureAction!\n\n    override func setUpWithError() throws {\n        self.action = EnableFeatureAction { return self.testPrompter }\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n\n        for situation in validSituations {\n            let args = ActionArguments(\n                string: EnableFeatureAction.locationActionValue,\n                situation: situation\n            )\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(\n                string: EnableFeatureAction.locationActionValue,\n                situation: situation\n            )\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testLocation() async throws {\n        let arguments = ActionArguments(\n            string: EnableFeatureAction.locationActionValue,\n            situation: .manualInvocation\n        )\n\n        let prompted = self.expectation(description: \"Prompted\")\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTAssertEqual(permission, .location)\n            XCTAssertTrue(enableAirshipUsage)\n            XCTAssertTrue(fallbackSystemSetting)\n            prompted.fulfill()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n      \n        _ = try await self.action.perform(arguments: arguments)\n        await self.fulfillment(of: [prompted], timeout: 10)\n    }\n\n    func testBackgroundLocation() async throws {\n        let arguments = ActionArguments(\n            string: EnableFeatureAction.backgroundLocationActionValue,\n            situation: .manualInvocation\n        )\n\n        let prompted = self.expectation(description: \"Prompted\")\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTAssertEqual(permission, .location)\n            XCTAssertTrue(enableAirshipUsage)\n            XCTAssertTrue(fallbackSystemSetting)\n            prompted.fulfill()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n        _ = try await self.action.perform(arguments: arguments)\n        await self.fulfillment(of: [prompted], timeout: 10)\n    }\n\n    func testNotifications() async throws {\n        let arguments = ActionArguments(\n            string: EnableFeatureAction.userNotificationsActionValue,\n            situation: .manualInvocation\n        )\n\n        let prompted = self.expectation(description: \"Prompted\")\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTAssertEqual(permission, .displayNotifications)\n            XCTAssertTrue(enableAirshipUsage)\n            XCTAssertTrue(fallbackSystemSetting)\n            prompted.fulfill()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n        _ = try await self.action.perform(arguments: arguments)\n        await self.fulfillment(of: [prompted], timeout: 10)\n    }\n\n    func testInvalidArgument() async throws {\n        let arguments = ActionArguments(\n            string: \"invalid\",\n            situation: .manualInvocation\n        )\n\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTFail()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n        do {\n            _ = try await self.action.perform(arguments: arguments)\n            XCTFail(\"should throw\")\n        } catch {}\n    }\n\n    func testResultReceiver() async throws {\n        let resultReceived = self.expectation(description: \"Result received\")\n\n        let resultRecevier:\n         @Sendable (AirshipPermission, AirshipPermissionStatus, AirshipPermissionStatus) async -> Void = {\n                permission,\n                start,\n                end in\n                XCTAssertEqual(.notDetermined, start)\n                XCTAssertEqual(.granted, end)\n                XCTAssertEqual(.location, permission)\n                resultReceived.fulfill()\n            }\n\n        let metadata = [\n            PromptPermissionAction.resultReceiverMetadataKey: resultRecevier\n        ]\n\n        let arguments = ActionArguments(\n            string: EnableFeatureAction.locationActionValue,\n            situation: .manualInvocation,\n            metadata: metadata\n        )\n        \n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .granted)\n        }\n      \n        _ = try await self.action.perform(arguments: arguments)\n        await self.fulfillment(of: [resultReceived], timeout: 10)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasEnvironmentTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\n// MARK: - Test Doubles\n\n@MainActor\nfinal class TestThomasDelegate: ThomasDelegate {\n    var visibilityChanges: [(isVisible: Bool, isForegrounded: Bool)] = []\n    var reportedEvents: [ThomasReportingEvent] = []\n    var dismissals: [Bool] = []\n    var stateChanges: [AirshipJSON] = []\n\n    func onVisibilityChanged(isVisible: Bool, isForegrounded: Bool) {\n        visibilityChanges.append((isVisible, isForegrounded))\n    }\n\n    func onReportingEvent(_ event: ThomasReportingEvent) {\n        reportedEvents.append(event)\n    }\n\n    func onDismissed(cancel: Bool) {\n        dismissals.append(cancel)\n    }\n\n    func onStateChanged(_ state: AirshipJSON) {\n        stateChanges.append(state)\n    }\n}\n\n@MainActor\nfinal class TestTimer: AirshipTimerProtocol {\n    var time: TimeInterval = 0\n    var isStarted: Bool = false\n    var startCount: Int = 0\n    var stopCount: Int = 0\n\n    func start() {\n        isStarted = true\n        startCount += 1\n    }\n\n    func stop() {\n        isStarted = false\n        stopCount += 1\n    }\n}\n\n// MARK: - Tests\n\n@MainActor\nstruct ThomasEnvironmentTest {\n\n    // MARK: - Helper Methods\n\n    private func makeEnvironment(\n        delegate: TestThomasDelegate? = nil,\n        timer: TestTimer? = nil,\n        pagerTracker: ThomasPagerTracker? = nil,\n        extensions: ThomasExtensions? = nil,\n        onDismiss: (() -> Void)? = nil\n    ) -> (ThomasEnvironment, TestThomasDelegate, TestTimer) {\n        let testDelegate = delegate ?? TestThomasDelegate()\n        let testTimer = timer ?? TestTimer()\n\n        let env = ThomasEnvironment(\n            delegate: testDelegate,\n            extensions: extensions,\n            pagerTracker: pagerTracker,\n            timer: testTimer,\n            onDismiss: onDismiss\n        )\n\n        return (env, testDelegate, testTimer)\n    }\n\n    private func setupAirship() -> (TestContact, TestChannel) {\n        let testAirship = TestAirshipInstance()\n        let testContact = TestContact()\n        let testChannel = TestChannel()\n        let date = UATestDate()\n\n        testContact.attributeEditor = AttributesEditor(date: date) { _ in }\n        testChannel.attributeEditor = AttributesEditor(date: date) { _ in }\n\n        testAirship.components = [testContact, testChannel]\n        testAirship.makeShared()\n\n        return (testContact, testChannel)\n    }\n\n    // MARK: - Initialization Tests\n\n    @Test\n    func testCustomDependencies() {\n        let customDelegate = TestThomasDelegate()\n        let customTimer = TestTimer()\n        let customTracker = ThomasPagerTracker()\n        var dismissCalled = false\n\n        let (env, delegate, timer) = makeEnvironment(\n            delegate: customDelegate,\n            timer: customTimer,\n            pagerTracker: customTracker,\n            onDismiss: { dismissCalled = true }\n        )\n\n        // Verify custom dependencies are used\n        #expect(delegate === customDelegate)\n        #expect(timer === customTimer)\n\n        // Verify onDismiss callback\n        env.dismiss()\n        #expect(dismissCalled)\n    }\n\n    // MARK: - Visibility & Timer Tests\n\n    @Test\n    func testVisibilityStartsTimer() {\n        let (env, delegate, timer) = makeEnvironment()\n\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n\n        #expect(timer.isStarted)\n        #expect(timer.startCount == 1)\n        #expect(delegate.visibilityChanges.count == 1)\n        #expect(delegate.visibilityChanges[0].isVisible == true)\n        #expect(delegate.visibilityChanges[0].isForegrounded == true)\n    }\n\n    @Test\n    func testVisibilityStopsTimerWhenNotVisible() {\n        let (env, delegate, timer) = makeEnvironment()\n\n        // Start timer first\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        #expect(timer.isStarted)\n\n        // Stop when not visible\n        env.onVisibilityChanged(isVisible: false, isForegrounded: true)\n\n        #expect(!timer.isStarted)\n        #expect(timer.stopCount == 1)\n        #expect(delegate.visibilityChanges.count == 2)\n        #expect(delegate.visibilityChanges[1].isVisible == false)\n    }\n\n    @Test\n    func testVisibilityStopsTimerWhenBackgrounded() {\n        let (env, delegate, timer) = makeEnvironment()\n\n        // Start timer first\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        #expect(timer.isStarted)\n\n        // Stop when backgrounded\n        env.onVisibilityChanged(isVisible: true, isForegrounded: false)\n\n        #expect(!timer.isStarted)\n        #expect(timer.stopCount == 1)\n        #expect(delegate.visibilityChanges.count == 2)\n        #expect(delegate.visibilityChanges[1].isForegrounded == false)\n    }\n\n    @Test\n    func testVisibilityTimerRestart() {\n        let (env, _, timer) = makeEnvironment()\n\n        // Start\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        #expect(timer.isStarted)\n        #expect(timer.startCount == 1)\n\n        // Background\n        env.onVisibilityChanged(isVisible: true, isForegrounded: false)\n        #expect(!timer.isStarted)\n\n        // Foreground again - should restart\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        #expect(timer.isStarted)\n        #expect(timer.startCount == 2)\n    }\n\n    // MARK: - State Management Tests\n\n    @Test\n    func testRetrieveStateCreatesNewState() {\n        let (env, _, _) = makeEnvironment()\n\n        let state = env.retrieveState(identifier: \"test\") {\n            ThomasState.MutableState()\n        }\n\n        #expect(state != nil)\n    }\n\n    @Test\n    func testRetrieveStateReturnsSameInstance() {\n        let (env, _, _) = makeEnvironment()\n\n        let state1 = env.retrieveState(identifier: \"test\") {\n            ThomasState.MutableState()\n        }\n\n        let state2 = env.retrieveState(identifier: \"test\") {\n            ThomasState.MutableState()\n        }\n\n        #expect(state1 === state2)\n    }\n\n    @Test\n    func testRetrieveStateIsolatesDifferentIdentifiers() {\n        let (env, _, _) = makeEnvironment()\n\n        let state1 = env.retrieveState(identifier: \"test1\") {\n            ThomasState.MutableState()\n        }\n\n        let state2 = env.retrieveState(identifier: \"test2\") {\n            ThomasState.MutableState()\n        }\n\n        #expect(state1 !== state2)\n    }\n\n    // MARK: - Form Event Tests\n\n    @Test\n    func testFormDisplayed() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let formState = ThomasFormState(\n            identifier: \"test-form\",\n            formType: .form,\n            formResponseType: \"response-type\",\n            validationMode: .immediate\n        )\n\n        env.formDisplayed(formState, layoutState: .empty)\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .formDisplay(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-form\")\n            #expect(event.formType == \"form\")\n        } else {\n            Issue.record(\"Expected formDisplay event\")\n        }\n    }\n\n    @Test\n    func testFormDisplayedWithNPSType() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let formState = ThomasFormState(\n            identifier: \"nps-form\",\n            formType: .nps(\"score-id\"),\n            formResponseType: \"response-type\",\n            validationMode: .immediate\n        )\n\n        env.formDisplayed(formState, layoutState: .empty)\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .formDisplay(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"nps-form\")\n            #expect(event.formType == \"nps\")\n        } else {\n            Issue.record(\"Expected formDisplay event with NPS type\")\n        }\n    }\n\n    @Test\n    func testSubmitFormReportsEvent() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let result = ThomasFormResult(\n            identifier: \"test-form\",\n            formData: .object([:])\n        )\n\n        env.submitForm(\n            result: result,\n            channels: [],\n            attributes: [],\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .formResult = delegate.reportedEvents[0] {\n            // Success\n        } else {\n            Issue.record(\"Expected formResult event\")\n        }\n    }\n\n    @Test\n    func testSubmitFormCallsAirshipWithEmailAndSMS() {\n        let (testContact, testChannel) = setupAirship()\n        defer { TestAirshipInstance.clearShared() }\n\n        let (env, delegate, _) = makeEnvironment()\n\n        let result = ThomasFormResult(\n            identifier: \"test-form\",\n            formData: .object([:])\n        )\n\n        let channels: [ThomasFormField.Channel] = [\n            .email(\"test@example.com\", ThomasEmailRegistrationOptions.optIn()),\n            .sms(\"15035551234\", ThomasSMSRegistrationOptions.optIn(senderID: \"12345\"))\n        ]\n\n        let attributes: [ThomasFormField.Attribute] = [\n            ThomasFormField.Attribute(\n                attributeName: ThomasAttributeName(channel: \"test_attr\", contact: \"contact_attr\"),\n                attributeValue: .string(\"test_value\")\n            )\n        ]\n\n        env.submitForm(\n            result: result,\n            channels: channels,\n            attributes: attributes,\n            layoutState: .empty\n        )\n\n        // Verify event was reported\n        #expect(delegate.reportedEvents.count == 1)\n\n        // TestContact and TestChannel provide no-ops for registerEmail/SMS/editAttributes\n        // but the fact that we didn't crash proves the Airship singleton was accessible\n    }\n\n    // MARK: - Button Event Tests\n\n    @Test\n    func testButtonTapped() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let metadata = AirshipJSON.object([\"key\": .string(\"value\")])\n\n        env.buttonTapped(\n            buttonIdentifier: \"test-button\",\n            reportingMetadata: metadata,\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .buttonTap(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-button\")\n            #expect(event.reportingMetadata == metadata)\n        } else {\n            Issue.record(\"Expected buttonTap event\")\n        }\n    }\n\n    // MARK: - Pager Event Tests\n\n    @Test\n    func testPageViewed() {\n        let (env, delegate, timer) = makeEnvironment()\n        timer.time = 5.0\n\n        let pagerState = PagerState(\n            identifier: \"test-pager\",\n            branching: nil\n        )\n\n        let pageInfo = ThomasPageInfo(\n            identifier: \"page-1\",\n            index: 0,\n            viewCount: 1\n        )\n\n        env.pageViewed(\n            pagerState: pagerState,\n            pageInfo: pageInfo,\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .pageView(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-pager\")\n            #expect(event.pageIdentifier == \"page-1\")\n            #expect(event.pageIndex == 0)\n        } else {\n            Issue.record(\"Expected pageView event\")\n        }\n    }\n\n    @Test\n    func testPagerCompleted() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let pagerState = PagerState(\n            identifier: \"test-pager\",\n            branching: nil\n        )\n\n        env.pagerCompleted(pagerState: pagerState, layoutState: .empty)\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .pagerCompleted(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-pager\")\n        } else {\n            Issue.record(\"Expected pagerCompleted event\")\n        }\n    }\n\n    @Test\n    func testPageSwiped() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let pagerState = PagerState(\n            identifier: \"test-pager\",\n            branching: nil\n        )\n\n        let fromPage = ThomasPageInfo(identifier: \"page-0\", index: 0, viewCount: 1)\n        let toPage = ThomasPageInfo(identifier: \"page-1\", index: 1, viewCount: 1)\n\n        env.pageSwiped(\n            pagerState: pagerState,\n            from: fromPage,\n            to: toPage,\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .pageSwipe(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-pager\")\n            #expect(event.fromPageIdentifier == \"page-0\")\n            #expect(event.toPageIdentifier == \"page-1\")\n            #expect(event.fromPageIndex == 0)\n            #expect(event.toPageIndex == 1)\n        } else {\n            Issue.record(\"Expected pageSwipe event\")\n        }\n    }\n\n    @Test\n    func testPageGestureWithIdentifier() {\n        let (env, delegate, _) = makeEnvironment()\n\n        env.pageGesture(\n            identifier: \"test-gesture\",\n            reportingMetadata: nil,\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .gesture(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-gesture\")\n        } else {\n            Issue.record(\"Expected gesture event\")\n        }\n    }\n\n    @Test\n    func testPageGestureWithoutIdentifier() {\n        let (env, delegate, _) = makeEnvironment()\n\n        env.pageGesture(\n            identifier: nil,\n            reportingMetadata: nil,\n            layoutState: .empty\n        )\n\n        // Should not report event when identifier is nil\n        #expect(delegate.reportedEvents.isEmpty)\n    }\n\n    @Test\n    func testPageAutomatedWithIdentifier() {\n        let (env, delegate, _) = makeEnvironment()\n\n        env.pageAutomated(\n            identifier: \"test-action\",\n            reportingMetadata: nil,\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .pageAction(let event, _) = delegate.reportedEvents[0] {\n            #expect(event.identifier == \"test-action\")\n        } else {\n            Issue.record(\"Expected pageAction event\")\n        }\n    }\n\n    @Test\n    func testPageAutomatedWithoutIdentifier() {\n        let (env, delegate, _) = makeEnvironment()\n\n        env.pageAutomated(\n            identifier: nil,\n            reportingMetadata: nil,\n            layoutState: .empty\n        )\n\n        // Should not report event when identifier is nil\n        #expect(delegate.reportedEvents.isEmpty)\n    }\n\n    @Test\n    func testMultiplePageViewsWithHistory() {\n        let tracker = ThomasPagerTracker()\n        let (env, delegate, timer) = makeEnvironment(pagerTracker: tracker)\n\n        let pagerState = PagerState(\n            identifier: \"test-pager\",\n            branching: nil\n        )\n\n        // View multiple pages in sequence\n        let page1 = ThomasPageInfo(identifier: \"page-0\", index: 0, viewCount: 1)\n        timer.time = 0\n        env.pageViewed(pagerState: pagerState, pageInfo: page1, layoutState: .empty)\n\n        let page2 = ThomasPageInfo(identifier: \"page-1\", index: 1, viewCount: 1)\n        timer.time = 5.0\n        env.pageViewed(pagerState: pagerState, pageInfo: page2, layoutState: .empty)\n\n        let page3 = ThomasPageInfo(identifier: \"page-2\", index: 2, viewCount: 1)\n        timer.time = 10.0\n        env.pageViewed(pagerState: pagerState, pageInfo: page3, layoutState: .empty)\n\n        // Verify all page views were reported\n        #expect(delegate.reportedEvents.count == 3)\n\n        // Verify each event has progressively more history in context\n        if case .pageView(_, let context1) = delegate.reportedEvents[0] {\n            #expect(context1.pager?.pageHistory.count == 0)\n        }\n\n        if case .pageView(_, let context2) = delegate.reportedEvents[1] {\n            #expect(context2.pager?.pageHistory.count == 1)\n            #expect(context2.pager?.pageHistory[0].identifier == \"page-0\")\n        }\n\n        if case .pageView(_, let context3) = delegate.reportedEvents[2] {\n            #expect(context3.pager?.pageHistory.count == 2)\n            #expect(context3.pager?.pageHistory[0].identifier == \"page-0\")\n            #expect(context3.pager?.pageHistory[1].identifier == \"page-1\")\n        }\n    }\n\n    // MARK: - Dismiss Tests\n\n    @Test\n    func testDismissWithButtonVerifyEventDetails() {\n        let (env, delegate, timer) = makeEnvironment()\n        timer.time = 10.5\n\n        env.dismiss(\n            buttonIdentifier: \"close-btn\",\n            buttonDescription: \"Close Button\",\n            cancel: true,\n            layoutState: .empty\n        )\n\n        #expect(env.isDismissed)\n        #expect(!timer.isStarted)\n        #expect(timer.stopCount == 1)\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .dismiss(let dismissType, let displayTime, _) = delegate.reportedEvents[0] {\n            // Verify it's buttonTapped type\n            if case .buttonTapped(let id, let desc) = dismissType {\n                #expect(id == \"close-btn\")\n                #expect(desc == \"Close Button\")\n            } else {\n                Issue.record(\"Expected buttonTapped dismiss type\")\n            }\n            // Verify display time\n            #expect(displayTime == 10.5)\n        } else {\n            Issue.record(\"Expected dismiss event\")\n        }\n\n        #expect(delegate.dismissals[0] == true)\n    }\n\n    @Test\n    func testDismissUserDismissedEventType() {\n        let (env, delegate, timer) = makeEnvironment()\n        timer.time = 7.3\n\n        env.dismiss(cancel: false, layoutState: .empty)\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .dismiss(let dismissType, let displayTime, _) = delegate.reportedEvents[0] {\n            // Verify it's userDismissed type\n            if case .userDismissed = dismissType {\n                // Success\n            } else {\n                Issue.record(\"Expected userDismissed dismiss type\")\n            }\n            // Verify display time\n            #expect(displayTime == 7.3)\n        } else {\n            Issue.record(\"Expected dismiss event\")\n        }\n    }\n\n    @Test\n    func testTimedOutEventType() {\n        let (env, delegate, timer) = makeEnvironment()\n        timer.time = 30.0\n\n        env.timedOut(layoutState: .empty)\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .dismiss(let dismissType, let displayTime, _) = delegate.reportedEvents[0] {\n            // Verify it's timedOut type\n            if case .timedOut = dismissType {\n                // Success\n            } else {\n                Issue.record(\"Expected timedOut dismiss type\")\n            }\n            // Verify display time\n            #expect(displayTime == 30.0)\n        } else {\n            Issue.record(\"Expected dismiss event\")\n        }\n    }\n\n    @Test\n    func testRepeatedDismissIsIdempotent() {\n        let (env, delegate, timer) = makeEnvironment()\n\n        env.dismiss()\n        env.dismiss()\n        env.dismiss()\n\n        // Should only dismiss once\n        #expect(env.isDismissed)\n        #expect(timer.stopCount == 1)\n        #expect(delegate.dismissals.count == 1)\n    }\n\n    @Test\n    func testOnDismissCallbackCalledOnce() {\n        var callCount = 0\n        let (env, _, _) = makeEnvironment(onDismiss: {\n            callCount += 1\n        })\n\n        env.dismiss()\n        env.dismiss()\n\n        #expect(callCount == 1)\n    }\n\n    @Test\n    func testDismissFromWithinCallback() {\n        var env: ThomasEnvironment!\n        var recursiveCallAttempted = false\n\n        let delegate = TestThomasDelegate()\n        let timer = TestTimer()\n        timer.time = 5.0\n\n        env = ThomasEnvironment(\n            delegate: delegate,\n            extensions: nil,\n            pagerTracker: nil,\n            timer: timer,\n            onDismiss: {\n                // Attempt to dismiss again from within callback\n                recursiveCallAttempted = true\n                env.dismiss()\n            }\n        )\n\n        env.dismiss()\n\n        // Should be dismissed only once\n        #expect(env.isDismissed)\n        #expect(recursiveCallAttempted)\n        #expect(delegate.dismissals.count == 1)\n        #expect(delegate.reportedEvents.count == 1)\n    }\n\n    // MARK: - Pager Summary Tests\n\n    @Test\n    func testPagerSummaryEmittedBeforeDismiss() {\n        let tracker = ThomasPagerTracker()\n        let (env, delegate, timer) = makeEnvironment(pagerTracker: tracker)\n\n        let pagerState = PagerState(identifier: \"test-pager\", branching: nil)\n        let pageInfo = ThomasPageInfo(identifier: \"page-0\", index: 0, viewCount: 1)\n\n        // View a page\n        timer.time = 0\n        env.pageViewed(pagerState: pagerState, pageInfo: pageInfo, layoutState: .empty)\n\n        timer.time = 10.0\n        env.dismiss()\n\n        // Should have pageView, pagerSummary, then dismiss - in that order\n        #expect(delegate.reportedEvents.count == 3)\n\n        // Verify order\n        if case .pageView = delegate.reportedEvents[0] {\n            // Correct\n        } else {\n            Issue.record(\"Expected pageView as first event\")\n        }\n\n        if case .pagerSummary = delegate.reportedEvents[1] {\n            // Correct - summary before dismiss\n        } else {\n            Issue.record(\"Expected pagerSummary before dismiss\")\n        }\n\n        if case .dismiss = delegate.reportedEvents[2] {\n            // Correct - dismiss last\n        } else {\n            Issue.record(\"Expected dismiss as last event\")\n        }\n    }\n\n    @Test\n    func testPagerTrackerStoppedOnDismiss() {\n        let tracker = ThomasPagerTracker()\n        let (env, _, timer) = makeEnvironment(pagerTracker: tracker)\n\n        // Track a page view using environment\n        let pagerState = PagerState(identifier: \"test-pager\", branching: nil)\n        let pageInfo = ThomasPageInfo(identifier: \"page-0\", index: 0, viewCount: 1)\n\n        timer.time = 0\n        env.pageViewed(pagerState: pagerState, pageInfo: pageInfo, layoutState: .empty)\n\n        timer.time = 5.0\n        env.dismiss()\n\n        // Verify tracker was stopped (viewed pages should be captured)\n        let viewedPages = tracker.viewedPages(pagerIdentifier: \"test-pager\")\n        #expect(viewedPages.count == 1)\n        #expect(viewedPages[0].displayTime == 5.0)\n    }\n\n    // MARK: - State Change Tests\n\n    @Test\n    func testStateChangeForwardedToDelegate() {\n        let (env, delegate, _) = makeEnvironment()\n\n        let state = AirshipJSON.object([\"key\": .string(\"value\")])\n        env.onStateChange(state)\n\n        #expect(delegate.stateChanges.count == 1)\n        #expect(delegate.stateChanges[0] == state)\n    }\n\n    // MARK: - Layout Context Tests\n\n    @Test\n    func testLayoutContextWithNilStates() {\n        let (env, delegate, _) = makeEnvironment()\n\n        env.buttonTapped(\n            buttonIdentifier: \"test\",\n            reportingMetadata: nil,\n            layoutState: .empty\n        )\n\n        #expect(delegate.reportedEvents.count == 1)\n\n        if case .buttonTap(_, let context) = delegate.reportedEvents[0] {\n            #expect(context.pager == nil)\n            #expect(context.form == nil)\n        } else {\n            Issue.record(\"Expected buttonTap event with context\")\n        }\n    }\n\n    // MARK: - Action Runner Tests\n\n    @Test\n    func testRunActionsWithNilPayload() {\n        let (env, _, _) = makeEnvironment()\n\n        // Should not crash with nil payload\n        env.runActions(nil, layoutState: .empty)\n    }\n\n    @Test\n    func testRunActionsWithEmptyValue() {\n        let (env, _, _) = makeEnvironment()\n\n        // Create payload with nil value\n        let emptyPayload = ThomasActionsPayload(value: .null)\n\n        // Should return early when value is nil\n        env.runActions(emptyPayload, layoutState: .empty)\n    }\n\n    @Test\n    func testRunActionsWithCustomRunner() {\n        let testRunner = TestThomasActionRunner()\n        let extensions = ThomasExtensions(\n            imageProvider: nil,\n            actionRunner: testRunner\n        )\n        let (env, _, _) = makeEnvironment(extensions: extensions)\n\n        let payload = ThomasActionsPayload(value: .object([\"test_action\": .string(\"test_value\")]))\n\n        env.runActions(payload, layoutState: .empty)\n\n        // Verify custom runner was called\n        #expect(testRunner.runAsyncCalled)\n        #expect(testRunner.lastActions != nil)\n    }\n\n    @Test\n    func testRunActionWithCustomRunner() async {\n        let testRunner = TestThomasActionRunner()\n        let extensions = ThomasExtensions(\n            imageProvider: nil,\n            actionRunner: testRunner\n        )\n        let (env, _, _) = makeEnvironment(extensions: extensions)\n\n        let arguments = ActionArguments(\n            string: \"test_value\",\n            situation: .automation\n        )\n\n        _ = await env.runAction(\n            \"test_action\",\n            arguments: arguments,\n            layoutState: .empty\n        )\n\n        // Verify custom runner was called\n        #expect(testRunner.runCalled)\n        #expect(testRunner.lastActionName == \"test_action\")\n    }\n\n    // MARK: - Integration Tests\n\n    @Test\n    func testFullLifecycleWithPager() {\n        let tracker = ThomasPagerTracker()\n        let (env, delegate, timer) = makeEnvironment(pagerTracker: tracker)\n\n        // Initialize - not visible\n        #expect(!timer.isStarted)\n\n        // Make visible and foregrounded\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        #expect(timer.isStarted)\n\n        // View pages\n        let pagerState = PagerState(identifier: \"lifecycle-pager\", branching: nil)\n        timer.time = 1.0\n        env.pageViewed(pagerState: pagerState, pageInfo: ThomasPageInfo(identifier: \"page-0\", index: 0, viewCount: 1), layoutState: .empty)\n\n        timer.time = 5.0\n        env.pageViewed(pagerState: pagerState, pageInfo: ThomasPageInfo(identifier: \"page-1\", index: 1, viewCount: 1), layoutState: .empty)\n\n        // Background\n        env.onVisibilityChanged(isVisible: true, isForegrounded: false)\n        #expect(!timer.isStarted)\n\n        // Foreground again\n        env.onVisibilityChanged(isVisible: true, isForegrounded: true)\n        #expect(timer.isStarted)\n\n        // Dismiss\n        timer.time = 10.0\n        env.dismiss(buttonIdentifier: \"close\", buttonDescription: \"Close\", cancel: false, layoutState: .empty)\n\n        // Verify full sequence\n        #expect(env.isDismissed)\n        #expect(!timer.isStarted)\n        #expect(delegate.visibilityChanges.count == 3)\n\n        // Events: 2 pageViews + 1 pagerSummary + 1 dismiss\n        #expect(delegate.reportedEvents.count == 4)\n\n        // Verify summary came before dismiss\n        if case .pagerSummary = delegate.reportedEvents[2] {\n            // Correct\n        } else {\n            Issue.record(\"Expected pagerSummary before dismiss\")\n        }\n\n        if case .dismiss = delegate.reportedEvents[3] {\n            // Correct\n        } else {\n            Issue.record(\"Expected dismiss last\")\n        }\n    }\n\n    @Test\n    func testPagerTrackerIsolationBetweenEnvironments() {\n        let sharedTracker = ThomasPagerTracker()\n\n        // Create two environments sharing same tracker\n        let (env1, delegate1, timer1) = makeEnvironment(pagerTracker: sharedTracker)\n        let (env2, delegate2, timer2) = makeEnvironment(pagerTracker: sharedTracker)\n\n        let pagerState = PagerState(identifier: \"shared-pager\", branching: nil)\n\n        // View page in env1\n        timer1.time = 0\n        env1.pageViewed(pagerState: pagerState, pageInfo: ThomasPageInfo(identifier: \"page-0\", index: 0, viewCount: 1), layoutState: .empty)\n\n        // View page in env2\n        timer2.time = 5.0\n        env2.pageViewed(pagerState: pagerState, pageInfo: ThomasPageInfo(identifier: \"page-1\", index: 1, viewCount: 1), layoutState: .empty)\n\n        // Dismiss env1\n        timer1.time = 10.0\n        env1.dismiss()\n\n        // Env1 should have pager summary\n        let env1Summaries = delegate1.reportedEvents.filter {\n            if case .pagerSummary = $0 { return true }\n            return false\n        }\n        #expect(env1Summaries.count == 1)\n\n        // Env2 should still be able to emit its own summary\n        timer2.time = 15.0\n        env2.dismiss()\n\n        let env2Summaries = delegate2.reportedEvents.filter {\n            if case .pagerSummary = $0 { return true }\n            return false\n        }\n        #expect(env2Summaries.count == 1)\n    }\n}\n\n// MARK: - Test Action Runner\n\n@MainActor\nfinal class TestThomasActionRunner: ThomasActionRunner {\n    var runAsyncCalled = false\n    var runCalled = false\n    var lastActions: AirshipJSON?\n    var lastActionName: String?\n    var lastLayoutContext: ThomasLayoutContext?\n\n    func runAsync(actions: AirshipJSON, layoutContext: ThomasLayoutContext) {\n        runAsyncCalled = true\n        lastActions = actions\n        lastLayoutContext = layoutContext\n    }\n\n    func run(actionName: String, arguments: ActionArguments, layoutContext: ThomasLayoutContext) async -> ActionResult {\n        runCalled = true\n        lastActionName = actionName\n        lastLayoutContext = layoutContext\n        return .completed(AirshipJSON.null)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasFormDataCollectorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\nimport Combine\n\n@testable import AirshipCore\n\n@MainActor\nstruct ThomasFormDataCollectorTest {\n    private let pagerState: PagerState = PagerState(\n        identifier: UUID().uuidString,\n        branching: nil\n    )\n\n    private let formState: ThomasFormState = ThomasFormState(\n        identifier: UUID().uuidString,\n        formType: .form,\n        formResponseType: nil,\n        validationMode: .onDemand\n    )\n\n    private let pages: [ThomasViewInfo.Pager.Item] = [\n        .init(\n            identifier: UUID().uuidString,\n            view: .emptyView(.init(commonProperties: .init(), properties: .init())),\n            displayActions: nil,\n            automatedActions: nil,\n            accessibilityActions: nil,\n            stateActions: nil,\n            branching: nil\n        ),\n        .init(\n            identifier: UUID().uuidString,\n            view: .emptyView(.init(commonProperties: .init(), properties: .init())),\n            displayActions: nil,\n            automatedActions: nil,\n            accessibilityActions: nil,\n            stateActions: nil,\n            branching: nil\n        ),\n        .init(\n            identifier: UUID().uuidString,\n            view: .emptyView(.init(commonProperties: .init(), properties: .init())),\n            displayActions: nil,\n            automatedActions: nil,\n            accessibilityActions: nil,\n            stateActions: nil,\n            branching: nil\n        )\n    ]\n\n    init() {\n        pagerState.setPagesAndListenForUpdates(\n            pages: self.pages,\n            thomasState: .init(formState: self.formState, pagerState: .init(identifier: \"\", branching: nil)) { _ in },\n            swipeDisableSelectors: nil\n        )\n    }\n\n    @Test(\"Test collect no page ID.\")\n    func testCollectNoPageID() async throws {\n        let collector = ThomasFormDataCollector(\n            formState: self.formState,\n            pagerState: self.pagerState\n        )\n\n        collector.updateField(.invalidField(identifier: \"invalid\", input: .score(1.0)), pageID: nil)\n        #expect(self.formState.activeFields[\"invalid\"] != nil)\n    }\n\n    @Test(\"Test collect with page ID.\")\n    func testCollectWithPageID() async throws {\n        let collector = ThomasFormDataCollector(\n            formState: self.formState,\n            pagerState: self.pagerState\n        )\n\n        var activeFields = self.formState.$activeFields.values.makeAsyncIterator()\n\n        collector.updateField(\n            .invalidField(\n                identifier: \"invalid\",\n                input: .score(1.0)\n            ),\n            pageID: pages[1].id\n        )\n\n        await #expect(activeFields.next()?[\"invalid\"] == nil)\n        self.pagerState.process(request: .next)\n        await #expect(activeFields.next()?[\"invalid\"] != nil)\n    }\n    \n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasFormFieldProcessorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\n@MainActor\nstruct ThomasFormFieldProcessorTest {\n    private let processor: DefaultThomasFormFieldProcessor\n    private let taskSleeper = TestTaskSleeper()\n    private let date = UATestDate(offset: 0, dateOverride: Date())\n\n    init() {\n        self.processor = DefaultThomasFormFieldProcessor(\n            date: self.date,\n            taskSleeper: self.taskSleeper\n        )\n    }\n\n    @Test(\"Test early process delay\")\n    func testProcessingEarlyProcessDelay() async throws {\n        await taskSleeper.pause()\n\n        let request = processor.submit(processDelay: 200.0) {\n            return .invalid\n        }\n\n        var sleeps = await taskSleeper.sleepUpdates.makeAsyncIterator()\n        #expect(await sleeps.next() == [200])\n        #expect(request.result == nil)\n\n        await taskSleeper.resume()\n        await request.process(retryErrors: false)\n        #expect(request.result == .invalid)\n    }\n\n    @Test(\"Test negative early process delay should pass to task sleeper\")\n    func testProcessingEarlyProcessNatativeDelay() async throws {\n        await taskSleeper.pause()\n\n        let request = processor.submit(processDelay: -1.0) {\n            return .invalid\n        }\n\n        var sleeps = await taskSleeper.sleepUpdates.makeAsyncIterator()\n        #expect(await sleeps.next() == [-1.0])\n        #expect(request.result == nil)\n\n        await taskSleeper.resume()\n        await request.process(retryErrors: false)\n        #expect(request.result == .invalid)\n    }\n\n    @Test(\"Test invalid result does not retry\")\n    func testInvalidResultDoesNotRetry() async throws {\n        await confirmation { confirmation in\n            let request = processor.submit(processDelay: 1.0) {\n                confirmation.confirm()\n                return .invalid\n            }\n            await request.process(retryErrors: true)\n            await request.process(retryErrors: true)\n            await request.process(retryErrors: true)\n            #expect(request.result == .invalid)\n        }\n\n        #expect(await taskSleeper.sleeps == [1.0])\n    }\n\n    @Test(\"Test valid result does not retry\")\n    func testValidResultDoesNotRetry() async throws {\n        let result = ThomasFormFieldPendingResult.valid(.init(value: .score(100.0)))\n        await confirmation { confirmation in\n            let request = processor.submit(processDelay: 1.0) {\n                confirmation.confirm()\n                return result\n            }\n            await request.process(retryErrors: true)\n            await request.process(retryErrors: true)\n            await request.process(retryErrors: true)\n            #expect(request.result == result)\n        }\n\n        #expect(await taskSleeper.sleeps == [1.0])\n    }\n\n    @Test(\"Test error result will retry\")\n    func testErrorRetries() async throws {\n        await confirmation(expectedCount: 3) { confirmation in\n            let request = processor.submit(processDelay: 1.0) {\n                confirmation.confirm()\n                return .error\n            }\n            await request.process(retryErrors: true)\n            await request.process(retryErrors: true)\n            await request.process(retryErrors: true)\n            #expect(request.result == .error)\n        }\n    }\n\n    @Test(\"Test retry backoff\")\n    func testAsyncValidationError() async throws {\n        await confirmation(expectedCount: 8) { confirmation in\n            let request = processor.submit(processDelay: 1.0) {\n                confirmation.confirm()\n                return .error\n            }\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0])\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0])\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0, 6.0])\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0, 6.0, 12.0])\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0, 6.0, 12.0, 15.0])\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0, 6.0, 12.0, 15.0, 15.0])\n\n            date.offset += 10.0\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0, 6.0, 12.0, 15.0, 15.0, 5.0])\n\n            await request.process(retryErrors: true)\n            #expect(await taskSleeper.sleeps == [1.0, 3.0, 6.0, 12.0, 15.0, 15.0, 5.0, 15.0])\n        }\n    }\n\n    @Test(\"Test updates\")\n    func testUpdates() async throws {\n        var resultStream = AsyncStream<ThomasFormFieldPendingResult> { continuation in\n            continuation.yield(.error)\n            continuation.yield(.error)\n            continuation.yield(.invalid)\n        }.makeAsyncIterator()\n\n        let request = processor.submit(processDelay: 1.0) {\n            return await resultStream.next()!\n        }\n\n        var updates = request.resultUpdates { result in\n            guard let result else {\n                return \"pending\"\n            }\n            return if result == .error {\n                \"error\"\n            } else {\n                \"not an error\"\n            }\n        }.makeAsyncIterator()\n\n        #expect(await updates.next() == \"pending\")\n\n        await request.process(retryErrors: true)\n        #expect(await updates.next() == \"error\")\n\n        await request.process(retryErrors: true)\n        #expect(await updates.next() == \"error\")\n\n        await request.process(retryErrors: true)\n        #expect(await updates.next() == \"not an error\")\n    }\n\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasFormFieldTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\n@MainActor\nstruct ThomasFormFieldTest {\n\n    @Test(\"Test invalid field.\")\n    func testInvalidField() async throws {\n        let field = ThomasFormField.invalidField(\n            identifier: \"some-ID\",\n            input: .text(\"some-text\")\n        )\n\n        #expect(field.status == .invalid)\n\n        // Process does nothing\n        await field.process(retryErrors: true)\n        await field.process(retryErrors: false)\n\n        #expect(field.status == .invalid)\n\n        var statusUpdates = field.statusUpdates.makeAsyncIterator()\n        #expect(await statusUpdates.next() == .invalid)\n    }\n\n    @Test(\"Test valid field.\")\n    func testValidFieldStatus() async throws {\n        let field = ThomasFormField.validField(\n            identifier: \"some-ID\",\n            input: .text(\"some-text\"),\n            result: .init(value: .text(\"some-other-text\"))\n        )\n\n        #expect(field.status == .valid(.init(value: .text(\"some-other-text\"))))\n\n        // Process does nothing\n        await field.process(retryErrors: true)\n        await field.process(retryErrors: false)\n\n        #expect(field.status == .valid(.init(value: .text(\"some-other-text\"))))\n\n        var statusUpdates = field.statusUpdates.makeAsyncIterator()\n        #expect(await statusUpdates.next() == .valid(.init(value: .text(\"some-other-text\"))))\n    }\n\n    @Test(\"Test async field.\")\n    func testAsyncField() async throws {\n        let pendingRequest = TestPendinRequest()\n        let processor = TestProcesssor()\n        processor.onSubmit = { interval, resultBlock in\n            #expect(interval == 3.0)\n            pendingRequest.resultBlock = resultBlock\n            return pendingRequest\n        }\n\n        let field = ThomasFormField.asyncField(\n            identifier: \"some-ID\",\n            input: .text(\"some-text\"),\n            processDelay: 3.0,\n            processor: processor\n        ) {\n            .valid(.init(value: .text(\"some valid text\")))\n        }\n\n        #expect(field.status == .pending)\n        #expect(pendingRequest.didProcess == false)\n        #expect(pendingRequest.didRetry == false)\n\n        await field.process(retryErrors: false)\n        #expect(pendingRequest.didProcess == true)\n        #expect(pendingRequest.didRetry == false)\n\n        pendingRequest.didProcess = false\n        await field.process(retryErrors: true)\n        #expect(pendingRequest.didProcess == true)\n        #expect(pendingRequest.didRetry == true)\n\n        var statusUpdates = field.statusUpdates.makeAsyncIterator()\n        #expect(await statusUpdates.next() == .pending)\n\n        // Update the result\n        pendingRequest.result = try await pendingRequest.resultBlock?()\n        #expect(await statusUpdates.next() == .valid(.init(value: .text(\"some valid text\"))))\n\n        // Update the result to the error\n        pendingRequest.result = .error\n        #expect(await statusUpdates.next() == .error)\n        #expect(field.status == .error)\n\n        // Update the result to the error\n        pendingRequest.result = .invalid\n        #expect(await statusUpdates.next() == .invalid)\n        #expect(field.status == .invalid)\n    }\n\n    @MainActor\n    fileprivate class TestPendinRequest: ThomasFormFieldPendingRequest {\n        func cancel() {\n\n        }\n        \n        var result: ThomasFormFieldPendingResult? {\n            didSet {\n                onResult.values.forEach { $0(result) }\n            }\n        }\n\n        var onResult: [String: (ThomasFormFieldPendingResult?) -> Void] = [:]\n        var resultBlock: (@MainActor @Sendable () async throws -> ThomasFormFieldPendingResult)?\n\n        func resultUpdates<T>(\n            mapper: @escaping @Sendable (ThomasFormFieldPendingResult?) -> T\n        ) -> AsyncStream<T> where T : Sendable {\n            return AsyncStream { continuation in\n                continuation.yield(mapper(result))\n\n                let id = UUID().uuidString\n\n                onResult[id] = { result in\n                    continuation.yield(mapper(result))\n                }\n\n                continuation.onTermination = { _ in\n                    Task { @MainActor in\n                        self.onResult[id] = nil\n                    }\n                }\n            }\n        }\n\n        var didProcess: Bool = false\n        var didRetry: Bool = false\n\n        func process(retryErrors: Bool) async {\n            didProcess = true\n            didRetry = retryErrors\n        }\n    }\n\n    @MainActor\n    fileprivate class TestProcesssor: ThomasFormFieldProcessor {\n\n        var onSubmit: ((TimeInterval, @escaping @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult) -> TestPendinRequest)?\n\n        func submit(\n            processDelay: TimeInterval,\n            resultBlock: @escaping @MainActor @Sendable () async throws -> ThomasFormFieldPendingResult\n        ) -> any ThomasFormFieldPendingRequest {\n            return onSubmit!(processDelay, resultBlock)\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasFormPayloadGeneratorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\n@MainActor\nstruct ThomasFormPayloadGeneratorTest {\n\n    @Test(\"Test form data\")\n    func testFormData() throws {\n        let form: ThomasFormField.Value = .form(\n                responseType: \"user_feedback\",\n                children: [\n                    \"some-radio-input\": .radio(AirshipJSON.string(\"some-radio-input-value\")),\n                    \"some-toggle-input\": .toggle(true),\n                    \"some-score-input\": .score(7.0),\n                    \"some-text-input\": .text(\"neat text\"),\n                    \"some-email-input\": .email(\"email@email.email\"),\n                    \"some-sms-input\": .sms(\"123\", nil),\n                    \"some-child-score\": .score(8.0),\n                    \"some-child-form\": .form(\n                        responseType: \"some-child-form-response\",\n                        children: [\n                            \"some-other-text-input\": .text(\"other neat text\")\n                        ]\n                    ),\n                    \"some-child-nps-form\": .npsForm(\n                        responseType: \"some-nps-child-form-response\",\n                        scoreID: \"some-other-child-score\",\n                        children: [\n                            \"some-other-child-score\": .score(9.0)\n                        ]\n                    ),\n                    \"text-nil\": .text(nil),\n                    \"email-nil\": .email(nil),\n                    \"sms-nil\": .sms(nil, .init(countryCode: \"US\", prefix: \"+1\")),\n                    \"score-nil\": .score(nil),\n                    \"radio-nil\": .radio(nil)\n                ]\n        )\n\n        let expectedJSON: String = \"\"\"\n        {\n          \"some-form-id\": {\n            \"type\": \"form\",\n            \"response_type\": \"user_feedback\",\n            \"children\": {\n              \"some-radio-input\": {\n                \"type\": \"single_choice\",\n                \"value\": \"some-radio-input-value\"\n              },\n              \"some-toggle-input\": {\n                \"type\": \"toggle\",\n                \"value\": true\n              },\n              \"some-score-input\": {\n                \"type\": \"score\",\n                \"value\": 7.0\n              },\n              \"some-text-input\": {\n                \"type\": \"text_input\",\n                \"value\": \"neat text\"\n              },\n              \"some-email-input\": {\n                \"type\": \"email_input\",\n                \"value\": \"email@email.email\"\n              },\n              \"some-sms-input\": {\n                \"type\": \"sms_input\",\n                \"value\": \"123\"\n              },\n              \"some-child-score\": {\n                \"type\": \"score\",\n                \"value\": 8.0\n              },\n              \"text-nil\": {\n                \"type\": \"text_input\"\n              },\n              \"email-nil\": {\n                \"type\": \"email_input\"\n              },\n              \"sms-nil\": {\n                \"type\": \"sms_input\"\n              },\n              \"score-nil\": {\n                \"type\": \"score\"\n              },\n              \"radio-nil\": {\n                \"type\": \"single_choice\"\n              },\n              \"some-child-form\": {\n                \"type\": \"form\",\n                \"response_type\": \"some-child-form-response\",\n                \"children\": {\n                  \"some-other-text-input\": {\n                    \"type\": \"text_input\",\n                    \"value\": \"other neat text\"\n                  }\n                }\n              },\n              \"some-child-nps-form\": {\n                \"type\": \"nps\",\n                \"response_type\": \"some-nps-child-form-response\",\n                \"score_id\": \"some-other-child-score\",\n                \"children\": {\n                  \"some-other-child-score\": {\n                    \"type\": \"score\",\n                    \"value\": 9\n                  }\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try ThomasFormPayloadGenerator.makeFormEventPayload(\n            identifier: \"some-form-id\",\n            formValue: form\n        )\n        #expect(actual == expected)\n    }\n\n    @Test(\"Test nps form data\")\n    func testNPSFormData() throws {\n        let npsForm: ThomasFormField.Value = .npsForm(\n                responseType: \"user_feedback\",\n                scoreID: \"some-child-score\",\n                children: [\n                    \"some-text-input\": .text(\"neat text\"),\n                    \"some-email-input\": .email(\"email@email.email\"),\n                    \"some-child-score\": .score(8.0),\n                ]\n        )\n\n        let expectedJSON: String = \"\"\"\n        {\n          \"some-form-id\": {\n            \"type\": \"nps\",\n            \"score_id\": \"some-child-score\",\n            \"response_type\": \"user_feedback\",\n            \"children\": {\n              \"some-child-score\": {\n                \"type\": \"score\",\n                \"value\": 8\n              },\n              \"some-text-input\": {\n                \"type\": \"text_input\",\n                \"value\": \"neat text\"\n              },\n              \"some-email-input\": {\n                \"type\": \"email_input\",\n                \"value\": \"email@email.email\"\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = try ThomasFormPayloadGenerator.makeFormEventPayload(\n            identifier: \"some-form-id\",\n            formValue: npsForm\n        )\n        #expect(actual == expected)\n    }\n\n    @Test(\"Test passing other values throws\")\n    func testFormDataThrows() throws {\n        #expect(throws: NSError.self) {\n            try ThomasFormPayloadGenerator.makeFormEventPayload(\n                identifier: \"some-form-id\",\n                formValue: .text(\"some-text\")\n            )\n        }\n    }\n\n    @Test(\n        \"Test state data\",\n        arguments: [\n            ThomasFormState.Status.valid,\n            ThomasFormState.Status.invalid,\n            ThomasFormState.Status.error,\n            ThomasFormState.Status.pendingValidation,\n            ThomasFormState.Status.submitted,\n            ThomasFormState.Status.validating\n        ]\n    )\n    func testStateData(formStatus: ThomasFormState.Status) async throws {\n        let errorField = ThomasFormField.asyncField(\n            identifier: \"some-async-id\",\n            input: .score(7.0),\n            processDelay: 0\n        ) { .error }\n        await errorField.process() // gets the error\n\n        let pendingField = ThomasFormField.asyncField(\n            identifier: \"some-pending-async-id\",\n            input: .score(7.0),\n            processDelay: 100.0\n        ) { .invalid }\n\n        let fields: [ThomasFormField] = [\n            ThomasFormField.invalidField(identifier: \"some-invalid-id\", input: .email(\"neat\")),\n            ThomasFormField.validField(identifier: \"some-valid-id\", input: .email(\"neat\"), result: .init(value: .email(\"actual\"))),\n            errorField,\n            pendingField\n        ]\n\n        let expectedJSON = \"\"\"\n        {\n           \"data\":{\n              \"children\":{\n                 \"some-valid-id\":{\n                    \"value\":\"neat\",\n                    \"type\":\"email_input\",\n                    \"status\":{\n                       \"result\":{\n                          \"value\":\"actual\",\n                          \"type\":\"email_input\"\n                       },\n                       \"type\":\"valid\"\n                    }\n                 },\n                 \"some-invalid-id\":{\n                    \"status\":{\n                       \"type\":\"invalid\"\n                    },\n                    \"value\":\"neat\",\n                    \"type\":\"email_input\"\n                 },\n                 \"some-async-id\":{\n                    \"value\":7,\n                    \"status\":{\n                       \"type\":\"error\"\n                    },\n                    \"type\":\"score\"\n                 },\n                 \"some-pending-async-id\":{\n                    \"type\":\"score\",\n                    \"value\":7,\n                    \"status\":{\n                       \"type\":\"pending\"\n                    }\n                 }\n              },\n              \"type\": \"form\"\n           },\n           \"status\":{\n              \"type\": \"\\(formStatus.rawValue)\"\n           }\n        }\n        \"\"\"\n\n\n        let expected = try AirshipJSON.from(json: expectedJSON)\n        let actual = ThomasFormPayloadGenerator.makeFormStatePayload(\n            status: formStatus,\n            fields: fields,\n            formType: .form\n        )\n        #expect(actual == expected)\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasFormStateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\nimport Combine\n\n@MainActor\nstruct ThomasFormStateTest {\n\n    @Test(\"Test empty form\")\n    func testEmptyForm() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        #expect(form.identifier ==  \"some-form-id\")\n        #expect(form.formType == .form)\n        #expect(form.formResponseType == \"response type\")\n        #expect(form.validationMode == .immediate)\n\n        #expect(form.status == .invalid)\n        #expect(form.isFormInputEnabled == true)\n        #expect(form.isEnabled == true)\n        #expect(form.isVisible == false)\n        #expect(form.activeFields.isEmpty == true)\n    }\n\n    @Test(\"Test empty nps form\")\n    func testEmptyNPSForm() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .nps(\"score-id\"),\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        #expect(form.identifier ==  \"some-form-id\")\n        #expect(form.formType == .nps(\"score-id\"))\n        #expect(form.formResponseType == \"response type\")\n        #expect(form.validationMode == .immediate)\n\n        #expect(form.status == .invalid)\n        #expect(form.isFormInputEnabled == true)\n        #expect(form.isEnabled == true)\n        #expect(form.isVisible == false)\n        #expect(form.activeFields.isEmpty == true)\n    }\n\n    @Test(\"Test empty form with on demand validation\")\n    func testEmptyFormOnDemandValidation() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n\n        #expect(form.identifier ==  \"some-form-id\")\n        #expect(form.formType == .form)\n        #expect(form.formResponseType == \"response type\")\n        #expect(form.validationMode == .onDemand)\n\n        #expect(form.status == .pendingValidation)\n        #expect(form.isFormInputEnabled == true)\n        #expect(form.isEnabled == true)\n        #expect(form.isVisible == false)\n        #expect(form.activeFields.isEmpty == true)\n    }\n\n    @Test(\"Test empty form submit\")\n    func testEmptyFormSubmit() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n        form.onSubmit = { _, _, _ in }\n\n        await #expect(throws: NSError.self) {\n            try await form.submit(layoutState: .empty)\n        }\n    }\n\n    @Test(\"Test submit empty data throws.\")\n    func testInvalidFormSubmit() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n        form.onSubmit = { _, _, _ in }\n\n        form.updateField(.invalidField(identifier: \"some-id\", input: .email(nil)))\n\n        await #expect(throws: NSError.self) {\n            try await form.submit(layoutState: .empty)\n        }\n    }\n\n    @Test(\"Test update field predicate does not apply\")\n    func testSubmitSingleFilteredField() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n        form.onSubmit = { _, _, _ in }\n\n        form.updateField(\n            .validField(identifier: \"some-id\", input: .email(nil), result: .init(value: .email(\"valid email\")))\n        ) {\n            false\n        }\n\n        await #expect(throws: NSError.self) {\n            try await form.submit(layoutState: .empty)\n        }\n    }\n\n    @Test(\"Test submit.\")\n    func testSubmit() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n\n        let field: ThomasFormField = .validField(\n            identifier: \"some-id\",\n            input: .email(nil),\n            result: .init(\n                value: .email(\"valid email\"),\n                channels: [\n                    .email(\"some email\", .doubleOptIn(.init()))\n                ],\n                attributes: [\n                    .init(\n                        attributeName: .init(channel: \"some-id\"),\n                        attributeValue: .string(\"some value\")\n                    )\n                ]\n            )\n        )\n\n        let anotherField: ThomasFormField = .validField(\n            identifier: \"some-other-id\",\n            input: .email(nil),\n            result: .init(\n                value: .email(\"other valid email\"),\n                channels: [\n                    .email(\"some other email\", .doubleOptIn(.init()))\n                ],\n                attributes: [\n                    .init(\n                        attributeName: .init(channel: \"some-id\"),\n                        attributeValue: .string(\"some other value\")\n                    )\n                ]\n            )\n        )\n\n        form.updateField(field)\n        form.updateField(anotherField)\n        #expect(form.activeFields.count == 2)\n\n        try await confirmation { confirmation in\n            form.onSubmit = { id, result, _ in\n                let expectedResult: ThomasFormField.Result = .init(\n                    value: .form(\n                        responseType: form.formResponseType,\n                        children: [\n                            \"some-other-id\": .email(\"other valid email\"),\n                            \"some-id\": .email(\"valid email\"),\n                        ]\n                    ),\n                    channels: field.channels + anotherField.channels,\n                    attributes: field.attributes + anotherField.attributes\n                )\n\n                #expect(id == \"some-form-id\")\n                #expect(result == expectedResult)\n\n                confirmation.confirm()\n            }\n\n            try await form.submit(layoutState: .empty)\n        }\n    }\n\n    @Test(\"Test submit checks predicate\")\n    func testSubmitChecksPredicate() async throws {\n        let screen = AirshipMainActorValue(\"foo\")\n\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n\n        let fooField: ThomasFormField = .validField(\n            identifier: \"foo-id\",\n            input: .email(nil),\n            result: .init(\n                value: .email(\"foo\"),\n                channels: [\n                    .email(\"some email\", .doubleOptIn(.init()))\n                ],\n                attributes: [\n                    .init(\n                        attributeName: .init(channel: \"some-id\"),\n                        attributeValue: .string(\"some value\")\n                    )\n                ]\n            )\n        )\n\n        let barField: ThomasFormField = .validField(\n            identifier: \"bar-id\",\n            input: .email(nil),\n            result: .init(\n                value: .email(\"bar\"),\n                channels: [\n                    .email(\"some other email\", .doubleOptIn(.init()))\n                ],\n                attributes: [\n                    .init(\n                        attributeName: .init(channel: \"some-id\"),\n                        attributeValue: .string(\"some other value\")\n                    )\n                ]\n            )\n        )\n\n        form.updateField(fooField) {\n            screen.value == \"foo\"\n        }\n        #expect(form.activeFields.count == 1)\n\n        form.updateField(barField) {\n            screen.value == \"bar\"\n        }\n        #expect(form.activeFields.count == 1)\n\n        screen.update { $0 = \"bar\" }\n\n        try await confirmation { confirmation in\n            form.onSubmit = { id, result, _ in\n                let expectedResult: ThomasFormField.Result = .init(\n                    value: .form(\n                        responseType: form.formResponseType,\n                        children: [\n                            \"bar-id\": .email(\"bar\"),\n                        ]\n                    ),\n                    channels: barField.channels,\n                    attributes: barField.attributes\n                )\n\n                #expect(id == \"some-form-id\")\n                #expect(result == expectedResult)\n\n                confirmation.confirm()\n            }\n\n            try await form.submit(layoutState: .empty)\n        }\n    }\n\n    @Test(\"Test data change for onDemand mode.\")\n    func testDataChangeOnDemand() async throws {\n        let screen = AirshipMainActorValue(\"foo\")\n\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n\n        var updates = form.statusUpdates.makeAsyncIterator()\n\n        await #expect(updates.next() == .pendingValidation)\n\n        print(\"start\")\n\n        let fooField: ThomasFormField = .validField(\n            identifier: \"foo-id\",\n            input: .email(nil),\n            result: .init(\n                value: .email(\"foo\")\n            )\n        )\n\n        let barField: ThomasFormField = .invalidField(\n            identifier: \"bar-id\",\n            input: .email(nil)\n        )\n\n        form.updateField(fooField) {\n            screen.value == \"foo\"\n        }\n        #expect(form.activeFields.count == 1)\n\n        form.updateField(barField) {\n            screen.value == \"bar\"\n        }\n        #expect(form.activeFields.count == 1)\n\n        await #expect(form.validate() == true)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        screen.update { $0 = \"bar\" }\n        form.dataChanged()\n\n        await #expect(form.validate() == false)\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        screen.update { $0 = \"foo\" }\n        form.dataChanged()\n\n        await #expect(updates.next() == .valid)\n\n        await #expect(form.validate() == true)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        form.updateField(\n            .asyncField(\n                identifier: \"bar-id\",\n                input: .score(2.0),\n                processDelay: 0.1\n            ) {\n                .valid(.init(value: .score(1.0)))\n            }\n        ) {\n            screen.value == \"bar\"\n        }\n\n        await #expect(form.validate() == true)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        screen.update { $0 = \"bar\" }\n        form.dataChanged()\n        await #expect(updates.next() == .pendingValidation)\n    }\n\n    @Test(\"Test data change for immediate mode.\")\n    func testDataChangeImmediate() async throws {\n        let screen = AirshipMainActorValue(\"foo\")\n\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        var updates = form.statusUpdates.makeAsyncIterator()\n        await #expect(updates.next() == .invalid)\n\n        let fooField: ThomasFormField = .validField(\n            identifier: \"foo-id\",\n            input: .email(nil),\n            result: .init(\n                value: .email(\"foo\")\n            )\n        )\n\n        let barField: ThomasFormField = .invalidField(\n            identifier: \"bar-id\",\n            input: .email(nil)\n        )\n\n        form.updateField(barField) {\n            screen.value == \"bar\"\n        }\n\n        await #expect(updates.next() == .valid)\n\n        form.updateField(fooField) {\n            screen.value == \"foo\"\n        }\n\n        #expect(form.activeFields.count == 1)\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        await #expect(form.validate() == true)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        screen.update { $0 = \"bar\" }\n        form.dataChanged()\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        await #expect(form.validate() == false)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        screen.update { $0 = \"foo\" }\n        form.dataChanged()\n\n        await #expect(updates.next() == .valid)\n\n        await #expect(form.validate() == true)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        form.updateField(\n            .asyncField(\n                identifier: \"bar-id\",\n                input: .score(2.0),\n                processDelay: 0.1\n            ) {\n                .valid(.init(value: .score(1.0)))\n            }\n        ) {\n            screen.value == \"bar\"\n        }\n\n        await #expect(form.validate() == true)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        screen.update { $0 = \"bar\" }\n        form.dataChanged()\n        await #expect(updates.next() == .pendingValidation)\n    }\n\n    @Test(\"Test updating fields on demand\")\n    func testUpdateFieldsOnDemand() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .onDemand\n        )\n\n        var updates = form.statusUpdates.makeAsyncIterator()\n\n        await #expect(updates.next() == .pendingValidation)\n\n        form.updateField(.validField(identifier: \"some-valid-id\", input: .score(1.0), result: .init(value: .score(1.0))))\n        #expect(form.status == .pendingValidation)\n\n        form.updateField(.invalidField(identifier: \"some-id\", input: .score(1.0)))\n        #expect(form.status == .pendingValidation)\n\n        form.updateField(.invalidField(identifier: \"some-other-id\", input: .score(2.0)))\n        #expect(form.status == .pendingValidation)\n\n        await #expect(form.validate() == false)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        // Update the invalid fields with more invalid data\n        form.updateField(.invalidField(identifier: \"some-id\", input: .score(1.0)))\n        #expect(form.status == .invalid)\n        form.updateField(.invalidField(identifier: \"some-other-id\", input: .score(2.0)))\n        #expect(form.status == .invalid)\n\n        // Update the invalid fields with valid and pending fields\n        form.updateField(.validField(identifier: \"some-id\", input: .score(1.0), result: .init(value: .score(1.0))))\n        #expect(form.status == .invalid)\n\n        form.updateField(\n            .asyncField(\n                identifier: \"some-other-id\",\n                input: .score(2.0),\n                processDelay: 0.1\n            ) {\n                .valid(.init(value: .score(1.0)))\n            }\n        )\n        #expect(form.status == .pendingValidation)\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(form.validate() == true)\n\n\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n        #expect(form.status == .valid)\n    }\n\n    @Test(\"Test updating fields in immediate mode starts a validation task\")\n    func testUpdateFieldsImmediate() async throws {\n        let form = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        var updates = form.statusUpdates.makeAsyncIterator()\n\n        await #expect(updates.next() == .invalid)\n\n        form.updateField(.validField(identifier: \"some-valid-id\", input: .score(1.0), result: .init(value: .score(1.0))))\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        form.updateField(.invalidField(identifier: \"some-id\", input: .score(1.0)))\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        form.updateField(.invalidField(identifier: \"some-other-id\", input: .score(2.0)))\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        // Update the invalid fields with more invalid data\n        form.updateField(.invalidField(identifier: \"some-id\", input: .score(1.0)))\n        form.updateField(.invalidField(identifier: \"some-other-id\", input: .score(2.0)))\n\n        // Update the invalid fields with valid fields\n        form.updateField(.validField(identifier: \"some-id\", input: .score(1.0), result: .init(value: .score(1.0))))\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .invalid)\n\n        form.updateField(.validField(identifier: \"some-other-id\", input: .score(1.0), result: .init(value: .score(1.0))))\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n\n        // Update a field with pending\n        form.updateField(\n            .asyncField(\n                identifier: \"some-other-id\",\n                input: .score(2.0),\n                processDelay: 0.1\n            ) {\n                .valid(.init(value: .score(1.0)))\n            }\n        )\n        await #expect(updates.next() == .pendingValidation)\n        await #expect(updates.next() == .validating)\n        await #expect(updates.next() == .valid)\n    }\n\n    @Test(\"Test enable effects form input enabled\")\n    func testEnable() async throws {\n        let parent = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        let child = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate,\n            parentFormState: parent\n        )\n\n        #expect(child.isEnabled)\n        #expect(child.isFormInputEnabled)\n        #expect(parent.isEnabled)\n        #expect(parent.isFormInputEnabled)\n\n        parent.isEnabled = false\n\n        #expect(parent.isEnabled == false)\n        #expect(parent.isFormInputEnabled  == false)\n        #expect(child.isEnabled)\n        #expect(child.isFormInputEnabled == false)\n\n        parent.isEnabled = true\n        child.isEnabled = false\n\n        #expect(parent.isEnabled)\n        #expect(parent.isFormInputEnabled)\n        #expect(child.isEnabled == false)\n        #expect(child.isFormInputEnabled == false)\n    }\n\n    @Test(\"Test mark child visible.\")\n    func testMarkChildVisible() async throws {\n        let parent = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        let child = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate,\n            parentFormState: parent\n        )\n\n        #expect(child.isVisible == false)\n        #expect(parent.isVisible == false)\n\n        child.markVisible()\n        #expect(child.isVisible)\n        #expect(parent.isVisible)\n    }\n\n    @Test(\"Test mark parent visible.\")\n    func testMarkParentVisible() async throws {\n        let parent = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate\n        )\n\n        let child = ThomasFormState(\n            identifier: \"some-form-id\",\n            formType: .form,\n            formResponseType: \"response type\",\n            validationMode: .immediate,\n            parentFormState: parent\n        )\n\n        #expect(child.isVisible == false)\n        #expect(parent.isVisible == false)\n\n        parent.markVisible()\n        #expect(parent.isVisible)\n        #expect(child.isVisible == false)\n\n        child.markVisible()\n        #expect(child.isVisible)\n        #expect(parent.isVisible)\n    }\n}\n\nextension ThomasFormField {\n    var channels: [ThomasFormField.Channel] {\n        return if case let .valid(result) = self.status {\n            result.channels ?? []\n        } else {\n            []\n        }\n    }\n\n    var attributes: [ThomasFormField.Attribute] {\n        return if case let .valid(result) = self.status {\n            result.attributes ?? []\n        } else {\n            []\n        }\n    }\n}\n\nextension ThomasFormState {\n    // $status.values seems to debounce updates so using a custom updates for\n    // testing\n    var statusUpdates: AsyncStream<ThomasFormState.Status> {\n        return AsyncStream { continuation in\n            let sub = self.$status.sink { status in\n                continuation.yield(status)\n            }\n\n            continuation.onTermination = { _ in\n                sub.cancel()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasPagerTrackerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\n\n@MainActor\nstruct ThomasPagerTrackerTest {\n\n    private let tracker: ThomasPagerTracker = ThomasPagerTracker()\n    \n    @Test\n    func testSummary() throws {\n        let fooPage0 = makePageViewEvent(pager: \"foo\", page: 0)\n        let fooPage1 = makePageViewEvent(pager: \"foo\", page: 1)\n        let barPage0 = makePageViewEvent(pager: \"bar\", page: 0)\n        let barPage1 = makePageViewEvent(pager: \"bar\", page: 1)\n\n        #expect(self.tracker.summary.isEmpty)\n\n        self.tracker.onPageView(pageEvent: fooPage0, currentDisplayTime: 0)\n        #expect(\n            self.tracker.summary == Set([\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"foo\",\n                    viewedPages: [],\n                    pageCount: fooPage0.pageCount,\n                    completed: fooPage0.completed\n                )\n            ])\n        )\n\n        self.tracker.onPageView(pageEvent: fooPage1, currentDisplayTime: 10)\n        #expect(\n            self.tracker.summary == Set([\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"foo\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 10\n                        )\n                    ],\n                    pageCount: fooPage0.pageCount,\n                    completed: fooPage0.completed\n                )\n            ])\n        )\n\n        self.tracker.onPageView(pageEvent: barPage0, currentDisplayTime: 10)\n        #expect(\n            self.tracker.summary == Set([\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"foo\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 10\n                        )\n                    ],\n                    pageCount: fooPage1.pageCount,\n                    completed: fooPage1.completed\n                ),\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"bar\",\n                    viewedPages: [],\n                    pageCount: barPage0.pageCount,\n                    completed: barPage0.completed\n                )\n            ])\n        )\n\n        self.tracker.onPageView(pageEvent: fooPage0, currentDisplayTime: 20)\n        #expect(\n            self.tracker.summary == Set([\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"foo\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 10\n                        ),\n                        .init(\n                            identifier: \"page-1\",\n                            index: 1,\n                            displayTime: 10\n                        )\n                    ],\n                    pageCount: fooPage0.pageCount,\n                    completed: fooPage0.completed\n                ),\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"bar\",\n                    viewedPages: [],\n                    pageCount: barPage0.pageCount,\n                    completed: barPage0.completed\n                )\n            ])\n        )\n\n        self.tracker.onPageView(pageEvent: barPage1, currentDisplayTime: 30)\n        #expect(\n            self.tracker.summary == Set([\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"foo\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 10\n                        ),\n                        .init(\n                            identifier: \"page-1\",\n                            index: 1,\n                            displayTime: 10\n                        )\n                    ],\n                    pageCount: fooPage0.pageCount,\n                    completed: fooPage0.completed\n                ),\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"bar\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 20\n                        ),\n                    ],\n                    pageCount: barPage0.pageCount,\n                    completed: barPage0.completed\n                )\n            ])\n        )\n\n        self.tracker.stopAll(currentDisplayTime: 40)\n        #expect(\n            self.tracker.summary == Set([\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"foo\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 10\n                        ),\n                        .init(\n                            identifier: \"page-1\",\n                            index: 1,\n                            displayTime: 10\n                        ),\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0, displayTime: 20\n                        )\n                    ],\n                    pageCount: fooPage0.pageCount,\n                    completed: fooPage0.completed\n                ),\n                ThomasReportingEvent.PagerSummaryEvent(\n                    identifier: \"bar\",\n                    viewedPages: [\n                        .init(\n                            identifier: \"page-0\",\n                            index: 0,\n                            displayTime: 20\n                        ),\n                        .init(\n                            identifier: \"page-1\",\n                            index: 1,\n                            displayTime: 10\n                        )\n                    ],\n                    pageCount: barPage0.pageCount,\n                    completed: barPage0.completed\n                )\n            ])\n        )\n    }\n\n    @Test\n    func testViewedPages() throws {\n        self.tracker.onPageView(\n            pageEvent: makePageViewEvent(pager: \"foo\", page: 0),\n            currentDisplayTime: 0\n        )\n\n        self.tracker.onPageView(\n            pageEvent: makePageViewEvent(pager: \"foo\", page: 1),\n            currentDisplayTime: 1\n        )\n\n        self.tracker.onPageView(\n            pageEvent: makePageViewEvent(pager: \"bar\", page: 0),\n            currentDisplayTime: 1\n        )\n\n        self.tracker.onPageView(\n            pageEvent: makePageViewEvent(pager: \"foo\", page: 2),\n            currentDisplayTime: 4\n        )\n\n        #expect(\n            self.tracker.viewedPages(pagerIdentifier: \"foo\") == [\n                .init(\n                    identifier: \"page-0\",\n                    index: 0,\n                    displayTime: 1\n                ),\n                .init(\n                    identifier: \"page-1\",\n                    index: 1,\n                    displayTime: 3\n                )\n            ]\n        )\n\n        // Still on page 0 so its empty\n        #expect(self.tracker.viewedPages(pagerIdentifier: \"bar\") == [])\n\n        // Baz does not exist\n        #expect(self.tracker.viewedPages(pagerIdentifier: \"baz\") == [])\n    }\n\n    private func makePageViewEvent(pager: String, page: Int) -> ThomasReportingEvent.PageViewEvent {\n        return ThomasReportingEvent.PageViewEvent(\n           identifier: pager,\n           pageIdentifier: \"page-\\(page)\",\n           pageIndex: page,\n           pageViewCount: 1,\n           pageCount: 100,\n           completed: false\n       )\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Environment/ThomasStateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\nstruct ThomasStateTest {\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/EventAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class EventAPIClientTest: XCTestCase {\n    private let requestSession = TestAirshipRequestSession()\n    private var client: EventAPIClient!\n\n    private let eventData = [\n        AirshipEventData.makeTestData()\n    ]\n\n    private let headers: [String: String] = [\n        \"some\": \"header\"\n    ]\n\n    override func setUpWithError() throws {\n        client = EventAPIClient(\n            config: .testConfig(),\n            session: requestSession\n        )\n    }\n\n    func testUpload() async throws {\n        let responseHeaders = [\n            \"X-UA-Max-Total\": \"200\",\n            \"X-UA-Max-Batch\": \"100\",\n            \"X-UA-Min-Batch-Interval\": \"10.4\"\n        ]\n\n        self.requestSession.response = HTTPURLResponse(\n            url: URL(string: \"https://www.airship.com\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: responseHeaders\n        )\n\n        let response = try await self.client.uploadEvents(\n            self.eventData,\n            channelID: \"some channel\",\n            headers: self.headers\n        )\n\n        XCTAssertEqual(100, response.result!.maxBatchSizeKB)\n        XCTAssertEqual(200, response.result!.maxTotalStoreSizeKB)\n        XCTAssertEqual(10.4, response.result!.minBatchInterval)\n        XCTAssertEqual(self.requestSession.lastRequest?.auth, .channelAuthToken(identifier: \"some channel\"))\n\n\n    }\n\n    func testUploadBadHeaders() async throws {\n        let responseHeaders = [\n            \"X-UA-Max-Total\": \"string\",\n            \"X-UA-Max-Batch\": \"true\",\n        ]\n\n        self.requestSession.response = HTTPURLResponse(\n            url: URL(string: \"https://www.airship.com\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: responseHeaders\n        )\n\n        let response = try await self.client.uploadEvents(\n            self.eventData,\n            channelID: \"some channel\",\n            headers: self.headers\n        )\n\n        XCTAssertNil(response.result!.maxBatchSizeKB)\n        XCTAssertNil(response.result!.maxTotalStoreSizeKB)\n        XCTAssertNil(response.result!.minBatchInterval)\n    }\n\n    func testUploadFailed() async throws {\n\n        self.requestSession.response = HTTPURLResponse(\n            url: URL(string: \"https://www.airship.com\")!,\n            statusCode: 400,\n            httpVersion: \"\",\n            headerFields: [:]\n        )\n\n        let response = try await self.client.uploadEvents(\n            self.eventData,\n            channelID: \"some channel\",\n            headers: self.headers\n        )\n\n        XCTAssertNil(response.result!.maxBatchSizeKB)\n        XCTAssertNil(response.result!.maxTotalStoreSizeKB)\n        XCTAssertNil(response.result!.minBatchInterval)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/EventManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class EventManagerTest: XCTestCase {\n\n    private let eventAPIClient = TestEventAPIClient()\n    private let eventScheduler = TestEventUploadScheduler()\n    private let channel = TestChannel()\n\n    private let eventStore = EventStore(\n        appKey: UUID().uuidString,\n        inMemory: true\n    )\n    private let dataStore = PreferenceDataStore(\n        appKey: UUID().uuidString\n    )\n    private var eventManager: EventManager!\n\n    @MainActor\n    override func setUp() async throws {\n        self.eventManager = EventManager(\n            dataStore: dataStore,\n            channel: channel,\n            eventStore: eventStore,\n            eventAPIClient: eventAPIClient,\n            eventScheduler: eventScheduler\n        )\n        channel.identifier = \"some channel\"\n    }\n\n    func testAddEvent() async throws {\n        let eventData = AirshipEventData.makeTestData()\n\n        try await eventManager.addEvent(eventData)\n        let events = try await eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n        XCTAssertEqual([eventData], events)\n    }\n\n    func testScheduleUpload() async throws {\n        self.eventManager.uploadsEnabled = true\n        await self.eventManager.scheduleUpload(eventPriority: .high)\n        XCTAssertEqual(\n            60, // min batch interval\n            self.eventScheduler.lastMinBatchInterval\n        )\n\n        XCTAssertEqual(\n            AirshipEventPriority.high,\n            self.eventScheduler.lastScheduleUploadPriority\n        )\n    }\n\n    func testScheduleUploadDisabled() async throws {\n        self.eventManager.uploadsEnabled = false\n        await self.eventManager.scheduleUpload(eventPriority: .high)\n        XCTAssertNil(self.eventScheduler.lastMinBatchInterval)\n        XCTAssertNil(self.eventScheduler.lastScheduleUploadPriority)\n    }\n\n    func testDeleteAll() async throws {\n        let eventData = AirshipEventData.makeTestData()\n\n        try await eventManager.addEvent(eventData)\n        try await eventManager.deleteEvents()\n\n        let events = try await eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n        XCTAssertTrue(events.isEmpty)\n    }\n\n    func testEventUpload() async throws {\n        self.eventManager.uploadsEnabled = true\n\n        var requestCalled = false\n\n        let events = [\n            AirshipEventData.makeTestData(),\n            AirshipEventData.makeTestData()\n        ]\n\n        let headers = [\"some\": \"header\"]\n\n        await self.eventManager.addHeaderProvider {\n            return headers\n        }\n\n        for event in events {\n            try await self.eventStore.save(event: event)\n        }\n\n        self.eventAPIClient.requestBlock = { reqEvents, channelID, reqHeaders in\n            requestCalled = true\n            XCTAssertEqual(events, reqEvents)\n            XCTAssertEqual(headers, reqHeaders)\n            XCTAssertEqual(channelID, \"some channel\")\n\n            let tuningInfo = EventUploadTuningInfo(\n                maxTotalStoreSizeKB: nil,\n                maxBatchSizeKB: nil,\n                minBatchInterval: nil\n            )\n\n            return AirshipHTTPResponse(\n                result: tuningInfo,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.eventScheduler.workBlock?()\n        XCTAssertEqual(AirshipWorkResult.success, result)\n        XCTAssertTrue(requestCalled)\n\n        let storedEvents = try await self.eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n        XCTAssertTrue(storedEvents.isEmpty)\n    }\n\n    func testEventUploadFailed() async throws {\n        self.eventManager.uploadsEnabled = true\n\n        try await self.eventStore.save(\n            event: AirshipEventData.makeTestData()\n        )\n\n        self.eventAPIClient.requestBlock = { reqEvents, _, reqHeaders in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.eventScheduler.workBlock?()\n        XCTAssertEqual(AirshipWorkResult.failure, result)\n\n        let storedEvents = try await self.eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n        XCTAssertEqual(1, storedEvents.count)\n    }\n\n    func testEventUploadNoTuningInfo() async throws {\n        self.eventManager.uploadsEnabled = true\n\n        try await self.eventStore.save(\n            event: AirshipEventData.makeTestData()\n        )\n\n        self.eventAPIClient.requestBlock = { reqEvents, _, reqHeaders in\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.eventScheduler.workBlock?()\n        XCTAssertEqual(AirshipWorkResult.success, result)\n    }\n\n    func testEventUploadHeaders() async throws {\n        self.eventManager.uploadsEnabled = true\n        var requestCalled = false\n\n        await self.eventManager.addHeaderProvider {\n            [\"foo\": \"1\", \"baz\": \"1\"]\n        }\n\n        await self.eventManager.addHeaderProvider {\n            [\"foo\": \"2\", \"bar\": \"2\"]\n        }\n\n        try await self.eventStore.save(\n            event: AirshipEventData.makeTestData()\n        )\n\n        self.eventAPIClient.requestBlock = { reqEvents, _, reqHeaders in\n            let expectedHeaders = [\n                \"foo\": \"2\",\n                \"bar\": \"2\",\n                \"baz\": \"1\"\n            ]\n            XCTAssertEqual(expectedHeaders, reqHeaders)\n            requestCalled = true\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.eventScheduler.workBlock?()\n        XCTAssertEqual(AirshipWorkResult.success, result)\n        XCTAssertTrue(requestCalled)\n    }\n\n    func testEventUploadDisabled() async throws {\n        self.eventManager.uploadsEnabled = false\n\n        try await self.eventStore.save(\n            event: AirshipEventData.makeTestData()\n        )\n\n        self.eventAPIClient.requestBlock = { reqEvents, _, reqHeaders in\n            XCTFail(\"Should not be called\")\n\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.eventScheduler.workBlock?()\n        XCTAssertEqual(AirshipWorkResult.success, result)\n    }\n\n    func testEventUploadUpdatedMinInterval() async throws {\n        self.eventManager.uploadsEnabled = true\n\n        try await self.eventStore.save(\n            event: AirshipEventData.makeTestData()\n        )\n\n        self.eventAPIClient.requestBlock = { reqEvents, _, reqHeaders in\n            let tuningInfo = EventUploadTuningInfo(\n                maxTotalStoreSizeKB: nil,\n                maxBatchSizeKB: nil,\n                minBatchInterval: 100\n            )\n\n            return AirshipHTTPResponse(\n                result: tuningInfo,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let result = try await self.eventScheduler.workBlock?()\n        XCTAssertEqual(AirshipWorkResult.success, result)\n\n        await self.eventManager.scheduleUpload(eventPriority: .normal)\n        XCTAssertEqual(\n            100, // min batch interval\n            self.eventScheduler.lastMinBatchInterval\n        )\n    }\n}\n\nfinal class TestEventAPIClient: EventAPIClientProtocol, @unchecked Sendable {\n    var requestBlock: (([AirshipEventData], String, [String: String]) async throws -> AirshipHTTPResponse<EventUploadTuningInfo>)?\n\n    func uploadEvents(_ events: [AirshipEventData], channelID: String, headers: [String : String]) async throws -> AirshipHTTPResponse<EventUploadTuningInfo> {\n\n        guard let block = requestBlock else {\n            throw AirshipErrors.error(\"Request block not set\")\n        }\n\n        return try await block(events, channelID, headers)\n    }\n}\n\nfinal class TestEventUploadScheduler: EventUploadSchedulerProtocol, @unchecked Sendable {\n    var workBlock: (() async throws -> AirshipWorkResult)?\n\n    var lastScheduleUploadPriority: AirshipEventPriority?\n    var lastMinBatchInterval: TimeInterval?\n\n    func scheduleUpload(\n        eventPriority: AirshipEventPriority,\n        minBatchInterval: TimeInterval\n    ) async {\n        self.lastMinBatchInterval  = minBatchInterval\n        self.lastScheduleUploadPriority = eventPriority\n    }\n\n    func setWorkBlock(\n        _ workBlock: @escaping () async throws -> AirshipCore.AirshipWorkResult\n    ) async {\n\n        self.workBlock = workBlock\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/EventSchedulerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class EventSchedulerTest: XCTestCase {\n\n    private let date = UATestDate()\n    private let workManager = TestWorkManager()\n    private let appStateTracker = TestAppStateTracker()\n    private var eventScheduler: EventUploadScheduler!\n    private let taskSleeper: TestTaskSleeper = TestTaskSleeper()\n\n    @MainActor\n    override func setUp() async throws {\n        self.eventScheduler = EventUploadScheduler(\n            appStateTracker: appStateTracker,\n            workManager: workManager,\n            date: date,\n            taskSleeper: taskSleeper\n        )\n    }\n\n    @MainActor\n    func testScheduleNormalPriority() async throws  {\n        self.appStateTracker.currentState = .active\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n        XCTAssertEqual(15.0, self.workManager.workRequests[0].initialDelay)\n    }\n\n    @MainActor\n    func testScheduleHighPriority() async throws  {\n        self.appStateTracker.currentState = .active\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .high,\n            minBatchInterval: 60.0\n        )\n\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n        XCTAssertEqual(0, self.workManager.workRequests[0].initialDelay)\n    }\n\n    @MainActor\n    func testScheduleNormalPriorityBackground() async throws  {\n        self.appStateTracker.currentState = .background\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n        XCTAssertEqual(0, self.workManager.workRequests[0].initialDelay)\n    }\n\n    @MainActor\n    func testAlreadyScheduled() async throws  {\n        self.appStateTracker.currentState = .active\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n        XCTAssertEqual(15.0, self.workManager.workRequests[0].initialDelay)\n    }\n\n    @MainActor\n    func testScheduleEarlier() async throws  {\n        self.appStateTracker.currentState = .active\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .high,\n            minBatchInterval: 60.0\n        )\n\n        XCTAssertEqual(2, self.workManager.workRequests.count)\n        XCTAssertEqual(15.0, self.workManager.workRequests[0].initialDelay)\n        XCTAssertEqual(0, self.workManager.workRequests[1].initialDelay)\n    }\n\n    @MainActor\n    func testBatchInterval() async throws {\n        self.date.dateOverride = Date()\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let _ = try await self.workManager.workers[0].workHandler(request)\n\n        self.appStateTracker.currentState = .active\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        XCTAssertEqual(1, self.workManager.workRequests.count)\n        XCTAssertEqual(60.0, self.workManager.workRequests[0].initialDelay)\n    }\n\n    @MainActor\n    func testSmallerBatchInterval() async throws {\n        self.date.dateOverride = Date()\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let _ = try await self.workManager.workers[0].workHandler(request)\n\n        self.appStateTracker.currentState = .active\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 60.0\n        )\n\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 90.0\n        )\n\n        await self.eventScheduler.scheduleUpload(\n            eventPriority: .normal,\n            minBatchInterval: 30.0\n        )\n\n        XCTAssertEqual(2, self.workManager.workRequests.count)\n        XCTAssertEqual(60.0, self.workManager.workRequests[0].initialDelay)\n        XCTAssertEqual(30.0, self.workManager.workRequests[1].initialDelay)\n    }\n\n    func testWorkHandlerNotSet() async throws {\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let result = try await self.workManager.workers[0].workHandler(request)\n        XCTAssertEqual(AirshipWorkResult.success, result)\n    }\n\n    func testWorkBlockFailed() async throws {\n        let called = AirshipAtomicValue<Bool>(false)\n        await self.eventScheduler.setWorkBlock {\n            called.value = true\n            return .failure\n        }\n\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let result = try await self.workManager.workers[0].workHandler(request)\n        XCTAssertEqual(AirshipWorkResult.failure, result)\n        XCTAssertTrue(called.value)\n    }\n\n    func testWorkBlockSuccess() async throws {\n        let called = AirshipAtomicValue<Bool>(false)\n        await self.eventScheduler.setWorkBlock {\n            called.value = true\n            return .success\n        }\n\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let result = try await self.workManager.workers[0].workHandler(request)\n        XCTAssertEqual(AirshipWorkResult.success, result)\n        XCTAssertTrue(called.value)\n    }\n\n    @MainActor\n    func testBatchDelay() async throws {\n        self.appStateTracker.currentState = .inactive\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let result = try await self.workManager.workers[0].workHandler(request)\n        XCTAssertEqual(AirshipWorkResult.success, result)\n        let sleeps = await self.taskSleeper.sleeps\n        XCTAssertEqual([1.0], sleeps)\n    }\n\n    @MainActor\n    func testActiveBatchDelay() async throws {\n        self.appStateTracker.currentState = .active\n        let request = AirshipWorkRequest(workID: \"neat\")\n        let result = try await self.workManager.workers[0].workHandler(request)\n        XCTAssertEqual(AirshipWorkResult.success, result)\n\n        let sleeps = await self.taskSleeper.sleeps\n        XCTAssertEqual([5.0], sleeps)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/EventStoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class EventStoreTest: XCTestCase {\n\n    private let eventStore = EventStore(\n        appKey: UUID().uuidString,\n        inMemory: true\n    )\n\n    func testAdd() async throws {\n        let events = generateEvents(count: 2)\n        for event in events {\n            try await self.eventStore.save(event: event)\n        }\n\n        let storedEvents = try await eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n        XCTAssertEqual(events, storedEvents)\n    }\n\n    func testDeleteAll() async throws {\n        let events = generateEvents(count: 10)\n        for event in events {\n            try await self.eventStore.save(event: event)\n        }\n\n        try await self.eventStore.deleteAllEvents()\n\n        let storedEvents = try await eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n\n\n        XCTAssertTrue(storedEvents.isEmpty)\n    }\n\n    func testDeleteEventIDs() async throws {\n        let events = generateEvents(count: 10)\n        for event in events {\n            try await self.eventStore.save(event: event)\n        }\n\n        try await self.eventStore.deleteEvents(\n            eventIDs: [\n                events[0].id,\n                events[1].id,\n                events[2].id\n            ]\n        )\n\n        let storedEvents = try await eventStore.fetchEvents(\n            maxBatchSizeKB: 1000\n        )\n\n        let expectedEvents = Array(events[3...9])\n        XCTAssertEqual(expectedEvents, storedEvents)\n    }\n\n    func generateEvents(\n        count: Int\n    ) -> [AirshipEventData] {\n        var events: [AirshipEventData] = []\n\n        for _ in 1...count {\n            events.append(\n                AirshipEventData.makeTestData()\n            )\n        }\n\n        return events\n    }\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/EventTestUtils.swift",
    "content": "import Foundation\n\n@testable\nimport AirshipCore\n\nextension AirshipEventData {\n   \n    static func makeTestData(type: EventType = .appInit) -> AirshipEventData {\n        return AirshipEventData(\n            body: try! AirshipJSON.wrap([\"cool\": \"story\"]),\n            id: UUID().uuidString,\n            date: Date(),\n            sessionID: UUID().uuidString,\n            type: type\n        )\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ExperimentManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ExperimentManagerTest: XCTestCase {\n\n    private var deviceInfo: TestAudienceDeviceInfoProvider = TestAudienceDeviceInfoProvider()\n    private let remoteData: TestRemoteData = TestRemoteData()\n    private var subject: ExperimentManager!\n    private let audienceChecker: TestAudienceChecker = TestAudienceChecker()\n\n    private let testDate: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n\n    override func setUpWithError() throws {\n        self.deviceInfo.channelID = \"channel-id\"\n        self.deviceInfo.stableContactInfo = StableContactInfo(contactID: \"some-contact-id\")\n\n        self.subject = ExperimentManager(\n            dataStore: PreferenceDataStore(appKey: UUID().uuidString),\n            remoteData: remoteData,\n            audienceChecker: audienceChecker,\n            date: testDate\n        )\n    }\n\n    func testExperimentManagerOmitsInvalidExperiments() async throws {\n        let experiment = Experiment.generate(id: \"valid\")\n        self.remoteData.payloads = [createPayload([experiment.toString, \"{ \\\"not valid\\\": true }\"])]\n\n        self.audienceChecker.onEvaluate = { audience, _, _ in\n            .init(isMatch: true)\n        }\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )!\n\n        XCTAssertEqual(\n            [\n                experiment.reportingMetadata\n            ],\n            result.reportingMetadata\n        )\n    }\n\n    func testExperimentManagerParseMultipleExperiments() async throws {\n        let experiment1 = Experiment.generate(id: \"id1\")\n        let experiment2 = Experiment.generate(id: \"id2\")\n\n        self.remoteData.payloads = [\n            createPayload([experiment1.toString]),\n            createPayload([experiment2.toString])\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            .init(isMatch: false)\n        }\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )!\n\n        XCTAssertEqual(\n            [\n                experiment1.reportingMetadata,\n                experiment2.reportingMetadata\n            ],\n            result.reportingMetadata\n        )\n    }\n\n    func testExperimentManagerHandleNoExperimentsPayload() async throws {\n        self.remoteData.payloads = [createPayload([\"{}\"])]\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )\n        XCTAssertNil(result)\n    }\n\n    func testExperimentManagerHandleInvalidPayload() async throws {\n        let experiment = \"{\\\"invalid\\\": \\\"experiment\\\"}\"\n        self.remoteData.payloads = [createPayload([experiment])]\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )\n        XCTAssertNil(result)\n    }\n\n    func testResultNoExperiments() async throws {\n        self.remoteData.payloads = [createPayload([])]\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )\n\n        XCTAssertNil(result)\n    }\n\n    func testResultNoMatch() async throws {\n        let experiment = Experiment.generate(id: \"fake-id\", reportingMetadata: AirshipJSON.string(\"reporting data!\"))\n        self.remoteData.payloads = [createPayload([experiment.toString])]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            .init(isMatch: false)\n        }\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )!\n\n        XCTAssertFalse(result.isMatch)\n        XCTAssertEqual(self.deviceInfo.stableContactInfo.contactID, result.contactID)\n        XCTAssertEqual(self.deviceInfo.channelID, result.channelID)\n\n        XCTAssertEqual(\n            [\n                experiment.reportingMetadata\n            ],\n            result.reportingMetadata\n        )\n    }\n\n    func testResultMatch() async throws {\n        let audienceSelector1 = DeviceAudienceSelector(newUser: true)\n        let experiment1 = Experiment.generate(\n            id: \"id1\",\n            reportingMetadata: AirshipJSON.string(\"reporting data 1\"),\n            audienceSelector: audienceSelector1\n        )\n\n        let audienceSelector2 = DeviceAudienceSelector(newUser: false)\n        let experiment2 = Experiment.generate(\n            id: \"id2\",\n            reportingMetadata: AirshipJSON.string(\"reporting data 2\"),\n            audienceSelector: audienceSelector2\n        )\n\n        self.deviceInfo.stableContactInfo = StableContactInfo(contactID: \"active-contact-id\")\n\n        self.remoteData.payloads = [createPayload([\n            experiment1.toString,\n            experiment2.toString\n        ])]\n\n        self.audienceChecker.onEvaluate = { audience, _, _ in\n            .init(isMatch: audience == .atomic(audienceSelector2))\n        }\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )!\n\n        XCTAssertTrue(result.isMatch)\n        XCTAssertEqual(\"active-contact-id\", result.contactID)\n        XCTAssertEqual(\"channel-id\", result.channelID)\n\n        XCTAssertEqual(\n            [\n                experiment1.reportingMetadata,\n                experiment2.reportingMetadata\n            ],\n            result.reportingMetadata\n        )\n    }\n\n    func testResultMatchExcludesInactive() async throws {\n        let audienceSelector1 = DeviceAudienceSelector(newUser: true)\n        let experiment1 = Experiment.generate(\n            id: \"id1\",\n            reportingMetadata: AirshipJSON.string(\"reporting data 1\"),\n            audienceSelector: audienceSelector1,\n            timeCriteria: AirshipTimeCriteria(\n                start: self.testDate.now + 0.01,\n                end: self.testDate.now + 0.02\n            )\n        )\n\n        let audienceSelector2 = DeviceAudienceSelector(newUser: false)\n        let experiment2 = Experiment.generate(\n            id: \"id2\",\n            reportingMetadata: AirshipJSON.string(\"reporting data 2\"),\n            audienceSelector: audienceSelector2,\n            timeCriteria: AirshipTimeCriteria(\n                start: self.testDate.now,\n                end: self.testDate.now + 0.01\n            )\n        )\n\n        self.deviceInfo.stableContactInfo = StableContactInfo(contactID: \"active-contact-id\")\n\n        self.remoteData.payloads = [createPayload([\n            experiment1.toString,\n            experiment2.toString\n        ])]\n\n        self.audienceChecker.onEvaluate = { audience, _, _ in\n            .init(isMatch: audience == .atomic(audienceSelector2))\n        }\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo.empty,\n            deviceInfoProvider: self.deviceInfo\n        )!\n\n        XCTAssertTrue(result.isMatch)\n        XCTAssertEqual(\"active-contact-id\", result.contactID)\n        XCTAssertEqual(\"channel-id\", result.channelID)\n\n        XCTAssertEqual(\n            [\n                experiment2.reportingMetadata\n            ],\n            result.reportingMetadata\n        )\n    }\n\n    func testResultMatchExclusions() async throws {\n        let messageTypePredicate = JSONPredicate(\n            jsonMatcher: JSONMatcher(valueMatcher: .matcherWhereStringEquals(\"transactional\"))\n        )\n\n        let campaignsPredicate = JSONPredicate(\n            jsonMatcher: JSONMatcher(\n                valueMatcher: JSONValueMatcher.matcherWithArrayContainsPredicate(\n                    JSONPredicate(\n                        jsonMatcher: JSONMatcher(valueMatcher: .matcherWhereStringEquals(\"transactional campaign\"))\n                    )\n                )!,\n                scope: [\"categories\"]\n            )\n        )\n\n        let experiment = Experiment.generate(\n            id: \"id1\",\n            reportingMetadata: AirshipJSON.string(\"reporting data 1\"),\n            exclusions: [\n                MessageCriteria(\n                    messageTypePredicate: messageTypePredicate,\n                    campaignsPredicate: campaignsPredicate\n                )\n            ]\n        )\n\n        self.remoteData.payloads = [createPayload([experiment.toString])]\n\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        let result = try await subject.evaluateExperiments(\n            info: MessageInfo(\n                messageType: \"commercial\",\n                campaigns: try! AirshipJSON.wrap([\"categories\": [\"foo\", \"bar\"]])\n            ),\n            deviceInfoProvider: self.deviceInfo\n        )!\n\n        XCTAssertTrue(result.isMatch)\n        XCTAssertEqual([experiment.reportingMetadata], result.reportingMetadata)\n\n        var emptyResult = try await subject.evaluateExperiments(\n            info: MessageInfo(messageType: \"transactional\"),\n            deviceInfoProvider: self.deviceInfo\n        )\n\n        XCTAssertNil(emptyResult)\n\n        emptyResult = try await subject.evaluateExperiments(\n            info: MessageInfo(\n                messageType: \"commercial\",\n                campaigns: try! AirshipJSON.wrap([\"categories\": [\"foo\", \"bar\", \"transactional campaign\"]])\n            ),\n            deviceInfoProvider: self.deviceInfo\n        )\n\n        XCTAssertNil(emptyResult)\n    }\n    \n    private func createPayload(_ json: [String], type: String = \"experiments\") -> RemoteDataPayload {\n        let wrapped = \"{\\\"\\(type)\\\": [\\(json.joined(separator: \",\"))]}\"\n        let data =\n            try! JSONSerialization.jsonObject(\n                with: wrapped.data(using: .utf8)!,\n                options: []\n            ) as! [AnyHashable: Any]\n\n        return RemoteDataPayload(\n            type: type,\n            timestamp: Date(),\n            data: try! AirshipJSON.wrap(data),\n            remoteDataInfo: nil\n        )\n    }\n}\n\nprivate extension MessageInfo {\n    static let empty = MessageInfo(messageType: \"\", campaigns: nil)\n}\n\nfileprivate extension Experiment {\n    var toString: String {\n        let encoder = JSONEncoder()\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSS\"\n        encoder.dateEncodingStrategy = .formatted(formatter)\n\n        return try! AirshipJSON.wrap(self).toString(encoder: encoder)\n    }\n\n    static func generate(\n        id: String,\n        created: Date = Date(),\n        reportingMetadata: AirshipJSON = AirshipJSON.string(\"reporting!\"),\n        audienceSelector: DeviceAudienceSelector = DeviceAudienceSelector(),\n        exclusions: [MessageCriteria]? = nil,\n        timeCriteria: AirshipTimeCriteria? = nil\n    ) -> Experiment {\n\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSS\"\n\n        let dateString = formatter.string(from: created)\n        let normalized = formatter.date(from: dateString)!\n        \n        return Experiment(\n            id: id,\n            lastUpdated: normalized,\n            created: normalized,\n            reportingMetadata: reportingMetadata,\n            audienceSelector: audienceSelector,\n            exclusions: exclusions,\n            timeCriteria: timeCriteria\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ExperimentTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ExperimentTest: XCTestCase {\n\n    var encoder: JSONEncoder {\n        let encoder = JSONEncoder()\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSS\"\n        encoder.dateEncodingStrategy = .formatted(dateFormatter)\n        return encoder\n    }\n\n    var decoder: JSONDecoder {\n        let decoder = JSONDecoder()\n        let dateFormatter = DateFormatter()\n        dateFormatter.dateFormat = \"yyyy-MM-dd'T'HH:mm:ss.SSS\"\n        decoder.dateDecodingStrategy = .formatted(dateFormatter)\n        return decoder\n    }\n\n\n    func testCodable() throws {\n        let json: String =  \"\"\"\n        {\n           \"created\" : \"2023-07-10T18:10:46.203\",\n           \"experiment_definition\" : {\n              \"audience_selector\" : {\n                 \"hash\" : {\n                    \"audience_hash\" : {\n                       \"hash_algorithm\" : \"farm_hash\",\n                       \"hash_identifier\" : \"contact\",\n                       \"hash_prefix\" : \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c:\",\n                       \"num_hash_buckets\" : 16384\n                    },\n                    \"audience_subset\" : {\n                       \"max_hash_bucket\" : 8192,\n                       \"min_hash_bucket\" : 0\n                    }\n                 }\n              },\n              \"experiment_type\" : \"holdout\",\n              \"message_exclusions\" : [\n                 {\n                    \"message_type\" : {\n                       \"value\" : {\n                          \"equals\" : \"transactional\"\n                       }\n                    }\n                 }\n              ],\n              \"reporting_metadata\" : {\n                 \"experiment_id\" : \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\"\n              },\n              \"time_criteria\" : {\n                 \"end_timestamp\" : 1689091608000,\n                 \"start_timestamp\" : 1689012595000\n              },\n              \"type\" : \"static\"\n           },\n           \"experiment_id\" : \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\",\n           \"last_updated\" : \"2023-07-11T16:06:49.003\",\n        }\n        \"\"\"\n\n        let decoded: Experiment = try self.decoder.decode(\n            Experiment.self,\n            from: json.data(using: .utf8)!\n        )\n        \n        let expected = Experiment(\n            id: \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\",\n            lastUpdated: decoded.lastUpdated,\n            created: decoded.created,\n            reportingMetadata: [\"experiment_id\" : \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\"],\n            audienceSelector: DeviceAudienceSelector(\n                hashSelector: .init(\n                    hash: .init(\n                        prefix: \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c:\",\n                        property: .contact,\n                        algorithm: .farm,\n                        seed: nil,\n                        numberOfBuckets: 16384,\n                        overrides: nil),\n                    bucket: .init(min: 0, max: 8192))\n            ),\n            exclusions: [\n                .init(messageTypePredicate: try! .fromJson(json: [\"value\": [\"equals\": \"transactional\"]]), campaignsPredicate: nil)\n            ],\n            timeCriteria: .init(start: Date(milliseconds: 1689012595000), end: Date(milliseconds: 1689091608000))\n        )\n\n        let encoded = String(data: try encoder.encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n        XCTAssertEqual(expected, decoded)\n    }\n    \n    func testCodableWithCompoundAudience() throws {\n        let json: String =  \"\"\"\n        {\n          \"created\": \"2023-07-10T18:10:46.203\",\n          \"experiment_definition\": {\n            \"audience_selector\": {\n              \"hash\": {\n                \"audience_hash\": {\n                  \"hash_algorithm\": \"farm_hash\",\n                  \"hash_identifier\": \"contact\",\n                  \"hash_prefix\": \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c:\",\n                  \"num_hash_buckets\": 16384\n                },\n                \"audience_subset\": {\n                  \"max_hash_bucket\": 8192,\n                  \"min_hash_bucket\": 0\n                }\n              }\n            },\n            \"compound_audience\": {\n              \"selector\": {\n                \"type\": \"atomic\",\n                \"audience\": {\n                  \"new_user\": true\n                }\n              }\n            },\n            \"experiment_type\": \"holdout\",\n            \"message_exclusions\": [\n              {\n                \"message_type\": {\n                  \"value\": {\n                    \"equals\": \"transactional\"\n                  }\n                }\n              }\n            ],\n            \"reporting_metadata\": {\n              \"experiment_id\": \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\"\n            },\n            \"time_criteria\": {\n              \"end_timestamp\": 1689091608000,\n              \"start_timestamp\": 1689012595000\n            },\n            \"type\": \"static\"\n          },\n          \"experiment_id\": \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\",\n          \"last_updated\": \"2023-07-11T16:06:49.003\"\n        }\n        \"\"\"\n\n        let decoded: Experiment = try self.decoder.decode(\n            Experiment.self,\n            from: json.data(using: .utf8)!\n        )\n        \n        let expected = Experiment(\n            id: \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\",\n            lastUpdated: decoded.lastUpdated,\n            created: decoded.created,\n            reportingMetadata: [\"experiment_id\" : \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c\"],\n            audienceSelector: DeviceAudienceSelector(\n                hashSelector: .init(\n                    hash: .init(\n                        prefix: \"cf9b8c05-05e2-4b8e-a2a3-7ed06d99cc1c:\",\n                        property: .contact,\n                        algorithm: .farm,\n                        seed: nil,\n                        numberOfBuckets: 16384,\n                        overrides: nil),\n                    bucket: .init(min: 0, max: 8192))\n            ),\n            compoundAudience: .init(selector: .atomic(.init(newUser: true))),\n            exclusions: [\n                .init(messageTypePredicate: try! .fromJson(json: [\"value\": [\"equals\": \"transactional\"]]), campaignsPredicate: nil)\n            ],\n            timeCriteria: .init(start: Date(milliseconds: 1689012595000), end: Date(milliseconds: 1689091608000))\n        )\n\n        let encoded = String(data: try encoder.encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n        XCTAssertEqual(expected, decoded)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/FarmHashFingerprint64Test.swift",
    "content": "\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class FarmHashFingerprint64Test: XCTestCase {\n\n    private let testData: [String: UInt64] = [\n        \"dXB@tDQ-v5<H]rq2Pcc*s>nC-[Mdy\": 8365906589669344754,\n        \"!@#$%^&*():=-_][\\\\|/?.,<> \": 11772040268694734364,\n        \"&&3gRU?[^&ok:He[|K:\": 11792583603419566171,\n        \"9JqLl0AW7e69Y.&vMHQ5C\": 2827089714349584095,\n        \"F7479877-4690-4A44-AFC9-8FE987EA512F:some_other_id\": 6862335115798125349,\n        \"hg[F|$D&hb$,V4OeXHOa\": 11873385450325105043,\n        \"/dWQW6&i7h$1@\": 11452602314494946942,\n        \"2/?98ns)xbzEVL^:wCS$7l3@_g!zP^<D.-bd6\": 9728733090894310797,\n        \"?c^6BkI#-SLw\": 13133570674398037786,\n        \"wE,gHSvhK Jv=KR#(R |!%vctTJ0fx)\": 413905253809041649,\n        \"5C $WnO2K@:(4#h\": 2463546464490189,\n        \"Ijiq13Mb_Nn]sA^jhM7eZ\\\\ExAzSJ\": 12345582939087509209,\n        \")D<l91\": 6440615040757739207,\n        \"mC=6Tz,AYH|&n99(G!6LyG&QfZ=1^:\": 10432240328043398052,\n        \"7.b^/n=oR_w(vLN?c?xN<5t$p8HY2!s:U\": 2506644862971557451,\n        \"t,SRdW>l=?AH4\\\\JQ!.A)Wh,O4\\\\8\": 4614517891525318442,\n        \"K6Pjv<>ad\": 16506019169567922731,\n        \"\": 11160318154034397263,\n        \"Q\": 13816109650407654260,\n        \"bF&d$MYIhB.Ac=qC\": 17582099205024456557,\n        \"#cDR^sLO\": 328147020574120176,\n        \"NXooOPwHej5=c_V0(47=-)N!vNdd:$fMs1B\": 5510670329273533446,\n        \"y2=B@rsu:g9bWU\": 2493222351070148393,\n        \"wi=%v]GoIPI6zm[Rrgmq]7J?.|\": 8222623341199956836,\n        \"Sl,xx&O^l@=TQ[QI(TJ^aD*PS3.K]@Mk:e)e\": 12943788826636368730,\n        \"@05Mz\\\\\\\\)VhZ\\\\S&9vVU,egF%sW)IMIGVHE%#I)D|\": 134798502762145076,\n        \"e#p8\": 252499459662674074,\n        \">EtzDE,xUUZ%!aCvx#vyN(][Q.eRQO2sBZCwFH\": 5047171257402399197,\n        \"ECCD828C-5D7A-4C8B-9A1B-F244747E96C3\": 9693850515132481856,\n        \"D<wQ1DVVpS\": 876767294656777789,\n        \",=\": 1326014594494455617,\n        \"EsIIjI65<^!j$)V.,!]M]@Q5[$(oyxI_nF\": 4212098974972931626,\n        \"fDVY|(%&aF#3<l>b?1Y Hqt)qY(0%b@VIk#Rlofs\": 1687730506231245221,\n        \"^b2z)XYJ\\\\95\": 3150268469206098298,\n        \"9>Nleb)=|CR#4=G2&7[HOP\": 10511875379468936029,\n        \"M)(iJ1-nf>5XCc0L?\": 9968500208262240300,\n        \"WW5\": 6316074107149058620,\n        \"ZyzWj:&3hH78.WUCNW4e&Z \": 13218358187761524434,\n        \"P9|0-Xg\": 15614415556471156694,\n        \"n?(o|a[EX|KN-9./=tCVEmN%?<MXe8F<\": 1754206644017466002,\n        \"&QEO\\\\\": 673322083973757863,\n        \"T#e:),mqALpU]hrJ%f.*|=&r\": 11789374840096016445,\n        \"xi\\\\PvQpHpM:$5\\\\Zh^U\": 4169389423472268625,\n        \"!/tU|0cMaw=/-Yg)m_*4UNvwB\": 14890523890635468863\n    ]\n\n    private let crossPlatformCases: [String: UInt64] = [\n           \"0376d8dc-a717-425a-9dd2-d4b36bcaddac:65d52a5d-4f88-4a78-97c9-08464d44bbb6\": 14340110469024474010,\n           \"62087079-4f3b-4350-88a9-67667493a48f:a9689ef1-ef7f-45e7-8841-ba0f6cdc6b4d\": 3536341280875387670,\n           \"3d50751d-360f-4c37-a818-0e2d7b83a795:962a1b84-8ecc-44d7-95ff-3ec60215075a\": 6852554232698863320,\n           \"9ecfe0bf-b24b-422f-83e2-e8de9c336493:27abe283-5937-43f0-a4f2-c8ee71f682e8\": 16343172889285518932,\n           \"601cfb3b-b69f-4b88-b52d-e1fc201df11c:29d407e8-bbc9-4b48-b789-e5af84b22810\": 18171507073648632955,\n           \"67b9dcdf-4d8d-42ed-a000-bf90c3b47fa8:e90c7986-0231-4c80-a10d-fc60bdf05ebc\": 6180626819026048726,\n           \"9431edf5-a862-405e-82bd-0f64283304e9:478f73b9-f324-42f6-9d8f-bb0445f11247\": 5342572022420056632,\n           \"5c24c242-4b81-496d-9d56-31f320d20a26:8155f96f-f3b1-4bcd-8a54-378150ea3d03\": 5403761470481847248,\n           \"976bddc7-7b5c-48fa-a285-a10dc2d64009:6a4ed766-017e-4b19-9cb2-0299e97a995b\": 404533724234009115,\n           \"cede4631-b57a-447d-b94b-4c31a71e1f3c:f46f1e00-1e78-4c01-8a89-989106182ecd\": 2662685979233479610,\n           \"dd464de7-2f57-4787-a14b-35bd57dd515d:2da0af42-35b0-423e-99ed-bc5cb5dd7099\": 5656984155782857542,\n           \"3f1d41bc-ac7e-49e2-8f88-30744f0fff4e:8561c5d7-cbb1-4b67-afd0-669e369420b6\": 3506311998853318899,\n           \"c65ef2ae-44b0-4c5e-b37e-57b4fda7ee8e:d4933f58-d257-41ff-b0fa-11aa524d642e\": 14192866033732275238,\n           \"d19964d4-59e3-49d6-8d61-4dfa97e794f7:6cbc589e-3695-4cc6-afe7-4bcdcea01480\": 8310185173796126101,\n           \"813c09f5-a0ae-410d-99c6-7bf7e87b2738:c5d50a64-bacf-4887-b3ac-e53d8cdc555b\": 15599208209427113891,\n           \"a1db3c20-673e-48e4-9967-b49834b6fac6:a3bc58d1-f389-4113-97d1-28d3bf12cbe5\": 1700656031758233133,\n           \"5b431ab8-975e-4207-8550-62da7665a01b:095c1b48-131e-477c-90d4-17894acc1246\": 7441422609642864761,\n           \"92f4a2ca-46d5-4e15-87c5-f7b33497286c:7811c125-2348-47a6-84e6-9343bb12a0f7\": 592674394864765514,\n           \"cbb0399d-a803-4a27-91a0-7732b308278e:f61a366a-7c20-45eb-b40a-bed0b012607c\": 492797389607996305,\n           \"2c333e41-e702-4096-a71f-8c3df488a990:9c1d45d9-439c-490e-99cc-f159ce7010e7\": 764412364649713065,\n           \"optic_acquit:eef25358-6577-4b84-bbcd-82c0f2de80e2\": 2791352902118037828,\n           \"warthog_punts:bbe20d0c-143d-4d1a-8973-182b7d10c7bb\": 7332285015592839891,\n           \"vanilla_hither:70d7d1ce-09da-468c-9864-d0188f70c1fe\": 8273296097385490599,\n           \"crepe_frumps:bbe484c8-af06-4477-863e-35cfdb284f71\": 8795467158546487560,\n           \"clinic_scouts:ec85318f-dd20-4cde-bc81-69bb1e21b12b\": 6650034920187666365,\n           \"trying_gapped:d72565c0-2d7e-4e37-a309-ad47c9c14da9\": 4989233212801864762,\n           \"snuffly_pithy:84811fbf-badd-405a-8564-7f354190943f\": 15791669038156053022,\n           \"graters_fields:37281aec-3848-4ef2-ac7d-e926618865f7\": 9056534536604691350,\n           \"mirrors_dangs:ddb42326-49f6-40e5-b428-b966f6ab4887\": 4084541845741700082,\n           \"expend_raying:b3054772-ed90-4a79-8866-4a8753f93d2d\": 11334098313106439423,\n           \"peewees_autobus:5de6faf8-e039-4b2c-ba37-ccdd0401758e\": 1590885424516612823,\n           \"giant_boozy:9ceecbc5-0372-4a5c-b12f-4a6d696dece9\": 3196424533567189237,\n           \"glazers_zagging:74e3f557-3064-4d99-8809-f6b4c897a710\": 18418949167652646364,\n           \"paces_acuate:4c08c06d-7ddc-4773-8fcb-4833c5a03b36\": 13404925037839805568,\n           \"makes_coiner:108af86f-b273-463c-96ee-9b4c948e92ac\": 13939548535417169537,\n           \"patinas_posted:e8ff9cd9-e335-4b6c-93ff-51aba68951c9\": 15877907202098665149,\n           \"further_agents:4fb082f2-2db8-4cf0-b367-b22ee0e590e1\": 16609400165765915699,\n           \"hubbubs_parked:b97ff960-af53-42a2-b5fe-9b8e248012f9\": 1116732196685121691,\n           \"deeply_outworn:ef48d2be-76ef-465d-a993-b92cf8f958ac\": 16625481623000662505,\n           \"girded_heave:30aebf7f-8a0c-4b89-adbc-b010b0619f94\": 4262921022933957472,\n       ]\n\n    func testKnownOutputs() throws {\n        self.testData.forEach { (key: String, value: UInt64) in\n            XCTAssertEqual(value, key.farmHashFingerprint64)\n        }\n    }\n\n    func testCrossPlatformCases() throws {\n        self.crossPlatformCases.forEach { (key: String, value: UInt64) in\n            XCTAssertEqual(value, key.farmHashFingerprint64)\n        }\n    }\n\n    /**\n     * Based on https://github.com/google/guava/blob/master/guava-tests/test/com/google/common/hash/FarmHashFingerprint64Test.java#L38\n     */\n    func testReallySimpleFingerprints() throws {\n        XCTAssertEqual(\n            8581389452482819506,\n            \"test\".farmHashFingerprint64\n        )\n        XCTAssertEqual(\n            UInt64(bitPattern: -4196240717365766262),\n            String(repeating: \"test\", count: 8).farmHashFingerprint64\n        )\n        XCTAssertEqual(\n            3500507768004279527,\n            String(repeating: \"test\", count: 64).farmHashFingerprint64\n        )\n    }\n\n    /**\n     * Based on https://github.com/google/guava/blob/master/guava-tests/test/com/google/common/hash/FarmHashFingerprint64Test.java#L158\n     */\n    func testMultipleLengths() throws {\n        let iterations = 800\n        var buf = [UInt8](repeating: 0, count: iterations * 4)\n        var bufLen : Int = 0\n\n        var h : UInt64 = 0\n        for i in 0..<iterations {\n\n            h ^= FarmHashFingerprint64.fingerprint(buf, i)\n            h = remix(h)\n            buf[bufLen] = getChar(h)\n            bufLen += 1\n\n            h ^= FarmHashFingerprint64.fingerprint(buf, i * i % bufLen)\n            h = remix(h)\n            buf[bufLen] = getChar(h)\n            bufLen += 1\n\n            h ^= FarmHashFingerprint64.fingerprint(buf, i * i * i % bufLen)\n            h = remix(h)\n            buf[bufLen] = getChar(h)\n            bufLen += 1\n\n            h ^= FarmHashFingerprint64.fingerprint(buf, bufLen)\n            h = remix(h)\n            buf[bufLen] = getChar(h)\n            bufLen += 1\n\n            let x0 : Int = Int(buf[bufLen - 1])\n            let x1 : Int = Int(buf[bufLen - 2])\n            let x2 : Int = Int(buf[bufLen - 3])\n            let x3 : Int = Int(buf[bufLen / 2])\n\n            buf[((x0 << 16) + (x1 << 8) + x2) % bufLen] ^= UInt8(x3)\n            buf[((x1 << 16) + (x2 << 8) + x3) % bufLen] ^= UInt8(i % 256)\n        }\n        XCTAssertEqual(0x7a1d67c50ec7e167, h)\n    }\n\n    private func remix(_ v: UInt64) -> UInt64 {\n        var h = v\n        h ^= h >> 41\n        h &*= 949921979\n        return h\n    }\n\n    private func getChar(_ h: UInt64) -> UInt8 {\n        return UInt8(0x61/*a*/) + UInt8((h & 0xfffff) % 26)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/FetchDeviceInfoActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class FetchDeviceInfoActionTest: XCTestCase {\n\n    private let channel = TestChannel()\n    private let contact = TestContact()\n    private let push = TestPush()\n\n    var action: FetchDeviceInfoAction!\n\n    override func setUp() async throws {\n        action = FetchDeviceInfoAction(\n            channel: { [channel] in return channel },\n            contact: { [contact] in return contact },\n            push: { [push] in return push }\n        )\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundInteractiveButton,\n            ActionSituation.backgroundPush\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(value: AirshipJSON.null, situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n    }\n\n    @MainActor\n    func testPerform() async throws {\n        self.channel.identifier = \"some-channel-id\"\n        self.contact.namedUserID = \"some-named-user\"\n        self.channel.tags = [\"tag1\", \"tag2\", \"tag3\"]\n        self.push.isPushNotificationsOptedIn = true\n\n        let actionResult = try await self.action.perform(\n            arguments: ActionArguments(\n                value: AirshipJSON.null,\n                situation: .manualInvocation\n            )\n        )\n\n        let expectedResult = try! AirshipJSON.wrap([\n            \"tags\": [\"tag1\", \"tag2\", \"tag3\"],\n            \"push_opt_in\": true,\n            \"named_user\": \"some-named-user\",\n            \"channel_id\": \"some-channel-id\"\n        ] as [String : Any])\n\n        XCTAssertEqual(actionResult, expectedResult)\n    }\n \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/HashCheckerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\nfinal class HashCheckerTest: XCTestCase {\n    private let cache: TestCache = TestCache()\n    private let testDeviceInfo: TestAudienceDeviceInfoProvider = TestAudienceDeviceInfoProvider()\n\n    private var checker: HashChecker!\n\n    override func setUp() async throws {\n        self.checker = HashChecker(cache: cache)\n    }\n\n    func testStickyCacheMatch() async throws {\n        self.testDeviceInfo.channelID = \"some channel\"\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n\n        let stickyHash = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000),\n            sticky: AudienceHashSelector.Sticky(\n                id: \"sticky ID\",\n                reportingMetadata: \"sticky reporting\",\n                lastAccessTTL: 100.0\n            )\n        )\n\n\n        let result = try await checker.evaluate(\n            hashSelector: stickyHash,\n            deviceInfoProvider: self.testDeviceInfo\n        )\n\n        XCTAssertEqual(\n            AirshipDeviceAudienceResult(\n                isMatch: true,\n                reportingMetadata: [.string(\"sticky reporting\")]\n            ),\n            result\n        )\n\n        let entry = await self.cache.entry(key: \"StickyHash:match:some channel:sticky ID\")!\n        XCTAssertEqual(entry.ttl, 100.0)\n        let decodedData = try JSONDecoder().decode(AirshipDeviceAudienceResult.self, from: entry.data)\n        XCTAssertEqual(decodedData, result)\n    }\n\n    func testStickyHashFromCacheStillCaches() async throws {\n        self.testDeviceInfo.channelID = \"some channel\"\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"match\")\n\n        var stickyHash = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000),\n            sticky: AudienceHashSelector.Sticky(\n                id: \"sticky ID\",\n                reportingMetadata: \"sticky reporting\",\n                lastAccessTTL: 100.0\n            )\n        )\n\n\n        var result = try await checker.evaluate(\n            hashSelector: stickyHash,\n            deviceInfoProvider: self.testDeviceInfo\n        )\n\n        XCTAssertEqual(\n            AirshipDeviceAudienceResult(\n                isMatch: true,\n                reportingMetadata: [.string(\"sticky reporting\")]\n            ),\n            result\n        )\n\n        var entry = await self.cache.entry(key: \"StickyHash:match:some channel:sticky ID\")!\n        XCTAssertEqual(entry.ttl, 100.0)\n\n        stickyHash.sticky = AudienceHashSelector.Sticky(\n            id: \"sticky ID\",\n            reportingMetadata: \"updated sticky reporting\",\n            lastAccessTTL: 50.0\n        )\n\n        result = try await checker.evaluate(\n            hashSelector: stickyHash,\n            deviceInfoProvider: self.testDeviceInfo\n        )\n\n        XCTAssertEqual(\n            AirshipDeviceAudienceResult(\n                isMatch: true,\n                reportingMetadata: [.string(\"sticky reporting\")]\n            ),\n            result\n        )\n\n        entry = await self.cache.entry(key: \"StickyHash:match:some channel:sticky ID\")!\n        XCTAssertEqual(entry.ttl, 50.0)\n\n    }\n\n    func testStickyCacheMiss() async throws {\n        self.testDeviceInfo.channelID = \"some channel\"\n        self.testDeviceInfo.stableContactInfo = StableContactInfo(contactID: \"not a match\")\n\n        let stickyHash = AudienceHashSelector(\n            hash: AudienceHashSelector.Hash(\n                prefix: \"e66a2371-fecf-41de-9238-cb6c28a86cec:\",\n                property: .contact,\n                algorithm: .farm,\n                seed: 100,\n                numberOfBuckets: 16384,\n                overrides: nil\n            ),\n            bucket: AudienceHashSelector.Bucket(min: 11600, max: 13000),\n            sticky: AudienceHashSelector.Sticky(\n                id: \"sticky ID\",\n                reportingMetadata: \"sticky reporting\",\n                lastAccessTTL: 100.0\n            )\n        )\n\n        let result = try await checker.evaluate(\n            hashSelector: stickyHash,\n            deviceInfoProvider: self.testDeviceInfo\n        )\n\n        XCTAssertEqual(\n            AirshipDeviceAudienceResult(\n                isMatch: false,\n                reportingMetadata: [.string(\"sticky reporting\")]\n            ),\n            result\n        )\n\n        let entry = await self.cache.entry(key: \"StickyHash:not a match:some channel:sticky ID\")!\n        XCTAssertEqual(entry.ttl, 100.0)\n        let decodedData = try JSONDecoder().decode(AirshipDeviceAudienceResult.self, from: entry.data)\n        XCTAssertEqual(decodedData, result)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundlePackageType</key>\n\t<string>BNDL</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n\t<key>PROJECT_FILE_PATH</key>\n\t<string>$(PROJECT_FILE_PATH)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Input Validation/AirshipInputValidationTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\nstruct AirshipInputValidationTest {\n    private let smsValidatorAPIClient = TestSMSValidatorAPIClient()\n\n    @Test(\n        \"Test valid email addresses\",\n        arguments: [\n            \"simple@example.com\",\n            \"very.common@example.com\",\n            \"disposable.style.email.with+symbol@example.com\",\n            \"other.email-with-hyphen@example.com\",\n            \"fully-qualified-domain@example.com\",\n            \"user.name+tag+sorting@example.com\",\n            \"x@y.z\",\n            \"user123@domain.com\",\n            \"user.name@domain.com\",\n            \"a@domain.com\",\n            \"user@sub.domain.com\",\n            \"user-name@domain.com\",\n            \"user.@domain.com\",\n            \".user@domain.com\",\n            \"user@.domain.com\",\n            \"user@domain..com\",\n            \"user..name@domain.com\",\n            \"user+name@domain.com\",\n            \"user!#$%&'*+-/=?^_`{|}~@domain.com\"\n        ]\n    )\n    func testValidEmail(arg: String) async throws {\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        let request = AirshipInputValidation.Request.email(\n            .init(rawInput: arg)\n        )\n\n        let result = try await validator.validateRequest(request)\n\n        #expect(result == .valid(address: arg))\n    }\n\n\n    @Test(\n        \"Test invalid emails\",\n        arguments: [\n            \"user\",\n            \"user \",\n            \"\",\n            \"user@\",\n            \"user@domain\",\n            \"user @domain.com\",\n            \"user@ domain.com\",\n            \"us er@domain.com\",\n            \"user@do main.com\",\n            \"user@domain.com.\",\n            \"user@domain@example.com\"\n        ]\n    )\n    func testInvalidEmails(arg: String) async throws {\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        let request = AirshipInputValidation.Request.email(\n            .init(rawInput: arg)\n        )\n\n        let result = try await validator.validateRequest(request)\n\n        #expect(result == .invalid)\n    }\n\n    @Test(\n        \"Test valid email formatting\",\n        arguments: [\n            \" user@domain.com\",\n            \"user@domain.com   \",\n            \"      user@domain.com   \"\n        ]\n    )\n    func testEmailFormatting(arg: String) async throws {\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        let request = AirshipInputValidation.Request.email(\n            .init(rawInput: arg)\n        )\n\n        let result = try await validator.validateRequest(request)\n\n        let trimmed = arg.trimmingCharacters(in: .whitespacesAndNewlines)\n        #expect(result == .valid(address: trimmed))\n    }\n\n    @Test(\"Test email override.\")\n    func testEmailOverride() async throws {\n        let request = AirshipInputValidation.Request.email(\n            .init(rawInput: \"some-valid@email.com\")\n        )\n\n        try await confirmation { confirmation in\n            let validator = AirshipInputValidation.DefaultValidator(\n                smsValidatorAPIClient: smsValidatorAPIClient\n            ) { arg in\n                #expect(arg == request)\n                confirmation.confirm()\n                return .override(.valid(address: \"some other result\"))\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .valid(address: \"some other result\"))\n        }\n    }\n\n    @Test(\"Test email override default fallback.\")\n    func testEmailOverrideFallback() async throws {\n        let request = AirshipInputValidation.Request.email(\n            .init(rawInput: \" some-valid@email.com \")\n        )\n\n        try await confirmation { confirmation in\n            let validator = AirshipInputValidation.DefaultValidator(\n                smsValidatorAPIClient: smsValidatorAPIClient\n            ) { arg in\n                #expect(arg == request)\n                confirmation.confirm()\n                return .useDefault\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .valid(address: \"some-valid@email.com\"))\n        }\n    }\n\n    @Test(\"Test sms validation with sender ID\")\n    func testSMSValidationWithSenderID() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil)\n            )\n        )\n\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        try await confirmation { confirmation in\n            await smsValidatorAPIClient.setOnValidate { apiRequest in\n                #expect(apiRequest.msisdn == \"555555555\")\n                #expect(apiRequest.sender == \"some sender\")\n                confirmation.confirm()\n                return AirshipHTTPResponse(\n                    result: .valid(\"+1555555555\"),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .valid(address: \"+1555555555\"))\n        }\n    }\n\n    @Test(\"Test sms validation with prefix\")\n    func testSMSValidationWithPrefix() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .prefix(prefix: \"+1\")\n            )\n        )\n\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        try await confirmation { confirmation in\n            await smsValidatorAPIClient.setOnValidate { apiRequest in\n                #expect(apiRequest.msisdn == \"555555555\")\n                #expect(apiRequest.prefix == \"+1\")\n                confirmation.confirm()\n                return AirshipHTTPResponse(\n                    result: .valid(\"+1555555555\"),\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .valid(address: \"+1555555555\"))\n        }\n    }\n\n    @Test(\"Test sms validation 4xx response should return invalid\")\n    func testSMSValidationWith400Response() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil)\n            )\n        )\n\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        try await confirmation { confirmation in\n            await smsValidatorAPIClient.setOnValidate { apiRequest in\n                #expect(apiRequest.msisdn == \"555555555\")\n                #expect(apiRequest.sender == \"some sender\")\n                confirmation.confirm()\n                return AirshipHTTPResponse(\n                    result: nil,\n                    statusCode: Int.random(in: 400...499),\n                    headers: [:]\n                )\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .invalid)\n        }\n    }\n\n    @Test(\"Test sms validation 5xx should throw\")\n    func testSMSValidationWith500Response() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil)\n            )\n        )\n\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        await confirmation { confirmation in\n            await smsValidatorAPIClient.setOnValidate { apiRequest in\n                #expect(apiRequest.msisdn == \"555555555\")\n                #expect(apiRequest.sender == \"some sender\")\n                confirmation.confirm()\n                return AirshipHTTPResponse(\n                    result: nil,\n                    statusCode: Int.random(in: 500...599),\n                    headers: [:]\n                )\n            }\n\n            await #expect(throws: NSError.self) {\n                _ = try await validator.validateRequest(request)\n            }\n        }\n    }\n\n    @Test(\"Test sms validation 2xx without a result should throw\")\n    func testSMSValidationWith200ResponseNoResult() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil)\n            )\n        )\n\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        await confirmation { confirmation in\n            await smsValidatorAPIClient.setOnValidate { apiRequest in\n                #expect(apiRequest.msisdn == \"555555555\")\n                #expect(apiRequest.sender == \"some sender\")\n                confirmation.confirm()\n                return AirshipHTTPResponse(\n                    result: nil,\n                    statusCode: Int.random(in: 200...299),\n                    headers: [:]\n                )\n            }\n\n            await #expect(throws: NSError.self) {\n                _ = try await validator.validateRequest(request)\n            }\n        }\n    }\n\n    @Test(\"Test validation hints are checked before API client\")\n    func testValidationHints() async throws {\n        // Setup a valid response\n        await smsValidatorAPIClient.setOnValidate { apiRequest in\n            return AirshipHTTPResponse(\n                result: .valid(\"+1555555555\"),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let validator = AirshipInputValidation.DefaultValidator(\n            smsValidatorAPIClient: smsValidatorAPIClient\n        )\n\n        // Test 0-3 digits\n        for i in 0...3 {\n            let request = AirshipInputValidation.Request.sms(\n                .init(\n                    rawInput: generateRandomNumberString(length: i),\n                    validationOptions: .sender(senderID: \"some sender\", prefix: nil),\n                    validationHints: .init(minDigits: 4, maxDigits: 6)\n                )\n            )\n            try await #expect(validator.validateRequest(request) == .invalid)\n        }\n\n        // Test 4-6 digits\n        for i in 4...6 {\n            let request = AirshipInputValidation.Request.sms(\n                .init(\n                    rawInput: generateRandomNumberString(length: i),\n                    validationOptions: .sender(senderID: \"some sender\", prefix: nil),\n                    validationHints: .init(minDigits: 4, maxDigits: 6)\n                )\n            )\n            try await #expect(validator.validateRequest(request) == .valid(address: \"+1555555555\"))\n        }\n\n        // Test over 6 digits\n        for i in 7...10 {\n            let request = AirshipInputValidation.Request.sms(\n                .init(\n                    rawInput: generateRandomNumberString(length: i),\n                    validationOptions: .sender(senderID: \"some sender\", prefix: nil),\n                    validationHints: .init(minDigits: 4, maxDigits: 6)\n                )\n            )\n            try await #expect(validator.validateRequest(request) == .invalid)\n        }\n\n        // Test digits with other characters\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"a1b2c3d4b5e6\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil),\n                validationHints: .init(minDigits: 4, maxDigits: 6)\n            )\n        )\n        try await #expect(validator.validateRequest(request) == .valid(address: \"+1555555555\"))\n    }\n\n    @Test(\"Test SMS override.\")\n    func testSMSOverride() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil)\n            )\n        )\n\n        try await confirmation { confirmation in\n            let validator = AirshipInputValidation.DefaultValidator(\n                smsValidatorAPIClient: smsValidatorAPIClient\n            ) { arg in\n                #expect(arg == request)\n                confirmation.confirm()\n                return .override(.valid(address: \"some other result\"))\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .valid(address: \"some other result\"))\n        }\n    }\n\n    @Test(\"Test SMS override default fallback.\")\n    func testSMSOverrideFallback() async throws {\n        let request = AirshipInputValidation.Request.sms(\n            .init(\n                rawInput: \"555555555\",\n                validationOptions: .sender(senderID: \"some sender\", prefix: nil)\n            )\n        )\n\n        try await confirmation(expectedCount: 2) { confirmation in\n            await smsValidatorAPIClient.setOnValidate { apiRequest in\n                #expect(apiRequest.msisdn == \"555555555\")\n                #expect(apiRequest.sender == \"some sender\")\n                confirmation.confirm()\n                return AirshipHTTPResponse(\n                    result: .valid(\"API result\"),\n                    statusCode: Int.random(in: 200...299),\n                    headers: [:]\n                )\n            }\n\n            let validator = AirshipInputValidation.DefaultValidator(\n                smsValidatorAPIClient: smsValidatorAPIClient\n            ) { arg in\n                #expect(arg == request)\n                confirmation.confirm()\n                return .useDefault\n            }\n\n            let result = try await validator.validateRequest(request)\n            #expect(result == .valid(address: \"API result\"))\n        }\n    }\n}\n\n// Helpers\nfileprivate extension AirshipInputValidationTest {\n    func generateRandomNumberString(length: Int) -> String {\n        let digits = \"0123456789\"\n        var result = \"\"\n\n        for _ in 0..<length {\n            if let randomCharacter = digits.randomElement() {\n                result.append(randomCharacter)\n            }\n        }\n\n        return result\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Input Validation/CachingSMSValidatorAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\nstruct CachingSMSValidatorAPIClientTest {\n    private let testClient: TestSMSValidatorAPIClient\n    private let apiClient: CachingSMSValidatorAPIClient\n    private static let maxCacheEntries: UInt = 5\n\n    init() {\n        let testClient = TestSMSValidatorAPIClient()\n        self.testClient = testClient\n        self.apiClient = CachingSMSValidatorAPIClient(\n            client: testClient,\n            maxCachedEntries: Self.maxCacheEntries\n        )\n    }\n\n    @Test(\"Test caches success results for prefix\")\n    func testCachesSuccessResultsPrefix() async throws {\n        let successResult = AirshipHTTPResponse(\n            result: SMSValidatorAPIClientResult.valid(\"valid string\"),\n            statusCode: 200,\n            headers: [:]\n        )\n\n        await testClient.setOnValidate { _ in\n            return successResult\n        }\n\n        let msisdn = UUID().uuidString\n        let prefix = UUID().uuidString\n\n        var result = try await self.apiClient.validateSMS(msisdn: msisdn, prefix: prefix)\n        #expect(result.isSuccess)\n        #expect(result.result == successResult.result)\n        await #expect(testClient.requests.count == 1)\n\n        // Should be cached\n        result = try await self.apiClient.validateSMS(msisdn: msisdn, prefix: prefix)\n        #expect(result.isSuccess)\n        #expect(result.result == successResult.result)\n        await #expect(testClient.requests.count == 1)\n    }\n\n    @Test(\"Test caches success results for sender\")\n    func testCachesSuccessResultsSender() async throws {\n        let successResult = AirshipHTTPResponse(\n            result: SMSValidatorAPIClientResult.valid(\"valid string\"),\n            statusCode: 200,\n            headers: [:]\n        )\n\n        await testClient.setOnValidate { _ in\n            return successResult\n        }\n\n        let msisdn = UUID().uuidString\n        let sender = UUID().uuidString\n\n        var result = try await self.apiClient.validateSMS(msisdn: msisdn, prefix: sender)\n        #expect(result.isSuccess)\n        #expect(result.result == successResult.result)\n        await #expect(testClient.requests.count == 1)\n\n        // Should be cached\n        result = try await self.apiClient.validateSMS(msisdn: msisdn, prefix: sender)\n        #expect(result.isSuccess)\n        #expect(result.result == successResult.result)\n        await #expect(testClient.requests.count == 1)\n    }\n\n    @Test(\"Test caches results for the given parameters even for same msisdn\")\n    func testCachesResultForRequestParams() async throws {\n        await testClient.setOnValidate { call in\n            return AirshipHTTPResponse(\n                result: SMSValidatorAPIClientResult.valid(call.msisdn + \" valid\"),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        let msisdn = UUID().uuidString\n        let prefix = UUID().uuidString\n        let sender = UUID().uuidString\n\n        var result = try await self.apiClient.validateSMS(msisdn: msisdn, prefix: prefix)\n        #expect(result.isSuccess)\n        #expect(result.result == .valid(msisdn + \" valid\"))\n        await #expect(testClient.requests.count == 1)\n\n        // Should not be cached since we are requesting the validation on a sender instead of a prefix\n        result = try await self.apiClient.validateSMS(msisdn: msisdn, sender: sender)\n        #expect(result.isSuccess)\n        #expect(result.result == .valid(msisdn + \" valid\"))\n        await #expect(testClient.requests.count == 2)\n\n        let expectedRequests: [TestSMSValidatorAPIClient.Request] = [\n            .init(msisdn: msisdn, prefix: prefix),\n            .init(msisdn: msisdn, sender: sender)\n        ]\n        await #expect(testClient.requests == expectedRequests)\n    }\n\n    @Test(\"Test max cache entries\")\n    func testMaxCacheEntries() async throws {\n        await testClient.setOnValidate { call in\n            return AirshipHTTPResponse(\n                result: SMSValidatorAPIClientResult.valid(call.msisdn + \" valid\"),\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        // Fill the cache\n        for i in 1...Self.maxCacheEntries {\n            print(\"cool i: \\(i)\")\n            _ = try await self.apiClient.validateSMS(\n                msisdn: UUID().uuidString,\n                prefix: UUID().uuidString\n            )\n        }\n\n        await #expect(testClient.requests.count == Self.maxCacheEntries)\n\n        _ = try await self.apiClient.validateSMS(\n            msisdn: UUID().uuidString,\n            prefix: UUID().uuidString\n        )\n\n        await #expect(testClient.requests.count == Self.maxCacheEntries + 1)\n\n        // Do the second request again, should still be cached\n        _ = try await self.apiClient.validateSMS(\n            msisdn: testClient.requests[1].msisdn,\n            prefix: testClient.requests[1].prefix!\n        )\n\n        await #expect(testClient.requests.count == Self.maxCacheEntries + 1)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Input Validation/SMSValidatorAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\nstruct SMSValidatorAPIClientTest {\n\n    private let session: TestAirshipRequestSession\n    private let config: RuntimeConfig\n    private let apiClient: SMSValidatorAPIClient\n\n    private let msisdn = UUID().uuidString\n    private let sender = UUID().uuidString\n    private let prefix = UUID().uuidString\n\n    init() {\n        let config = RuntimeConfig.testConfig()\n        let session = TestAirshipRequestSession()\n        self.session = session\n        self.config = RuntimeConfig.testConfig()\n        self.apiClient = SMSValidatorAPIClient(config: config, session: session)\n    }\n\n    @Test(\"Test validate SMS with sender\")\n    func testSendSMSWithSender() async throws {\n        let expectedRequest = AirshipRequest(\n            url: URL(string: \"https://device-api.urbanairship.com/api/channels/sms/format\"),\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: try JSONEncoder().encode(\n                [\n                    \"msisdn\": msisdn,\n                    \"sender\": sender\n                ]\n            )\n        )\n\n        _ = try? await apiClient.validateSMS(\n            msisdn: msisdn,\n            sender: sender\n        )\n\n        #expect(\n            try requestsMatch(expectedRequest, session.lastRequest)\n        )\n    }\n\n    @Test(\"Test validate SMS with prefix\")\n    func testSendSMSWithPrefix() async throws {\n\n        let expectedRequest = AirshipRequest(\n            url: URL(string: \"https://device-api.urbanairship.com/api/channels/sms/format\"),\n            headers: [\n                \"Accept\":  \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\"\n            ],\n            method: \"POST\",\n            auth: .generatedAppToken,\n            body: try JSONEncoder().encode(\n                [\n                    \"msisdn\": msisdn,\n                    \"prefix\": prefix\n                ]\n            )\n        )\n\n        _ = try? await apiClient.validateSMS(\n            msisdn: msisdn,\n            prefix: prefix\n        )\n\n        #expect(\n            try requestsMatch(expectedRequest, session.lastRequest)\n        )\n    }\n\n    @Test(\"Test valid number response parsing.\")\n    func testResponseParsing() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        self.session.data = try AirshipJSONUtils.data([\n            \"valid\": true,\n            \"msisdn\": msisdn + \"valid\"\n        ])\n\n        let response = try await apiClient.validateSMS(\n            msisdn: msisdn,\n            sender: sender\n        )\n\n        #expect(response.isSuccess)\n        #expect(response.result == .valid(msisdn + \"valid\"))\n    }\n\n    @Test(\"Test invalid number response parsing.\")\n    func testResponseParsingInvalidNumber() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n\n        self.session.data = try AirshipJSONUtils.data([\n            \"valid\": false\n        ])\n\n        let response = try await apiClient.validateSMS(\n            msisdn: msisdn,\n            sender: sender\n        )\n\n        #expect(response.isSuccess)\n        #expect(response.result == .invalid)\n    }\n\n    private func requestsMatch(\n        _ first: AirshipRequest,\n        _ second: AirshipRequest?\n    ) throws -> Bool {\n        guard\n            let second,\n            first.auth == second.auth,\n            first.contentEncoding == second.contentEncoding,\n            first.headers ==  second.headers,\n            first.url == second.url,\n            first.method == second.method\n        else {\n            return false\n        }\n\n        let firstBody = try AirshipJSON.from(data: first.body)\n        let secondBody = try AirshipJSON.from(data: second.body)\n        return firstBody == secondBody\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Input Validation/TestSMSValidatorAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable import AirshipCore\n\nactor TestSMSValidatorAPIClient: SMSValidatorAPIClientProtocol {\n    struct Request: Sendable, Equatable {\n        var msisdn: String\n        var sender: String?\n        var prefix: String?\n    }\n\n    private var onValidate: ((Request) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult>)?\n\n    func setOnValidate(_ onValidate: ((Request) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult>)?) {\n        self.onValidate = onValidate\n    }\n\n    private(set) var requests: [Request] = []\n\n    func validateSMS(msisdn: String, sender: String) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        let request = Request(msisdn: msisdn, sender: sender)\n        self.requests.append(request)\n        guard let onValidate else {\n            throw AirshipErrors.error(\"Validator not set\")\n        }\n        return try await onValidate(request)\n    }\n\n    func validateSMS(msisdn: String, prefix: String) async throws -> AirshipHTTPResponse<SMSValidatorAPIClientResult> {\n        let request = Request(msisdn: msisdn, prefix: prefix)\n        self.requests.append(request)\n        guard let onValidate else {\n            throw AirshipErrors.error(\"Validator not set\")\n        }\n        return try await onValidate(request)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/JSONPredicateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class JSONPredicateTest: XCTestCase {\n\n    var fooMatcher: JSONMatcher!\n    var storyMatcher: JSONMatcher!\n    var stringMatcher: JSONMatcher!\n\n    override func setUp() {\n        fooMatcher = JSONMatcher(valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"bar\"), scope: [\"foo\"])\n        storyMatcher = JSONMatcher(valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"story\"), scope: [\"cool\"])\n        stringMatcher = JSONMatcher(valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"cool\"))\n    }\n\n    func testCodable() throws {\n\n        let json: String = \"\"\"\n        {\n            \"or\":[\n                 {\n                     \"value\":{\n                         \"equals\":\"bar\"\n                     },\n                     \"scope\":[\n                         \"foo\"\n                     ]\n                 },\n                 {\n                     \"value\":{\n                         \"equals\":\"story\"\n                     },\n                     \"scope\":[\n                         \"cool\"\n                     ]\n                 }\n            ]\n        }\n        \"\"\"\n\n        let decoded: JSONPredicate = try JSONDecoder().decode(\n            JSONPredicate.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected: JSONPredicate = .orPredicate(\n            subpredicates: [\n                JSONPredicate(\n                    jsonMatcher: JSONMatcher(\n                        valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"bar\"),\n                        scope: [\"foo\"]\n                    )\n                ),\n                JSONPredicate(\n                    jsonMatcher: JSONMatcher(\n                        valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"story\"),\n                        scope: [\"cool\"]\n                    )\n                )\n            ]\n        )\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n\n    func testJSONMatcherPredicate() throws {\n        let predicate = JSONPredicate(jsonMatcher: stringMatcher)\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(\"cool\")))\n\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(predicate)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(\"falset cool\")))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testJSONMatcherPredicatePayload() throws {\n        let json = [\"value\": [\"equals\": \"cool\"]]\n        let predicate = JSONPredicate(jsonMatcher: stringMatcher)\n\n        XCTAssertEqual(try AirshipJSON.wrap(json), try AirshipJSON.wrap(predicate))\n\n        // Verify the JSONValue recreates the expected payload\n        XCTAssertEqual(predicate, try AirshipJSON.wrap(json).decode())\n    }\n\n    func testNotPredicate() throws {\n        let predicate = JSONPredicate.notPredicate(subpredicate: JSONPredicate(jsonMatcher: stringMatcher))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(\"cool\")))\n\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(\"no cool\")))\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testNotPredicatePayload() throws {\n        let json = [ \"not\": [ [\"value\": [\"equals\": \"cool\" ]] ] ]\n        let predicate = JSONPredicate.notPredicate(subpredicate: JSONPredicate(jsonMatcher: stringMatcher))\n        XCTAssertEqual(try AirshipJSON.wrap(json), try AirshipJSON.wrap(predicate))\n\n        // Verify the JSONValue recreates the expected payload\n        XCTAssertEqual(predicate, try AirshipJSON.wrap(json).decode())\n    }\n\n    func testJSONPredicateNotNoArray() throws {\n        let json: String = \"\"\"\n        {\n            \"not\": {\n                 \"value\":{\n                     \"equals\":\"bar\"\n                 },\n                 \"scope\":[\n                     \"foo\"\n                 ]\n           }\n        }\n        \"\"\"\n\n        let decoded: JSONPredicate = try JSONDecoder().decode(\n            JSONPredicate.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected: JSONPredicate = .notPredicate(\n            subpredicate: JSONPredicate(\n                jsonMatcher: JSONMatcher(\n                    valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"bar\"),\n                    scope: [\"foo\"]\n                )\n            )\n        )\n\n        XCTAssertEqual(decoded, expected)\n    }\n\n    func testJSONPredicateNotWithArray() throws {\n        let json: String = \"\"\"\n        {\n            \"not\": [{\n                 \"value\":{\n                     \"equals\":\"bar\"\n                 },\n                 \"scope\":[\n                     \"foo\"\n                 ]\n           }]\n        }\n        \"\"\"\n\n        let decoded: JSONPredicate = try JSONDecoder().decode(\n            JSONPredicate.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected: JSONPredicate = .notPredicate(\n            subpredicate: JSONPredicate(\n                jsonMatcher: JSONMatcher(\n                    valueMatcher: JSONValueMatcher.matcherWhereStringEquals(\"bar\"),\n                    scope: [\"foo\"]\n                )\n            )\n        )\n\n        XCTAssertEqual(decoded, expected)\n    }\n\n    func testJSONPredicateNotWithArrayMultipleElements() throws {\n        let json: String = \"\"\"\n        {\n            \"not\":[\n                 {\n                     \"value\":{\n                         \"equals\":\"bar\"\n                     },\n                     \"scope\":[\n                         \"foo\"\n                     ]\n                 },\n                 {\n                     \"value\":{\n                         \"equals\":\"bar\"\n                     },\n                     \"scope\":[\n                         \"foo\"\n                     ]\n                 }\n            ]\n        }\n        \"\"\"\n\n        do {\n            _  = try JSONDecoder().decode(\n                JSONPredicate.self,\n                from: json.data(using: .utf8)!\n            )\n            XCTFail(\"shoudl throw\")\n        } catch {\n\n        }\n    }\n\n    func testJSONPredicateArrayLength() throws {\n        // This JSON is flawed as you cant have an array of matchers for value. However it shows\n        // order of matcher parsing and its the same test on web, so we are using it.\n        let json: String = \"\"\"\n        {\n          \"value\": {\n            \"array_contains\": {\n                \"value\": {\n                  \"equals\": 2,\n                },\n            },\n            \"array_length\": {\n                \"value\": {\n                  \"equals\": 1,\n                },\n            },\n          },\n        }\n        \"\"\"\n\n        let predicate: JSONPredicate = try JSONDecoder().decode(\n            JSONPredicate.self,\n            from: json.data(using: .utf8)!\n        )\n\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap([2])))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap([0, 1, 2])))\n    }\n\n    func testAndPredicate() throws {\n        let fooPredicate = JSONPredicate(jsonMatcher: fooMatcher)\n        let storyPredicate = JSONPredicate(jsonMatcher: storyMatcher)\n        let predicate = JSONPredicate.andPredicate(subpredicates: [fooPredicate, storyPredicate])\n\n        var payload: [String: String] = [\"foo\": \"bar\", \"cool\": \"story\"]\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"foo\": \"bar\", \"cool\": \"story\", \"something\": \"else\"]\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"foo\": \"bar\", \"cool\": \"book\"]\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"foo\": \"bar\"]\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"cool\": \"story\"]\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(predicate)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(\"bar\")))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testAndPredicatePayload() throws {\n        let json = [\n            \"and\": [\n                [\"value\": [\"equals\": \"bar\"], \"scope\": [\"foo\"]],\n                [\"value\": [\"equals\": \"story\"], \"scope\": [\"cool\"]],\n            ]\n        ]\n\n        let fooPredicate = JSONPredicate(jsonMatcher: fooMatcher)\n        let storyPredicate = JSONPredicate(jsonMatcher: storyMatcher)\n        let predicate = JSONPredicate.andPredicate(subpredicates: [fooPredicate, storyPredicate])\n\n        XCTAssertEqual(try AirshipJSON.wrap(json), try AirshipJSON.wrap(predicate))\n\n        // Verify the JSONValue recreates the expected payload\n        XCTAssertEqual(predicate, try AirshipJSON.wrap(json).decode())\n\n    }\n\n    func testOrPredicate() throws {\n        let fooPredicate = JSONPredicate(jsonMatcher: fooMatcher)\n        let storyPredicate = JSONPredicate(jsonMatcher: storyMatcher)\n        let predicate = JSONPredicate.orPredicate(subpredicates: [fooPredicate, storyPredicate])\n\n        var payload: [String: String] = [\"foo\": \"bar\", \"cool\": \"story\"]\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"foo\": \"bar\", \"cool\": \"story\", \"something\": \"else\"]\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"foo\": \"bar\"]\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"cool\": \"story\"]\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        payload = [\"foo\": \"falset bar\", \"cool\": \"book\"]\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(payload)))\n\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(predicate)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(\"bar\")))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testOrPredicatePayload() throws {\n        let json = [\n            \"or\": [\n                [\"value\": [\"equals\": \"bar\"], \"scope\": [\"foo\"]],\n                [\"value\": [\"equals\": \"story\"], \"scope\": [\"cool\"]],\n            ]\n        ]\n\n        let fooPredicate = JSONPredicate(jsonMatcher: fooMatcher)\n        let storyPredicate = JSONPredicate(jsonMatcher: storyMatcher)\n        let predicate = JSONPredicate.orPredicate(subpredicates: [fooPredicate, storyPredicate])\n\n        XCTAssertEqual(try AirshipJSON.wrap(json), try AirshipJSON.wrap(predicate))\n\n        // Verify the JSONValue recreates the expected payload\n        XCTAssertEqual(predicate, try AirshipJSON.wrap(json).decode())\n    }\n\n    func testEqualArray() throws {\n        let json = [\"value\": [ \"equals\": [\"cool\", \"story\"]]]\n        let predicate = try JSONPredicate(json: json)\n\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap([\"cool\", \"story\"])))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap([\"cool\"])))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap([\"cool\", \"story\", \"afalsether key\"])))\n\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(predicate)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(\"bar\")))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testEqualObject() throws {\n        let json = [\"value\": [ \"equals\": [ \"cool\": \"story\" ] ]]\n        let predicate = try JSONPredicate(json: json)\n\n        XCTAssertTrue(predicate.evaluate(json: try AirshipJSON.wrap([\"cool\": \"story\"])))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap([\"cool\": \"story?\"])))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap([\"cool\": \"story\", \"afalsether_key\": \"afalsether_value\"])))\n\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(predicate)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(\"bar\")))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(predicate.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testInvalidPayload() throws {\n        // Invalid type\n        var json: [String: Any] = [\n            \"what\": [\n                [\"value\": [ \"equals\": \"bar\" ], \"key\": \"foo\"],\n                [\"value\": [ \"equals\": \"story\" ], \"key\": \"cool\"]\n            ]\n        ]\n\n        XCTAssertThrowsError(try JSONPredicate(json: json))\n\n        // Invalid key value\n        json = [\n            \"or\": [\n                \"not_cool\",\n                [\"value\": [\"equals\": \"story\"], \"key\": \"cool\" ]\n            ]\n        ]\n        XCTAssertThrowsError(try JSONPredicate(json: json))\n\n        // Invalid object\n        XCTAssertThrowsError(try JSONPredicate(json: \"not cool\"))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/JavaScriptCommandTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\nimport AirshipCore\n\nfinal class JavaScriptCommandTest: XCTestCase {\n\n    func testCommandForURL() {\n            let URL = URL(string: \"uairship://whatever/argument-one/argument-two?foo=bar&foo=barbar&foo\")!\n            let command = JavaScriptCommand(url: URL)\n\n            XCTAssertNotNil(command, \"data should be non-nil\")\n            XCTAssertEqual(command.arguments.count, 2, \"data should have two arguments\")\n            XCTAssertEqual(command.arguments.first, \"argument-one\", \"first arg should be 'argument-one'\")\n            XCTAssertEqual(command.arguments[1], \"argument-two\", \"second arg should be 'argument-two'\")\n\n            let expectedValues = [\"bar\", \"barbar\", \"\"]\n            XCTAssertEqual(command.options[\"foo\"], expectedValues, \"key 'foo' should have values 'bar', 'barbar', and ''\")\n        }\n\n        func testCommandForURLSlashBeforeArgs() {\n            let URL = URL(string: \"uairship://whatever/?foo=bar\")!\n            let command = JavaScriptCommand(url: URL)\n            XCTAssertNotNil(command, \"data should be non-nil\")\n            XCTAssertEqual(command.arguments.count, 0, \"data should have no arguments\")\n            XCTAssertEqual(command.options[\"foo\"], [\"bar\"], \"key 'foo' should have values 'bar'\")\n        }\n\n        func testCallDataForURLEncodedArguments() {\n            let URL = URL(string: \"uairship://run-action-cb/%5Eu/%22https%3A%2F%2Fdocs.urbanairship.com%2Fengage%2Frich-content-editor%2F%23rich-content-image%22/ua-cb-2?query%20argument=%5E\")!\n            let command = JavaScriptCommand(url: URL)\n\n            XCTAssertEqual(command.arguments.count, 3)\n            XCTAssertEqual(command.arguments[0], \"^u\")\n            XCTAssertEqual(command.arguments[1], \"\\\"https://docs.urbanairship.com/engage/rich-content-editor/#rich-content-image\\\"\")\n            XCTAssertEqual(command.arguments[2], \"ua-cb-2\")\n            XCTAssertEqual(command.options[\"query argument\"], [\"^\"])\n        }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/JsonMatcherTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class JsonMatcherTest: XCTestCase {\n\n    var subject = JSONValueMatcher.matcherWhereStringEquals(\"cool\")\n\n    func testMatcherOnly() throws {\n        let matcher = JSONMatcher(valueMatcher: subject)\n        XCTAssertNotNil(matcher)\n\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap(\"cool\")))\n\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(true)))\n    }\n\n    func testMatcherOnlyIgnoreCase() throws {\n        let matcher = JSONMatcher(valueMatcher: subject, ignoreCase: true)\n\n        XCTAssertNotNil(matcher)\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap(\"cool\")))\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap(\"COOL\")))\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap(\"CooL\")))\n\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"NOT COOL\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"cool\"])))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(true)))\n    }\n\n    func testMatcherOnlyPayload() throws {\n        let json = \"\"\"\n        {\n            \"value\": {\n                \"equals\": \"cool\"\n            }\n        }\n        \"\"\"\n        let matcher = JSONMatcher(valueMatcher: subject)\n\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.wrap(matcher))\n\n        let fromJSON: JSONMatcher = try AirshipJSON.from(json: json).decode()\n        XCTAssertEqual(matcher, fromJSON)\n    }\n\n    func testMatcherOnlyIgnoreCasePayload() throws {\n        let json = \"\"\"\n        {\n            \"value\": { \n                \"equals\": \"cool\"\n            },\n            \"ignore_case\": true    \n        }\n        \"\"\"\n\n\n        let matcher = JSONMatcher(valueMatcher: subject, ignoreCase: true)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.wrap(matcher))\n\n        // Verify a matcher created from the JSON matches\n        var fromJsonMatcher: JSONMatcher = try AirshipJSON.from(json: json).decode()\n        XCTAssertNotNil(fromJsonMatcher)\n        XCTAssertEqual(fromJsonMatcher, matcher)\n\n        // Verify a matcher created from the JSON from the first matcher matches\n        fromJsonMatcher = try AirshipJSON.wrap(matcher).decode()\n        XCTAssertNotNil(fromJsonMatcher)\n        XCTAssertEqual(fromJsonMatcher, matcher)\n    }\n\n    func testMatcherOnlyPayloadWithUnknownKey() throws {\n        let json = \"\"\"\n        {\n            \"value\": { \n                \"equals\": \"cool\"\n            },\n            \"unknown\": true    \n        }\n        \"\"\"\n\n        let matcher = JSONMatcher(valueMatcher: subject)\n        XCTAssertNotNil(matcher)\n\n        // Verify a matcher created from the JSON matches\n        var fromJsonMatcher: JSONMatcher = try AirshipJSON.from(json: json).decode()\n        XCTAssertNotNil(fromJsonMatcher)\n        XCTAssertEqual(fromJsonMatcher, matcher)\n\n        // Verify a matcher created from the JSON from the first matcher matches\n        fromJsonMatcher = try AirshipJSON.wrap(matcher).decode()\n        XCTAssertNotNil(fromJsonMatcher)\n        XCTAssertEqual(fromJsonMatcher, matcher)\n    }\n\n    func testMatcherWithKey() throws {\n        let matcher = JSONMatcher(valueMatcher: subject, scope: [\"property\"])\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"cool\"])))\n\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"property\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"not cool\"])))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(true)))\n    }\n\n    func testMatcherWithScopeIgnoreCase() throws {\n        let matcher = JSONMatcher(valueMatcher: subject, scope: [\"property\"], ignoreCase: true)\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"cool\"])))\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"COOL\"])))\n        XCTAssertTrue(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"CooL\"])))\n\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"property\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"not cool\"])))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap([\"property\": \"NOT COOL\"])))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try! AirshipJSON.wrap(true)))\n    }\n\n    func testScopeAsString() throws {\n        let json = \"\"\"\n        {\n            \"value\": { \n                \"equals\": \"cool\"\n            },\n            \"key\": \"subproperty\",\n            \"scope\": [\"property\"]  \n        }\n        \"\"\"\n\n        let fromJSON: JSONMatcher = try AirshipJSON.from(json: json).decode()\n\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.wrap(fromJSON))\n    }\n\n    func testInvalidKey() {\n        // Invalid key value\n        let json = \"\"\"\n        {\n            \"value\": { \"equals\": \"cool\" },\n            \"key\": 123,\n            \"scope\": [\"property\"]\n        }\n        \"\"\"\n\n        do {\n            let _: JSONMatcher = try AirshipJSON.from(json: json).decode()\n            XCTFail()\n        } catch {}\n    }\n\n    func testInvalidPayload() {\n        let json = \"\"\"\n        {\n            \"not\": \"cool\"\n        }\n        \"\"\"\n\n        do {\n            let _: JSONMatcher = try AirshipJSON.from(json: json).decode()\n            XCTFail()\n        } catch {}\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/JsonValueMatcherTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class JsonValueMatcherTest: XCTestCase {\n\n    func testEqualsString() throws {\n        let matcher = JSONValueMatcher.matcherWhereStringEquals(\"cool\")\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\"), ignoreCase:false))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\"), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"COOL\"), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"CooL\"), ignoreCase:true))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"COOL\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"CooL\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"NOT COOL\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true)))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil), ignoreCase: false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"COOL\"), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"CooL\"), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"NOT COOL\"), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\"), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(1), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"NOT COOL\"), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\"), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(1), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true), ignoreCase:true))\n    }\n\n    func testEqualsStringPayload() throws {\n        let json = \"\"\"\n        {\n            \"equals\": \"cool\"\n        }\n        \"\"\"\n        let matcher = JSONValueMatcher.matcherWhereStringEquals(\"cool\")\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testEqualsBoolean() throws {\n        let matcher = JSONValueMatcher.matcherWhereBooleanEquals(false)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(false)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(false), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(false), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true), ignoreCase:false))\n    }\n\n    func testEqualsBooleanPayload() throws {\n        let json = \"\"\"\n        {\n            \"equals\": true\n        }\n        \"\"\"\n\n        let matcher = JSONValueMatcher.matcherWhereBooleanEquals(true)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testEqualsNumber() throws {\n        let matcher = JSONValueMatcher.matcherWhereNumberEquals(to: 123.35)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.35)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.350)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.350), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.350), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.3)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.3), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.3), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testEqualsNumberPayload() throws {\n        let json = \"\"\"\n        {\n            \"equals\": 123.456\n        }\n        \"\"\"\n        let match = JSONValueMatcher.matcherWhereNumberEquals(to: 123.456)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(match, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testAtLeast() throws {\n        let matcher = JSONValueMatcher.matcherWhereNumberAtLeast(123.35)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.35)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.36)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.36), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.36), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.3)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.3), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.3), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(true)))\n    }\n\n    func testAtLeastPayload() throws {\n        let json = \"\"\"\n        {\n            \"at_least\": 100\n        }\n        \"\"\"\n\n        let matcher = JSONValueMatcher.matcherWhereNumberAtLeast(100)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testAtMost() throws {\n        let matcher = JSONValueMatcher.matcherWhereNumberAtMost(123.35)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.35)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.34)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.34), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.34), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.36)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.36), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(123.36), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(124)))\n    }\n\n    func testAtMostPayload() throws {\n        let json = \"\"\"\n        {\n            \"at_most\": 100\n        }\n        \"\"\"\n\n        let matcher = JSONValueMatcher.matcherWhereNumberAtMost(100)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testAtLeastAtMost() throws {\n        let matcher = JSONValueMatcher.matcherWhereNumberAtLeast(100, atMost: 150)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(100)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(150)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.456)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.456), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(123.456), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"not cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(99)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(151)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(151), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(151), ignoreCase:false))\n    }\n\n    func testAtLeastAtMostPayload() throws {\n        let json = \"\"\"\n        {\n            \"at_least\": 1,\n            \"at_most\": 100\n        }\n        \"\"\"\n        let matcher = JSONValueMatcher.matcherWhereNumberAtLeast(1, atMost: 100)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testPresence() throws {\n        let matcher = JSONValueMatcher.matcherWhereValueIsPresent(true)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(100)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\"), ignoreCase:true))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil), ignoreCase:false))\n    }\n\n    func testPresencePayload() throws {\n        let json = \"\"\"\n        {\n            \"is_present\": true\n        }\n        \"\"\"\n        let matcher = JSONValueMatcher.matcherWhereValueIsPresent(true)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testAbsence() throws {\n        let matcher = JSONValueMatcher.matcherWhereValueIsPresent(false)\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(nil), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(nil), ignoreCase:false))\n\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(100)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(matcher)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"cool\"), ignoreCase:true))\n    }\n\n    func testAbsencePayload() throws {\n        let json = \"\"\"\n        {\n            \"is_present\": false\n        }\n        \"\"\"\n        let matcher = JSONValueMatcher.matcherWhereValueIsPresent(false)\n\n        // Verify the JSONValue recreates the expected matcher\n        XCTAssertEqual(matcher, try AirshipJSON.from(json: json).decode())\n    }\n\n    func testVersionRangeConstraints() throws {\n        var matcher = JSONValueMatcher.matcherWithVersionConstraint(\"1.0\")!\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\"), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\" 2.0 \")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\" 2.0 \"), ignoreCase:true))\n\n        matcher = JSONValueMatcher.matcherWithVersionConstraint(\"1.0+\")!\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\"), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"2\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"2\"), ignoreCase:true))\n\n        matcher = JSONValueMatcher.matcherWithVersionConstraint(\"[1.0,2.0]\")!\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\"), ignoreCase:true))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"2.0.0\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"2.0.1\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"2.0.1\"), ignoreCase:true))\n    }\n\n    func testArrayContains() throws {\n        let valueMatcher = JSONValueMatcher.matcherWhereStringEquals(\"bingo\")\n        var jsonMatcher = JSONMatcher(valueMatcher: valueMatcher)\n        var predicate = JSONPredicate(jsonMatcher: jsonMatcher)\n        var matcher = JSONValueMatcher.matcherWithArrayContainsPredicate(predicate)!\n\n        // Invalid values\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap([\"bingo\": \"what\"])))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap([\"BINGO\": \"what\"]), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n\n        var value = [\"thats\", \"a\", \"BINGO\"]\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n        value = [\"thats\", \"a\"]\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        value = []\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n\n        // Valid values\n        value = [\"thats\", \"a\", \"bingo\"]\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n\n        // ignore case\n        jsonMatcher = JSONMatcher(valueMatcher: valueMatcher, ignoreCase: true)\n        predicate = JSONPredicate(jsonMatcher: jsonMatcher)\n        matcher = JSONValueMatcher.matcherWithArrayContainsPredicate(predicate)!\n\n        value = [\"thats\", \"a\", \"BINGO\"]\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n    }\n\n    func testArrayContainsAtIndex() throws {\n        let valueMatcher = JSONValueMatcher.matcherWhereStringEquals(\"bingo\")\n        var jsonMatcher = JSONMatcher(valueMatcher: valueMatcher)\n        var predicate = JSONPredicate(jsonMatcher: jsonMatcher)\n        var matcher = JSONValueMatcher.matcherWithArrayContainsPredicate(predicate, at: 1)!\n\n        // Invalid values\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"1.0\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap([\"bingo\": \"what\"])))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap([\"bingo\": \"what\"]), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(1)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(nil)))\n\n        var value = [\"thats\", \"a\", \"BINGO\"]\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n\n        value = [\"thats\", \"a\"]\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n\n        value = []\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n\n        value = [\"thats\", \"BINGO\", \"a\"]\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n\n        // Valid values\n        value = [\"thats\", \"bingo\", \"a\"]\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n\n        value = [\"thats\", \"bingo\"]\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n\n        value = [\"a\", \"bingo\"]\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n\n        // ignore case\n        jsonMatcher = JSONMatcher(valueMatcher: valueMatcher, ignoreCase: true)\n        predicate = JSONPredicate(jsonMatcher: jsonMatcher)\n        matcher = JSONValueMatcher.matcherWithArrayContainsPredicate(predicate, at: 1)!\n\n        value = [\"thats\", \"a\", \"BINGO\"]\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n\n        value = [\"thats\", \"BINGO\", \"a\"]\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value)))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:false))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(value), ignoreCase:true))\n    }\n\n    func testVersionMatcher() throws {\n        let jsonV9 = \"\"\"\n        {\n            \"version_matches\": \"9.9\"\n        }\n        \"\"\"\n\n        let jsonV8 = \"\"\"\n        {\n            \"version_matches\": \"8.9\"\n        }\n        \"\"\"\n\n        var matcher: JSONValueMatcher = try AirshipJSON.from(json: jsonV9).decode()\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"9.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"9.9\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"9.9\"), ignoreCase:true))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"10.0\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"10.0\"), ignoreCase:true))\n\n        matcher = try AirshipJSON.from(json: jsonV8).decode()\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"8.0\")))\n        XCTAssertTrue(matcher.evaluate(json: try AirshipJSON.wrap(\"8.9\")))\n        XCTAssertFalse(matcher.evaluate(json: try AirshipJSON.wrap(\"9.0\")))\n    }\n\n    func testInvalidPayload() {\n        let invalid = \"\"\"\n        {\n            \"cool\": \"neat\"\n        }\n        \"\"\"\n\n        // Invalid object\n        do {\n            let _: JSONValueMatcher = try AirshipJSON.from(json: invalid).decode()\n            XCTFail()\n        } catch {\n\n        }\n    }\n\n    func testStringBeginsMatcherParsing() throws {\n        let json = \"\"\"\n        {\n            \"string_begins\": \"neat\"\n        }\n        \"\"\"\n\n        let fromJSON: JSONValueMatcher = try AirshipJSON.from(json: json).decode()\n        let expected = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringBeginsPredicate(stringBegins: \"neat\")\n        )\n        XCTAssertEqual(fromJSON, expected)\n    }\n\n    func testStringEndsMatcherParsing() throws {\n        let json = \"\"\"\n        {\n            \"string_ends\": \"neat\"\n        }\n        \"\"\"\n\n        let fromJSON: JSONValueMatcher = try AirshipJSON.from(json: json).decode()\n        let expected = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringEndsPredicate(stringEnds: \"neat\")\n        )\n        XCTAssertEqual(fromJSON, expected)\n    }\n\n    func testStringContainsMatcherParsing() throws {\n        let json = \"\"\"\n        {\n            \"string_contains\": \"neat\"\n        }\n        \"\"\"\n\n        let fromJSON: JSONValueMatcher = try AirshipJSON.from(json: json).decode()\n        let expected = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringContainsPredicate(stringContains: \"neat\")\n        )\n        XCTAssertEqual(fromJSON, expected)\n    }\n\n    func testStringBeginsMatcher() throws {\n        let matcher = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringBeginsPredicate(stringBegins: \"foo\")\n        )\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"foobar\")))\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"FOOBAR\"), ignoreCase: true))\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"FOOBAR\")))\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"barfoo\")))\n    }\n\n    func testStringEndsMatcher() throws {\n        let matcher = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringEndsPredicate(stringEnds: \"bar\")\n        )\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"foobar\")))\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"FOOBAR\"), ignoreCase: true))\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"FOOBAR\")))\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"barfoo\")))\n    }\n\n    func testStringContainsMatcher() throws {\n        let matcher = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringContainsPredicate(stringContains: \"oob\")\n        )\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"foobar\")))\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"FOOBAR\"), ignoreCase: true))\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"FOOBAR\")))\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"barfoo\")))\n    }\n\n    func testStringEndsMatcherEdgeCase() throws {\n        let matcher = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringEndsPredicate(stringEnds: \"i\")\n        )\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"fooİ\")))\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"fooİ\"), ignoreCase: true))\n    }\n\n    func testStringBeginsMatcherEdgeCase() throws {\n        let matcher = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringBeginsPredicate(stringBegins: \"i\")\n        )\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"İfoo\")))\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"İfoo\"), ignoreCase: true))\n    }\n\n    func testStringContainsMatcherEdgeCase() throws {\n        let matcher = JSONValueMatcher(\n            predicate: JSONValueMatcher.StringContainsPredicate(stringContains: \"i\")\n        )\n        XCTAssertFalse(matcher.evaluate(json: AirshipJSON.string(\"fooİẞar\")))\n        XCTAssertTrue(matcher.evaluate(json: AirshipJSON.string(\"FOOİẞAR\"), ignoreCase: true))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/LayoutModelsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass LayoutModelsTest: XCTestCase {\n\n    func testSize() throws {\n        let json = \"\"\"\n            {\n                \"presentation\": {\n                    \"type\": \"modal\",\n                    \"default_placement\": {\n                        \"size\": {\n                            \"width\": \"60%\",\n                            \"height\": \"60%\"\n                        },\n                        \"placement\": {\n                            \"horizontal\": \"center\",\n                            \"vertical\": \"center\"\n                        }\n                    }\n                },\n                \"version\": 1,\n                \"view\": {\n                  \"type\": \"container\",\n                  \"items\": [\n                    {\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                      },\n                      \"size\": {\n                        \"height\": \"auto\",\n                        \"width\": \"75%\"\n                      },\n                      \"view\": {\n                        \"type\": \"empty_view\"\n                      }\n                    }\n                  ]\n                }\n            }\n            \"\"\"\n\n        let layout = try! self.decode(json.data(using: .utf8)!)\n        guard case .container(let container) = layout.view else {\n            XCTFail()\n            return\n        }\n\n        let size = container.properties.items.first?.size\n\n        XCTAssertEqual(ThomasSizeConstraint.auto, size?.height)\n        XCTAssertEqual(ThomasSizeConstraint.percent(75), size?.width)\n    }\n\n    func testComplexExample() throws {\n        let json = \"\"\"\n            {\n                \"presentation\": {\n                    \"type\": \"modal\",\n                    \"default_placement\": {\n                        \"size\": {\n                            \"width\": \"60%\",\n                            \"height\": \"60%\"\n                        },\n                        \"placement\": {\n                            \"horizontal\": \"center\",\n                            \"vertical\": \"center\"\n                        }\n                    }\n                },\n                \"version\": 1,\n                \"view\": {\n                  \"type\": \"container\",\n                  \"items\": [\n                    {\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                      },\n                      \"size\": {\n                        \"height\": \"100%\",\n                        \"width\": \"100%\"\n                      },\n                      \"view\": {\n                        \"type\": \"linear_layout\",\n                        \"direction\": \"vertical\",\n                        \"items\": [\n                          {\n                            \"position\": {\n                              \"horizontal\": \"center\",\n                              \"vertical\": \"center\"\n                            },\n                            \"margin\": {\n                              \"top\": 0,\n                              \"bottom\": 0,\n                              \"start\": 16,\n                              \"end\": 16\n                            },\n                            \"size\": {\n                              \"width\": \"100%\",\n                              \"height\": \"auto\"\n                            },\n                            \"view\": {\n                              \"type\": \"label_button\",\n                              \"identifier\": \"BUTTON\",\n                              \"background_color\": { \"default\": { \"hex\": \"#FF00FF\" } },\n                              \"label\": {\n                                \"type\": \"label\",\n                                \"text_appearance\": {\n                                    \"font_size\": 24,\n                                    \"alignment\": \"center\",\n                                    \"text_styles\": [\n                                      \"bold\",\n                                      \"italic\",\n                                      \"underlined\"\n                                    ],\n                                    \"font_families\": [\n                                      \"permanent_marker\"\n                                    ],\n                                    \"color\": { \"default\": { \"hex\": \"#FF00FF\"} }\n                                },\n                                \"text\": \"NO\"\n                              }\n                            }\n                          }\n                        ]\n                      }\n                    }\n                  ]\n                }\n            }\n            \"\"\"\n\n        let layout = try self.decode(json.data(using: .utf8)!)\n        XCTAssertNotNil(layout)\n    }\n\n    private func decode(_ data: Data) throws -> AirshipLayout {\n        try JSONDecoder().decode(AirshipLayout.self, from: data)\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/LiveActivityRegistryTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class LiveActivityRegistryTest: XCTestCase {\n\n    let date: UATestDate = UATestDate()\n    let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    var registry: LiveActivityRegistry!\n    var tracker = TestPushToStartTracker()\n\n    override func setUpWithError() throws {\n        self.date.dateOverride = Date(timeIntervalSince1970: 0)\n\n        self.registry = LiveActivityRegistry(\n            dataStore: self.dataStore,\n            date: self.date\n        )\n    }\n\n    func testAdd() async throws {\n        let activity = TestLiveActivity(\"foo id\")\n        await self.registry.addLiveActivity(activity, name: \"foo\")\n\n        self.date.offset += 1.0\n        activity.pushTokenString = \"foo token\"\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 1000, \n                token: \"foo token\"\n            )\n        )\n\n        self.date.offset += 1.0\n        activity.isUpdatable = false\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 2000\n            )\n        )\n    }\n\n    func testReplace() async throws {\n        let activityFirst = TestLiveActivity(\"first id\")\n        activityFirst.pushTokenString = \"first token\"\n\n        await self.registry.addLiveActivity(activityFirst, name: \"foo\")\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"first id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 0,\n                token: \"first token\"\n            )\n        )\n\n        let activitySecond = TestLiveActivity(\"second id\")\n        await self.registry.addLiveActivity(activitySecond, name: \"foo\")\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"first id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 0\n            )\n        )\n    }\n\n    func testRestore() async throws {\n        var activity = TestLiveActivity(\"foo id\")\n        await self.registry.addLiveActivity(activity, name: \"foo\")\n\n        // Recreate it\n        self.registry = LiveActivityRegistry(\n            dataStore: self.dataStore,\n            date: self.date\n        )\n        activity = TestLiveActivity(\"foo id\")\n\n        await self.registry.restoreTracking(activities: [activity], startTokenTrackers: [])\n\n        activity.pushTokenString = \"neat\"\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 0,\n                token: \"neat\"\n            )\n        )\n    }\n    \n    func testRestoreEmitsStartTokenEvent() async throws {\n        tracker.token = \"activity-token\"\n        \n        await self.registry.restoreTracking(activities: [], startTokenTrackers: [tracker])\n\n        await assertUpdate(LiveActivityUpdate(\n            action: .set,\n            source: .startToken(attributeType: \"TestPushToStartTracker\"),\n            actionTimeMS: 0,\n            token: \"activity-token\"\n        ))\n\n        // Recreate it\n        self.registry = LiveActivityRegistry(\n            dataStore: self.dataStore,\n            date: self.date\n        )\n\n        await self.registry.restoreTracking(activities: [], startTokenTrackers: [])\n\n        await assertUpdate(LiveActivityUpdate(\n            action: .remove,\n            source: .startToken(attributeType: \"TestPushToStartTracker\"),\n            actionTimeMS: 0\n        ))\n    }\n    \n    func testRestoreResendsStaleTokens() async throws {\n        tracker.token = \"activity-token\"\n\n        await self.registry.restoreTracking(activities: [], startTokenTrackers: [tracker])\n\n        await assertUpdate(LiveActivityUpdate(\n            action: .set,\n            source: .startToken(attributeType: \"TestPushToStartTracker\"),\n            actionTimeMS: 0,\n            token: \"activity-token\"\n        ))\n\n        self.date.offset = 172800 + 2\n\n        // Recreate it\n        self.registry = LiveActivityRegistry(\n            dataStore: self.dataStore,\n            date: self.date\n        )\n\n        await self.registry.restoreTracking(activities: [], startTokenTrackers: [tracker])\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .set,\n                source: .startToken(attributeType: \"TestPushToStartTracker\"),\n                actionTimeMS: 172802000,\n                token: \"activity-token\"\n            )\n        )\n    }\n\n    func testCleareUntracked() async throws {\n        let activity = TestLiveActivity(\"foo id\")\n        activity.pushTokenString = \"neat\"\n        await self.registry.addLiveActivity(activity, name: \"foo\")\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 0,\n                token: \"neat\"\n            )\n        )\n\n        // Recreate it\n        self.registry = LiveActivityRegistry(\n            dataStore: self.dataStore,\n            date: self.date\n        )\n\n        self.date.offset += 3\n        await self.registry.restoreTracking(activities: [], startTokenTrackers: [])\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 3000\n            )\n        )\n    }\n\n    func testCleareUntrackedMaxActionTime() async throws {\n        let activity = TestLiveActivity(\"foo id\")\n        activity.pushTokenString = \"neat\"\n        await self.registry.addLiveActivity(activity, name: \"foo\")\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .set,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 0,\n                token: \"neat\"\n            )\n        )\n\n        // Recreate it\n        self.registry = LiveActivityRegistry(\n            dataStore: self.dataStore,\n            date: self.date\n        )\n\n        self.date.offset += 28800.1  // 8 hours and .1 second\n        await self.registry.restoreTracking(activities: [], startTokenTrackers: [])\n\n        await assertUpdate(\n            LiveActivityUpdate(\n                action: .remove,\n                source: .liveActivity(id: \"foo id\", name: \"foo\", startTimeMS: 0),\n                actionTimeMS: 2_880_0000  // 8 hours\n            )\n        )\n    }\n\n    @available(iOS 16.1, *)\n    public func testRegistrationStatusByID() async {\n        // notTracked\n        var updates = registry.registrationUpdates(name: nil, id: \"some-id\").makeAsyncIterator()\n        var status = await updates.next()\n        XCTAssertEqual(status, .notTracked)\n\n        let activity = TestLiveActivity(\"some-id\")\n        await self.registry.addLiveActivity(activity, name: \"some-name\")\n\n        // pending\n        status = await updates.next()\n        XCTAssertEqual(status, .pending)\n\n        await self.registry.updatesProcessed(\n            updates: [\n                LiveActivityUpdate(\n                    action: .set,\n                    source: .liveActivity(id: \"some-id\", name: \"some-name\", startTimeMS: 100),\n                    actionTimeMS: 100\n                )\n            ]\n        )\n\n        // registered\n        status = await updates.next()\n        XCTAssertEqual(status, .registered)\n\n        // Register an activity over it\n        let otherActivity = TestLiveActivity(\"some-other-id\")\n        await self.registry.addLiveActivity(otherActivity, name: \"some-name\")\n\n        // notTracked since its by ID and has been replaced\n        status = await updates.next()\n        XCTAssertEqual(status, .notTracked)\n    }\n\n    @available(iOS 16.1, *)\n    public func testRegistrationStatusByName() async {\n        // notTracked\n        var updates = registry.registrationUpdates(name: \"some-name\", id: nil).makeAsyncIterator()\n        var status = await updates.next()\n        XCTAssertEqual(status, .notTracked)\n\n        let activity = TestLiveActivity(\"some-id\")\n        await self.registry.addLiveActivity(activity, name: \"some-name\")\n\n        // pending\n        status = await updates.next()\n        XCTAssertEqual(status, .pending)\n\n        await self.registry.updatesProcessed(\n            updates: [\n                LiveActivityUpdate(\n                    action: .set,\n                    source: .liveActivity(id: \"some-id\", name: \"some-name\", startTimeMS: 100),\n                    actionTimeMS: 100\n                )\n            ]\n        )\n\n        // registered\n        status = await updates.next()\n        XCTAssertEqual(status, .registered)\n\n        let otherActivity = TestLiveActivity(\"some-other-id\")\n        await self.registry.addLiveActivity(otherActivity, name: \"some-name\")\n\n        // pending since its by name\n        status = await updates.next()\n        XCTAssertEqual(status, .pending)\n    }\n\n    @available(iOS 16.1, *)\n    public func testRegistrationStatus() async {\n        // Not tracked\n        var updates = registry.registrationUpdates(name: \"some-name\", id: nil).makeAsyncIterator()\n        var status = await updates.next()\n        XCTAssertEqual(status, .notTracked)\n\n        let activity = TestLiveActivity(\"some-id\")\n        await self.registry.addLiveActivity(activity, name: \"some-name\")\n\n        // pending\n        status = await updates.next()\n        XCTAssertEqual(status, .pending)\n\n        await self.registry.updatesProcessed(\n            updates: [\n                LiveActivityUpdate(\n                    action: .set,\n                    source: .liveActivity(id: \"some-id\", name: \"some-name\", startTimeMS: 100),\n                    actionTimeMS: 100\n                )\n            ]\n        )\n\n        // registered\n        status = await updates.next()\n        XCTAssertEqual(status, .registered)\n    }\n\n\n    @available(iOS 16.1, *)\n    public func testStatusPending() async {\n        let activity = TestLiveActivity(\"foo id\")\n        await self.registry.addLiveActivity(activity, name: \"foo\")\n\n        var updates = registry.registrationUpdates(name: \"foo\", id: nil).makeAsyncIterator()\n        let status = await updates.next()\n        XCTAssertEqual(status, .pending)\n    }\n    \n    func testLiveUpdateV1Restoring() throws {\n        let payload: [String: Any] = [\n            \"id\": \"test-id\",\n            \"action\": \"set\",\n            \"name\": \"update-name\",\n            \"token\": \"some token\",\n            \"action_ts_ms\": 123,\n            \"start_ts_ms\": 100\n        ]\n        \n        let updateToken = try decode(payload)\n\n        let expected = LiveActivityUpdate(\n            action: .set,\n            source: .liveActivity(\n                id: \"test-id\",\n                name: \"update-name\",\n                startTimeMS: 100\n            ),\n            actionTimeMS: 123,\n            token: \"some token\"\n        )\n\n        XCTAssertEqual(updateToken, expected)\n    }\n    \n    func testLiveUpdateV2RestoringUpdateToken() throws {\n        let payload: [String: Any] = [\n            \"id\": \"test-id\",\n            \"action\": \"set\",\n            \"name\": \"update-name\",\n            \"token\": \"some token\",\n            \"action_ts_ms\": 123,\n            \"start_ts_ms\": 100,\n            \"type\": \"update_token\"\n        ]\n\n        let updateToken = try decode(payload)\n        let expected = LiveActivityUpdate(\n            action: .set,\n            source: .liveActivity(\n                id: \"test-id\",\n                name: \"update-name\",\n                startTimeMS: 100\n            ),\n            actionTimeMS: 123,\n            token: \"some token\"\n        )\n\n        XCTAssertEqual(updateToken, expected)\n    }\n\n    func testLiveUpdateV2RestoringStartToken() throws {\n        let payload: [String: Any] = [\n            \"action\": \"set\",\n            \"token\": \"some token\",\n            \"action_ts_ms\": 123,\n            \"attributes_type\": \"test-attribute types\",\n            \"type\": \"start_token\"\n        ]\n\n        let startToken = try decode(payload)\n\n        let expected = LiveActivityUpdate(\n            action: .set,\n            source: .startToken(attributeType: \"test-attribute types\"),\n            actionTimeMS: 123,\n            token: \"some token\"\n        )\n\n        XCTAssertEqual(startToken, expected)\n    }\n    \n    private func decode(_ dict: [String: Any]) throws -> LiveActivityUpdate {\n        let data = try JSONSerialization.data(withJSONObject: dict)\n        return try JSONDecoder().decode(LiveActivityUpdate.self, from: data)\n    }\n\n    private func assertUpdate(\n        _ update: LiveActivityUpdate,\n        file: StaticString = #filePath,\n        line: UInt = #line\n    ) async {\n        let next = await self.registry.updates.first(where: { _ in true })\n        XCTAssertEqual(update, next, file: file, line: line)\n    }\n}\n\n/// Tried to match as closely as I coudl to the real object\nprivate final class TestLiveActivity: LiveActivityProtocol, @unchecked Sendable {\n    let id: String\n    var isUpdatable: Bool = true {\n        didSet {\n            statusUpdatesContinuation.yield(isUpdatable)\n        }\n    }\n    var pushTokenString: String? {\n        didSet {\n            pushTokenUpdatesContinuation.yield(pushTokenString ?? \"\")\n        }\n    }\n\n    private let pushTokenUpdates: AsyncStream<String>\n    private let pushTokenUpdatesContinuation: AsyncStream<String>.Continuation\n    private let statusUpdates: AsyncStream<Bool>\n    private let statusUpdatesContinuation: AsyncStream<Bool>.Continuation\n\n    init(_ id: String) {\n        self.id = id\n\n        var pushTokenUpdatesEscapee: AsyncStream<String>.Continuation? = nil\n        self.pushTokenUpdates = AsyncStream { continuation in\n            pushTokenUpdatesEscapee = continuation\n        }\n        self.pushTokenUpdatesContinuation = pushTokenUpdatesEscapee!\n\n        var statusUpdateEscapee: AsyncStream<Bool>.Continuation? = nil\n        self.statusUpdates = AsyncStream { continuation in\n            statusUpdateEscapee = continuation\n        }\n        self.statusUpdatesContinuation = statusUpdateEscapee!\n    }\n\n    func track(tokenUpdates: @Sendable @escaping (String) async -> Void) async {\n        guard self.isUpdatable else {\n            return\n        }\n\n        let task = Task {\n            for await token in self.pushTokenUpdates {\n                try Task.checkCancellation()\n                await tokenUpdates(token)\n            }\n        }\n\n        if let token = self.pushTokenString {\n            await tokenUpdates(token)\n        }\n\n        for await update in self.statusUpdates {\n            if !update || Task.isCancelled {\n                task.cancel()\n                break\n            }\n        }\n    }\n}\n\nfinal class TestPushToStartTracker: LiveActivityPushToStartTrackerProtocol, @unchecked Sendable {\n    var attributeType: String { return String(describing: Self.self) }\n    \n    var token: String?\n    \n    func track(tokenUpdates: @escaping @Sendable (String) async -> Void) async {\n        guard let token = self.token else { return }\n        await tokenUpdates(token)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/MediaEventTemplateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class MediaEventTemplateTest: XCTestCase {\n\n    func testBrowsed() {\n        let event = CustomEvent(mediaTemplate: .browsed)\n        XCTAssertEqual(\"browsed_content\", event.eventName)\n        XCTAssertEqual(\"media\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testConsumed() {\n        let event = CustomEvent(mediaTemplate: .consumed)\n        XCTAssertEqual(\"consumed_content\", event.eventName)\n        XCTAssertEqual(\"media\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testShared() {\n        let event = CustomEvent(mediaTemplate: .shared(source: \"some source\", medium: \"some medium\"))\n        XCTAssertEqual(\"shared_content\", event.eventName)\n        XCTAssertEqual(\"media\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"ltv\": false,\n            \"source\": \"some source\",\n            \"medium\": \"some medium\"\n        ]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testSharedEmptyDetails() {\n        let event = CustomEvent(mediaTemplate: .shared())\n        XCTAssertEqual(\"shared_content\", event.eventName)\n        XCTAssertEqual(\"media\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testStarred() {\n        let event = CustomEvent(mediaTemplate: .starred)\n        XCTAssertEqual(\"starred_content\", event.eventName)\n        XCTAssertEqual(\"media\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testProperties() {\n        let date = Date.now\n        let properties = CustomEvent.MediaProperties(\n            id: \"some id\",\n            category: \"some category\",\n            type: \"some type\",\n            eventDescription: \"some description\",\n            isLTV: true,\n            author: \"some author\",\n            publishedDate: date,\n            isFeature: true\n        )\n\n        let event = CustomEvent(\n            mediaTemplate: .shared(source: \"some source\", medium: \"some medium\"),\n            properties: properties\n        )\n        XCTAssertEqual(\"shared_content\", event.eventName)\n        XCTAssertEqual(\"media\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"id\": \"some id\",\n            \"category\": \"some category\",\n            \"type\": \"some type\",\n            \"description\": \"some description\",\n            \"ltv\": true,\n            \"author\": \"some author\",\n            \"published_date\": try! AirshipJSON.wrap(date, encoder: CustomEvent.defaultEncoder()),\n            \"feature\": true,\n            \"source\": \"some source\",\n            \"medium\": \"some medium\"\n        ]\n\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/MeteredUsageApiClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class MeteredUsageApiClientTest: XCTestCase {\n    \n    private let requestSession = TestAirshipRequestSession()\n    private let configDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var target: MeteredUsageAPIClient!\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n\n    @MainActor\n    override func setUp() async throws {\n        self.config.updateRemoteConfig(\n            RemoteConfig(\n                airshipConfig: RemoteConfig.AirshipConfig(\n                    remoteDataURL: \"test://remoteUrl\",\n                    deviceAPIURL: \"test://device\",\n                    analyticsURL: \"test://analytics\",\n                    meteredUsageURL: \"test://meteredUsage\"\n                )\n            )\n        )\n\n        target = MeteredUsageAPIClient(config: config, session: requestSession)\n    }\n\n    func testUploadEventsNoConfig() async throws {\n        await self.config.updateRemoteConfig(RemoteConfig())\n        let timestamp = Date()\n\n        let events = [\n            AirshipMeteredUsageEvent(\n                eventID: \"event.1\",\n                entityID: \"message.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"message\",\n                reportingContext: try! AirshipJSON.wrap(\"event.1\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-1\"\n            ),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.2\",\n                entityID: \"landing-page.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"landingpage\",\n                reportingContext: try! AirshipJSON.wrap(\"event.2\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-2\"\n            ),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.3\",\n                entityID: \"scene.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Scene\",\n                reportingContext: try! AirshipJSON.wrap(\"event.3\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-3\"\n            ),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.4\",\n                entityID: \"survey.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Survey\",\n                reportingContext: try! AirshipJSON.wrap(\"event.4\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-4\"\n            )\n        ]\n\n        requestSession.response = HTTPURLResponse(\n            url: URL(string: \"test://repose.url\")!,\n            statusCode: 200,\n            httpVersion: \"1\",\n            headerFields: nil)\n\n        await self.config.updateRemoteConfig(RemoteConfig())\n        do {\n            let _ = try await target.uploadEvents(events, channelID: \"test.channel.id\")\n            XCTFail(\"Should throw\")\n        } catch {\n        }\n    }\n\n    func testUploadEvents() async throws {\n        let timestamp = Date()\n\n        let events = [\n            AirshipMeteredUsageEvent(\n                eventID: \"event.1\",\n                entityID: \"message.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"message\",\n                reportingContext: try! AirshipJSON.wrap(\"event.1\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-1\"\n            ),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.2\",\n                entityID: \"landing-page.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"landingpage\",\n                reportingContext: try! AirshipJSON.wrap(\"event.2\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-2\"\n            ),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.3\",\n                entityID: \"scene.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Scene\",\n                reportingContext: try! AirshipJSON.wrap(\"event.3\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-3\"\n            ),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.4\",\n                entityID: \"survey.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Survey\",\n                reportingContext: try! AirshipJSON.wrap(\"event.4\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-4\"\n            )\n        ]\n\n        requestSession.response = HTTPURLResponse(\n            url: URL(string: \"test://repose.url\")!,\n            statusCode: 200,\n            httpVersion: \"1\",\n            headerFields: nil)\n\n        let _ = try await target.uploadEvents(events, channelID: \"test.channel.id\")\n\n        let request = requestSession.lastRequest\n        XCTAssertNotNil(request)\n        XCTAssertEqual(\"test://meteredUsage/api/metered-usage\", request?.url?.absoluteString)\n        XCTAssertEqual([\n            \"Content-Type\": \"application/json\",\n            \"X-UA-Lib-Version\": AirshipVersion.version,\n            \"X-UA-Device-Family\": \"ios\",\n            \"X-UA-Channel-ID\": \"test.channel.id\",\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ], request?.headers)\n        XCTAssertEqual(\"POST\", request?.method)\n\n        let body = request?.body\n        XCTAssertNotNil(body)\n\n        let decodedBody = try JSONSerialization.jsonObject(with: body!) as! [String : [[String: String]]]\n\n        let timestampString = AirshipDateFormatter.string(fromDate: timestamp, format: .isoDelimitter)\n\n        XCTAssertEqual([\n            [\n                \"entity_id\": \"message.id\",\n                \"event_id\": \"event.1\",\n                \"product\": \"message\",\n                \"occurred\": timestampString,\n                \"usage_type\": \"iax_impression\",\n                \"reporting_context\": \"event.1\",\n                \"contact_id\": \"contact-id-1\"\n            ],\n            [\n                \"entity_id\": \"landing-page.id\",\n                \"event_id\": \"event.2\",\n                \"product\": \"landingpage\",\n                \"occurred\": timestampString,\n                \"usage_type\": \"iax_impression\",\n                \"reporting_context\": \"event.2\",\n                \"contact_id\": \"contact-id-2\"\n            ],\n            [\n                \"entity_id\": \"scene.id\",\n                \"event_id\": \"event.3\",\n                \"product\": \"Scene\",\n                \"occurred\": timestampString,\n                \"usage_type\": \"iax_impression\",\n                \"reporting_context\": \"event.3\",\n                \"contact_id\": \"contact-id-3\"\n            ],\n            [\n                \"entity_id\": \"survey.id\",\n                \"event_id\": \"event.4\",\n                \"product\": \"Survey\",\n                \"occurred\": timestampString,\n                \"usage_type\": \"iax_impression\",\n                \"reporting_context\": \"event.4\",\n                \"contact_id\": \"contact-id-4\"\n            ]], decodedBody[\"usage\"])\n\n    }\n    \n    func testUploadStrippedEvents() async throws {\n        let timestamp = Date()\n        \n        let events = [\n            AirshipMeteredUsageEvent(\n                eventID: \"event.1\",\n                entityID: \"message.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"message\",\n                reportingContext: try! AirshipJSON.wrap(\"event.1\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-1\"\n            ).withDisabledAnalytics(),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.2\",\n                entityID: \"landing-page.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"landingpage\",\n                reportingContext: try! AirshipJSON.wrap(\"event.2\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-2\"\n            ).withDisabledAnalytics(),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.3\",\n                entityID: \"scene.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Scene\",\n                reportingContext: try! AirshipJSON.wrap(\"event.3\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-3\"\n            ).withDisabledAnalytics(),\n            AirshipMeteredUsageEvent(\n                eventID: \"event.4\",\n                entityID: \"survey.id\",\n                usageType: .inAppExperienceImpression,\n                product: \"Survey\",\n                reportingContext: try! AirshipJSON.wrap(\"event.4\"),\n                timestamp: timestamp,\n                contactID: \"contact-id-4\"\n            ).withDisabledAnalytics()\n        ]\n\n        requestSession.response = HTTPURLResponse(\n            url: URL(string: \"test://repose.url\")!,\n            statusCode: 200,\n            httpVersion: \"1\",\n            headerFields: nil)\n\n        let _ = try await target.uploadEvents(events, channelID: \"test.channel.id\")\n\n        let request = requestSession.lastRequest\n        XCTAssertNotNil(request)\n        XCTAssertEqual(\"test://meteredUsage/api/metered-usage\", request?.url?.absoluteString)\n        XCTAssertEqual([\n            \"Content-Type\": \"application/json\",\n            \"X-UA-Lib-Version\": AirshipVersion.version,\n            \"X-UA-Device-Family\": \"ios\",\n            \"X-UA-Channel-ID\": \"test.channel.id\",\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ], request?.headers)\n        XCTAssertEqual(\"POST\", request?.method)\n\n        let body = request?.body\n        XCTAssertNotNil(body)\n\n        let decodedBody = try JSONSerialization.jsonObject(with: body!) as! [String : [[String: String]]]\n        \n        XCTAssertEqual([\n            [\n                \"event_id\": \"event.1\",\n                \"product\": \"message\",\n                \"usage_type\": \"iax_impression\",\n            ],\n            [\n                \"event_id\": \"event.2\",\n                \"product\": \"landingpage\",\n                \"usage_type\": \"iax_impression\",\n            ],\n            [\n                \"event_id\": \"event.3\",\n                \"product\": \"Scene\",\n                \"usage_type\": \"iax_impression\",\n            ],\n            [\n                \"event_id\": \"event.4\",\n                \"product\": \"Survey\",\n                \"usage_type\": \"iax_impression\",\n            ]], decodedBody[\"usage\"])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ModifyAttributesActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ModifyAttributesActionTest: XCTestCase {\n\n    private let channel = TestChannel()\n    private let contact = TestContact()\n    private let push = TestPush()\n    private let date = UATestDate()\n    private var action: ModifyAttributesAction!\n\n    override func setUp() async throws {\n        date.dateOverride = Date()\n        action = ModifyAttributesAction(\n            channel: { [channel] in return channel },\n            contact: { [contact] in return contact }\n        )\n    }\n\n    func testAcceptsArguments() async throws {\n        let validValue = [\n            \"channel\": [\n                \"set\": [\"name\": \"clive\"],\n            ]\n        ]\n\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush\n        ]\n\n\n        for situation in validSituations {\n            let args = ActionArguments(value: try AirshipJSON.wrap(validValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in validSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(value: try AirshipJSON.wrap(validValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n    \n    func testAcceptReturnsFalseForInvalidJsonValue() async throws {\n        let jsons = [\n            [[\n                \"action\": \"set\",\n                \"type\": \"channel\",\n                \"name\": \"another name\",\n                \"value\": [\n                    \"json_test\": [\n                        \"exp\": 1012,\n                        \"nested\": [ \"foo\": \"bar\" ]\n                    ]\n                ]\n            ]],\n            [[\n                \"action\": \"set\",\n                \"type\": \"channel\",\n                \"name\": \"another name\",\n                \"value\": [\n                    \"json#te#st#\": [\n                        \"exp\": 1012,\n                        \"nested\": [ \"foo\": \"bar\" ]\n                    ]\n                ]\n            ]],\n            [[\n                \"action\": \"set\",\n                \"type\": \"channel\",\n                \"name\": \"another name\",\n                \"value\": [\n                    \"json_test#\": [\n                        \"exp\": 1012,\n                        \"nested\": [ \"foo\": \"bar\" ]\n                    ]\n                ]\n            ]]\n        ]\n        \n        for item in jsons {\n            let args = ActionArguments(value: try AirshipJSON.wrap(item), situation: .manualInvocation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testPerform() async throws {\n        let value: [String: Any] = [\n            \"channel\": [\n                \"set\": [\"name\": \"clive\"],\n                \"remove\": [\"zipcode\"]\n            ] as [String : Any],\n            \"named_user\": [\n                \"set\": [\"some other name\": \"owen\"],\n                \"remove\": [\"location\"]\n            ] as [String : Any]\n        ]\n\n        let expectedChannelAttributes = [\n            AttributeUpdate(\n                attribute: \"zipcode\",\n                type: .remove,\n                jsonValue: nil,\n                date: self.date.now\n            ),\n            AttributeUpdate(\n                attribute: \"name\",\n                type: .set,\n                jsonValue: \"clive\",\n                date: self.date.now\n            )\n        ]\n\n        let expectedContactAttributes = [\n            AttributeUpdate(\n                attribute: \"location\",\n                type: .remove,\n                jsonValue: nil,\n                date: self.date.now\n            ),\n            AttributeUpdate(\n                attribute: \"some other name\",\n                type: .set,\n                jsonValue: \"owen\",\n                date: self.date.now\n            )\n        ]\n\n        let attributesSet = self.expectation(description: \"attributes\")\n        attributesSet.expectedFulfillmentCount = 2\n\n        self.channel.attributeEditor = AttributesEditor(\n            date: self.date\n        ) { attributes in\n            XCTAssertEqual(expectedChannelAttributes, attributes)\n            attributesSet.fulfill()\n        }\n\n\n        self.contact.attributeEditor = AttributesEditor(\n            date: self.date\n        ) { attributes in\n            XCTAssertEqual(expectedContactAttributes, attributes)\n            attributesSet.fulfill()\n        }\n\n\n        let _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try AirshipJSON.wrap(value),\n                situation: .manualInvocation\n            )\n        )\n\n        await fulfillment(of: [attributesSet])\n\n    }\n    \n    func testJsonValue() async throws {\n        let value = [\n            [\n                \"action\": \"set\",\n                \"type\": \"channel\",\n                \"name\": \"another name\",\n                \"value\": [\n                    \"json#test\": [\n                        \"exp\": 1234567890,\n                        \"nested\": [\"foo\": \"bar\"]\n                    ]\n                ]\n            ]\n        ]\n        \n        let expectedAttributes = [\n            AttributeUpdate(\n                attribute: \"json#test\",\n                type: .set,\n                jsonValue: try AirshipJSON.wrap([\"nested\": [\"foo\": \"bar\"], \"exp\": 1234567890]),\n                date: self.date.now\n            )\n        ]\n        \n        let attributesSet = self.expectation(description: \"attributes\")\n        \n        self.contact.attributeEditor = AttributesEditor(\n            date: self.date\n        ) { attributes in\n            XCTFail(\"shouldn't be called\")\n        }\n\n        self.channel.attributeEditor = AttributesEditor(\n            date: self.date\n        ) { attributes in\n            XCTAssertEqual(expectedAttributes, attributes)\n            attributesSet.fulfill()\n        }\n        \n        let _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try AirshipJSON.wrap(value),\n                situation: .manualInvocation\n            )\n        )\n\n        await fulfillment(of: [attributesSet])\n    }\n    \n    func testJsonValueNoExpiration() async throws {\n        let value = [\n            [\n                \"action\": \"set\",\n                \"type\": \"channel\",\n                \"name\": \"another name\",\n                \"value\": [\n                    \"json#test\": [\n                        \"nested\": [\"foo\": \"bar\"]\n                    ]\n                ]\n            ]\n        ]\n        \n        let expectedAttributes = [\n            AttributeUpdate(\n                attribute: \"json#test\",\n                type: .set,\n                jsonValue: try AirshipJSON.wrap([\"nested\": [\"foo\": \"bar\"]]),\n                date: self.date.now\n            )\n        ]\n        \n        let attributesSet = self.expectation(description: \"attributes\")\n        \n        self.contact.attributeEditor = AttributesEditor(\n            date: self.date\n        ) { attributes in\n            XCTFail(\"shouldn't be called\")\n        }\n\n        self.channel.attributeEditor = AttributesEditor(\n            date: self.date\n        ) { attributes in\n            XCTAssertEqual(expectedAttributes, attributes)\n            attributesSet.fulfill()\n        }\n        \n        let _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try AirshipJSON.wrap(value),\n                situation: .manualInvocation\n            )\n        )\n\n        await fulfillment(of: [attributesSet])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ModifyTagsActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\n\nimport Testing\n\n@testable\nimport AirshipCore\n\nstruct ModifyTagsActionTest {\n    \n    private let channel = TestChannel()\n    private let contact = TestContact()\n    \n    private func makeAction() -> ModifyTagsAction {\n        return ModifyTagsAction(\n            channel: { [channel] in return channel },\n            contact: { [contact] in return contact }\n        )\n    }\n    \n    @Test(\n        \"Test accepts arguments\",\n        arguments: [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundInteractiveButton\n        ]\n    )\n    func testAcceptsArguments(situation: ActionSituation) async throws {\n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(channelAddPayload),\n            situation: situation\n        )\n        #expect(await makeAction().accepts(arguments: args))\n    }\n    \n    @Test(\n        \"Rejects backgound push situation\",\n        arguments: [ActionSituation.backgroundPush]\n    )\n    func testRejectSituations(situation: ActionSituation) async throws {\n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(channelAddPayload),\n            situation: situation\n        )\n        #expect(!(await makeAction().accepts(arguments: args)))\n    }\n    \n    @Test\n    func testChannelAdd() async throws {\n        self.channel.tags = [\"channel_tag_1\", \"channel_tag_3\"]\n        mockEditors()\n        \n        #expect(self.channel.tags == [\"channel_tag_1\", \"channel_tag_3\"])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([channelAddPayload]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(self.channel.tags.sorted() == [\"channel_tag_1\", \"channel_tag_1\", \"channel_tag_2\", \"channel_tag_3\"])\n    }\n    \n    @Test\n    func testChannelAddGroup() async throws {\n        var groupUpdates: [TagGroupUpdate] = []\n        mockEditors(\n            channelGroup: TagGroupsEditor { groupUpdates = $0 }\n        )\n        \n        #expect(groupUpdates == [])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([channelAddGroupPayload]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(groupUpdates == [\n            .init(\n                group: \"test_group\",\n                tags: [\"channel_tag_1\", \"channel_tag_2\"],\n                type: .add)\n        ])\n    }\n    \n    @Test\n    func testChannelRemove() async throws {\n        self.channel.tags = [\"channel_tag_1\", \"channel_tag_3\"]\n        mockEditors()\n        #expect(self.channel.tags == [\"channel_tag_1\", \"channel_tag_3\"])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([channelRemovePayload]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(self.channel.tags.sorted() == [\"channel_tag_3\"])\n    }\n    \n    @Test\n    func testChannelRemoveGroup() async throws {\n        var groupUpdates: [TagGroupUpdate] = []\n        mockEditors(channelGroup: TagGroupsEditor { groupUpdates = $0 })\n        \n        #expect(groupUpdates == [])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([channelRemoveGroupPayload]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(groupUpdates == [\n            .init(\n                group: \"test_group\",\n                tags: [\"channel_tag_1\", \"channel_tag_2\"],\n                type: .remove)\n        ])\n    }\n    \n    @Test(\"Throws on invalid JSON\")\n    func testThrowsOnInvalidChannelJson() async throws {\n        mockEditors()\n        \n        await #expect(throws: DecodingError.self) {\n            _ = try await makeAction().perform(\n                arguments: ActionArguments(\n                    value: try! AirshipJSON.wrap([channelInvalidPayload]),\n                    situation: .launchedFromPush\n                ))\n        }\n    }\n    \n    @Test\n    func testAddContactTags() async throws {\n        var groupUpdates: [TagGroupUpdate] = []\n        mockEditors(contactGroup: TagGroupsEditor { groupUpdates = $0 })\n        \n        #expect(groupUpdates == [])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([contactAddPayload]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(groupUpdates == [\n            .init(\n                group: \"test_group\",\n                tags: [\"contact_tag_1\", \"contact_tag_2\"],\n                type: .add)\n        ])\n    }\n    \n    @Test\n    func testRemoveContactTags() async throws {\n        var groupUpdates: [TagGroupUpdate] = []\n        mockEditors(contactGroup: TagGroupsEditor { groupUpdates = $0 })\n        \n        #expect(groupUpdates == [])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([contactRemovePayload]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(groupUpdates == [\n            .init(\n                group: \"test_group\",\n                tags: [\"contact_tag_1\", \"contact_tag_2\"],\n                type: .remove)\n        ])\n    }\n    \n    @Test(\"Throws on invalid payload\")\n    func testThrowsOnInvalidContactPayload() async throws {\n        mockEditors()\n        \n        await #expect(throws: DecodingError.self) {\n            _ = try await makeAction().perform(\n                arguments: ActionArguments(\n                    value: try! AirshipJSON.wrap(contactInvalidPayload),\n                    situation: .launchedFromPush\n                ))\n        }\n    }\n    \n    @Test\n    func testMultipleOperations() async throws {\n        self.channel.tags = [\"channel_tag_1\", \"channel_tag_3\"]\n        #expect(self.channel.tags.sorted() == [\"channel_tag_1\", \"channel_tag_3\"])\n        \n        var contactUpdate: [TagGroupUpdate] = []\n        var channelUpdates: [TagGroupUpdate] = []\n        mockEditors(\n            channelGroup: TagGroupsEditor { channelUpdates = $0 },\n            contactGroup: TagGroupsEditor { contactUpdate = $0 }\n        )\n        \n        #expect(channelUpdates == [])\n        #expect(contactUpdate == [])\n        \n        _ = try await makeAction().perform(\n            arguments: ActionArguments(\n                value: try! AirshipJSON.wrap([\n                    contactRemovePayload,\n                    channelAddPayload,\n                    channelAddGroupPayload,\n                    channelRemovePayload\n                ]),\n                situation: .launchedFromPush\n            ))\n        \n        #expect(self.channel.tags.sorted() == [\"channel_tag_3\"])\n        #expect(contactUpdate == [\n            .init(group: \"test_group\", tags: [\"contact_tag_1\", \"contact_tag_2\"], type: .remove),\n        ])\n        \n        #expect(channelUpdates == [\n            .init(group: \"test_group\", tags: [\"channel_tag_1\", \"channel_tag_2\"], type: .add),\n        ])\n    }\n    \n    private func mockEditors(\n        channelGroup: TagGroupsEditor = TagGroupsEditor { _ in },\n        contactGroup: TagGroupsEditor = TagGroupsEditor { _ in }\n    ) {\n        self.contact.tagGroupEditor = contactGroup\n        self.channel.tagGroupEditor = channelGroup\n    }\n    \n    private let channelAddPayload: [String: Any] = [\n        \"action\": \"add\",\n        \"tags\": [\n          \"channel_tag_1\",\n          \"channel_tag_2\"\n        ],\n        \"type\": \"channel\"\n    ]\n    private let channelAddGroupPayload: [String: Any] = [\n        \"action\": \"add\",\n        \"group\": \"test_group\",\n        \"tags\": [\n          \"channel_tag_1\",\n          \"channel_tag_2\"\n        ],\n        \"type\": \"channel\"\n    ]\n    private let channelRemovePayload: [String: Any] = [\n        \"action\": \"remove\",\n        \"tags\": [\n          \"channel_tag_1\",\n          \"channel_tag_2\"\n        ],\n        \"type\": \"channel\"\n    ]\n    private let channelRemoveGroupPayload: [String: Any] = [\n        \"action\": \"remove\",\n        \"group\": \"test_group\",\n        \"tags\": [\n          \"channel_tag_1\",\n          \"channel_tag_2\"\n        ],\n        \"type\": \"channel\"\n    ]\n    \n    private let contactAddPayload: [String: Any] = [\n        \"action\": \"add\",\n        \"group\": \"test_group\",\n        \"tags\": [\n          \"contact_tag_1\",\n          \"contact_tag_2\"\n        ],\n        \"type\": \"contact\"\n    ]\n    private let contactRemovePayload: [String: Any] = [\n        \"action\": \"remove\",\n        \"group\": \"test_group\",\n        \"tags\": [\n          \"contact_tag_1\",\n          \"contact_tag_2\"\n        ],\n        \"type\": \"contact\"\n    ]\n    \n    private let channelInvalidPayload: [String: Any] = [\n        \"action\": \"remove\",\n        \"type\": \"channel\"\n    ]\n    \n    private let contactInvalidPayload: [String: Any] = [\n        \"action\": \"remove\",\n        \"tags\": [\n          \"contact_tag_1\",\n          \"contact_tag_2\"\n        ],\n        \"type\": \"contact\"\n    ]\n    \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/NativeBridgeActionHandlerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\npublic import AirshipCore\n\nimport WebKit\n\nfinal class NativeBridgeActionHandlerTest: XCTestCase {\n\n    private let metadata: [String: String] = [\"some\": UUID().uuidString]\n\n    private let testActionRunner = TestActionRunner()\n\n    private let webView = WKWebView()\n    private var actionHandler: NativeBridgeActionHandler!\n    override func setUpWithError() throws {\n        self.actionHandler = NativeBridgeActionHandler(actionRunner: testActionRunner)\n    }\n\n    @MainActor\n    func testRunActionsMultiple() async throws {\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-actions?test%2520action=%22hi%22&also_test_action\"\n            )!\n        )\n\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        XCTAssertNil(result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test%20action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)],\n            \"also_test_action\": [ActionArguments(situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionsMultipleArgs() async throws {\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-actions?test_action&test_action\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        XCTAssertNil(result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [\n                ActionArguments(situation: .webViewInvocation, metadata: metadata),\n                ActionArguments(situation: .webViewInvocation, metadata: metadata)\n            ]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionsInvalidArgs() async throws {\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-actions?test_action=blah\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        XCTAssertNil(result)\n\n        XCTAssertEqual([:], self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionCBNullResult() async throws {\n        self.testActionRunner.actionResult = .completed(AirshipJSON.null)\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-action-cb/test_action/%22hi%22/callback-ID-1\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        let expectedResult = \"UAirship.finishAction(null, null, \\\"callback-ID-1\\\");\"\n        XCTAssertEqual(expectedResult, result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionCBValueResult() async throws {\n        self.testActionRunner.actionResult = .completed(AirshipJSON.string(\"neat\"))\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-action-cb/test_action/%22hi%22/callback-ID-2\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        let expectedResult = \"UAirship.finishAction(null, \\\"neat\\\", \\\"callback-ID-2\\\");\"\n        XCTAssertEqual(expectedResult, result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionCBError() async throws {\n        self.testActionRunner.actionResult = .error(AirshipErrors.error(\"Some error\"))\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-action-cb/test_action/%22hi%22/callback-ID-2\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        let expectedResult = \"var error = new Error(); error.message = \\\"Some error\\\"; UAirship.finishAction(error, null, \\\"callback-ID-2\\\");\"\n        XCTAssertEqual(expectedResult, result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionCBActionNotFound() async throws {\n        self.testActionRunner.actionResult = .actionNotFound\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-action-cb/test_action/%22hi%22/callback-ID-2\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        let expectedResult = \"var error = new Error(); error.message = \\\"No action found with name test_action, skipping action.\\\"; UAirship.finishAction(error, null, \\\"callback-ID-2\\\");\"\n        XCTAssertEqual(expectedResult, result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunActionCBActionArgsRejected() async throws {\n        self.testActionRunner.actionResult = .argumentsRejected\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-action-cb/test_action/%22hi%22/callback-ID-2\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        let expectedResult = \"var error = new Error(); error.message = \\\"Action test_action rejected arguments.\\\"; UAirship.finishAction(error, null, \\\"callback-ID-2\\\");\"\n        XCTAssertEqual(expectedResult, result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunBasicActions() async throws {\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-basic-actions?test_action=hi&also_test_action\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        XCTAssertNil(result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [ActionArguments(string: \"hi\", situation: .webViewInvocation, metadata: metadata)],\n            \"also_test_action\": [ActionArguments(situation: .webViewInvocation, metadata: metadata)]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n\n    @MainActor\n    func testRunBasicActionsMultipleArgs() async throws {\n        let command = JavaScriptCommand(\n            url: URL(\n                string: \"uairship://run-basic-actions?test_action&test_action\"\n            )!\n        )\n\n        let result = await self.actionHandler.runActionsForCommand(command: command, metadata: metadata, webView: self.webView)\n        XCTAssertNil(result)\n\n        let expecteActions: [String: [ActionArguments]] = [\n            \"test_action\": [\n                ActionArguments(situation: .webViewInvocation, metadata: metadata),\n                ActionArguments(situation: .webViewInvocation, metadata: metadata)\n            ]\n        ]\n\n        XCTAssertEqual(expecteActions, self.testActionRunner.ranActions)\n    }\n}\n\n\nfinal class TestActionRunner: NativeBridgeActionRunner {\n    @MainActor\n    var actionResult: ActionResult = .completed(AirshipJSON.null)\n    @MainActor\n    var ranActions: [String: [ActionArguments]] = [:]\n\n    @MainActor\n    func runAction(actionName: String, arguments: AirshipCore.ActionArguments, webView: WKWebView) async -> AirshipCore.ActionResult {\n        ranActions[actionName] = ranActions[actionName] ?? []\n        ranActions[actionName]?.append(arguments)\n        return actionResult\n    }\n}\n\nextension ActionArguments: @retroactive Equatable {\n    public static func == (lhs: AirshipCore.ActionArguments, rhs: AirshipCore.ActionArguments) -> Bool {\n        lhs.value == rhs.value && lhs.situation == rhs.situation && NSDictionary(dictionary: lhs.metadata) == NSDictionary(dictionary: rhs.metadata)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/NotificationCategoriesTest.swift",
    "content": "// Copyright Airship and Contributors\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class NotificationCategoriesTest: XCTestCase {\n    \n    func testDefaultCategories() {\n        let categories = NotificationCategories.defaultCategories()\n        XCTAssertEqual(37, categories.count)\n        \n        // Require auth defaults to true for background actions\n        categories.forEach { category in\n            category.actions\n                .filter({ !$0.options.contains(.foreground) })\n                .forEach { action in\n                    XCTAssert(action.options.contains(.authenticationRequired))\n                }\n        }\n    }\n    \n    func testDefaultCategoriesOverrideAuth() {\n        let categories = NotificationCategories.defaultCategories(withRequireAuth: false)\n        XCTAssertEqual(37, categories.count)\n        \n        // Verify require auth is false for background actions\n        categories.forEach { category in\n            category.actions\n                .filter({ !$0.options.contains(.foreground) })\n                .forEach { action in\n                    XCTAssertFalse(action.options.contains(.authenticationRequired))\n                }\n        }\n    }\n    \n    \n    func testCreateFromPlist() {\n        let plist = Bundle(for: self.classForCoder).path(forResource: \"CustomNotificationCategories\", ofType: \"plist\")!\n        let categories = NotificationCategories.createCategories(fromFile: plist)\n        \n        XCTAssertEqual(4, categories.count)\n        \n        // Share category\n        let share = categories.first(where: { $0.identifier == \"share_category\" })\n        XCTAssertNotNil(share)\n        XCTAssertEqual(1, share?.actions.count)\n\n        // Share action in share category\n        let shareAction = share?.actions.first(where: { $0.identifier == \"share_button\" })\n        XCTAssertNotNil(shareAction)\n        XCTAssertEqual(\"Share\", shareAction?.title)\n        XCTAssertTrue(shareAction!.options.contains(.foreground))\n        XCTAssertFalse(shareAction!.options.contains(.authenticationRequired))\n        XCTAssertFalse(shareAction!.options.contains(.destructive))\n\n        // Yes no category\n        let yesNo = categories.first(where: { $0.identifier == \"yes_no_category\" })\n        XCTAssertNotNil(yesNo)\n        XCTAssertEqual(2, yesNo?.actions.count)\n\n        // Yes action in yes no category\n        let yesAction = yesNo?.actions.first(where: { $0.identifier == \"yes_button\" })\n        XCTAssertNotNil(yesAction)\n        XCTAssertEqual(\"Yes\", yesAction?.title)\n        XCTAssertTrue(yesAction!.options.contains(.foreground))\n        XCTAssertFalse(yesAction!.options.contains(.authenticationRequired))\n        XCTAssertFalse(yesAction!.options.contains(.destructive))\n\n        // No action in yes no category\n        let noAction = yesNo?.actions.first(where: { $0.identifier == \"no_button\" })\n        XCTAssertNotNil(noAction)\n        XCTAssertEqual(\"No\", noAction?.title)\n\n        XCTAssertFalse(noAction!.options.contains(.foreground))\n        XCTAssertTrue(noAction!.options.contains(.authenticationRequired))\n        XCTAssertTrue(noAction!.options.contains(.destructive))\n\n        // text_input category\n        let textInput = categories.first(where: { $0.identifier == \"text_input_category\" })\n        XCTAssertNotNil(textInput)\n        XCTAssertEqual(1, textInput?.actions.count)\n        \n        // Follow action in follow category\n        let textInputAction = textInput?.actions.first(where: { $0.identifier == \"text_input\" }) as? UNTextInputNotificationAction\n        XCTAssertNotNil(textInputAction)\n        \n        // Test when 'title_resource' value does not exist will fall back to 'title' value\n        XCTAssertEqual(\"TextInput\", textInputAction?.title)\n        XCTAssertEqual(\"text_input_button\", textInputAction?.textInputButtonTitle)\n        XCTAssertEqual(\"placeholder_text\", textInputAction?.textInputPlaceholder)\n        XCTAssertTrue(textInputAction!.options.contains(.foreground))\n        XCTAssertFalse(textInputAction!.options.contains(.authenticationRequired))\n        XCTAssertFalse(textInputAction!.options.contains(.destructive))\n        \n        // Follow category\n        let follow = categories.first(where: { $0.identifier == \"follow_category\" })\n        XCTAssertNotNil(follow)\n        XCTAssertEqual(1, follow?.actions.count)\n\n        // Follow action in follow category\n        let followAction = follow?.actions.first(where: { $0.identifier == \"follow_button\" })\n        XCTAssertNotNil(followAction)\n\n        // Test when 'title_resource' value does not exist will fall back to 'title' value\n        XCTAssertEqual(\"FollowMe\", followAction?.title)\n        XCTAssertTrue(followAction!.options .contains(.foreground))\n        XCTAssertFalse(followAction!.options.contains(.authenticationRequired))\n        XCTAssertFalse(followAction!.options.contains(.destructive))\n    }\n    \n    func testDoesNotCreateCategoryMissingTitle() {\n        let actions = [\n            [\"identifier\": \"yes\", \"foreground\": true, \"authenticationRequired\": true],\n            [\"identifier\": \"no\", \"foreground\": false, \"destructive\": true, \"authenticationRequired\": false]\n        ]\n        \n        XCTAssertNil(NotificationCategories.createCategory(\"category\", actions: actions))\n    }\n    \n    func testCreateFromInvalidPlist() {\n        let categories = NotificationCategories.createCategories(fromFile: \"no file\")\n        XCTAssertEqual(0, categories.count, \"No categories should be created.\")\n    }\n    \n    func testCreateCategory() {\n        let actions = [\n            [\"identifier\": \"yes\", \"foreground\": true, \"title\": \"Yes\", \"authenticationRequired\": true],\n            [\"identifier\": \"no\", \"foreground\": false, \"title\": \"No\", \"destructive\": true, \"authenticationRequired\": false]\n        ]\n        \n        let category = NotificationCategories.createCategory(\"category\", actions: actions)\n        \n        // Yes action\n        let yesAction = category?.actions.first(where: { $0.identifier == \"yes\" })\n        XCTAssertNotNil(yesAction)\n        XCTAssertEqual(\"Yes\", yesAction?.title)\n\n        XCTAssertTrue(yesAction!.options.contains(.foreground))\n        XCTAssertTrue(yesAction!.options.contains(.authenticationRequired))\n        XCTAssertFalse(yesAction!.options.contains(.destructive))\n\n        // No action\n        let noAction = category?.actions.first(where: { $0.identifier == \"no\" })\n        XCTAssertNotNil(noAction)\n        XCTAssertEqual(\"No\", noAction?.title)\n\n        XCTAssertFalse(noAction!.options.contains(.foreground))\n        XCTAssertFalse(noAction!.options.contains(.authenticationRequired))\n        XCTAssertTrue(noAction!.options.contains(.destructive))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/OpenExternalURLActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass OpenExternalURLActionTest: XCTestCase {\n\n    private let testURLOpener: TestURLOpener = TestURLOpener()\n    private let urlAllowList: TestURLAllowList = TestURLAllowList()\n    private var airship: TestAirshipInstance!\n\n    private var action: OpenExternalURLAction!\n\n    @MainActor\n    override func setUp() {\n        airship = TestAirshipInstance()\n        self.action = OpenExternalURLAction(urlOpener: self.testURLOpener)\n        self.airship.urlAllowList = self.urlAllowList\n        self.airship.makeShared()\n    }\n\n    override func tearDown() async throws {\n        TestAirshipInstance.clearShared()\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush,\n            ActionSituation.backgroundInteractiveButton\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    @MainActor\n    func testPerform() async throws {\n        self.urlAllowList.isAllowedReturnValue = true\n        self.testURLOpener.returnValue = true\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        let result = try await action.perform(arguments: args)\n        XCTAssertEqual(args.value, result)\n        XCTAssertEqual(\"http://some-valid-url\", self.testURLOpener.lastURL?.absoluteString)\n    }\n\n    @MainActor\n    func testPerformRejectsURL() async throws {\n        self.urlAllowList.isAllowedReturnValue = false\n        self.testURLOpener.returnValue = true\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"Should throw\")\n        } catch {}\n\n        XCTAssertNil(self.testURLOpener.lastURL)\n    }\n\n    @MainActor\n    func testPerformUnableToOpenURL() async throws {\n        self.urlAllowList.isAllowedReturnValue = true\n        self.testURLOpener.returnValue = false\n\n        let args = ActionArguments(\n            string: \"http://some-valid-url\",\n            situation: .manualInvocation\n        )\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"Should throw\")\n        } catch {}\n\n        XCTAssertEqual(\"http://some-valid-url\", self.testURLOpener.lastURL?.absoluteString)\n    }\n\n    @MainActor\n    func testPerformInvalidURL() async throws {\n        self.urlAllowList.isAllowedReturnValue = true\n        self.testURLOpener.returnValue = true\n\n        let args = ActionArguments(\n            double: 10.0,\n            situation: .manualInvocation\n        )\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"Should throw\")\n        } catch {}\n\n        XCTAssertNil(self.testURLOpener.lastURL)\n    }\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/PagerControllerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport Foundation\n\n@testable import AirshipCore\n\nstruct PagerControllerTest {\n    \n    @MainActor\n    @Test\n    func initWithNullState() {\n        let controller = AirshipSceneController.PagerController(pagerState: nil)\n        #expect(controller.canGoBack == false)\n        #expect(controller.canGoNext == false)\n        \n        #expect(controller.navigate(request: .back) == false)\n        #expect(controller.navigate(request: .next) == false)\n    }\n    \n    @MainActor\n    @Test\n    func controllerDisplaysCorrectStateOnNavigation() {\n        let pagerState = PagerState(\n            identifier: \"test\",\n            branching: nil\n        )\n        pagerState.setPagesAndListenForUpdates(\n            pages: [\n                makePageItem(id: \"page-1\"),\n                makePageItem(id: \"page-2\")\n            ],\n            thomasState: .empty,\n            swipeDisableSelectors: nil\n        )\n\n        let controller = AirshipSceneController.PagerController(pagerState: pagerState)\n        #expect(controller.canGoBack == false)\n        #expect(controller.canGoNext == true)\n\n        #expect(controller.navigate(request: .back) == false)\n        #expect(controller.navigate(request: .next) == true)\n\n        #expect(controller.canGoBack == true)\n        #expect(controller.canGoNext == false)\n\n        #expect(controller.navigate(request: .next) == false)\n        #expect(controller.navigate(request: .back) == true)\n\n        #expect(controller.canGoBack == false)\n        #expect(controller.canGoNext == true)\n    }\n\n    @MainActor\n    @Test\n    func disableTouchDuringNavigation() async throws {\n        let sleeper = TestTaskSleeper()\n        let sleepUpdates = await sleeper.sleepUpdates\n        var iterator = sleepUpdates.makeAsyncIterator()\n\n        let pagerState = PagerState(\n            identifier: \"test\",\n            branching: nil,\n            taskSleeper: sleeper\n        )\n\n        #expect(pagerState.isNavigationInProgress == false)\n\n        pagerState.disableTouchDuringNavigation()\n        #expect(pagerState.isNavigationInProgress == true)\n\n        let sleeps = await iterator.next()\n        #expect(sleeps?.count == 1)\n        #expect(pagerState.isNavigationInProgress == false)\n    }\n    \n    private func makePageItem(id: String) -> ThomasViewInfo.Pager.Item {\n        return .init(\n            identifier: id,\n            view: .emptyView(.init(commonProperties: .init(), properties: .init())),\n            displayActions: nil,\n            automatedActions: nil,\n            accessibilityActions: nil,\n            stateActions: nil,\n            branching: nil\n        )\n    }\n}\n\nextension ThomasState {\n    static var empty: ThomasState {\n        return .init(\n            formState: .init(\n                identifier: \"empty\",\n                formType: .form,\n                formResponseType: \"none\",\n                validationMode: .immediate,\n            ),\n            pagerState: .init(identifier: \"\", branching: nil),\n            onStateChange: { _ in }\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/PasteboardActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\nimport Foundation\n\n@Suite struct PasteboardActionTest {\n\n    private let testPasteboard: TestPasteboard = TestPasteboard()\n    private let action: PasteboardAction!\n\n\n    init() {\n        self.action = PasteboardAction(pasteboard: self.testPasteboard)\n    }\n\n    @Test\n    func testAcceptsArguments() async throws {\n        let validStringValue = \"pasteboard string\"\n        let validDictValue = [\"text\": \"pasteboard string\"]\n\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundPush\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(\n                string: validStringValue,\n                situation: situation\n            )\n            let result = await self.action.accepts(arguments: args)\n            #expect(result, \"Should accept valid situation: \\(situation)\")\n        }\n\n        for situation in validSituations {\n            let args = ActionArguments(\n                value: try AirshipJSON.wrap(validDictValue),\n                situation: situation\n            )\n            let result = await self.action.accepts(arguments: args)\n            #expect(result, \"Should accept valid dictionary value in situation: \\(situation)\")\n        }\n\n        for situation in validSituations {\n            let args = ActionArguments(\n                situation: situation\n            )\n            let result = await self.action.accepts(arguments: args)\n            #expect(!result, \"Should reject empty arguments in situation: \\(situation)\")\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(\n                string: validStringValue,\n                situation: situation\n            )\n            let result = await self.action.accepts(arguments: args)\n            #expect(!result, \"Should reject invalid situation: \\(situation)\")\n        }\n    }\n    \n    @Test\n    @MainActor\n    func testPerformWithString() async throws {\n        let value = \"pasteboard_string\"\n        let arguments = ActionArguments(string: value)\n        \n        let result = try await self.action.perform(arguments: arguments)\n        \n        #expect(result == arguments.value)\n        #expect(testPasteboard.lastCopyValue == value)\n    }\n    \n    @Test\n    @MainActor\n    func testPerformWithDictionary() async throws {\n        let value = \"pasteboard string\"\n        let arguments = ActionArguments(value: try AirshipJSON.wrap([\"text\": value]))\n        \n        let result = try await self.action.perform(arguments: arguments)\n        \n        #expect(result == arguments.value)\n        #expect(testPasteboard.lastCopyValue == value)\n    }\n}\n\nfileprivate final class TestPasteboard: AirshipPasteboardProtocol, @unchecked Sendable {\n    private let lock = NSLock()\n    private var _lastCopyValue: String?\n    \n    var lastCopyValue: String? {\n        lock.lock()\n        defer { lock.unlock() }\n        return _lastCopyValue\n    }\n\n    func copy(value: String, expiry: TimeInterval) {\n        lock.lock()\n        defer { lock.unlock() }\n        _lastCopyValue = value\n    }\n\n    func copy(value: String) {\n        lock.lock()\n        defer { lock.unlock() }\n        _lastCopyValue = value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/PermissionsManagerTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable public import AirshipCore\n\nclass PermissionsManagerTests: XCTestCase {\n\n    var delegate: TestPermissionsDelegate!\n\n    var systemSettingsNavigator: TestSystemSettingsNavigator!\n    var permissionsManager: DefaultAirshipPermissionsManager!\n    let appStateTracker = TestAppStateTracker()\n    @MainActor\n    override func setUp() async throws {\n        self.systemSettingsNavigator = TestSystemSettingsNavigator()\n        permissionsManager = DefaultAirshipPermissionsManager(\n            appStateTracker: appStateTracker,\n            systemSettingsNavigator: systemSettingsNavigator\n        )\n        self.delegate = TestPermissionsDelegate()\n    }\n    func testCheckPermissionNotConfigured() async throws {\n        let status = await self.permissionsManager.checkPermissionStatus(.displayNotifications)\n        \n        XCTAssertEqual(AirshipPermissionStatus.notDetermined, status)\n    }\n\n    @MainActor\n    func testCheckPermission() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .granted\n\n        let status = await self.permissionsManager.checkPermissionStatus(.location)\n\n        XCTAssertEqual(AirshipPermissionStatus.granted, status)\n        XCTAssertTrue(self.delegate.checkCalled)\n        XCTAssertFalse(self.delegate.requestCalled)\n    }\n\n    @MainActor\n    func testStatusUpdate() async {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        var stream = self.permissionsManager.statusUpdate(for: .location).makeAsyncIterator()\n        let status = await self.permissionsManager.requestPermission(.location)\n\n        let currentStatus = await stream.next()\n        XCTAssertEqual(AirshipPermissionStatus.denied, status)\n        XCTAssertEqual(status, currentStatus)\n    }\n\n    @MainActor\n    func testStatusRefreshOnActive() async {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        var stream = self.permissionsManager.statusUpdate(for: .location).makeAsyncIterator()\n\n        var currentStatus = await stream.next()\n        XCTAssertEqual(AirshipPermissionStatus.denied, currentStatus)\n\n        self.delegate.permissionStatus = .granted\n\n        await self.appStateTracker.updateState(.active)\n\n        currentStatus = await stream.next()\n        XCTAssertEqual(AirshipPermissionStatus.granted, currentStatus)\n    }\n\n    func testRequestPermissionNotConfigured() async throws {\n        let status = await self.permissionsManager.requestPermission(.displayNotifications)\n\n        XCTAssertEqual(AirshipPermissionStatus.notDetermined, status)\n    }\n\n    @MainActor\n    func testRequestPermissionNotDetermined() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .notDetermined\n\n        let status = await self.permissionsManager.requestPermission(.location)\n\n        XCTAssertEqual(AirshipPermissionStatus.notDetermined, status)\n        XCTAssertTrue(self.delegate.requestCalled)\n        XCTAssertTrue(self.delegate.checkCalled)\n    }\n\n    @MainActor\n    func testRequestPermissionDenied() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        let status = await self.permissionsManager.requestPermission(.location)\n\n        XCTAssertEqual(AirshipPermissionStatus.denied, status)\n        XCTAssertTrue(self.delegate.requestCalled)\n        XCTAssertTrue(self.delegate.checkCalled)\n    }\n\n    @MainActor\n    func testRequestPermissionGranted() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .granted\n\n        let status = await self.permissionsManager.requestPermission(.location)\n\n        XCTAssertEqual(AirshipPermissionStatus.granted, status)\n        XCTAssertTrue(self.delegate.requestCalled)\n        XCTAssertTrue(self.delegate.checkCalled)\n    }\n\n    @MainActor\n    func testRequestPermissionSystemSettingsFallback() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        _ = await self.permissionsManager.requestPermission(.location, enableAirshipUsageOnGrant: false, fallback: .systemSettings)\n\n        XCTAssertTrue(self.delegate.requestCalled)\n        XCTAssertTrue(self.delegate.checkCalled)\n        XCTAssertEqual(systemSettingsNavigator.permissionOpens, [.location])\n    }\n\n    @MainActor\n    func testRequestPermissionSystemSettingsFallbackFailsToOpen() async throws {\n        self.systemSettingsNavigator.permissionOpenResult = false\n\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        _ = await self.permissionsManager.requestPermission(.location, enableAirshipUsageOnGrant: false, fallback: .systemSettings)\n\n        XCTAssertTrue(self.delegate.requestCalled)\n        XCTAssertTrue(self.delegate.checkCalled)\n        XCTAssertEqual(systemSettingsNavigator.permissionOpens, [.location])\n    }\n\n    @MainActor\n    func testRequestPermissionCallbackFallback() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        let status = await self.permissionsManager.requestPermission(\n            .location,\n            enableAirshipUsageOnGrant: false,\n            fallback: .callback({\n                self.delegate.permissionStatus = .granted\n            })\n        )\n\n        XCTAssertEqual(AirshipPermissionStatus.granted, status.endStatus)\n        XCTAssertTrue(self.delegate.requestCalled)\n        XCTAssertTrue(self.delegate.checkCalled)\n    }\n\n    func testConfiguredPermissionsEmpty() throws {\n        XCTAssertTrue(self.permissionsManager.configuredPermissions.isEmpty)\n    }\n\n    func testConfiguredPermissions() throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .displayNotifications\n        )\n\n        let expected = Set<AirshipPermission>([.location, .displayNotifications])\n        let configured = self.permissionsManager.configuredPermissions\n        XCTAssertEqual(expected, configured)\n    }\n\n    @MainActor\n    func testAirshipEnablers() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .displayNotifications\n        )\n        self.delegate.permissionStatus = .granted\n\n        let enablerCalled = self.expectation(description: \"Enabler called\")\n        self.permissionsManager.addAirshipEnabler(\n            permission: .displayNotifications\n        ) {\n            enablerCalled.fulfill()\n        }\n\n        let _ = await self.permissionsManager.requestPermission(\n            .displayNotifications,\n            enableAirshipUsageOnGrant: true\n        )\n        await self.fulfillment(of: [enablerCalled], timeout: 1)\n    }\n\n    @MainActor\n    func testRequestExtender() async throws {\n        self.permissionsManager.setDelegate(\n            self.delegate,\n            permission: .location\n        )\n        self.delegate.permissionStatus = .denied\n\n        let listener1 = self.expectation(description: \"Listener 1\")\n        self.permissionsManager.addRequestExtender(permission: .location) { status in\n            listener1.fulfill()\n        }\n\n        let listener2 = self.expectation(description: \"Listener 2\")\n        self.permissionsManager.addRequestExtender(permission: .location) { status in\n            listener2.fulfill()\n        }\n\n        let status = await self.permissionsManager.requestPermission(.location) \n\n        XCTAssertEqual(AirshipPermissionStatus.denied, status)\n        await self.fulfillment(\n            of: [listener1, listener2],\n            timeout: 1,\n            enforceOrder: true\n        )\n    }\n}\n\n@MainActor\nfinal class TestPermissionsDelegate: AirshipPermissionDelegate {\n\n    public var permissionStatus: AirshipPermissionStatus = .notDetermined\n    var checkCalled: Bool = false\n    var requestCalled: Bool = false\n\n    public func checkPermissionStatus() async -> AirshipPermissionStatus {\n        self.checkCalled = true\n        return permissionStatus\n    }\n\n    public func requestPermission() async -> AirshipPermissionStatus {\n        self.requestCalled = true\n       return permissionStatus\n    }\n}\n\n\n@MainActor\npublic final class TestSystemSettingsNavigator: SystemSettingsNavigatorProtocol {\n    var permissionOpens: [AirshipPermission] =  []\n    var permissionOpenResult = false\n    public func open(for permission: AirshipPermission) async -> Bool {\n        permissionOpens.append(permission)\n        return permissionOpenResult\n    }\n    \n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/PreferenceDataStoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass PreferenceDataStoreTest: XCTestCase {\n\n    private let airshipDefaults = UserDefaults(\n        suiteName: \"\\(Bundle.main.bundleIdentifier ?? \"\").airship.settings\"\n    )!\n    private let appKey = UUID().uuidString\n    private let testDeviceID = TestDeviceID()\n    \n    func testPrefix() throws {\n        let dataStore = PreferenceDataStore(\n            appKey: self.appKey,\n            dispatcher: TestDispatcher(),\n            deviceID: testDeviceID\n        )\n        dataStore.setObject(\"neat\", forKey: \"some-key\")\n        XCTAssertEqual(\n            \"neat\",\n            airshipDefaults.string(forKey: \"\\(self.appKey)some-key\")\n        )\n    }\n\n    /// Tests merging data from the old keys in either standard or the Airship defaults:\n    ///  - If a value exists under the old key but not the new key, it will be restored under the new key\n    ///  - If channel tags exists under both keys we will merge the two tag arrays\n    func testMergeKeys() throws {\n        let standardDefaults = UserDefaults.standard\n        let legacyPrefix = \"com.urbanairship.\\(appKey).\"\n        let newPrefix = self.appKey\n        let tagsKey = \"com.urbanairship.channel.tags\"\n\n        standardDefaults.set(\"keep-new: old\", forKey: \"\\(legacyPrefix)keep-new\")\n        self.airshipDefaults.set(\n            \"keep-new: new\",\n            forKey: \"\\(newPrefix)keep-new\"\n        )\n        standardDefaults.set(\n            \"restore-old: old\",\n            forKey: \"\\(legacyPrefix)restore-old\"\n        )\n\n        self.airshipDefaults.set(\n            \"another-keep-new: old\",\n            forKey: \"\\(legacyPrefix)another-keep-new\"\n        )\n        self.airshipDefaults.set(\n            \"another-keep-new: new\",\n            forKey: \"\\(newPrefix)another-keep-new\"\n        )\n        self.airshipDefaults.set(\n            \"another-restore-old: old\",\n            forKey: \"\\(legacyPrefix)another-restore-old\"\n        )\n\n        standardDefaults.set([\"a\", \"b\"], forKey: \"\\(legacyPrefix)\\(tagsKey)\")\n        self.airshipDefaults.set([\"c\"], forKey: \"\\(newPrefix)\\(tagsKey)\")\n\n        let dataStore = PreferenceDataStore(appKey: self.appKey)\n\n        XCTAssertEqual(\n            \"another-keep-new: new\",\n            dataStore.string(forKey: \"another-keep-new\")\n        )\n        XCTAssertEqual(\n            \"another-restore-old: old\",\n            dataStore.string(forKey: \"another-restore-old\")\n        )\n        XCTAssertEqual(\"keep-new: new\", dataStore.string(forKey: \"keep-new\"))\n        XCTAssertEqual(\n            \"restore-old: old\",\n            dataStore.string(forKey: \"restore-old\")\n        )\n        XCTAssertEqual([\"a\", \"b\", \"c\"], dataStore.stringArray(forKey: tagsKey))\n    }\n\n    func testData() throws {\n        let dataStore = PreferenceDataStore(appKey: self.appKey)\n\n        let data = \"neat\".data(using: .utf8)\n        dataStore.setObject(data, forKey: \"data\")\n        XCTAssertEqual(data, dataStore.data(forKey: \"data\"))\n\n        dataStore.setBool(false, forKey: \"falseBool\")\n        XCTAssertFalse(dataStore.bool(forKey: \"falseBool\"))\n\n        dataStore.setBool(true, forKey: \"trueBool\")\n        XCTAssertTrue(dataStore.bool(forKey: \"trueBool\"))\n\n        let array = [\"neat\", \"rad\"]\n        dataStore.setObject(array, forKey: \"array\")\n        XCTAssertEqual(array, dataStore.array(forKey: \"array\"))\n\n        let dict = [\"neat\": \"rad\"]\n        dataStore.setObject(dict, forKey: \"dict\")\n        XCTAssertEqual(\n            dict,\n            dataStore.dictionary(forKey: \"dict\") as! [String: String]\n        )\n\n        let float: Float = 2.0\n        dataStore.setFloat(float, forKey: \"float\")\n        XCTAssertEqual(float, dataStore.float(forKey: \"float\"))\n\n        let double: Double = 3.0\n        dataStore.setDouble(double, forKey: \"double\")\n        XCTAssertEqual(double, dataStore.double(forKey: \"double\"))\n\n        let int: Int = 1\n        dataStore.setInteger(int, forKey: \"int\")\n        XCTAssertEqual(int, dataStore.integer(forKey: \"int\"))\n\n        let date = Date()\n        dataStore.setObject(date, forKey: \"date\")\n        XCTAssertEqual(date, dataStore.object(forKey: \"date\") as! Date)\n    }\n\n    func testNil() throws {\n        let dataStore = PreferenceDataStore(appKey: self.appKey)\n\n        XCTAssertNil(dataStore.object(forKey: \"nil?\"))\n        dataStore.setObject(\"not nil\", forKey: \"nil?\")\n        XCTAssertNotNil(dataStore.object(forKey: \"nil?\"))\n        dataStore.setObject(nil, forKey: \"nil?\")\n        XCTAssertNil(dataStore.object(forKey: \"nil?\"))\n    }\n\n    func testDefaults() throws {\n        let dataStore = PreferenceDataStore(appKey: self.appKey)\n        XCTAssertEqual(\n            100.0,\n            dataStore.double(forKey: \"neat\", defaultValue: 100.0)\n        )\n        XCTAssertEqual(true, dataStore.bool(forKey: \"neat\", defaultValue: true))\n\n        XCTAssertEqual(\n            dataStore.double(forKey: \"neat\"),\n            self.airshipDefaults.double(forKey: \"neat\")\n        )\n\n        XCTAssertEqual(\n            dataStore.float(forKey: \"neat\"),\n            self.airshipDefaults.float(forKey: \"neat\")\n        )\n\n        XCTAssertEqual(\n            dataStore.bool(forKey: \"neat\"),\n            self.airshipDefaults.bool(forKey: \"neat\")\n        )\n\n        XCTAssertEqual(\n            dataStore.integer(forKey: \"neat\"),\n            self.airshipDefaults.integer(forKey: \"neat\")\n        )\n    }\n\n    func testCodable() throws {\n        let dataStore = PreferenceDataStore(appKey: self.appKey)\n        let nilValue: FooCodable? = try dataStore.codable(forKey: \"codable\")\n        XCTAssertNil(nilValue)\n        let codable = FooCodable(foo: \"woot\")\n        try dataStore.setCodable(codable, forKey: \"codable\")\n        XCTAssertEqual(codable, try dataStore.codable(forKey: \"codable\"))\n    }\n\n    func testCodableWrongType() throws {\n        let dataStore = PreferenceDataStore(appKey: self.appKey)\n        let foo = FooCodable(foo: \"woot\")\n\n        try dataStore.setCodable(foo, forKey: \"codable\")\n        XCTAssertThrowsError(\n            try {\n                let _: BarCodable? = try dataStore.codable(forKey: \"codable\")\n            }()\n        )\n    }\n    \n    func testAppNotRestoredNoData() async throws {\n        let dataStore = PreferenceDataStore(\n            appKey: self.appKey,\n            dispatcher: TestDispatcher(),\n            deviceID: testDeviceID\n        )\n\n        let value = await dataStore.isAppRestore\n        XCTAssertFalse(value)\n    }\n    \n    func testAppRestoredDeviceIDChange() async throws {\n        let dataStore = PreferenceDataStore(\n            appKey: self.appKey,\n            dispatcher: TestDispatcher(),\n            deviceID: testDeviceID\n        )\n        var value = await dataStore.isAppRestore\n        XCTAssertFalse(value)\n\n\n        await self.testDeviceID.setValue(value: UUID().uuidString)\n        value = await dataStore.isAppRestore\n        XCTAssertTrue(value)\n    }\n\n    func testKeyIsStoredAndRetrieved() {\n        let dataStore = PreferenceDataStore(\n            appKey: self.appKey,\n            dispatcher: TestDispatcher(),\n            deviceID: testDeviceID\n        )\n\n        let value = ProcessInfo.processInfo.globallyUniqueString\n        dataStore.setObject(value, forKey: \"key\")\n        XCTAssertEqual(dataStore.string(forKey: \"key\"), value)\n    }\n\n    func testKeyIsRemoved() {\n        let dataStore = PreferenceDataStore(\n            appKey: self.appKey,\n            dispatcher: TestDispatcher(),\n            deviceID: testDeviceID\n        )\n\n        let value = ProcessInfo.processInfo.globallyUniqueString\n        dataStore.setObject(value, forKey: \"key\")\n        XCTAssertEqual(dataStore.object(forKey: \"key\") as? String, value)\n        dataStore.removeObject(forKey: \"key\")\n        XCTAssertNil(dataStore.object(forKey: \"key\"))\n    }\n\n    func testMigration() {\n        let prefix = UUID().uuidString\n        UserDefaults.standard.set(true, forKey: \"\\(prefix)some-key\")\n\n        let dataStore = PreferenceDataStore(\n            appKey: prefix,\n            dispatcher: TestDispatcher(),\n            deviceID: testDeviceID\n        )\n\n        XCTAssertTrue(dataStore.bool(forKey: \"some-key\"))\n    }\n}\n\nprivate struct FooCodable: Codable, Equatable {\n    let foo: String\n}\n\nprivate struct BarCodable: Codable, Equatable {\n    let bar: String\n}\n\n\nfileprivate actor TestDeviceID: AirshipDeviceIDProtocol {\n    var value: String = UUID().uuidString\n\n    init() {}\n\n    public func setValue(value: String) {\n        self.value = value\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/PromptPermissionActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass PromptPermissionActionTest: XCTestCase {\n\n    let testPrompter = TestPermissionPrompter()\n    var action: PromptPermissionAction!\n\n    override func setUpWithError() throws {\n        self.action = PromptPermissionAction {\n            return self.testPrompter\n        }\n    }\n\n    func testAcceptsArguments() async throws {\n\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(value: AirshipJSON.string(\"anything\"), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(value: AirshipJSON.string(\"anything\"), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testPrompt() async throws {\n        let actionValue: [String: Any] = [\n            \"permission\": AirshipPermission.location.rawValue,\n            \"enable_airship_usage\": true,\n            \"fallback_system_settings\": true,\n        ]\n\n        let arguments = ActionArguments(\n            value: try! AirshipJSON.wrap(actionValue)\n        )\n\n        let prompted = self.expectation(description: \"Prompted\")\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTAssertEqual(permission, .location)\n            XCTAssertTrue(enableAirshipUsage)\n            XCTAssertTrue(fallbackSystemSetting)\n            prompted.fulfill()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n\n        let result = try await self.action.perform(arguments: arguments)\n        XCTAssertNil(result)\n        await self.fulfillment(of: [prompted], timeout: 10)\n    }\n\n    func testPromptDefaultArguments() async throws {\n        let actionValue = [\n            \"permission\": AirshipPermission.displayNotifications.rawValue\n        ]\n        let arguments = ActionArguments(\n            value: try! AirshipJSON.wrap(actionValue),\n            situation: .manualInvocation\n        )\n\n        let prompted = self.expectation(description: \"Prompted\")\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTAssertEqual(permission, .displayNotifications)\n            XCTAssertFalse(enableAirshipUsage)\n            XCTAssertFalse(fallbackSystemSetting)\n            prompted.fulfill()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n\n        let result = try await self.action.perform(arguments: arguments)\n        XCTAssertNil(result)\n        await self.fulfillment(of: [prompted], timeout: 10)\n    }\n\n    func testInvalidPermission() async throws {\n        let actionValue: [String: Any] = [\n            \"permission\": \"not a permission\"\n        ]\n\n        let arguments = ActionArguments(\n            value: try! AirshipJSON.wrap(actionValue),\n            situation: .manualInvocation\n        )\n\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            XCTFail()\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .notDetermined)\n        }\n\n        do {\n            _ = try await self.action.perform(arguments: arguments)\n            XCTFail(\"Should throw\")\n        } catch {}\n    }\n\n    func testResultReceiver() async throws {\n        let actionValue: [String: Any] = [\n            \"permission\": AirshipPermission.location.rawValue\n        ]\n\n        let resultReceived = self.expectation(description: \"Result received\")\n\n        let resultRecevier:\n            @Sendable (AirshipPermission, AirshipPermissionStatus, AirshipPermissionStatus) async -> Void = {\n                permission,\n                start,\n                end in\n                XCTAssertEqual(.notDetermined, start)\n                XCTAssertEqual(.granted, end)\n                XCTAssertEqual(.location, permission)\n                resultReceived.fulfill()\n            }\n\n        let metadata = [\n            PromptPermissionAction.resultReceiverMetadataKey: resultRecevier\n        ]\n\n        let arguments = ActionArguments(\n            value: try! AirshipJSON.wrap(actionValue),\n            situation: .manualInvocation,\n            metadata: metadata\n        )\n\n        testPrompter.onPrompt = {\n            permission,\n            enableAirshipUsage,\n            fallbackSystemSetting in\n            return AirshipPermissionResult(startStatus: .notDetermined, endStatus: .granted)\n        }\n\n        _ = try await self.action.perform(arguments: arguments)\n        await self.fulfillment(of: [resultReceived], timeout: 10)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ProximityRegionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class ProximityRegionTest: XCTestCase {\n    \n    private var defaultRegionId: String {\n        return \"\".padding(toLength: 255, withPad: \"PROXIMITY_ID\", startingAt: 0)\n    }\n    \n    /**\n     * Test creating a proximity region with a valid proximity ID major and minor\n     */\n    func testCreateValidProximityRegion() {\n        let region = ProximityRegion(proximityID: defaultRegionId, major: 1.0, minor: 2.0)\n        \n        XCTAssertNotNil(region)\n    }\n    \n    /**\n     * Test creating a proximity region with invalid proximity IDs\n     */\n    func testSetInvalidProximityID() {\n        var id = \"\".padding(toLength: 256, withPad: \"PROXIMITY_ID\", startingAt: 0)\n        \n        // test proximity ID greater than max\n        var region = ProximityRegion(proximityID: id, major: 1.0, minor: 2.0)\n        XCTAssertNil(region, \"Proximity region should be nil if proximity ID fails to set.\")\n\n        // test proximity ID less than min\n        id = \"\"\n        region = ProximityRegion(proximityID: id, major: 1.0, minor: 2.0)\n        XCTAssertNil(region, \"Proximity region should be nil if proximity ID fails to set.\")\n    }\n    \n    /**\n     * Test creating a proximity region with invalid major and minor\n     */\n    func testSetInvalidMajorMinor() {\n        var major: Double = -1\n        var minor: Double = -2\n        \n        // test major and minor less than min\n        var region = ProximityRegion(proximityID: defaultRegionId, major: major, minor: minor)\n        XCTAssertNil(region, \"Proximity region should be nil if major or minor fails to set.\")\n\n\n        // test major and minor greater than max\n        major = Double(UINT16_MAX + 1)\n        minor = Double(UINT16_MAX + 1)\n        region = ProximityRegion(proximityID: defaultRegionId, major: major, minor: minor)\n        XCTAssertNil(region, \"Proximity region should be nil if major or minor fails to set.\")\n    }\n    \n    /**\n     * Test creating a proximity region with a valid RSSI\n     */\n    func testSetValidRSSI() {\n        let region59dBm = ProximityRegion(proximityID: defaultRegionId, major: 1, minor: 2, rssi: -59)\n        \n        // test an RSSI of -59 dBm\n        XCTAssertNotNil(region59dBm)\n        \n        let region0dBm = ProximityRegion(proximityID: defaultRegionId, major: 1, minor: 2, rssi: 0)\n        \n        // test RSSI of 0 dBm\n        XCTAssertNotNil(region0dBm)\n    }\n    \n    /**\n     * Test creating a proximity region and setting a invalid RSSIs\n     */\n    func testSetInvalidRSSI() {\n        var region = ProximityRegion(proximityID: defaultRegionId, major: 1, minor: 2, rssi: 101)\n        XCTAssertNil(region, \"RSSIs over 100 or under -100 dBm should be ignored.\")\n        \n        region = ProximityRegion(proximityID: defaultRegionId, major: 1, minor: 2, rssi: -101)\n        XCTAssertNil(region, \"RSSIs over 100 or under -100 dBm should be ignored.\")\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RateAppActionTest.swift",
    "content": "import XCTest\n\n@testable import AirshipCore\n\nclass RateAppActionTest: XCTestCase {\n    private let testAppRater = TestAppRater()\n    private var configItunesID: String? = nil\n    private var action: RateAppAction!\n\n    override func setUpWithError() throws {\n        self.action = RateAppAction(\n            appRater: self.testAppRater\n        ) {\n            return self.configItunesID\n        }\n    }\n\n    func testShowPrompt() async throws {\n        let args: [String: Any] = [\n            \"show_link_prompt\": true,\n            \"itunes_id\": \"test id\",\n        ]\n\n        let result = try await action.perform(arguments:\n            ActionArguments(\n                value: try AirshipJSON.wrap(args),\n                situation: .manualInvocation\n            )\n        )\n        XCTAssertNil(result)\n        XCTAssertTrue(testAppRater.showPromptCalled)\n        XCTAssertNil(testAppRater.openStoreItunesID)\n    }\n\n    func testOpenAppStore() async throws {\n        let args: [String: Any] = [\n            \"itunes_id\": \"test id\"\n        ]\n\n        let result = try await action.perform(arguments:\n            ActionArguments(\n                value: try AirshipJSON.wrap(args),\n                situation: .manualInvocation\n            )\n        )\n        XCTAssertNil(result)\n        XCTAssertFalse(testAppRater.showPromptCalled)\n        XCTAssertEqual(\"test id\", testAppRater.openStoreItunesID)\n    }\n\n    func testOpenAppStoreFallbackItunesID() async throws {\n        self.configItunesID = \"config iTunes ID\"\n        let args: [String: Any] = [:]\n\n        let result = try await action.perform(arguments:\n            ActionArguments(\n                value: try AirshipJSON.wrap(args),\n                situation: .manualInvocation\n            )\n        )\n        XCTAssertNil(result)\n        XCTAssertFalse(testAppRater.showPromptCalled)\n        XCTAssertEqual(configItunesID, testAppRater.openStoreItunesID)\n    }\n\n    func testNilConfig() async throws {\n        self.configItunesID = \"config iTunes ID\"\n\n        let result = try await action.perform(arguments:\n            ActionArguments(\n                value: AirshipJSON.null,\n                situation: .manualInvocation\n            )\n        )\n        XCTAssertNil(result)\n        XCTAssertFalse(testAppRater.showPromptCalled)\n        XCTAssertEqual(configItunesID, testAppRater.openStoreItunesID)\n    }\n\n    func testNoItunesID() async throws {\n        self.configItunesID = nil\n\n        do {\n            _ = try await action.perform(arguments:\n                ActionArguments(\n                    value: AirshipJSON.null,\n                    situation: .manualInvocation\n                )\n            )\n            XCTFail(\"should throw\")\n        } catch {}\n\n        XCTAssertFalse(testAppRater.showPromptCalled)\n        XCTAssertNil(testAppRater.openStoreItunesID)\n    }\n\n    func testInvalidArgs() async throws {\n        self.configItunesID = \"config id\"\n        do {\n            _ = try await action.perform(arguments:\n                ActionArguments(\n                    string: \"invalid\"\n                )\n            )\n            XCTFail(\"should throw\")\n        } catch {}\n        XCTAssertFalse(testAppRater.showPromptCalled)\n        XCTAssertNil(testAppRater.openStoreItunesID)\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations: [ActionSituation] = [\n            .manualInvocation,\n            .automation,\n            .foregroundPush,\n            .foregroundInteractiveButton,\n            .webViewInvocation,\n            .launchedFromPush,\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n    }\n\n    func testRejectsArguments() async throws {\n        let invalidSituations: [ActionSituation] = [\n            .backgroundPush,\n            .backgroundInteractiveButton,\n        ]\n\n        for situation in invalidSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n\n    fileprivate class TestAppRater: AppRaterProtocol, @unchecked Sendable {\n        var showPromptCalled = false\n        var openStoreItunesID: String? = nil\n\n        func openStore(itunesID: String) async throws {\n            openStoreItunesID = itunesID\n        }\n\n        func showPrompt() throws {\n            showPromptCalled = true\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RegionEventTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class RegionEventTest: XCTestCase {\n    \n    private var coordinates: (latitude: Double, longitude: Double) = (45.5200, 122.6819)\n    private var validRegionId: String {\n        return \"\".padding(toLength: 255, withPad: \"REGION_ID\", startingAt: 0)\n    }\n    private var validSource: String {\n        return \"\".padding(toLength: 255, withPad: \"SOURCE\", startingAt: 0)\n    }\n    \n    /**\n     * Test region event data directly.\n     */\n    func testRegionEventData() {\n        let circular = CircularRegion(radius: 11, latitude: coordinates.latitude, longitude: coordinates.longitude)\n        let proximity = ProximityRegion(proximityID: \"proximity_id\", major: 1, minor: 11, rssi: -59,\n                                        latitude: coordinates.latitude, longitude: coordinates.longitude)\n        let event = RegionEvent(regionID: \"region_id\", source: \"source\", boundaryEvent: .enter, circularRegion: circular, proximityRegion: proximity)\n        \n        let expected: [String: Any] = [\n            \"action\": \"enter\",\n            \"region_id\": \"region_id\",\n            \"source\": \"source\",\n            \"circular_region\": [\n                \"latitude\": \"45.5200000\",\n                \"longitude\": \"122.6819000\",\n                \"radius\": \"11.0\"\n            ],\n            \"proximity\": [\n                \"minor\": 11,\n                \"rssi\": -59,\n                \"major\": 1,\n                \"proximity_id\": \"proximity_id\",\n                \"latitude\": \"45.5200000\",\n                \"longitude\": \"122.6819000\"\n            ]\n        ]\n        \n        XCTAssertEqual(try! AirshipJSON.wrap(expected), try! event?.eventBody(stringifyFields: true))\n    }\n    \n    /**\n     * Test setting a region event ID.\n     */\n    func testSetRegionEventID() {\n        var event = RegionEvent(regionID: self.validRegionId, source: self.validSource, boundaryEvent: .enter)\n        XCTAssertEqual(self.validRegionId, event?.regionID)\n        \n        let invalidRegionId = \"\".padding(toLength: 256, withPad: \"REGION_ID\", startingAt: 0)\n        event = RegionEvent(regionID: invalidRegionId, source: self.validSource, boundaryEvent: .enter)\n        XCTAssertNil(event, \"Region IDs larger than 255 characters should be ignored\")\n        \n        event = RegionEvent(regionID: \"\", source: self.validSource, boundaryEvent: .enter)\n        XCTAssertNil(event, \"Region IDs less than 1 character should be ignored\")\n    }\n    \n    /**\n     * Test setting a region event source.\n     */\n    func testSetSource() {\n        var event = RegionEvent(regionID: self.validRegionId, source: self.validSource, boundaryEvent: .enter)\n        XCTAssertEqual(event?.source, validSource, \"255 character source should be valid\")\n\n        let invalidSource = \"\".padding(toLength: 256, withPad: \"source\", startingAt: 0)\n        event = RegionEvent(regionID: self.validRegionId, source: invalidSource, boundaryEvent: .enter)\n        XCTAssertNil(event, \"Sources larger than 255 characters should be ignored\")\n\n        event = RegionEvent(regionID: self.validRegionId, source: \"\", boundaryEvent: .enter)\n        XCTAssertNil(event, \"Sources less than 1 character should be ignored\")\n\n        event = RegionEvent(regionID: self.validRegionId, source: self.validSource, boundaryEvent: .enter)\n        XCTAssertEqual(event?.source, validSource, \"255 character source should be valid\")\n    }\n    \n    /**\n     * Test creating a region event without a proximity or circular region\n     */\n    func testRegionEvent() {\n        let event = RegionEvent(regionID: self.validRegionId, source: self.validSource, boundaryEvent: .enter)\n        \n        let expected: [String: Any] = [\n            \"action\": \"enter\",\n            \"region_id\": \"\\(self.validRegionId)\",\n            \"source\": \"\\(self.validSource)\",\n        ]\n\n        XCTAssertEqual(try! AirshipJSON.wrap(expected), try! event?.eventBody(stringifyFields: true))\n\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteConfigManagerTest.swift",
    "content": "import XCTest\n\n@testable import AirshipCore\n\nclass RemoteConfigManagerTest: XCTestCase {\n\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let testRemoteData = TestRemoteData()\n    private let notificationCenter = AirshipNotificationCenter.shared\n\n    private var privacyManager: TestPrivacyManager!\n    private var remoteConfigManager: RemoteConfigManager!\n    private var config: RuntimeConfig = RuntimeConfig.testConfig()\n    override func setUp() async throws {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: self.config,\n            defaultEnabledFeatures: .all,\n            notificationCenter: self.notificationCenter\n        )\n\n        self.remoteConfigManager = RemoteConfigManager(\n            config: config,\n            remoteData: self.testRemoteData,\n            privacyManager: self.privacyManager,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"0.0.0\"\n        )\n        self.remoteConfigManager.airshipReady()\n    }\n\n    @MainActor\n    func testEmptyConfig() throws {\n        self.config.updateRemoteConfig(\n            RemoteConfig(\n                airshipConfig: RemoteConfig.AirshipConfig(\n                    remoteDataURL: \"cool://remote\",\n                    deviceAPIURL: \"cool://devices\",\n                    analyticsURL: \"cool://analytics\",\n                    meteredUsageURL: \"cool://meteredUsage\"\n                )\n            )\n        )\n\n        let payload = RemoteDataPayload(\n            type: \"app_config\",\n            timestamp: Date(),\n            data: AirshipJSON.null,\n            remoteDataInfo: nil\n        )\n\n        let expectation = expectation(description: \"config updated\")\n        self.config.addRemoteConfigListener(notifyCurrent: false) { _, new in\n            XCTAssertEqual(RemoteConfig(), new)\n            expectation.fulfill()\n        }\n\n        self.testRemoteData.payloads = [payload]\n        wait(for: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testRemoteConfig() throws {\n        let remoteConfig = RemoteConfig(\n            airshipConfig: RemoteConfig.AirshipConfig(\n                remoteDataURL: \"cool://remote\",\n                deviceAPIURL: \"cool://devices\",\n                analyticsURL: \"cool://analytics\",\n                meteredUsageURL: \"cool://meteredUsage\"\n            ),\n            meteredUsageConfig: RemoteConfig.MeteredUsageConfig(\n                isEnabled: true,\n                initialDelayMilliseconds: nil,\n                intervalMilliseconds: nil\n            )\n        )\n\n        let payload = RemoteDataPayload(\n            type: \"app_config\",\n            timestamp: Date(),\n            data: try! AirshipJSON.wrap(remoteConfig),\n            remoteDataInfo: nil\n        )\n\n        let expectation = expectation(description: \"config updated\")\n        self.config.addRemoteConfigListener(notifyCurrent: false) { _, new in\n            XCTAssertEqual(remoteConfig, new)\n            expectation.fulfill()\n        }\n\n        self.testRemoteData.payloads = [payload]\n        wait(for: [expectation], timeout: 10.0)\n    }\n\n    @MainActor\n    func testCombienConfig() throws {\n        let iosConfig = RemoteConfig(\n            airshipConfig: RemoteConfig.AirshipConfig(\n                remoteDataURL: \"ios://remote\",\n                deviceAPIURL: \"ios://devices\",\n                analyticsURL: \"ios://analytics\",\n                meteredUsageURL: \"ios://meteredUsage\"\n            )\n        )\n\n        let commonConfig = RemoteConfig(\n            airshipConfig: RemoteConfig.AirshipConfig(\n                remoteDataURL: \"common://remote\",\n                deviceAPIURL: \"common://devices\",\n                analyticsURL: \"common://analytics\",\n                meteredUsageURL: \"common://meteredUsage\"\n            ),\n            meteredUsageConfig: RemoteConfig.MeteredUsageConfig(\n                isEnabled: true,\n                initialDelayMilliseconds: nil,\n                intervalMilliseconds: nil\n            )\n        )\n\n        let expectedConfig = RemoteConfig(\n            airshipConfig: iosConfig.airshipConfig,\n            meteredUsageConfig: commonConfig.meteredUsageConfig\n        )\n\n        let platformPayload = RemoteDataPayload(\n            type: \"app_config:ios\",\n            timestamp: Date(),\n            data: try! AirshipJSON.wrap(iosConfig),\n            remoteDataInfo: nil\n        )\n\n        let commonPayload = RemoteDataPayload(\n            type: \"app_config\",\n            timestamp: Date(),\n            data: try! AirshipJSON.wrap(commonConfig),\n            remoteDataInfo: nil\n        )\n\n        let expectation = expectation(description: \"config updated\")\n        self.config.addRemoteConfigListener(notifyCurrent: false) { _, new in\n            XCTAssertEqual(expectedConfig, new)\n            expectation.fulfill()\n        }\n\n        self.testRemoteData.payloads = [commonPayload, platformPayload]\n        wait(for: [expectation], timeout: 10.0)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteConfigTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class RemoteConfigTest: XCTestCase {\n\n    private let encoder = JSONEncoder()\n    private let decoder = JSONDecoder()\n\n    func testParseEmpty() throws {\n        let json = \"{}\"\n\n        let emptyConfig = try self.decoder.decode(RemoteConfig.self, from: json.data(using: .utf8)!)\n        XCTAssertEqual(emptyConfig, RemoteConfig())\n    }\n\n    func testJson() throws {\n        let json = \"\"\"\n            {\n               \"metered_usage\":{\n                  \"initial_delay_ms\":100,\n                  \"interval_ms\":200,\n                  \"enabled\":true\n               },\n               \"airship_config\":{\n                  \"device_api_url\":\"device-api-url\",\n                  \"analytics_url\":\"analytics-url\",\n                  \"wallet_url\":\"wallet-url\",\n                  \"remote_data_url\":\"remote-data-url\",\n                  \"metered_usage_url\":\"metered-usage-url\"\n               },\n               \"contact_config\":{\n                  \"max_cra_resolve_age_ms\":300,\n                  \"foreground_resolve_interval_ms\":400\n               },\n               \"fetch_contact_remote_data\":true,\n               \"disabled_features\": [\"push\", \"analytics\"],\n               \"in_app_config\": {\n                   \"additional_audience_check\": {\n                       \"enabled\": true,\n                       \"context\": \"json-value\",\n                       \"url\": \"https://test.url\"\n                   }\n               }\n            }\n        \"\"\"\n\n        let expected = RemoteConfig(\n            airshipConfig: .init(\n                remoteDataURL: \"remote-data-url\",\n                deviceAPIURL: \"device-api-url\",\n                analyticsURL: \"analytics-url\",\n                meteredUsageURL: \"metered-usage-url\"\n            ),\n            meteredUsageConfig: .init(\n                isEnabled: true,\n                initialDelayMilliseconds: 100,\n                intervalMilliseconds: 200\n            ),\n            fetchContactRemoteData: true,\n            contactConfig: .init(\n                foregroundIntervalMilliseconds: 400,\n                channelRegistrationMaxResolveAgeMilliseconds: 300\n            ),\n            disabledFeatures: [.push, .analytics],\n            iaaConfig: .init(\n                retryingQueue: nil,\n                additionalAudienceConfig: .init(isEnabled: true, context: \"json-value\", url: \"https://test.url\"))\n        )\n\n        let config = try self.decoder.decode(RemoteConfig.self, from: json.data(using: .utf8)!)\n        XCTAssertEqual(config, expected)\n\n        let roundTrip = try self.decoder.decode(RemoteConfig.self, from: try self.encoder.encode(expected))\n        XCTAssertEqual(roundTrip, expected)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteDataAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class RemoteDataAPIClientTest: AirshipBaseTest {\n    var remoteDataAPIClient: RemoteDataAPIClient!\n    private let testSession: TestAirshipRequestSession = TestAirshipRequestSession()\n\n    private static let validData = \"\"\"\n         {\n            \"message_center\":{\n               \"background_color\":\"0000FF\",\n               \"font\":\"Comic Sans\"\n            }\n         }\n    \"\"\"\n\n    private static let validResponse = \"\"\"\n        {\n           \"ok\":true,\n           \"payloads\":[\n              {\n                 \"type\":\"test_data_type\",\n                 \"timestamp\":\"2017-01-01T12:00:00\",\n                 \"data\":\\(validData)\n              }\n           ]\n        }\n    \"\"\"\n\n    private let exampleURL: URL = URL(string: \"exampleurl://\")!\n\n    override func setUpWithError() throws {\n        self.remoteDataAPIClient = RemoteDataAPIClient(\n            config: self.config,\n            session: self.testSession\n        )\n    }\n\n    func testFetch() async throws {\n        self.testSession.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [\"Last-Modified\": \"new last modified\"]\n        )\n        self.testSession.data = RemoteDataAPIClientTest.validResponse.data(using: .utf8)\n\n        let exampleURL = self.exampleURL\n        let response = try await self.remoteDataAPIClient.fetchRemoteData(\n            url: exampleURL,\n            auth: .contactAuthToken(identifier: \"some contact ID\"),\n            lastModified: \"current last modified\"\n        ) { lastModified in\n            XCTAssertEqual(lastModified, \"new last modified\")\n            return RemoteDataInfo(url: exampleURL, lastModifiedTime: lastModified, source: .contact)\n        }\n\n        let expectedResult = RemoteDataResult(\n            payloads: [\n                RemoteDataPayload(\n                    type: \"test_data_type\",\n                    timestamp: AirshipDateFormatter.date(fromISOString: \"2017-01-01T12:00:00\")!,\n                    data: try! AirshipJSON.from(json: RemoteDataAPIClientTest.validData),\n                    remoteDataInfo: RemoteDataInfo(\n                        url: self.exampleURL,\n                        lastModifiedTime: \"new last modified\",\n                        source: .contact\n                    )\n                )\n            ],\n            remoteDataInfo: RemoteDataInfo(\n                url: self.exampleURL,\n                lastModifiedTime: \"new last modified\",\n                source: .contact\n            )\n        )\n\n        let expectedHeaders = [\n            \"X-UA-Appkey\": \"\\(config.appCredentials.appKey)\",\n            \"If-Modified-Since\": \"current last modified\",\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ]\n\n        XCTAssertEqual(200, response.statusCode)\n        XCTAssertEqual(expectedResult, response.result)\n\n        XCTAssertEqual(\"GET\", self.testSession.lastRequest?.method)\n        XCTAssertEqual(self.exampleURL, self.testSession.lastRequest?.url)\n        XCTAssertEqual(expectedHeaders, self.testSession.lastRequest?.headers)\n        XCTAssertEqual(AirshipRequestAuth.contactAuthToken(identifier: \"some contact ID\"), self.testSession.lastRequest?.auth)\n    }\n\n    func testFetch304() async throws {\n        self.testSession.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 304,\n            httpVersion: \"\",\n            headerFields: [\"Last-Modified\": \"new last modified\"]\n        )\n\n        let exampleURL = self.exampleURL\n        let response = try await self.remoteDataAPIClient.fetchRemoteData(\n            url: self.exampleURL,\n            auth: .contactAuthToken(identifier: \"some contact ID\"),\n            lastModified: \"current last modified\"\n        ) { lastModified in\n            XCTFail(\"Should not be reached\")\n            return RemoteDataInfo(url: exampleURL, lastModifiedTime: lastModified, source: .contact)\n        }\n\n        XCTAssertEqual(304, response.statusCode)\n        XCTAssertNil(response.result)\n    }\n\n    func testEmptyResponse() async throws {\n        self.testSession.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [\"Last-Modified\": \"new last modified\"]\n        )\n\n        self.testSession.data = \"{ \\\"ok\\\": true }\".data(using: .utf8)\n\n        let exampleURL = self.exampleURL\n        let response = try await self.remoteDataAPIClient.fetchRemoteData(\n            url: self.exampleURL,\n            auth: .contactAuthToken(identifier: \"some contact ID\"),\n            lastModified: \"current last modified\"\n        ) { lastModified in\n            return RemoteDataInfo(url: exampleURL, lastModifiedTime: lastModified, source: .contact)\n        }\n\n        let expectedResult = RemoteDataResult(\n            payloads: [],\n            remoteDataInfo: RemoteDataInfo(\n                url: self.exampleURL,\n                lastModifiedTime: \"new last modified\",\n                source: .contact\n            )\n        )\n\n        XCTAssertEqual(200, response.statusCode)\n        XCTAssertEqual(expectedResult, response.result)\n    }\n\n    func testNoLastModified() async throws {\n        self.testSession.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [:]\n        )\n\n        self.testSession.data = \"{ \\\"ok\\\": true }\".data(using: .utf8)\n\n        let exampleURL = self.exampleURL\n        let response = try await self.remoteDataAPIClient.fetchRemoteData(\n            url: self.exampleURL,\n            auth: .basicAppAuth,\n            lastModified: nil\n        ) { lastModified in\n            XCTAssertNil(lastModified)\n            return RemoteDataInfo(url: exampleURL, lastModifiedTime: lastModified, source: .app)\n        }\n\n        let expectedResult = RemoteDataResult(\n            payloads: [],\n            remoteDataInfo: RemoteDataInfo(\n                url: self.exampleURL,\n                lastModifiedTime: nil,\n                source: .app\n            )\n        )\n\n        let expectedHeaders = [\n            \"X-UA-Appkey\": config.appCredentials.appKey,\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ]\n\n        XCTAssertEqual(200, response.statusCode)\n        XCTAssertEqual(expectedResult, response.result)\n        XCTAssertEqual(expectedHeaders, self.testSession.lastRequest?.headers)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteDataProviderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nclass RemoteDataProviderTest: XCTestCase {\n    private let delegate = TestRemoteDataProviderDelegate(\n        source: .app,\n        storeName: \"RemoteDataProviderTest\"\n    )\n\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var provider: RemoteDataProvider!\n\n    override func setUpWithError() throws {\n        self.provider = RemoteDataProvider(dataStore: self.dataStore, delegate: self.delegate)\n    }\n\n    func testRefresh() async throws {\n        let locale = Locale(identifier: \"bs\")\n        let randomValue = 100\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        let refreshResult = RemoteDataResult(\n            payloads: [\n                RemoteDataTestUtils.generatePayload(\n                    type: \"some type\",\n                    timestamp: Date(),\n                    data: [\"cool\": \"data\"],\n                    remoteDataInfo: remoteDataInfo\n                ),\n                RemoteDataTestUtils.generatePayload(\n                    type: \"some other type\",\n                    timestamp: Date(),\n                    data: [\"cool\": \"data\"],\n                    remoteDataInfo: remoteDataInfo\n                )\n            ],\n            remoteDataInfo: remoteDataInfo\n        )\n        \n        self.delegate.fetchRemoteDataCallback = { requestLocale, requestRandomValue, lastRemoteInfo in\n            XCTAssertNil(lastRemoteInfo)\n            XCTAssertEqual(locale, requestLocale)\n            XCTAssertEqual(randomValue, requestRandomValue)\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        let result = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: locale,\n            randomeValue: randomValue\n        )\n        XCTAssertEqual(result, .newData)\n\n        let payloads = await self.provider.payloads(types: [\"some type\", \"some other type\"])\n        XCTAssertEqual(refreshResult.payloads, payloads)\n    }\n\n    func testRefreshDisabled() async throws {\n        let source = self.delegate.source\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            let remoteDataInfo = RemoteDataInfo(\n                url: URL(string: \"example://\")!,\n                lastModifiedTime: \"some last modified\",\n                source: source\n            )\n\n            let refreshResult = RemoteDataResult(\n                payloads: [\n                    RemoteDataTestUtils.generatePayload(\n                        type: \"foo\",\n                        timestamp: Date(),\n                        data: [\"cool\": \"data\"],\n                        remoteDataInfo: remoteDataInfo\n                    )\n                ],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        // Load data\n        var refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .newData)\n\n        var payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertFalse(payloads.isEmpty)\n\n\n        _ = await self.provider.setEnabled(false)\n\n        payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertTrue(payloads.isEmpty)\n\n        // should clear data\n        refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .newData)\n\n        // should no-op\n        refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .skipped)\n\n        _ = await self.provider.setEnabled(true)\n        payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertTrue(payloads.isEmpty)\n    }\n\n    func testRefreshSkipped() async throws {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            let refreshResult = RemoteDataResult(\n                payloads: [\n                    RemoteDataTestUtils.generatePayload(\n                        type: \"foo\",\n                        timestamp: Date(),\n                        data: [\"cool\": \"data\"],\n                        remoteDataInfo: remoteDataInfo\n                    )\n                ],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        // Load data\n        var refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .newData)\n        var payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertFalse(payloads.isEmpty)\n\n        // Refresh same data\n        self.delegate.isRemoteDataInfoUpToDateCallback = { info, locale, randomValue in\n            XCTAssertEqual(remoteDataInfo, info)\n            XCTAssertEqual(Locale.current, locale)\n            XCTAssertEqual(200, randomValue)\n            return true\n        }\n\n        refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 200\n        )\n        XCTAssertEqual(refreshResult, .skipped)\n        payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertFalse(payloads.isEmpty)\n\n        // Change token update\n        refreshResult = await self.provider.refresh(\n            changeToken: \"new change\",\n            locale: Locale.current,\n            randomeValue: 200\n        )\n        XCTAssertEqual(refreshResult, .newData)\n        payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertFalse(payloads.isEmpty)\n\n        // Out of date\n        self.delegate.isRemoteDataInfoUpToDateCallback = { info, locale, randomValue in\n            XCTAssertEqual(remoteDataInfo, info)\n            XCTAssertEqual(Locale.current, locale)\n            XCTAssertEqual(200, randomValue)\n            return false\n        }\n\n        refreshResult = await self.provider.refresh(\n            changeToken: \"new change\",\n            locale: Locale.current,\n            randomeValue: 200\n        )\n        XCTAssertEqual(refreshResult, .newData)\n        payloads = await self.provider.payloads(types: [\"foo\"])\n        XCTAssertFalse(payloads.isEmpty)\n    }\n\n    func testStatus() async throws {\n        let de = Locale(identifier: \"de\")\n\n        var status: RemoteDataSourceStatus!\n        status = await self.provider.status(changeToken: \"change\", locale: de, randomeValue: 100)\n        // No data\n        XCTAssertEqual(status, .outOfDate)\n\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        // Load data\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            let refreshResult = RemoteDataResult(\n                payloads: [\n                    RemoteDataTestUtils.generatePayload(\n                        type: \"foo\",\n                        timestamp: Date(),\n                        data: [\"cool\": \"data\"],\n                        remoteDataInfo: remoteDataInfo\n                    )\n                ],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        _ = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n\n\n\n        // Up to date\n        self.delegate.isRemoteDataInfoUpToDateCallback = { info, locale, randomValue in\n            return true\n        }\n        status = await self.provider.status(changeToken: \"change\", locale: de, randomeValue: 100)\n        XCTAssertEqual(status, .upToDate)\n\n        // Stale\n        status = await self.provider.status(changeToken: \"some other\", locale: de, randomeValue: 100)\n        XCTAssertEqual(status, .stale)\n\n        self.delegate.isRemoteDataInfoUpToDateCallback = { info, locale, randomValue in\n            return false\n        }\n\n        // Out of date from random value\n        status = await self.provider.status(changeToken: \"change\", locale: de, randomeValue: 200)\n        XCTAssertEqual(status, .outOfDate)\n\n        // Out of date check over stale\n        status = await self.provider.status(changeToken: \"some other\", locale: de, randomeValue: 100)\n        XCTAssertEqual(status, .outOfDate)\n    }\n\n    func testRefresh304() async throws {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            let refreshResult = RemoteDataResult(\n                payloads: [\n                    RemoteDataTestUtils.generatePayload(\n                        type: \"foo\",\n                        timestamp: Date(),\n                        data: [\"cool\": \"data\"],\n                        remoteDataInfo: remoteDataInfo\n                    )\n                ],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        // Load data\n        var refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .newData)\n\n        // 304\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            return AirshipHTTPResponse(result: nil, statusCode: 304, headers: [:])\n        }\n\n        refreshResult = await self.provider.refresh(\n            changeToken: \"new change\",\n            locale: Locale.current,\n            randomeValue: 200\n        )\n        XCTAssertEqual(refreshResult, .skipped)\n    }\n\n    func testRefresh304WithoutLastModifiedFails() async throws {\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            return AirshipHTTPResponse(result: nil, statusCode: 304, headers: [:])\n        }\n\n        let refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .failed)\n    }\n\n\n    func testRefreshClientError() async throws {\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            return AirshipHTTPResponse(result: nil, statusCode: 400, headers: [:])\n        }\n\n        let refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .failed)\n    }\n\n    func testRefreshServerError() async throws {\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            return AirshipHTTPResponse(result: nil, statusCode: 500, headers: [:])\n        }\n\n        let refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .failed)\n    }\n\n    func testRefreshThrows() async throws {\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            throw AirshipErrors.error(\"some error\")\n        }\n\n        let refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .failed)\n    }\n\n    func testNotifyOutdated() async throws {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        let requestCount = AirshipAtomicValue<Int>(0)\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            requestCount.value += 1\n            let refreshResult = RemoteDataResult(\n                payloads: [\n                    RemoteDataTestUtils.generatePayload(\n                        type: \"foo\",\n                        timestamp: Date(),\n                        data: [\"cool\": \"data\"],\n                        remoteDataInfo: remoteDataInfo\n                    )\n                ],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        self.delegate.isRemoteDataInfoUpToDateCallback = { _, _, _ in\n            return true\n        }\n\n        // Load data\n        var refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .newData)\n        XCTAssertEqual(1, requestCount.value)\n\n        // skipped\n        refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .skipped)\n        XCTAssertEqual(1, requestCount.value)\n\n\n        // Notify different outdated remote info\n        _ = await self.provider.notifyOutdated(\n            remoteDataInfo: RemoteDataInfo(\n                url: URL(string: \"example://\")!,\n                lastModifiedTime: \"some other last modified\",\n                source: self.delegate.source\n            )\n        )\n\n        // still skipped\n        refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .skipped)\n        XCTAssertEqual(1, requestCount.value)\n\n        // Notify outdated remote info\n        _ = await self.provider.notifyOutdated(\n            remoteDataInfo: remoteDataInfo\n        )\n\n        // Refresh\n        refreshResult = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 100\n        )\n        XCTAssertEqual(refreshResult, .newData)\n        XCTAssertEqual(2, requestCount.value)\n    }\n\n    func testIsCurrent() async throws {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        // No data\n        var isCurrent = await self.provider.isCurrent(\n            locale: Locale.current,\n            randomeValue: 100,\n            remoteDataInfo: remoteDataInfo\n        )\n        XCTAssertFalse(isCurrent)\n\n\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            let refreshResult = RemoteDataResult(\n                payloads: [],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        // Load data\n        _ = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 0\n        )\n\n        self.delegate.isRemoteDataInfoUpToDateCallback = { currentInfo, locale, randomValue in\n            XCTAssertEqual(currentInfo, remoteDataInfo)\n            XCTAssertEqual(Locale.current, locale)\n            XCTAssertEqual(0, randomValue)\n            return true\n        }\n\n        isCurrent = await self.provider.isCurrent(locale: Locale.current, randomeValue: 0, remoteDataInfo: remoteDataInfo)\n        XCTAssertTrue(isCurrent)\n\n        self.delegate.isRemoteDataInfoUpToDateCallback = { _, _, _ in\n            return false\n        }\n\n        isCurrent = await self.provider.isCurrent(locale: Locale.current, randomeValue: 0, remoteDataInfo: remoteDataInfo)\n        XCTAssertFalse(isCurrent)\n    }\n\n    func testIsCurrentDifferentRemoteDataInfo() async throws {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some last modified\",\n            source: self.delegate.source\n        )\n\n        self.delegate.fetchRemoteDataCallback = { _, _, _ in\n            let refreshResult = RemoteDataResult(\n                payloads: [],\n                remoteDataInfo: remoteDataInfo\n            )\n\n            return AirshipHTTPResponse(result: refreshResult, statusCode: 200, headers: [:])\n        }\n\n        // Load data\n        _ = await self.provider.refresh(\n            changeToken: \"change\",\n            locale: Locale.current,\n            randomeValue: 0\n        )\n\n        self.delegate.isRemoteDataInfoUpToDateCallback = { currentInfo, locale, randomValue in\n            return true\n        }\n\n        var isCurrent = await self.provider.isCurrent(locale: Locale.current, randomeValue: 0, remoteDataInfo: remoteDataInfo)\n        XCTAssertTrue(isCurrent)\n\n        let updatedRemoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: \"some other last modified\",\n            source: self.delegate.source\n        )\n\n        isCurrent = await self.provider.isCurrent(locale: Locale.current, randomeValue: 0, remoteDataInfo: updatedRemoteDataInfo)\n        XCTAssertFalse(isCurrent)\n    }\n}\n\nfileprivate class TestRemoteDataProviderDelegate: RemoteDataProviderDelegate, @unchecked Sendable {\n\n    let source: RemoteDataSource\n    let storeName: String\n\n    var isRemoteDataInfoUpToDateCallback: (@Sendable (RemoteDataInfo, Locale, Int) async -> Bool)?\n    var fetchRemoteDataCallback: (@Sendable (Locale, Int, RemoteDataInfo?) async throws -> AirshipHTTPResponse<RemoteDataResult>)?\n\n    init(source: RemoteDataSource, storeName: String) {\n        self.source = source\n        self.storeName = storeName\n    }\n\n    func isRemoteDataInfoUpToDate(\n        _ remoteDataInfo: RemoteDataInfo, locale: Locale, randomValue: Int\n    ) async -> Bool {\n        return await self.isRemoteDataInfoUpToDateCallback?(remoteDataInfo, locale, randomValue) ?? true\n    }\n\n    func fetchRemoteData(locale: Locale, randomValue: Int, lastRemoteDataInfo: RemoteDataInfo?) async throws -> AirshipHTTPResponse<RemoteDataResult> {\n        return try await self.fetchRemoteDataCallback!(locale, randomValue, lastRemoteDataInfo)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteDataStoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class RemoteDataStoreTest: XCTestCase {\n\n    private let remoteDataStore: RemoteDataStore = RemoteDataStore(\n        storeName: \"RemoteDataStoreTest\",\n        inMemory: true\n    )\n\n    func testFirstRemoteData() async throws {\n        let testPayload = createRemoteDataPayload()\n\n        try await self.remoteDataStore.overwriteCachedRemoteData([testPayload])\n\n        let remoteDataStorePayloads = try await self.remoteDataStore.fetchRemoteDataFromCache()\n\n        XCTAssertEqual([testPayload], remoteDataStorePayloads)\n    }\n\n    func testNewRemoteData() async throws {\n        let testPayloads = [\n            createRemoteDataPayload(),\n            createRemoteDataPayload(),\n            createRemoteDataPayload()\n        ].sorted(by: { first, second in\n            first.type > second.type\n        })\n\n        try await self.remoteDataStore.overwriteCachedRemoteData(testPayloads)\n\n        var remoteDataStorePayloads = try await self.remoteDataStore.fetchRemoteDataFromCache()\n            .sorted(by: { first, second in\n                first.type > second.type\n            })\n\n        XCTAssertEqual(testPayloads, remoteDataStorePayloads)\n\n        let testPayload = createRemoteDataPayload(withType: testPayloads[1].type)\n\n        // Sync only the modified message\n        try await self.remoteDataStore.overwriteCachedRemoteData([testPayload])\n\n        // Verify we only have the modified message with the updated title\n        remoteDataStorePayloads = try await self.remoteDataStore.fetchRemoteDataFromCache()\n\n        XCTAssertEqual([testPayload], remoteDataStorePayloads)\n    }\n\n\n    func createRemoteDataPayload(withType type: String? = nil) -> RemoteDataPayload {\n        return RemoteDataTestUtils.generatePayload(\n            type: type ?? UUID().uuidString,\n            timestamp: Date(),\n            data: [\"random\": UUID().uuidString],\n            source: .app\n        )\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteDataTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\nimport Combine\n\nfinal class RemoteDataTest: AirshipBaseTest {\n\n    private static let RefreshTask = \"RemoteData.refresh\"\n\n    private let contactProvider: TestRemoteDataProvider = TestRemoteDataProvider(source: .contact, enabled: false)\n    private let appProvider: TestRemoteDataProvider = TestRemoteDataProvider(source: .app, enabled: true)\n\n    private let testDate: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(\n        notificationCenter: NotificationCenter()\n    )\n    private let testContact: TestContact = TestContact()\n    private let testLocaleManager: TestLocaleManager = TestLocaleManager()\n    private let testWorkManager: TestWorkManager = TestWorkManager()\n    private var remoteData: RemoteData!\n    private var privacyManager: TestPrivacyManager!\n\n    override func setUp() async throws {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: self.config,\n            defaultEnabledFeatures: .all\n        )\n\n        self.testDate.dateOverride = Date()\n        self.testLocaleManager.currentLocale =  Locale(identifier: \"en-US\")\n        self.remoteData = await RemoteData(\n            config: config,\n            dataStore: self.dataStore,\n            localeManager: self.testLocaleManager,\n            privacyManager: self.privacyManager,\n            contact: self.testContact,\n            providers: [self.appProvider, self.contactProvider],\n            workManager: self.testWorkManager,\n            date: self.testDate,\n            notificationCenter: self.notificationCenter,\n            appVersion: \"SomeAppVersion\"\n        )\n        \n        await self.appProvider.setStatusCallback { _, _, _ in return .upToDate }\n        await self.contactProvider.setStatusCallback { _, _, _ in return .upToDate }\n    }\n\n    func testRemoteConfigUpdatedEnqueuesRefresh() async {\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n        await self.config.updateRemoteConfig(\n            RemoteConfig(\n                airshipConfig: .init(\n                    remoteDataURL: \"someURL\",\n                    deviceAPIURL: \"someURL\",\n                    analyticsURL: \"someURL\",\n                    meteredUsageURL: \"someURL\"\n                )\n            )\n        )\n        await self.remoteData.serialQueue.waitForCurrentOperations()\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    func testContactUpdateEnqueuesRefresh() {\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n        self.testContact.contactIDUpdatesSubject.send(\n            ContactIDInfo(contactID: \"some id\", isStable: true, namedUserID: nil)\n        )\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    func testLocaleUpdatesEnqueuesRefresh() {\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n        notificationCenter.post(\n            name: AirshipNotifications.LocaleUpdated.name\n        )\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    func testForegroundRefreshEnqueuesRefresh() {\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n        notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground\n        )\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n\n        notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground\n        )\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n\n        self.testDate.offset += self.remoteData.refreshInterval\n        notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground\n        )\n        XCTAssertEqual(2, testWorkManager.workRequests.count)\n    }\n\n    func testAirshipReadyEnqueuesRefresh() async {\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n        await self.remoteData.airshipReady()\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    func testNotifyOutdatedContact() async {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: nil,\n            source: .contact\n        )\n\n        let expectation = XCTestExpectation()\n        await self.contactProvider.setNotifyOutdatedCallback { @Sendable info in\n            XCTAssertEqual(remoteDataInfo, info)\n            expectation.fulfill()\n            return true\n        }\n\n        await self.remoteData.notifyOutdated(remoteDataInfo: remoteDataInfo)\n\n        await self.fulfillment(of: [expectation], timeout: 10)\n    }\n\n    func testNotifyOutdatedApp() async {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        let expectation = XCTestExpectation()\n        await self.appProvider.setNotifyOutdatedCallback { @Sendable info in\n            XCTAssertEqual(remoteDataInfo, info)\n            expectation.fulfill()\n            return true\n        }\n\n        await self.remoteData.notifyOutdated(remoteDataInfo: remoteDataInfo)\n\n        await self.fulfillment(of: [expectation], timeout: 10)\n    }\n\n    func testNotifyOutdatedEnqueusRefreshTask() async {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n\n        await self.appProvider.setNotifyOutdatedCallback { _ in return false }\n        await self.remoteData.notifyOutdated(remoteDataInfo: remoteDataInfo)\n        XCTAssertEqual(0, testWorkManager.workRequests.count)\n\n        await self.appProvider.setNotifyOutdatedCallback { _ in return true }\n        await self.remoteData.notifyOutdated(remoteDataInfo: remoteDataInfo)\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    func testIsCurrentContact() async {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: nil,\n            source: .contact\n        )\n\n        let expectation = XCTestExpectation()\n        let testLocaleManager = self.testLocaleManager\n        await self.contactProvider.setIsCurrentCallback { @Sendable locale, _, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return false\n        }\n\n        let result = await self.remoteData.isCurrent(remoteDataInfo: remoteDataInfo)\n        await self.fulfillment(of: [expectation], timeout: 10)\n\n        XCTAssertFalse(result)\n    }\n\n    func testIsCurrentApp() async {\n        let remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"example://\")!,\n            lastModifiedTime: nil,\n            source: .app\n        )\n\n        let expectation = XCTestExpectation()\n        let testLocaleManager = self.testLocaleManager\n        await self.appProvider.setIsCurrentCallback { @Sendable locale, _, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return true\n        }\n\n        let result = await self.remoteData.isCurrent(remoteDataInfo: remoteDataInfo)\n        await self.fulfillment(of: [expectation], timeout: 10)\n\n        XCTAssertTrue(result)\n    }\n\n    func testContactStatus() async {\n        let expectation = XCTestExpectation()\n        let testLocaleManager = self.testLocaleManager\n\n        await self.contactProvider.setStatusCallback { @Sendable _, locale, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return .upToDate\n        }\n\n        let result = await self.remoteData.status(source: .contact)\n        await self.fulfillment(of: [expectation], timeout: 10)\n\n        XCTAssertEqual(.upToDate, result)\n    }\n\n    func testAppStatus() async {\n        let expectation = XCTestExpectation()\n        let testLocaleManager = self.testLocaleManager\n\n        await self.appProvider.setStatusCallback { @Sendable _, locale, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return .stale\n        }\n\n        let result = await self.remoteData.status(source: .app)\n        await self.fulfillment(of: [expectation], timeout: 10)\n\n        XCTAssertEqual(.stale, result)\n    }\n\n    @MainActor\n    func testContentAvailableRefresh() async {\n        XCTAssertEqual(0, self.testWorkManager.workRequests.count)\n\n        let json = try! AirshipJSON.wrap([\n            \"com.urbanairship.remote-data.update\": NSNumber(value: true)\n        ])\n        \n        let result = await self.remoteData.receivedRemoteNotification(json)\n        XCTAssertEqual(.newData, result)\n\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    @MainActor\n    func testSettingRefreshInterval() {\n        XCTAssertEqual(self.remoteData.refreshInterval, 10)\n        self.config.updateRemoteConfig(RemoteConfig(remoteDataRefreshIntervalMilliseconds: 9999 * 1000))\n        XCTAssertEqual(self.remoteData.refreshInterval, 9999)\n    }\n\n    func testPayloads() async {\n        let contactPayloads = [\n            RemoteDataTestUtils.generatePayload(\n                type: \"foo\",\n                timestamp: Date(),\n                data: [\"cool\": \"contact\"],\n                source: .contact\n            )\n        ]\n\n        let appPayloads = [\n            RemoteDataTestUtils.generatePayload(\n                type: \"foo\",\n                timestamp: Date(),\n                data: [\"cool\": \"app\"],\n                source: .app\n            ),\n            RemoteDataTestUtils.generatePayload(\n                type: \"bar\",\n                timestamp: Date(),\n                data: [\"not cool\": \"app\"],\n                source: .app\n            )\n        ]\n\n        await self.contactProvider.setPayloads(contactPayloads)\n        await self.appProvider.setPayloads(appPayloads)\n\n        let barResult = await self.remoteData.payloads(types: [\"bar\"])\n        XCTAssertEqual(barResult, [appPayloads[1]])\n\n        let fooResult = await self.remoteData.payloads(types: [\"foo\"])\n        XCTAssertEqual(fooResult, [appPayloads[0], contactPayloads[0]])\n\n        let barFooResult = await self.remoteData.payloads(types: [\"bar\", \"foo\"])\n        XCTAssertEqual(barFooResult, [appPayloads[1], appPayloads[0], contactPayloads[0]])\n\n        let bazResult = await self.remoteData.payloads(types: [\"baz\"])\n        XCTAssertEqual(bazResult, [])\n    }\n\n    func testPayloadUpdates() async {\n        await self.contactProvider.setRefreshCallback { @Sendable _, _, _ in\n            return .newData\n        }\n        await self.appProvider.setRefreshCallback{ @Sendable _, _, _ in\n            return .newData\n        }\n\n        let expectation = XCTestExpectation()\n        expectation.expectedFulfillmentCount = 2   // baseline + ≥1 refresh\n        expectation.assertForOverFulfill = false   // tolerate extra publishes\n\n        let first = XCTestExpectation()\n        let isFirst = AirshipAtomicValue<Bool>(false)\n\n        let subscription = self.remoteData.publisher(types: [\"foo\"])\n            .sink { payloads in\n                if isFirst.compareAndSet(expected: false, value: true) {\n                    first.fulfill()\n                }\n                expectation.fulfill()\n                XCTAssertTrue(payloads.isEmpty)\n            }\n\n        await self.fulfillment(of: [first], timeout: 10)\n        await self.launchRefreshTask()\n        await self.fulfillment(of: [expectation], timeout: 10)\n        subscription.cancel()\n    }\n\n\n    func testForceRefresh() async {\n        await self.contactProvider.setRefreshCallback { @Sendable _, _, _ in\n            return .newData\n        }\n        await self.appProvider.setRefreshCallback{ @Sendable _, _, _ in\n            return .skipped\n        }\n\n        self.testWorkManager.autoLaunchRequests = true\n        let refreshFinished = expectation(description: \"refresh finished\")\n\n        let remoteData = self.remoteData\n        Task.detached {\n            await remoteData?.forceRefresh()\n            refreshFinished.fulfill()\n        }\n\n        await self.fulfillment(of: [refreshFinished])\n\n        XCTAssertEqual(1, testWorkManager.workRequests.count)\n    }\n\n    func testRefreshProviders() async {\n        let expectation = XCTestExpectation()\n        expectation.expectedFulfillmentCount = 2\n        let testLocaleManager = self.testLocaleManager\n\n        await self.contactProvider.setRefreshCallback { @Sendable _, locale, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return .skipped\n        }\n\n        await self.appProvider.setRefreshCallback{ @Sendable _, locale, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return .newData\n        }\n        \n\n        let result = await self.launchRefreshTask()\n        await self.fulfillment(of: [expectation], timeout: 10)\n\n        XCTAssertEqual(result, .success)\n    }\n\n    func testRefreshProviderFailed() async {\n        let expectation = XCTestExpectation()\n        expectation.expectedFulfillmentCount = 2\n        let testLocaleManager = self.testLocaleManager\n\n        await self.contactProvider.setRefreshCallback { @Sendable _, locale, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return .failed\n        }\n\n        await self.appProvider.setRefreshCallback{ @Sendable _, locale, _ in\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            expectation.fulfill()\n            return .newData\n        }\n\n        let result = await self.launchRefreshTask()\n        await self.fulfillment(of: [expectation], timeout: 10)\n\n        XCTAssertEqual(result, .failure)\n    }\n\n    @MainActor\n    func testChangeTokenBgPush() async {\n        let changeToken = AirshipAtomicValue<String?>(nil)\n\n        // Capture the change token\n        let testLocaleManager = self.testLocaleManager\n        await self.contactProvider.setRefreshCallback { @Sendable change, locale, _ in\n            changeToken.value = change\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            return .failed\n        }\n        await self.appProvider.setRefreshCallback{ @Sendable _, locale, _ in\n            return .newData\n        }\n\n        await self.launchRefreshTask()\n        XCTAssertNotNil(changeToken.value)\n\n        let last = changeToken.value\n        await self.launchRefreshTask()\n        XCTAssertEqual(last, changeToken.value)\n\n        // Send bg push\n        _ = await self.remoteData.receivedRemoteNotification(\n            try! AirshipJSON.wrap(\n                [\n                    \"com.urbanairship.remote-data.update\": NSNumber(value: true)\n                ]\n            )\n        )\n\n        await self.launchRefreshTask()\n        XCTAssertNotEqual(last, changeToken.value)\n    }\n\n    @MainActor\n    func testChangeTokenAppForeground() async {\n        let changeToken = AirshipAtomicValue<String?>(nil)\n\n        // Capture the change token\n        let testLocaleManager = self.testLocaleManager\n        await self.contactProvider.setRefreshCallback { @Sendable change, locale, _ in\n            changeToken.value = change\n            XCTAssertEqual(testLocaleManager.currentLocale, locale)\n            return .failed\n        }\n        await self.appProvider.setRefreshCallback{ @Sendable _, locale, _ in\n            return .newData\n        }\n\n        await self.launchRefreshTask()\n        XCTAssertNotNil(changeToken.value)\n\n        var last = changeToken.value\n\n        // Foreground\n        notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground\n        )\n\n        await self.launchRefreshTask()\n        XCTAssertNotEqual(last, changeToken.value)\n\n        // Foreground again without changing clock\n        last = changeToken.value\n        notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground\n        )\n        await self.launchRefreshTask()\n        // Should not change\n        XCTAssertEqual(last, changeToken.value)\n\n        // Foreground after refresh interval\n        self.testDate.offset += self.remoteData.refreshInterval\n        notificationCenter.post(\n            name: AppStateTracker.didTransitionToForeground\n        )\n\n        await self.launchRefreshTask()\n        XCTAssertNotEqual(last, changeToken.value)\n    }\n\n    @MainActor\n    func testWaitForRefresh() async {\n        await self.contactProvider.setRefreshCallback{ _, _, _ in\n            return .failed\n        }\n\n        await self.appProvider.setRefreshCallback{ _, _, _ in\n            return .failed\n        }\n\n        let finished = AirshipMainActorValue(false)\n        let task = Task {\n            await self.remoteData.waitRefresh(source: .app)\n            finished.set(true)\n        }\n\n        await self.launchRefreshTask()\n        var isFinished = finished.value\n        XCTAssertFalse(isFinished)\n\n        await self.appProvider.setRefreshCallback{ _, _, _ in\n            return .newData\n        }\n\n        await self.launchRefreshTask()\n        await task.value\n        isFinished = finished.value\n        XCTAssertTrue(isFinished)\n    }\n\n    @discardableResult\n    private func launchRefreshTask() async -> AirshipWorkResult {\n        return try! await self.testWorkManager.launchTask(\n            request: AirshipWorkRequest(\n                workID: RemoteDataTest.RefreshTask\n            )\n        )!\n    }\n}\n\nfileprivate actor TestRemoteDataProvider: RemoteDataProviderProtocol {\n    private var statusCallback: ((String, Locale, Int) async -> RemoteDataSourceStatus)?\n    func setStatusCallback(callback: @escaping (String, Locale, Int) async -> RemoteDataSourceStatus) {\n        self.statusCallback = callback\n    }\n\n    func status(changeToken: String, locale: Locale, randomeValue: Int) async -> RemoteDataSourceStatus {\n        return await self.statusCallback!(changeToken, locale, randomeValue)\n    }\n\n    let source: RemoteDataSource\n\n    private var payloads: [RemoteDataPayload] = []\n    var enabled: Bool\n\n    private var notifyOutdatedCallback: ((RemoteDataInfo) -> Bool)?\n    func setNotifyOutdatedCallback(callback: @escaping (RemoteDataInfo) -> Bool) {\n        self.notifyOutdatedCallback = callback\n    }\n\n    private var isCurrentCallback: ((Locale, Int, RemoteDataInfo) async -> Bool)?\n    func setIsCurrentCallback(callback: @escaping (Locale, Int, RemoteDataInfo) async -> Bool) {\n        self.isCurrentCallback = callback\n    }\n\n    private var refreshCallback: ((String, Locale, Int) async -> RemoteDataRefreshResult)?\n    func setRefreshCallback(callback: @escaping (String, Locale, Int) async -> RemoteDataRefreshResult) {\n        self.refreshCallback = callback\n    }\n\n    init(source: RemoteDataSource, enabled: Bool) {\n        self.source = source\n        self.enabled = enabled\n    }\n\n    func setPayloads(_ payloads: [RemoteDataPayload]) {\n        self.payloads = payloads\n    }\n\n    func payloads(types: [String]) async -> [RemoteDataPayload] {\n        return payloads.filter { types.contains($0.type) }.sortedByType(types)\n    }\n\n    func notifyOutdated(remoteDataInfo: RemoteDataInfo) -> Bool {\n        return self.notifyOutdatedCallback!(remoteDataInfo)\n    }\n\n    func isCurrent(locale: Locale, randomeValue: Int, remoteDataInfo: RemoteDataInfo) async -> Bool {\n       return await self.isCurrentCallback!(locale, randomeValue, remoteDataInfo)\n    }\n\n    func refresh(changeToken: String, locale: Locale, randomeValue: Int) async -> RemoteDataRefreshResult {\n        return await self.refreshCallback!(changeToken, locale, randomeValue)\n    }\n\n    func setEnabled(_ enabled: Bool) -> Bool {\n        guard self.enabled != enabled else {\n            return false\n        }\n\n        self.enabled = enabled\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteDataTestUtils.swift",
    "content": "\npublic import Foundation\n\n@testable\npublic import AirshipCore\n\npublic class RemoteDataTestUtils: NSObject {\n    public class func generatePayload(\n        type: String,\n        timestamp: Date,\n        data: [AnyHashable: Any],\n        source: RemoteDataSource\n    ) -> RemoteDataPayload {\n        return RemoteDataPayload(\n            type: type,\n            timestamp: timestamp,\n            data: try! AirshipJSON.wrap(data),\n            remoteDataInfo: RemoteDataInfo(url: URL(string: \"someurl\")!, lastModifiedTime: nil, source: source)\n        )\n    }\n\n    public class func generatePayload(\n        type: String,\n        timestamp: Date,\n        data: [AnyHashable: Any],\n        remoteDataInfo: RemoteDataInfo\n    ) -> RemoteDataPayload {\n        return RemoteDataPayload(\n            type: type,\n            timestamp: timestamp,\n            data: try! AirshipJSON.wrap(data),\n            remoteDataInfo: remoteDataInfo\n        )\n    }\n\n    public class func generatePayload(\n        type: String,\n        timestamp: Date,\n        data: [AnyHashable: Any],\n        source: RemoteDataSource,\n        lastModified: String\n    ) -> RemoteDataPayload {\n        return RemoteDataPayload(\n            type: type,\n            timestamp: timestamp,\n            data: try! AirshipJSON.wrap(data),\n            remoteDataInfo: RemoteDataInfo(url: URL(string: \"someurl\")!, lastModifiedTime: lastModified, source: source)\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoteDataURLFactoryTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipCore\n\nfinal class RemoteDataURLFactoryTest: XCTestCase {\n\n    private let config: RuntimeConfig = RuntimeConfig.testConfig()\n\n    func testURL() throws {\n        let remoteDataURL = try! RemoteDataURLFactory.makeURL(\n            config: config,\n            path: \"/some-path\",\n            locale: Locale(identifier: \"en-US\"),\n            randomValue: 100\n        )\n\n        let sdkVersion = AirshipVersion.version\n        XCTAssertEqual(\n            \"\\(config.remoteDataAPIURL)/some-path?language=en&country=US&sdk_version=\\(sdkVersion)&random_value=100\",\n            remoteDataURL.absoluteString\n        )\n    }\n\n    func testURLNoCountry() throws {\n        let remoteDataURL = try! RemoteDataURLFactory.makeURL(\n            config: config,\n            path: \"/some-path\",\n            locale: Locale(identifier: \"br\"),\n            randomValue: 100\n        )\n\n        let sdkVersion = AirshipVersion.version\n        XCTAssertEqual(\n            \"\\(config.remoteDataAPIURL)/some-path?language=br&sdk_version=\\(sdkVersion)&random_value=100\",\n            remoteDataURL.absoluteString\n        )\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RemoveTagsActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class RemoveTagsActionTest: XCTestCase {\n\n    private let simpleValue = [\"tag\", \"another tag\"]\n    private let complexValue: [String: AnyHashable] = [\n        \"channel\": [\n            \"channel_tag_group\": [\"channel_tag_1\", \"channel_tag_2\"],\n            \"other_channel_tag_group\": [\"other_channel_tag_1\"]\n        ],\n        \"named_user\": [\n            \"named_user_tag_group\": [\"named_user_tag_1\", \"named_user_tag_2\"],\n            \"other_named_user_tag_group\": [\"other_named_user_tag_1\"]\n        ],\n        \"device\": [ \"tag\", \"another_tag\"]\n    ]\n\n    private let channel = TestChannel()\n    private let contact = TestContact()\n    private var action: RemoveTagsAction!\n\n    override func setUp() async throws {\n        action = RemoveTagsAction(\n            channel: { [channel] in return channel },\n            contact: { [contact] in return contact }\n        )\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(value: try! AirshipJSON.wrap(simpleValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in validSituations {\n            let args = ActionArguments(value: try! AirshipJSON.wrap(complexValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(value: try! AirshipJSON.wrap(simpleValue), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testPerformSimple() async throws {\n        self.channel.tags = [\"foo\", \"bar\", \"tag\", \"another tag\"]\n        \n        let updates = await self.action.tagMutations\n\n        _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try! AirshipJSON.wrap(simpleValue),\n                situation: .manualInvocation\n            )\n        )\n        XCTAssertEqual(\n            [\"foo\", \"bar\"],\n            channel.tags\n        )\n        \n        var iterator = updates.makeAsyncIterator()\n        let mutation = await iterator.next()\n        XCTAssertEqual(TagActionMutation.channelTags([\"tag\", \"another tag\"]), mutation)\n    }\n\n    func testPerformComplex() async throws {\n        self.channel.tags = [\"foo\", \"bar\", \"tag\"]\n\n        let tagGroupsSet = self.expectation(description: \"tagGroupsSet\")\n        tagGroupsSet.expectedFulfillmentCount = 2\n\n        self.channel.tagGroupEditor = TagGroupsEditor { updates in\n            let expected = [\n                TagGroupUpdate(\n                    group: \"channel_tag_group\",\n                    tags: [\"channel_tag_1\", \"channel_tag_2\"],\n                    type: .remove\n                ),\n                TagGroupUpdate(\n                    group: \"other_channel_tag_group\",\n                    tags: [\"other_channel_tag_1\"],\n                    type: .remove\n                )\n            ]\n\n            XCTAssertEqual(Set(expected), Set(updates))\n            tagGroupsSet.fulfill()\n        }\n\n        self.contact.tagGroupEditor = TagGroupsEditor { updates in\n            let expected = [\n                TagGroupUpdate(\n                    group: \"named_user_tag_group\",\n                    tags: [\"named_user_tag_1\", \"named_user_tag_2\"],\n                    type: .remove\n                ),\n                TagGroupUpdate(\n                    group: \"other_named_user_tag_group\",\n                    tags: [\"other_named_user_tag_1\"],\n                    type: .remove\n                )\n            ]\n\n            XCTAssertEqual(Set(expected), Set(updates))\n            tagGroupsSet.fulfill()\n        }\n        \n        let updates = await self.action.tagMutations\n\n        _ = try await self.action.perform(arguments:\n            ActionArguments(\n                value: try! AirshipJSON.wrap(complexValue),\n                situation: .manualInvocation\n            )\n        )\n        \n        var expected: [TagActionMutation] = [\n            .channelTagGroups([\"channel_tag_group\": [\"channel_tag_1\", \"channel_tag_2\"], \"other_channel_tag_group\": [\"other_channel_tag_1\"]]),\n            .contactTagGroups([\"named_user_tag_group\": [\"named_user_tag_1\", \"named_user_tag_2\"], \"other_named_user_tag_group\": [\"other_named_user_tag_1\"]]),\n            .channelTags([\"tag\", \"another_tag\"])\n        ]\n        \n        for await item in updates {\n            XCTAssertEqual(expected.removeFirst(), item)\n            if expected.isEmpty {\n                break\n            }\n        }\n        \n\n        XCTAssertEqual(\n            [\"foo\", \"bar\"],\n            channel.tags\n        )\n        \n        await self.fulfillment(of: [tagGroupsSet], timeout: 10)\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RetailEventTemplateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class RetailEventTemplateTest: XCTestCase {\n\n    func testBrowsed() {\n        let event = CustomEvent(retailTemplate: .browsed)\n        XCTAssertEqual(\"browsed\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testAddedToCart() {\n        let event = CustomEvent(retailTemplate: .addedToCart)\n        XCTAssertEqual(\"added_to_cart\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testStarred() {\n        let event = CustomEvent(retailTemplate: .starred)\n        XCTAssertEqual(\"starred_product\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testPurchased() {\n        let event = CustomEvent(retailTemplate: .purchased)\n        XCTAssertEqual(\"purchased\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testShared() {\n        let event = CustomEvent(retailTemplate: .shared(source: \"some source\", medium: \"some medium\"))\n        XCTAssertEqual(\"shared_product\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"ltv\": false,\n            \"source\": \"some source\",\n            \"medium\": \"some medium\"\n        ]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testSharedEmptyDetails() {\n        let event = CustomEvent(retailTemplate: .shared())\n        XCTAssertEqual(\"shared_product\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testWishlist() {\n        let event = CustomEvent(retailTemplate: .wishlist(id: \"some id\", name: \"some name\"))\n        XCTAssertEqual(\"wishlist\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"ltv\": false,\n            \"wishlist_id\": \"some id\",\n            \"wishlist_name\": \"some name\"\n        ]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testWishlistEmptyDetails() {\n        let event = CustomEvent(retailTemplate: .wishlist())\n        XCTAssertEqual(\"wishlist\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testProperties() {\n        let properties = CustomEvent.RetailProperties(\n            id: \"some id\",\n            category: \"some category\",\n            type: \"some type\",\n            eventDescription: \"some description\",\n            isLTV: true,\n            brand: \"some brand\",\n            isNewItem: true,\n            currency: \"cred\"\n        )\n\n        let event = CustomEvent(retailTemplate: .wishlist(), properties: properties)\n        XCTAssertEqual(\"wishlist\", event.eventName)\n        XCTAssertEqual(\"retail\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"id\": \"some id\",\n            \"category\": \"some category\",\n            \"type\": \"some type\",\n            \"description\": \"some description\",\n            \"ltv\": true,\n            \"brand\": \"some brand\",\n            \"new_item\": true,\n            \"currency\": \"cred\",\n        ]\n\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RuntimeConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable\nimport AirshipCore\n\nextension RuntimeConfig {\n    class func testConfig(\n        site: CloudSite = .us,\n        useUserPreferredLocale: Bool = false,\n        requireInitialRemoteConfigEnabled: Bool = false,\n        initialConfigURL: String? = nil,\n        notifiaconCenter: NotificationCenter = NotificationCenter()\n    ) -> RuntimeConfig {\n        let credentails = AirshipAppCredentials(\n            appKey: UUID().uuidString,\n            appSecret: UUID().uuidString\n        )\n\n        var airshipConfig = AirshipConfig()\n        airshipConfig.site = site\n        airshipConfig.useUserPreferredLocale = useUserPreferredLocale\n        airshipConfig.initialConfigURL = initialConfigURL\n        airshipConfig.requireInitialRemoteConfigEnabled = requireInitialRemoteConfigEnabled\n        return RuntimeConfig(\n            airshipConfig: airshipConfig,\n            appCredentials: credentails,\n            dataStore: PreferenceDataStore(appKey: credentails.appKey),\n            requestSession: TestAirshipRequestSession(),\n            notificationCenter: notifiaconCenter\n        )\n    }\n\n    class func testConfig(\n        airshipConfig: AirshipConfig,\n        notifiaconCenter: NotificationCenter = NotificationCenter()\n    ) -> RuntimeConfig {\n        let credentails = AirshipAppCredentials(\n            appKey: UUID().uuidString,\n            appSecret: UUID().uuidString\n        )\n\n        return RuntimeConfig(\n            airshipConfig: airshipConfig,\n            appCredentials: credentails,\n            dataStore: PreferenceDataStore(appKey: credentails.appKey),\n            requestSession: TestAirshipRequestSession(),\n            notificationCenter: notifiaconCenter\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/RuntimeConfigTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass RuntimeConfigTest: XCTestCase {\n    func testUSSiteURLS() throws {\n        let config = RuntimeConfig.testConfig(site: .us)\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com\",\n            config.deviceAPIURL\n        )\n        XCTAssertEqual(\"https://combine.urbanairship.com\", config.analyticsURL)\n        XCTAssertEqual(\n            \"https://remote-data.urbanairship.com\",\n            config.remoteDataAPIURL\n        )\n    }\n\n    func testEUSiteURLS() throws {\n        let config = RuntimeConfig.testConfig(site: .eu)\n        XCTAssertEqual(\"https://device-api.asnapieu.com\", config.deviceAPIURL)\n        XCTAssertEqual(\"https://combine.asnapieu.com\", config.analyticsURL)\n        XCTAssertEqual(\n            \"https://remote-data.asnapieu.com\",\n            config.remoteDataAPIURL\n        )\n    }\n\n    func testInitialConfigURL() throws {\n        let config = RuntimeConfig.testConfig(initialConfigURL: \"cool://remote\")\n        XCTAssertEqual(\"cool://remote\", config.remoteDataAPIURL)\n    }\n\n    func testRequireInitialRemoteConfigEnabled() throws {\n        let config = RuntimeConfig.testConfig(\n            requireInitialRemoteConfigEnabled: true\n        )\n\n        XCTAssertNil(config.deviceAPIURL)\n        XCTAssertNil(config.analyticsURL)\n        XCTAssertEqual(\n            \"https://remote-data.urbanairship.com\",\n            config.remoteDataAPIURL\n        )\n    }\n\n    func testRemoteConfigOverride() async throws {\n        let notificationCenter = NotificationCenter()\n\n        let updatedCount = AirshipAtomicValue<Int>(0)\n        notificationCenter.addObserver(\n            forName: RuntimeConfig.configUpdatedEvent,\n            object: nil,\n            queue: nil\n        ) { _ in\n            updatedCount.value += 1\n        }\n\n        let config = RuntimeConfig.testConfig(notifiaconCenter: notificationCenter)\n\n        let airshipConfig = RemoteConfig.AirshipConfig(\n            remoteDataURL: \"cool://remote\",\n            deviceAPIURL: \"cool://devices\",\n            analyticsURL: \"cool://analytics\",\n            meteredUsageURL: \"cool://meteredUsage\"\n        )\n\n        await config.updateRemoteConfig(\n            RemoteConfig(airshipConfig: airshipConfig)\n        )\n\n        XCTAssertEqual(\"cool://devices\", config.deviceAPIURL)\n        XCTAssertEqual(\"cool://analytics\", config.analyticsURL)\n        XCTAssertEqual(\"cool://remote\", config.remoteDataAPIURL)\n        XCTAssertEqual(\"cool://meteredUsage\", config.meteredUsageURL)\n        XCTAssertEqual(1, updatedCount.value)\n\n        await config.updateRemoteConfig(\n            RemoteConfig(airshipConfig: airshipConfig)\n        )\n\n        XCTAssertEqual(\"cool://devices\", config.deviceAPIURL)\n        XCTAssertEqual(\"cool://analytics\", config.analyticsURL)\n        XCTAssertEqual(\"cool://remote\", config.remoteDataAPIURL)\n        XCTAssertEqual(\"cool://meteredUsage\", config.meteredUsageURL)\n        XCTAssertEqual(1, updatedCount.value)\n\n        let differentConfig = RemoteConfig.AirshipConfig(\n            remoteDataURL: \"neat://remote\",\n            deviceAPIURL: \"neat://devices\",\n            analyticsURL: \"neat://analytics\",\n            meteredUsageURL: \"neat://meteredUsage\"\n        )\n\n        await config.updateRemoteConfig(\n            RemoteConfig(airshipConfig: differentConfig)\n        )\n\n        XCTAssertEqual(\"neat://devices\", config.deviceAPIURL)\n        XCTAssertEqual(\"neat://analytics\", config.analyticsURL)\n        XCTAssertEqual(\"neat://remote\", config.remoteDataAPIURL)\n        XCTAssertEqual(\"neat://meteredUsage\", config.meteredUsageURL)\n        XCTAssertEqual(2, updatedCount.value)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/SearchEventTemplateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class SearchEventTemplateTest: XCTestCase {\n\n    func testSearch() {\n        let event = CustomEvent(searchTemplate: .search)\n        XCTAssertEqual(\"search\", event.eventName)\n        XCTAssertEqual(\"search\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\"ltv\": false]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n\n    func testProperties() {\n        let event = CustomEvent(\n            searchTemplate: .search,\n            properties: .init(\n                id: \"some id\",\n                category: \"some category\",\n                type: \"some type\",\n                isLTV: true,\n                query: \"some query\",\n                totalResults: 20\n            )\n        )\n\n        XCTAssertEqual(\"search\", event.eventName)\n        XCTAssertEqual(\"search\", event.templateType)\n\n        let expectedProperties: [String: AirshipJSON] = [\n            \"id\": \"some id\",\n            \"category\": \"some category\",\n            \"type\": \"some type\",\n            \"ltv\": true,\n            \"query\": \"some query\",\n            \"total_results\": 20\n\n        ]\n        XCTAssertEqual(expectedProperties, event.properties)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/SessionTrackerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipCore\n\nfinal class SessionTrackerTest: XCTestCase {\n\n    private let taskSleeper: TestTaskSleeper = TestTaskSleeper()\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter()\n    private let date: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n    private let appStateTracker: TestAppStateTracker = TestAppStateTracker()\n\n    var tracker: SessionTracker!\n\n    var sessionCount = AirshipAtomicValue<Int>(1)\n\n    @MainActor\n    override func setUp() async throws {\n        self.tracker = SessionTracker(\n            date: date,\n            taskSleeper: taskSleeper,\n            appStateTracker: appStateTracker,\n            sessionStateFactory: { [sessionCount] in\n                let state = SessionState(sessionID: \"\\(sessionCount.value)\")\n                sessionCount.value = sessionCount.value + 1\n                return state\n            }\n        )\n    }\n\n    func testDidBecomeActiveAppInit() async throws {\n        Task { @MainActor [notificationCenter] in\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n        }\n\n        var asyncIterator  = self.tracker.events.makeAsyncIterator()\n        let event = await asyncIterator.next()!\n        XCTAssertEqual(.foregroundInit, event.type)\n        XCTAssertEqual(self.date.now, event.date)\n    }\n\n    func testBackgroundBeforeForegroundEmitsAppInit() async throws {\n        Task { @MainActor [notificationCenter] in\n            notificationCenter.post(\n                name: AppStateTracker.didEnterBackgroundNotification,\n                object: nil\n            )\n        }\n\n        await ensureEvents([\n            SessionEvent(\n                type: .backgroundInit,\n                date: self.date.now,\n                sessionState: SessionState(sessionID: \"1\")\n            ),\n        ])\n\n        XCTAssertEqual(self.tracker.sessionState.sessionID, \"1\")\n    }\n\n    func testLaunchFromPushEmitsAppInit() async throws {\n        await self.tracker.launchedFromPush(sendID: \"some sendID\", metadata: \"some metadata\")\n\n        let expectedSessionState = SessionState(\n            sessionID: \"1\",\n            conversionSendID: \"some sendID\",\n            conversionMetadata: \"some metadata\"\n        )\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testAirshipReadyEmitsAppInitActiveState() async throws {\n        self.appStateTracker.currentState = .active\n\n        let expectedSessionState = SessionState(\n            sessionID: \"1\"\n        )\n\n        self.tracker.airshipReady()\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        let sleeps = await self.taskSleeper.sleeps\n        XCTAssertEqual([1.0], sleeps)\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testAirshipReadyEmitsAppInitInActiveState() async throws {\n        self.appStateTracker.currentState = .inactive\n\n        let expectedSessionState = SessionState(\n            sessionID: \"1\"\n        )\n\n        self.tracker.airshipReady()\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        let sleeps = await self.taskSleeper.sleeps\n        XCTAssertEqual([1.0], sleeps)\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testAirshipReadyEmitsAppBackgroundState() async throws {\n        self.appStateTracker.currentState = .background\n\n        let expectedSessionState = SessionState(\n            sessionID: \"1\"\n        )\n\n        self.tracker.airshipReady()\n        await ensureEvents([\n            SessionEvent(\n                type: .backgroundInit,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        let sleeps = await self.taskSleeper.sleeps\n        XCTAssertEqual([1.0], sleeps)\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testLaunchFromPushAppBackgroundState() async throws {\n        self.appStateTracker.currentState = .background\n\n        let expectedSessionState = SessionState(\n            sessionID: \"1\",\n            conversionSendID: \"some sendID\",\n            conversionMetadata: \"some metadata\"\n        )\n\n        Task {  @MainActor [tracker, notificationCenter] in\n            // launch from push\n            tracker?.launchedFromPush(sendID: \"some sendID\", metadata: \"some metadata\")\n\n            // This would normally be called with a delay, so calling it after\n            tracker?.airshipReady()\n\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n        }\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testLaunchFromPushAppInActiveState() async throws {\n        self.appStateTracker.currentState = .inactive\n\n        let expectedSessionState = SessionState(\n            sessionID: \"1\",\n            conversionSendID: \"some sendID\",\n            conversionMetadata: \"some metadata\"\n        )\n\n        Task { @MainActor [tracker, notificationCenter] in\n            // launch from push\n            tracker?.launchedFromPush(sendID: \"some sendID\", metadata: \"some metadata\")\n\n            // This would normally be called with a delay, so calling it after\n            tracker?.airshipReady()\n\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n        }\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testLaunchAppBackgroundState() async throws {\n        self.appStateTracker.currentState = .background\n\n        // App init\n        self.tracker.airshipReady()\n\n        await ensureEvents([\n            SessionEvent(\n                type: .backgroundInit,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\"\n                )\n            )\n        ])\n\n\n        Task { @MainActor [notificationCenter] in\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n\n            // Background\n            notificationCenter.post(\n                name: AppStateTracker.didEnterBackgroundNotification,\n                object: nil\n            )\n        }\n\n        let expectedSessionState = SessionState(\n            sessionID: \"2\"\n        )\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foreground,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            ),\n            SessionEvent(\n                type: .background,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        // Background should reset state\n        XCTAssertEqual(self.tracker.sessionState, SessionState(sessionID: \"3\"))\n\n    }\n\n    @MainActor\n    func testLaunchAppInactiveState() async throws {\n        self.appStateTracker.currentState = .inactive\n\n        // App init\n        self.tracker.airshipReady()\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\"\n                )\n            )\n        ])\n\n        Task { @MainActor [notificationCenter] in\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n\n            // Background\n            notificationCenter.post(\n                name: AppStateTracker.didEnterBackgroundNotification,\n                object: nil\n            )\n        }\n\n        await ensureEvents([\n            SessionEvent(\n                type: .background,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\"\n                )\n            )\n        ])\n\n        // Background should reset state\n        XCTAssertEqual(self.tracker.sessionState, SessionState(sessionID: \"2\"))\n    }\n\n    @MainActor\n    func testLaunchAppActiveState() async throws {\n        appStateTracker.currentState = .active\n\n        // App init\n        self.tracker.airshipReady()\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\"\n                )\n            )\n        ])\n\n        Task { @MainActor [notificationCenter] in\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n\n            // Background\n            notificationCenter.post(\n                name: AppStateTracker.didEnterBackgroundNotification,\n                object: nil\n            )\n        }\n\n        await ensureEvents([\n            SessionEvent(\n                type: .background,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\"\n                )\n            )\n        ])\n\n        // Background should reset state\n        XCTAssertEqual(self.tracker.sessionState, SessionState(sessionID: \"2\"))\n    }\n\n    @MainActor\n    func testLaunchContentAvailablePush() async throws {\n        self.appStateTracker.currentState = .background\n\n        // App init\n        self.tracker.airshipReady()\n\n        await ensureEvents([\n            SessionEvent(\n                type: .backgroundInit,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\"\n                )\n            )\n        ])\n\n\n        Task { @MainActor [tracker, notificationCenter] in\n            // launch from push\n            tracker?.launchedFromPush(sendID: \"some sendID\", metadata: \"some metadata\")\n\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n        }\n\n        let expectedSessionState = SessionState(\n            sessionID: \"2\",\n            conversionSendID: \"some sendID\",\n            conversionMetadata: \"some metadata\"\n        )\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foreground,\n                date: self.date.now,\n                sessionState: expectedSessionState\n            )\n        ])\n\n        // Foreground should generate new session ID\n        XCTAssertEqual(self.tracker.sessionState, expectedSessionState)\n    }\n\n    @MainActor\n    func testBackgroundClearPush() async throws {\n        self.appStateTracker.currentState = .background\n\n        self.tracker.launchedFromPush(sendID: \"some sendID\", metadata: \"some metadata\")\n\n        await ensureEvents([\n            SessionEvent(\n                type: .foregroundInit,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\",\n                    conversionSendID: \"some sendID\",\n                    conversionMetadata: \"some metadata\"\n                )\n            )\n        ])\n\n\n        Task { @MainActor [notificationCenter] in\n            // Background\n            notificationCenter.post(\n                name: AppStateTracker.didEnterBackgroundNotification,\n                object: nil\n            )\n\n            // Foreground\n            notificationCenter.post(\n                name: AppStateTracker.didBecomeActiveNotification,\n                object: nil\n            )\n        }\n\n        await ensureEvents([\n            SessionEvent(\n                type: .background,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"1\",\n                    conversionSendID: \"some sendID\",\n                    conversionMetadata: \"some metadata\"\n                )\n            ),\n            SessionEvent(\n                type: .foreground,\n                date: self.date.now,\n                sessionState: SessionState(\n                    sessionID: \"3\"\n                )\n            )\n        ])\n\n        // Foreground should generate new session ID\n        XCTAssertEqual(self.tracker.sessionState, SessionState(sessionID: \"3\"))\n    }\n\n    private func ensureEvents(_ events: [SessionEvent], line: UInt = #line) async {\n        let verifyTask = Task { [tracker] in\n            var asyncIterator = tracker!.events.makeAsyncIterator()\n            for expected in events {\n                if Task.isCancelled {\n                    break\n                }\n\n                let next = await asyncIterator.next()\n                XCTAssertEqual(expected, next, line: line)\n            }\n        }\n\n        let timeoutTask = Task {\n            try? await DefaultAirshipTaskSleeper().sleep(timeInterval:2.0)\n            if Task.isCancelled == false {\n                XCTFail(\"Failed to get events\", line: line)\n                verifyTask.cancel()\n            }\n        }\n\n        await verifyTask.value\n        timeoutTask.cancel()\n    }\n}\n\nactor TestTaskSleeper : AirshipTaskSleeper {\n    var sleeps : [TimeInterval] = []\n\n    private var updates: AirshipAsyncChannel<[TimeInterval]> = AirshipAsyncChannel()\n    var continuations: [CheckedContinuation<Void, Never>] = []\n\n    private var isPaused: Bool = false\n\n    func pause() {\n        self.isPaused = true\n    }\n\n    func resume() {\n        self.isPaused = false\n        continuations.forEach {\n            $0.resume()\n        }\n        continuations.removeAll()\n    }\n\n    var sleepUpdates: AsyncStream<[TimeInterval]> {\n        get async {\n            await updates.makeStream()\n        }\n    }\n\n    func sleep(timeInterval: TimeInterval) async throws {\n        sleeps.append(timeInterval)\n        await updates.send(sleeps)\n        \n        if (isPaused) {\n            await withCheckedContinuation {\n                continuations.append($0)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ShareActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\nfinal class ShareActionTest: XCTestCase {\n\n    private let action: ShareAction = ShareAction()\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush,\n            ActionSituation.backgroundInteractiveButton\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(value: AirshipJSON.string(\"some valid text\"), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(value: AirshipJSON.string(\"some valid text\"), situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/SubscriptionListAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport XCTest\n\n@testable import AirshipCore\n\nclass SubscriptionListAPIClientTest: XCTestCase {\n\n    var config: RuntimeConfig = .testConfig()\n    var session: TestAirshipRequestSession = TestAirshipRequestSession()\n    var client: SubscriptionListAPIClient!\n\n    override func setUpWithError() throws {\n        self.client = SubscriptionListAPIClient(\n            config: self.config,\n            session: self.session\n        )\n    }\n\n    func testGet() async throws {\n        let responseBody = \"\"\"\n                {\n                   \"ok\" : true,\n                   \"list_ids\": [\"example_listId-1\",\"example_listId-2\"]\n                }\n            \"\"\"\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n        self.session.data = responseBody.data(using: .utf8)\n\n        let response = try await self.client.get(channelID: \"some-channel\")\n\n        XCTAssertEqual(response.statusCode, 200)\n        XCTAssertEqual(\n            [\"example_listId-1\", \"example_listId-2\"],\n            response.result\n        )\n\n        XCTAssertEqual(\"GET\", self.session.lastRequest?.method)\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/subscription_lists/channels/some-channel\",\n            self.session.lastRequest?.url?.absoluteString\n        )\n    }\n\n    func testGetParseError() async throws {\n        let responseBody = \"What?\"\n\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"https://neat\")!,\n            statusCode: 200,\n            httpVersion: \"\",\n            headerFields: [String: String]()\n        )\n        self.session.data = responseBody.data(using: .utf8)\n\n\n        do {\n            _ = try await self.client.get(channelID: \"some-channel\")\n            XCTFail(\"Should throw\")\n        } catch {\n        }\n    }\n\n    func testGetError() async throws {\n        let sessionError = AirshipErrors.error(\"error!\")\n        self.session.error = sessionError\n\n        do {\n            _ = try await self.client.get(channelID: \"some-channel\")\n            XCTFail(\"Should throw\")\n        } catch {\n            XCTAssertEqual(sessionError as NSError, error as NSError)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/SubscriptionListActionTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass SubscriptionListActionTests: XCTestCase {\n\n    private let channel = TestChannel()\n    private let contact = TestContact()\n    private let date = UATestDate(offset: 0, dateOverride: Date())\n    private var action: SubscriptionListAction!\n\n    private var channelEdits: [SubscriptionListUpdate] = []\n    private var contactEdits: [ScopedSubscriptionListUpdate] = []\n\n    override func setUp() {\n        self.action = SubscriptionListAction(\n            channel: { [channel] in return channel },\n            contact: { [contact] in return contact }\n        )\n\n        self.channel.subscriptionListEditor = SubscriptionListEditor {\n            updates in\n            self.channelEdits.append(contentsOf: updates)\n        }\n\n        self.contact.subscriptionListEditor = ScopedSubscriptionListEditor(\n            date: date\n        ) { updates in\n            self.contactEdits.append(contentsOf: updates)\n        }\n    }\n\n    func testAcceptsArguments() async throws {\n        let validSituations = [\n            ActionSituation.foregroundInteractiveButton,\n            ActionSituation.launchedFromPush,\n            ActionSituation.manualInvocation,\n            ActionSituation.webViewInvocation,\n            ActionSituation.automation,\n            ActionSituation.foregroundPush,\n            ActionSituation.backgroundInteractiveButton,\n        ]\n\n        let rejectedSituations = [\n            ActionSituation.backgroundPush\n        ]\n\n        for situation in validSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertTrue(result)\n        }\n\n        for situation in rejectedSituations {\n            let args = ActionArguments(situation: situation)\n            let result = await self.action.accepts(arguments: args)\n            XCTAssertFalse(result)\n        }\n    }\n\n    func testPerformWithoutArgs() async throws {\n        let args = ActionArguments()\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"should throw\")\n        } catch {}\n    }\n\n    func testPerformWithValidPayload() async throws {\n        let actionValue: [[String: String]] = [\n            [\n                \"type\": \"channel\",\n                \"action\": \"subscribe\",\n                \"list\": \"456\",\n            ],\n            [\n                \"type\": \"contact\",\n                \"action\": \"unsubscribe\",\n                \"list\": \"4567\",\n                \"scope\": \"app\",\n            ],\n        ]\n\n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(actionValue)\n        )\n    \n        _ = try await action.perform(arguments: args)\n\n        let expectedChannelEdits = [\n            SubscriptionListUpdate(listId: \"456\", type: .subscribe)\n        ]\n        XCTAssertEqual(expectedChannelEdits, self.channelEdits)\n\n        let expectedContactEdits = [\n            ScopedSubscriptionListUpdate(\n                listId: \"4567\",\n                type: .unsubscribe,\n                scope: .app,\n                date: self.date.now\n            )\n        ]\n        XCTAssertEqual(expectedContactEdits, self.contactEdits)\n    }\n\n    func testPerformWithAltValidPayload() async throws {\n        let actionValue: [String: Any] = [\n            \"edits\": [\n                [\n                    \"type\": \"channel\",\n                    \"action\": \"subscribe\",\n                    \"list\": \"456\",\n                ],\n                [\n                    \"type\": \"contact\",\n                    \"action\": \"unsubscribe\",\n                    \"list\": \"4567\",\n                    \"scope\": \"app\",\n                ],\n            ]\n        ]\n\n\n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(actionValue)\n        )\n\n        _ = try await action.perform(arguments: args)\n\n        let expectedChannelEdits = [\n            SubscriptionListUpdate(listId: \"456\", type: .subscribe)\n        ]\n        XCTAssertEqual(expectedChannelEdits, self.channelEdits)\n\n        let expectedContactEdits = [\n            ScopedSubscriptionListUpdate(\n                listId: \"4567\",\n                type: .unsubscribe,\n                scope: .app,\n                date: self.date.now\n            )\n        ]\n        XCTAssertEqual(expectedContactEdits, self.contactEdits)\n    }\n\n    func testPerformWithInvalidPayload() async throws {\n        let actionValue: [String: Any] = [\n            \"edits\": [\n                [\n                    \"type\": \"channel\",\n                    \"action\": \"subscribe\",\n                    \"list\": \"456\",\n                ],\n                [\n                    \"type\": \"contact\",\n                    \"list\": \"4567\",\n                    \"scope\": \"app\",\n                ],\n            ]\n        ]\n\n\n        let args = ActionArguments(\n            value: try AirshipJSON.wrap(actionValue)\n        )\n\n\n        do {\n            _ = try await action.perform(arguments: args)\n            XCTFail(\"should throw\")\n        } catch {}\n\n        XCTAssertTrue(self.channelEdits.isEmpty)\n        XCTAssertTrue(self.contactEdits.isEmpty)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Support/AirshipConfig-Valid-Legacy.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>DEVELOPMENT_APP_KEY</key>\n\t<string>0A00000000000000000000</string>\n\t<key>DEVELOPMENT_APP_SECRET</key>\n\t<string>0A00000000000000000000</string>\n\t<key>PRODUCTION_APP_KEY</key>\n\t<string>0A00000000000000000000</string>\n\t<key>PRODUCTION_APP_SECRET</key>\n\t<string>0A00000000000000000000</string>\n\t<key>LOG_LEVEL</key>\n\t<integer>5</integer>\n\t<key>APP_STORE_OR_AD_HOC_BUILD</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Support/AirshipConfig-Valid.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>urlAllowListScopeOpenURL</key>\n\t<array>\n\t\t<string>*</string>\n\t</array>\n\t<key>requireInitialRemoteConfigEnabled</key>\n\t<true/>\n\t<key>developmentAppKey</key>\n\t<string>0A00000000000000000000</string>\n\t<key>developmentAppSecret</key>\n\t<string>0A00000000000000000000</string>\n\t<key>developmentLogLevel</key>\n\t<integer>1</integer>\n\t<key>developmentLogPrivacyLevel</key>\n\t<integer>0</integer>\n\t<key>productionAppKey</key>\n\t<string>0A00000000000000000000</string>\n\t<key>productionAppSecret</key>\n\t<string>0A00000000000000000000</string>\n\t<key>productionLogLevel</key>\n\t<integer>5</integer>\n\t<key>productionLogPrivacyLevel</key>\n\t<string>public</string>\n\t<key>isChannelCreationDelayEnabled</key>\n\t<true/>\n\t<key>isExtendedBroadcastsEnabled</key>\n\t<true/>\n\t<key>messageCenterStyleConfig</key>\n\t<string>ValidUAMessageCenterDefaultStyle</string>\n\t<key>site</key>\n\t<integer>1</integer>\n\t<key>resetEnabledFeatures</key>\n\t<true/>\n\t<key>inProduction</key>\n\t<true/>\n\t<key>enabledFeatures</key>\n\t<array>\n\t\t<string>in_app_automation</string>\n\t\t<string>push</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Support/TestAppStateTracker.swift",
    "content": "public import AirshipCore\nimport Foundation\n@preconcurrency import Combine\n\npublic final class TestAppStateTracker: AppStateTrackerProtocol, Sendable {\n    private let stateValue: AirshipMainActorValue<ApplicationState> = AirshipMainActorValue(.background)\n\n    public var stateUpdates: AsyncStream<ApplicationState> {\n        stateValue.updates\n    }\n\n    private let stateSubject: PassthroughSubject<ApplicationState, Never> = PassthroughSubject()\n\n    @MainActor\n    public func waitForActive() async {\n        guard self.currentState != .active else {\n            return\n        }\n        \n        var subscription: AnyCancellable?\n        await withCheckedContinuation { continuation in\n            subscription = stateSubject.eraseToAnyPublisher()\n                .filter { $0 == .active }\n                .first()\n                .sink { _ in\n                    continuation.resume()\n                }\n        }\n        subscription?.cancel()\n    }\n    \n    public var state: AirshipCore.ApplicationState { return currentState }\n    @MainActor\n    public var currentState: ApplicationState = .background {\n        didSet {\n            stateSubject.send(currentState)\n            stateValue.set(currentState)\n        }\n    }\n\n\n    @MainActor\n    public func updateState(_ state: ApplicationState) async {\n        self.currentState = state\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Support/testMCColorsCatalog.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  }\n}"
  },
  {
    "path": "Airship/AirshipCore/Tests/Support/testMCColorsCatalog.xcassets/seapunkTestColor.colorset/Contents.json",
    "content": "{\n  \"info\" : {\n    \"version\" : 1,\n    \"author\" : \"xcode\"\n  },\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"red\" : \"0.058\",\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.500\",\n          \"green\" : \"0.253\"\n        }\n      }\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"light\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"red\" : \"0.812\",\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.500\",\n          \"green\" : \"0.083\"\n        }\n      }\n    },\n    {\n      \"idiom\" : \"universal\",\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"red\" : \"0.058\",\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.500\",\n          \"green\" : \"0.620\"\n        }\n      }\n    }\n  ]\n}"
  },
  {
    "path": "Airship/AirshipCore/Tests/TagEditorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass TagEditorTest: XCTestCase {\n\n    func testEditor() throws {\n        var tags = [\"cool\", \"story\"]\n        let editor = TagEditor { tagApplicator in\n            tags = tagApplicator(tags)\n        }\n\n        editor.add([\"dog\", \"cat\"])\n        editor.remove([\"story\"])\n        editor.apply()\n\n        XCTAssertEqual(tags, [\"cool\", \"dog\", \"cat\"])\n\n        editor.set([\"what\", \"cool\"])\n        editor.add([\"nice\"])\n        editor.remove([\"cool\"])\n        editor.apply()\n\n        XCTAssertEqual(tags, [\"what\", \"nice\"])\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TagGroupsEditorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass TagGroupsEditorTest: XCTestCase {\n\n    func testEditor() throws {\n        var out: [TagGroupUpdate]?\n\n        let editor = TagGroupsEditor { updates in\n            out = updates\n        }\n\n        editor.add([\"tag one\"], group: \"some group\")\n        editor.remove([\"tag one\"], group: \"some group\")\n        editor.apply()\n\n        XCTAssertEqual(2, out?.count)\n    }\n\n    func testInvalidTagGroup() throws {\n        var out: [TagGroupUpdate]?\n\n        let editor = TagGroupsEditor { updates in\n            out = updates\n        }\n\n        editor.add([\"tag one\"], group: \"\")\n        editor.set([\"tag one\"], group: \"\")\n        editor.remove([\"tag one\"], group: \"\")\n        editor.apply()\n\n        XCTAssertTrue(out?.isEmpty ?? false)\n    }\n\n    func testEmptyTags() throws {\n        var out: [TagGroupUpdate]?\n\n        let editor = TagGroupsEditor { updates in\n            out = updates\n        }\n\n        editor.add([], group: \"group one\")\n        editor.set([], group: \"group two\")\n        editor.remove([], group: \"group three\")\n        editor.apply()\n\n        XCTAssertEqual(1, out?.count)\n        XCTAssertEqual(out?.first?.group, \"group two\")\n        XCTAssertTrue(out?.first?.tags.isEmpty ?? false)\n    }\n\n    func testNormalizeTags() throws {\n        var out: [TagGroupUpdate]?\n\n        let editor = TagGroupsEditor { updates in\n            out = updates\n        }\n\n        editor.add(\n            [\"foo  \", \"bar \\n\", \"neat tag\", \"  cool\"],\n            group: \"  group one  \"\n        )\n        editor.apply()\n\n        XCTAssertEqual(1, out?.count)\n        XCTAssertEqual(out?.first?.group, \"group one\")\n\n        let tags = [\"foo\", \"bar\", \"neat tag\", \"cool\"]\n        XCTAssertEqual(tags, out?.first?.tags)\n    }\n\n    func testPreventDeviceTagGroup() throws {\n        var out: [TagGroupUpdate]?\n\n        let editor = TagGroupsEditor(allowDeviceTagGroup: false) { updates in\n            out = updates\n        }\n\n        editor.add([\"cool\"], group: \"ua_device\")\n        editor.apply()\n\n        XCTAssertTrue(out?.isEmpty ?? false)\n    }\n\n    func testAllowDeviceTagGroup() throws {\n        var out: [TagGroupUpdate]?\n\n        let editor = TagGroupsEditor(allowDeviceTagGroup: true) { updates in\n            out = updates\n        }\n\n        editor.add([\"cool\"], group: \"ua_device\")\n        editor.apply()\n\n        XCTAssertEqual(1, out?.count)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestAirshipInstance.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable\nimport AirshipCore\n\nfinal class TestAirshipInstance: AirshipInstance, @unchecked Sendable {\n    var inputValidator: any AirshipInputValidation.Validator {\n        fatalError(\"Not implemented\")\n    }\n\n    var _permissionsManager: DefaultAirshipPermissionsManager?\n    var permissionsManager: any AirshipPermissionsManager {\n        return _permissionsManager!\n    }\n\n    public let preferenceDataStore: AirshipCore.PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n\n    private var _config: RuntimeConfig?\n    public var config: RuntimeConfig {\n        get {\n            return _config!\n        }\n        set {\n            _config = newValue\n        }\n    }\n\n    private var _actionRegistry: (any AirshipActionRegistry)?\n    public var actionRegistry: any AirshipActionRegistry {\n        get {\n            return _actionRegistry!\n        }\n        set {\n            _actionRegistry = newValue\n        }\n    }\n    \n    private var _channelCapture: (any AirshipChannelCapture)?\n    public var channelCapture: any AirshipChannelCapture {\n        get {\n            return _channelCapture!\n        }\n        set {\n            _channelCapture = newValue\n        }\n    }\n\n    private var _urlAllowList: (any AirshipURLAllowList)?\n    public var urlAllowList: any AirshipURLAllowList {\n        get {\n            return _urlAllowList!\n        }\n        set {\n            _urlAllowList = newValue\n        }\n    }\n\n    private var _localeManager: (any AirshipLocaleManager)?\n    public var localeManager: AirshipLocaleManager {\n        get {\n            return _localeManager!\n        }\n        set {\n            _localeManager = newValue\n        }\n    }\n\n    private var _privacyManager: (any InternalAirshipPrivacyManager)?\n    public var privacyManager: any InternalAirshipPrivacyManager {\n        get {\n            return _privacyManager!\n        }\n        set {\n            _privacyManager = newValue\n        }\n    }\n\n    public var javaScriptCommandDelegate: JavaScriptCommandDelegate?\n\n    public var deepLinkDelegate: DeepLinkDelegate?\n\n    @MainActor\n    public var onDeepLink: (@MainActor @Sendable (URL) async -> Void)?\n\n    public let urlOpener: any URLOpenerProtocol = TestURLOpener()\n\n    public var components: [AirshipComponent] = []\n\n    private var componentMap: [String: AirshipComponent] = [:]\n\n    public func component<E>(ofType componentType: E.Type) -> E? {\n        let key = \"Type:\\(componentType)\"\n        if componentMap[key] == nil {\n            self.componentMap[key] = self.components.first { ($0 as? E) != nil }\n        }\n\n        return componentMap[key] as? E\n    }\n\n    public func makeShared() {\n        Airship._shared = Airship(instance: self)\n    }\n\n    public class func clearShared() {\n        Airship._shared = nil\n    }\n\n    public func airshipReady() {\n    }\n    \n    @MainActor\n    init() {\n        _permissionsManager = DefaultAirshipPermissionsManager()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestAirshipRequestSession.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n@testable\npublic import AirshipCore\n\npublic final class TestAirshipRequestSession: AirshipRequestSession, @unchecked Sendable {\n\n    public var previousRequest: AirshipRequest?\n    public var lastRequest: AirshipRequest?\n    public var response: HTTPURLResponse?\n    public var error: (any Error)?\n    public var data: Data?\n\n    \n    public func performHTTPRequest(\n        _ request: AirshipRequest\n    ) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.performHTTPRequest(\n            request,\n            autoCancel: false,\n            responseParser: nil\n        )\n    }\n\n    public func performHTTPRequest(\n        _ request: AirshipRequest,\n        autoCancel: Bool\n    ) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.performHTTPRequest(\n            request,\n            autoCancel: autoCancel,\n            responseParser: nil\n        )\n    }\n\n    public func performHTTPRequest<T>(\n        _ request: AirshipRequest,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> AirshipHTTPResponse<T> {\n        return try await self.performHTTPRequest(\n            request,\n            autoCancel: false,\n            responseParser: responseParser\n        )\n    }\n\n    @MainActor\n    public func performHTTPRequest<T>(\n        _ request: AirshipRequest,\n        autoCancel: Bool,\n        responseParser: (@Sendable (Data?, HTTPURLResponse) throws -> T?)?\n    ) async throws -> AirshipHTTPResponse<T> {\n        self.previousRequest = self.lastRequest\n        self.lastRequest = request\n\n        if let error = self.error {\n            throw error\n        }\n\n        guard let response else {\n            throw AirshipErrors.error(\"No response\")\n        }\n\n        let result = AirshipHTTPResponse(\n            result: try responseParser?(data, response),\n            statusCode: response.statusCode,\n            headers: response.allHeaderFields as? [String: String] ?? [:]\n        )\n        return result\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestAnalytics.swift",
    "content": "import Foundation\n\n@testable\npublic import AirshipCore\npublic import Combine\npublic import UserNotifications\n\npublic class TestAnalytics: InternalAirshipAnalytics, AirshipComponent, @unchecked Sendable {\n    public var eventFeed: AirshipAnalyticsFeed = AirshipAnalyticsFeed { true }\n    \n    let eventSubject = PassthroughSubject<AirshipEventData, Never>()\n\n    /// Airship event publisher\n    public var eventPublisher: AnyPublisher<AirshipEventData, Never> {\n        eventSubject.eraseToAnyPublisher()\n    }\n\n\n    public func recordCustomEvent(_ event: CustomEvent) {\n        self.customEvents.append(event)\n    }\n    \n    public func recordRegionEvent(_ event: RegionEvent) {\n\n    }\n    \n    public func trackInstallAttribution(appPurchaseDate: Date?, iAdImpressionDate: Date?) {\n\n    }\n    \n    public func recordEvent(_ event: AirshipEvent) {\n        self.events.append(event)\n        \n        Task {\n            await eventFeed.notifyEvent(.analytics(eventType: event.eventType, body: event.eventData))\n        }\n    }\n\n    private let screen = AirshipMainActorValue<String?>(nil)\n    private let regions = AirshipMainActorValue<Set<String>>(Set())\n\n\n    @MainActor\n    public func setScreen(_ screen: String?) {\n        self.screen.set(screen)\n    }\n\n    @MainActor\n    public func setRegions(_ regions: Set<String>) {\n        self.regions.set(regions)\n    }\n\n\n    public var screenUpdates: AsyncStream<String?> {\n        return self.screen.updates\n    }\n\n    @MainActor\n    public var currentScreen: String? {\n        return self.screen.value\n    }\n\n    public var regionUpdates: AsyncStream<Set<String>> {\n        return self.regions.updates\n    }\n\n    public var currentRegions: Set<String> {\n        return self.regions.value\n    }\n\n    public func onNotificationResponse(response: UNNotificationResponse, action: UNNotificationAction?) {\n\n    }\n\n    public func addHeaderProvider(_ headerProvider: @escaping () async -> [String : String]) {\n        headerBlocks.append(headerProvider)\n    }\n\n\n    public var headerBlocks: [() async -> [String: String]] = []\n\n    public var headers: [String: String] {\n        get async {\n            var allHeaders: [String: String] = [:]\n            for headerBlock in self.headerBlocks {\n                let headers = await headerBlock()\n                allHeaders.merge(headers) { (_, new) in\n                    return new\n                }\n            }\n            return allHeaders\n        }\n\n    }\n\n    public var isComponentEnabled: Bool = true\n\n    public var events: [AirshipEvent] = []\n    public var customEvents: [CustomEvent] = []\n\n    public var conversionSendID: String?\n\n    public var conversionPushMetadata: String?\n\n    public var sessionID: String = \"\"\n\n    public func addEvent(_ event: AirshipEvent) {\n        events.append(event)\n    }\n\n    public func associateDeviceIdentifiers(\n        _ associatedIdentifiers: AssociatedIdentifiers\n    ) {\n    }\n\n    public func currentAssociatedDeviceIdentifiers() -> AssociatedIdentifiers {\n        return AssociatedIdentifiers()\n    }\n\n    @MainActor\n    public func trackScreen(_ screen: String?) {\n\n    }\n\n    public func scheduleUpload() {\n    }\n\n    public func registerSDKExtension(\n        _ ext: AirshipSDKExtension,\n        version: String\n    ) {\n    }\n\n    public func launched(fromNotification notification: [AnyHashable: Any]) {\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestAudienceChecker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable\nimport AirshipCore\n\nfinal class TestAudienceChecker: DeviceAudienceChecker, @unchecked Sendable {\n    func evaluate(\n        audienceSelector: CompoundDeviceAudienceSelector?,\n        newUserEvaluationDate: Date,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> AirshipDeviceAudienceResult {\n        guard let audienceSelector else {\n            return .match\n        }\n\n        return try await self.onEvaluate!(audienceSelector, newUserEvaluationDate, deviceInfoProvider)\n    }\n\n    var onEvaluate: ((CompoundDeviceAudienceSelector, Date, any AudienceDeviceInfoProvider) async throws -> AirshipDeviceAudienceResult)!\n}\n\nfinal class TestAudienceDeviceInfoProvider: AudienceDeviceInfoProvider, @unchecked Sendable {\n    var channelID: String = UUID().uuidString\n\n    var stableContactInfo: StableContactInfo = StableContactInfo(\n        contactID: \"stable\",\n        namedUserID: nil\n    )\n\n    var isChannelCreated: Bool = true\n\n    var sdkVersion: String = AirshipVersion.version\n    \n    var isAirshipReady: Bool = true\n\n    var tags: Set<String> = Set()\n\n    var locale: Locale = Locale.current\n\n    var appVersion: String? = nil\n\n    var permissions: [AirshipPermission : AirshipPermissionStatus] = [:]\n\n    var isUserOptedInPushNotifications: Bool = false\n\n    var analyticsEnabled: Bool = false\n\n    var installDate: Date = Date()\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestCache.swift",
    "content": "\n/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable\nimport AirshipCore\n\nstruct CacheEntry: Sendable {\n    let data: Data\n    let ttl: TimeInterval\n}\n\nactor TestCache: AirshipCache {\n    func deleteCachedValue(key: String) async {\n        values[key] = nil\n    }\n\n    private var values: [String: CacheEntry] = [:]\n\n    func entry(key: String) async -> CacheEntry? {\n        return self.values[key]\n    }\n\n    func getCachedValue<T>(key: String) async -> T? where T : Decodable, T : Encodable, T : Sendable {\n        guard let value = self.values[key] else {\n            return nil\n        }\n\n        return try? JSONDecoder().decode(T.self, from: value.data)\n    }\n\n    func setCachedValue<T>(\n        _ value: T?,\n        key: String,\n        ttl: TimeInterval\n    ) async where T : Decodable, T : Encodable, T : Sendable {\n        guard let value = value, let data = try? JSONEncoder().encode(value) else {\n            return\n        }\n\n        self.values[key] = CacheEntry(data: data, ttl: ttl)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestChannel.swift",
    "content": "import Foundation\nimport ActivityKit\n\n@testable\nimport AirshipCore\nimport Combine\n\nclass TestChannel: NSObject, AirshipChannel, AirshipComponent, @unchecked Sendable {\n    private var identifierSubject: CurrentValueSubject<String?, Never> = CurrentValueSubject(nil)\n\n    var identifierUpdates: AsyncStream<String> {\n        return AsyncStream { continuation in\n            let cancellable = identifierSubject\n                .compactMap { $0 }\n                .removeDuplicates()\n                .sink { update in\n                    continuation.yield(update)\n                }\n\n            continuation.onTermination = { _ in\n                cancellable.cancel()\n            }\n        }\n    }\n\n    private let subscriptionListEditsSubject = PassthroughSubject<SubscriptionListEdit, Never>()\n\n    public var extenders: [@Sendable (inout ChannelRegistrationPayload) async -> Void] = []\n\n    public var channelPayload: ChannelRegistrationPayload {\n        get async {\n            var result: ChannelRegistrationPayload = ChannelRegistrationPayload()\n\n            for extender in extenders {\n                await extender(&result)\n            }\n            return result\n        }\n    }\n\n    @objc\n    public var identifier: String? = nil {\n        didSet {\n            identifierSubject.send(identifier)\n        }\n    }\n\n    public var contactUpdates: [SubscriptionListUpdate] = []\n\n    public var updateRegistrationCalled: Bool = false\n\n    public var isChannelCreationEnabled: Bool = false\n\n    public var pendingAttributeUpdates: [AttributeUpdate] = []\n\n    public var pendingTagGroupUpdates: [TagGroupUpdate] = []\n\n    public var tags: [String] = []\n\n    public var isChannelTagRegistrationEnabled: Bool = false\n\n    public var tagGroupEditor: TagGroupsEditor?\n    \n    public var attributeEditor: AttributesEditor?\n\n    public var subscriptionListEditor: SubscriptionListEditor?\n\n    public func updateRegistration(forcefully: Bool) {\n        self.updateRegistrationCalled = true\n    }\n\n    public func editTags() -> TagEditor {\n        return TagEditor { applicator in\n            self.tags = applicator(self.tags)\n        }\n    }\n\n    public func editTags(_ editorBlock: (TagEditor) -> Void) {\n        let editor = editTags()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func editTagGroups() -> TagGroupsEditor {\n        return self.tagGroupEditor!\n    }\n\n    public func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void) {\n        let editor = editTagGroups()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func editSubscriptionLists() -> SubscriptionListEditor {\n        return self.subscriptionListEditor!\n    }\n\n    public func editSubscriptionLists(\n        _ editorBlock: (SubscriptionListEditor) -> Void\n    ) {\n        let editor = editSubscriptionLists()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func fetchSubscriptionLists() async throws -> [String] {\n        fatalError(\"Not implemented\")\n    }\n\n    public func editAttributes() -> AttributesEditor {\n        return self.attributeEditor!\n    }\n\n    public func editAttributes(_ editorBlock: (AttributesEditor) -> Void) {\n        let editor = editAttributes()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func enableChannelCreation() {\n        self.isChannelCreationEnabled = true\n    }\n\n    public func updateRegistration() {\n        self.updateRegistrationCalled = true\n    }\n\n    public override var description: String {\n        return \"TestChannel\"\n    }\n\n    public func addRegistrationExtender(\n        _ extender: @Sendable @escaping (inout ChannelRegistrationPayload) async -> Void\n    ) {\n        self.extenders.append(extender)\n    }\n\n    public func processContactSubscriptionUpdates(\n        _ updates: [SubscriptionListUpdate]\n    ) {\n        self.contactUpdates.append(contentsOf: updates)\n    }\n\n\n    public var subscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never> {\n        subscriptionListEditsSubject.eraseToAnyPublisher()\n    }\n\n\n    func liveActivityRegistrationStatusUpdates(name: String) -> LiveActivityRegistrationStatusUpdates {\n        return LiveActivityRegistrationStatusUpdates { _ in\n            return .notTracked\n        }\n    }\n\n    @available(iOS 16.1, *)\n    func liveActivityRegistrationStatusUpdates<T>(activity: Activity<T>) -> LiveActivityRegistrationStatusUpdates where T : ActivityAttributes {\n        return LiveActivityRegistrationStatusUpdates { _ in\n            return .notTracked\n        }\n    }\n\n    @available(iOS 16.1, *)\n    func trackLiveActivity<T>(_ activity: Activity<T>, name: String) where T : ActivityAttributes {\n\n    }\n\n    @available(iOS 16.1, *)\n    func restoreLiveActivityTracking(callback: @escaping @Sendable (any LiveActivityRestorer) async -> Void) {\n\n    }\n\n}\n\nextension TestChannel: InternalAirshipChannel {\n    func clearSubscriptionListsCache() {\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestChannelAudienceManager.swift",
    "content": "import Combine\nimport Foundation\n\n@testable import AirshipCore\n\nclass TestChannelAudienceManager: ChannelAudienceManagerProtocol, @unchecked Sendable {\n    var pendingLiveActivityUpdates: [LiveActivityUpdate] = []\n\n    let liveActivityUpdates: AsyncStream<[LiveActivityUpdate]>\n    let liveActivityUpdatesContinuation: AsyncStream<[LiveActivityUpdate]>.Continuation\n\n    private(set) var operations: [ContactOperation] = []\n    var generateDefaultContactIDCalled: Bool = false\n\n    init() {\n        (\n            self.liveActivityUpdates,\n            self.liveActivityUpdatesContinuation\n        ) = AsyncStream<[LiveActivityUpdate]>.airshipMakeStreamWithContinuation()\n    }\n\n    public let subscriptionListEditsSubject = PassthroughSubject<\n        SubscriptionListEdit, Never\n    >()\n    public var subscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never>\n    {\n        self.subscriptionListEditsSubject.eraseToAnyPublisher()\n    }\n\n    public var contactUpdates: [SubscriptionListUpdate] = []\n\n    public var pendingAttributeUpdates: [AttributeUpdate] = []\n\n    public var pendingTagGroupUpdates: [TagGroupUpdate] = []\n\n    public var channelID: String? = nil\n\n    public var enabled: Bool = false\n\n    public var tagGroupEditor: TagGroupsEditor?\n\n    public var attributeEditor: AttributesEditor?\n\n    public var subscriptionListEditor: SubscriptionListEditor?\n\n    public var fetchSubscriptionListCallback:\n        (() async throws -> [String])?\n\n    public func editSubscriptionLists() -> SubscriptionListEditor {\n        return subscriptionListEditor!\n    }\n\n    public func editTagGroups(allowDeviceGroup: Bool) -> TagGroupsEditor {\n        return tagGroupEditor!\n    }\n\n    public func editAttributes() -> AttributesEditor {\n        return attributeEditor!\n    }\n\n\n    public func fetchSubscriptionLists() async throws -> [String] {\n        return try await fetchSubscriptionListCallback!()\n    }\n\n    public func processContactSubscriptionUpdates(\n        _ updates: [SubscriptionListUpdate]\n    ) {\n        self.contactUpdates.append(contentsOf: updates)\n    }\n\n    public func addLiveActivityUpdate(_ update: LiveActivityUpdate) {\n    }\n\n    public func clearSubscriptionListCache() {\n        \n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestChannelAuthTokenAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable import AirshipCore\n\nfinal class TestChannelAuthTokenAPIClient: ChannelAuthTokenAPIClientProtocol, @unchecked Sendable {\n\n    var handler: ((String) async throws -> AirshipHTTPResponse<ChannelAuthTokenResponse>)?\n\n    func fetchToken(\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<ChannelAuthTokenResponse> {\n\n        guard let handler = handler else {\n            throw AirshipErrors.error(\"Request block not set\")\n        }\n\n        return try await handler(channelID)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestChannelBulkUpdateAPIClient.swift",
    "content": "import Foundation\n\n@testable import AirshipCore\n\nclass TestChannelBulkUpdateAPIClient: ChannelBulkUpdateAPIClientProtocol, @unchecked Sendable {\n\n    var updateCallback:\n        ((String, AudienceUpdate) async throws ->  AirshipHTTPResponse<Void>)?\n\n    init() {}\n\n    func update(_ update: AirshipCore.AudienceUpdate, channelID: String) async throws -> AirshipCore.AirshipHTTPResponse<Void> {\n        try await self.updateCallback!(channelID, update)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestChannelRegistrar.swift",
    "content": "import Foundation\nimport Combine\n@testable import AirshipCore\n\nclass TestChannelRegistrar:  ChannelRegistrarProtocol, @unchecked Sendable {\n    let registrationUpdates: AirshipAsyncChannel<ChannelRegistrationUpdate> = .init()\n\n    var payloadCreateBlock: (@Sendable () async -> AirshipCore.ChannelRegistrationPayload?)?\n\n    private var extenders: [@Sendable (inout ChannelRegistrationPayload) async -> Void] = []\n\n    public var channelPayload: ChannelRegistrationPayload {\n        get async {\n            let payload = await payloadCreateBlock?()\n            var result: ChannelRegistrationPayload = payload ?? ChannelRegistrationPayload()\n\n            for extender in extenders {\n                await extender(&result)\n            }\n            return result\n        }\n    }\n\n    public func addRegistrationExtender(_ extender: @Sendable @escaping (inout ChannelRegistrationPayload) async -> Void) {\n        self.extenders.append(extender)\n    }\n\n    public var channelID: String?\n\n    public var registerCalled = false\n\n    public func register(forcefully: Bool) {\n        registerCalled = true\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestContact.swift",
    "content": "import Foundation\nimport Combine\n\n@testable import AirshipCore\n\nclass TestContact: InternalAirshipContact, AirshipComponent, @unchecked Sendable {\n    var contactChannelUpdates: AsyncStream<AirshipCore.ContactChannelsResult> = AsyncStream<ContactChannelsResult>.init { _ in }\n\n    var contactChannelPublisher: AnyPublisher<AirshipCore.ContactChannelsResult, Never> = Just(.success([])).eraseToAnyPublisher()\n\n    func getStableContactInfo() async -> StableContactInfo {\n        return StableContactInfo(contactID: await getStableContactID(), namedUserID: namedUserID)\n    }\n    \n    init() {}\n\n    func registerEmail(_ address: String, options: AirshipCore.EmailRegistrationOptions) {\n\n    }\n    \n    func registerSMS(_ msisdn: String, options: AirshipCore.SMSRegistrationOptions) {\n\n    }\n    \n    func registerOpen(_ address: String, options: AirshipCore.OpenRegistrationOptions) {\n\n    }\n\n    func resend(_ channel: AirshipCore.ContactChannel) {\n\n    }\n    \n    func disassociateChannel(_ channel: AirshipCore.ContactChannel) {\n\n    }\n\n    func associateChannel(_ channelID: String, type: AirshipCore.ChannelType) {\n\n    }\n\n\n    func notifyRemoteLogin() {\n    }\n\n    var contactIDInfo: AirshipCore.ContactIDInfo? = nil\n\n    let contactIDUpdatesSubject = PassthroughSubject<ContactIDInfo, Never>()\n    var contactIDUpdates: AnyPublisher<ContactIDInfo, Never>  {\n        contactIDUpdatesSubject.eraseToAnyPublisher()\n    }\n\n    var contactID: String? = nil\n\n    var authTokenProvider: AuthTokenProvider = TestAuthTokenProvider { id in\n        return \"\"\n    }\n\n    func getStableContactID() async -> String {\n        return contactID ?? \"\"\n    }\n\n    public static let contactConflictEvent = NSNotification.Name(\n        \"com.urbanairship.contact_conflict\"\n    )\n\n    public static let contactConflictEventKey = \"event\"\n\n    public static let maxNamedUserIDLength = 128\n\n\n    private let conflictEventSubject = PassthroughSubject<ContactConflictEvent, Never>()\n    public var conflictEventPublisher: AnyPublisher<ContactConflictEvent, Never> {\n        conflictEventSubject.eraseToAnyPublisher()\n    }\n\n    private let namedUserUpdatesSubject = PassthroughSubject<String?, Never>()\n    public var namedUserIDPublisher: AnyPublisher<String?, Never> {\n        namedUserUpdatesSubject\n            .prepend(namedUserID)\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    public var subscriptionListEdits: AnyPublisher<AirshipCore.ScopedSubscriptionListEdit, Never> {\n        subscriptionListEditsSubject.eraseToAnyPublisher()\n    }\n    private let subscriptionListEditsSubject = PassthroughSubject<ScopedSubscriptionListEdit, Never>()\n\n    public var isComponentEnabled: Bool = true\n\n    public var namedUserID: String?\n\n    public var pendingAttributeUpdates: [AttributeUpdate] = []\n\n    public var pendingTagGroupUpdates: [TagGroupUpdate] = []\n\n    public var tagGroupEditor: TagGroupsEditor?\n\n    public var attributeEditor: AttributesEditor?\n\n    public var subscriptionListEditor: ScopedSubscriptionListEditor?\n\n    public func identify(_ namedUserID: String) {\n        self.namedUserID = namedUserID\n    }\n\n    public func reset() {\n        self.namedUserID = nil\n    }\n\n    public func editTagGroups() -> TagGroupsEditor {\n        return tagGroupEditor!\n    }\n\n    public func editAttributes() -> AttributesEditor {\n        return attributeEditor!\n    }\n\n    public func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void) {\n        let editor = editTagGroups()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func editAttributes(_ editorBlock: (AttributesEditor) -> Void) {\n        let editor = editAttributes()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func editSubscriptionLists() -> ScopedSubscriptionListEditor {\n        return subscriptionListEditor!\n    }\n\n    public func editSubscriptionLists(\n        _ editorBlock: (ScopedSubscriptionListEditor) -> Void\n    ) {\n        let editor = editSubscriptionLists()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    public func fetchSubscriptionLists() async throws ->  [String: [ChannelScope]] {\n        return [:]\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestContactAPIClient.swift",
    "content": "import Foundation\n\n@testable import AirshipCore\n\nclass TestContactAPIClient: ContactsAPIClientProtocol, @unchecked Sendable {\n    var resolveCallback:\n    ((String, String?, String?) async throws -> AirshipHTTPResponse<ContactIdentifyResult>)?\n\n    var identifyCallback:\n    ((String, String, String?, String?) async throws -> AirshipHTTPResponse<ContactIdentifyResult>)?\n\n    var resetCallback:\n    ((String, String?) async throws -> AirshipHTTPResponse<ContactIdentifyResult>)?\n\n    var resendCallback:\n    ((ResendOptions) async throws -> AirshipHTTPResponse<Bool>)?\n\n    var updateCallback:\n    ((String, [TagGroupUpdate]?, [AttributeUpdate]?, [ScopedSubscriptionListUpdate]?) async throws -> AirshipHTTPResponse<Void>)?\n\n    var associateChannelCallback:\n    ((String, String, ChannelType) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult>)?\n\n    var disassociateChannelCallback:\n    ((Bool, String, ChannelType) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult>)?\n\n    var disassociateEmailCallback:\n    ((Bool, String, String) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult>)?\n\n    var disassociateSMSCallback:\n    ((Bool, String, String, String) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult>)?\n\n    var registerEmailCallback:\n    ((String, String, EmailRegistrationOptions, Locale) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult>)?\n\n    var registerSMSCallback:\n    ((String, String, SMSRegistrationOptions, Locale) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult>)?\n\n    var registerOpenCallback:\n    ((String, String, OpenRegistrationOptions, Locale) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult>)?\n\n    init() {}\n\n    public func resolve(\n        channelID: String,\n        contactID: String?,\n        possiblyOrphanedContactID: String?\n    ) async throws -> AirshipHTTPResponse<ContactIdentifyResult> {\n        return try await resolveCallback!(channelID, contactID, possiblyOrphanedContactID)\n    }\n\n    public func identify(\n        channelID: String,\n        namedUserID: String,\n        contactID: String?,\n        possiblyOrphanedContactID: String?\n    ) async throws -> AirshipHTTPResponse<ContactIdentifyResult> {\n        return try await identifyCallback!(channelID, namedUserID, contactID, possiblyOrphanedContactID)\n    }\n\n    public func reset(\n        channelID: String,\n        possiblyOrphanedContactID: String?\n    ) async throws -> AirshipHTTPResponse<ContactIdentifyResult> {\n        return try await resetCallback!(channelID, possiblyOrphanedContactID)\n    }\n\n    public func update(\n        contactID: String,\n        tagGroupUpdates: [TagGroupUpdate]?,\n        attributeUpdates: [AttributeUpdate]?,\n        subscriptionListUpdates: [ScopedSubscriptionListUpdate]?\n    ) async throws -> AirshipHTTPResponse<Void> {\n        return try await updateCallback!(contactID, tagGroupUpdates, attributeUpdates, subscriptionListUpdates)\n    }\n\n    func associateChannel(contactID: String,\n                          channelID: String,\n                          channelType: ChannelType\n    ) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await associateChannelCallback!(contactID, channelID, channelType)\n    }\n\n    public func registerEmail(\n        contactID: String,\n        address: String,\n        options: EmailRegistrationOptions,\n        locale: Locale\n    ) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await registerEmailCallback!(contactID, address, options, locale)\n    }\n\n    public func registerSMS(\n        contactID: String,\n        msisdn: String,\n        options: SMSRegistrationOptions,\n        locale: Locale\n    ) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await registerSMSCallback!(contactID, msisdn, options, locale)\n    }\n    \n    public func registerOpen(\n        contactID: String,\n        address: String,\n        options: OpenRegistrationOptions,\n        locale: Locale\n    ) async throws -> AirshipHTTPResponse<ContactAssociateChannelResult> {\n        return try await registerOpenCallback!(contactID, address, options, locale)\n    }\n\n    func resend(resendOptions: ResendOptions) async throws -> AirshipHTTPResponse<Bool> {\n        return try await resendCallback!(resendOptions)\n    }\n\n    func disassociateChannel(\n        contactID: String,\n        disassociateOptions: DisassociateOptions\n    ) async throws -> AirshipHTTPResponse<ContactDisassociateChannelResult> {\n        switch disassociateOptions {\n        case .channel(let channel):\n            return try await disassociateChannelCallback!(true, channel.channelID, channel.channelType)\n        case .email(let email):\n            return try await disassociateEmailCallback!(false, email.address, email.channelType)\n        case .sms(let sms):\n            return try await disassociateSMSCallback!(false, sms.msisdn, sms.senderID, sms.channelType)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestContactSubscriptionListAPIClient.swift",
    "content": "import Foundation\n\n@testable import AirshipCore\n\nclass TestContactSubscriptionListAPIClient: ContactSubscriptionListAPIClientProtocol, @unchecked Sendable {\n    var fetchSubscriptionListsCallback:\n        ((String) async throws -> AirshipHTTPResponse<[String: [ChannelScope]]>)?\n\n    init() {}\n\n    func fetchSubscriptionLists(\n        contactID: String\n    ) async throws -> AirshipHTTPResponse<[String: [ChannelScope]]> {\n        return try await fetchSubscriptionListsCallback!(contactID)\n    }\n\n}\n\nfinal class TestContactChannelsProvider: ContactChannelsProviderProtocol, @unchecked Sendable {\n    func contactChannels(stableContactIDUpdates: AsyncStream<String>) -> AsyncStream<ContactChannelsResult> {\n        return AsyncStream<ContactChannelsResult> { _ in }\n    }\n    \n    func contactUpdates(contactID: String) async throws -> AsyncStream<[ContactChannel]> {\n        return AsyncStream<[ContactChannel]> { _ in }\n    }\n    \n    func refresh() async {\n        refreshedCalled = true\n    }\n\n    var refreshedCalled = false\n\n\n    func refreshAsync() {\n        refreshedCalled = true\n    }\n\n    init() {}\n}\n\n\n//ContactChannelsProviderProtocol\n//ContactChannelsAPIClientProtocol\n\nclass TestChannelsListAPIClient: ContactChannelsAPIClientProtocol, @unchecked Sendable {\n\n    func fetchAssociatedChannelsList(\n        contactID: String\n    ) async throws -> AirshipHTTPResponse<[ContactChannel]> {\n        return AirshipHTTPResponse(result: nil, statusCode: 200, headers: [:])\n    }\n\n    init() {}\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestDate.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable\npublic import AirshipCore\npublic import Foundation\n\npublic class UATestDate: @unchecked Sendable, AirshipDateProtocol  {\n\n    public init(offset: TimeInterval = 0, dateOverride: Date? = nil) {\n        self._offSet = AirshipAtomicValue(offset)\n        self.dateOverride = dateOverride\n    }\n\n    private var _offSet: AirshipAtomicValue<TimeInterval>\n\n    public func advance(by: TimeInterval) {\n        self._offSet.value += by\n    }\n\n    public var offset: TimeInterval {\n        get {\n            return self._offSet.value\n        }\n        set {\n            self._offSet.value = newValue\n        }\n    }\n\n    public var dateOverride: Date?\n\n    public var now: Date {\n        let date = dateOverride ?? Date()\n        return date.advanced(by: offset)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestDeferredResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable\nimport AirshipCore\n\nimport Foundation\n\nfinal actor TestDeferredResolver: AirshipDeferredResolverProtocol {\n    var dataCallback: ((DeferredRequest) async -> AirshipDeferredResult<Data>)?\n\n    func onData(_ onData: @escaping (DeferredRequest) async -> AirshipDeferredResult<Data>) {\n        self.dataCallback = onData\n    }\n\n    func resolve<T>(\n        request: DeferredRequest,\n        resultParser: @escaping @Sendable (Data) async throws -> T\n    ) async -> AirshipDeferredResult<T> where T : Sendable {\n        switch(await dataCallback?(request) ?? .timedOut) {\n        case .success(let data):\n            do {\n                let value = try await resultParser(data)\n                return .success(value)\n            } catch {\n                return .retriableError(statusCode: 200)\n            }\n        case .timedOut: return .timedOut\n        case .outOfDate: return .outOfDate\n        case .notFound: return .notFound\n        case .retriableError(retryAfter: let retryAfter, statusCode: let statusCode): return .retriableError(retryAfter: retryAfter, statusCode:statusCode)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestDispatcher.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable\nimport AirshipCore\n\nfinal class TestDispatcher: UADispatcher {\n    func dispatchAsync(_ block: @escaping @Sendable () -> Void) {\n        block()\n    }\n    \n    func doSync(_ block: @escaping @Sendable () -> Void) {\n        block()\n    }\n    \n    func dispatchAsyncIfNecessary(_ block: @escaping @Sendable () -> Void) {\n        block()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestExperimentDataProvider.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable\nimport AirshipCore\n\nimport Foundation\n\nfinal class TestExperimentDataProvider: ExperimentDataProvider, @unchecked Sendable {\n    var onEvaluate: ((MessageInfo, AudienceDeviceInfoProvider) async throws -> ExperimentResult?)? = nil\n\n    func evaluateExperiments(\n        info: MessageInfo,\n        deviceInfoProvider: AudienceDeviceInfoProvider\n    ) async throws -> ExperimentResult? {\n        return try await onEvaluate?(info, deviceInfoProvider)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestKeychainAccess.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable import AirshipCore\nimport Foundation\n\n// Test keychain that performs its in the same queueing as the real AirshipKeyChainAccess\nfinal class TestKeyChainAccess: AirshipKeychainAccessProtocol, @unchecked Sendable {\n    var storedCredentials: [String: AirshipKeychainCredentials] = [:]\n    private let dispatchQueue: DispatchQueue = DispatchQueue(\n        label: \"com.urbanairship.dispatcher.keychain\",\n        qos: .utility\n    )\n\n    public func writeCredentials(\n        _ credentials: AirshipKeychainCredentials,\n        identifier: String,\n        appKey: String\n    ) async -> Bool {\n        let key = \"\\(appKey).\\(identifier)\"\n        return await self.dispatch {\n            self.storedCredentials[key] = credentials\n            return true\n        }\n    }\n\n    public func deleteCredentials(identifier: String, appKey: String) async {\n        let key = \"\\(appKey).\\(identifier)\"\n        return await self.dispatch {\n            self.storedCredentials[key] = nil\n        }\n    }\n\n    public func readCredentails(\n        identifier: String,\n        appKey: String\n    ) async -> AirshipKeychainCredentials? {\n        let key = \"\\(appKey).\\(identifier)\"\n        return await self.dispatch {\n            return self.storedCredentials[key]\n        }\n    }\n\n    // Not really needed for this class but it matches the behavior of the real\n    // keychain access\n    private func dispatch<T>(block: @escaping @Sendable () -> T) async -> T {\n        return await withCheckedContinuation { continuation in\n            dispatchQueue.async {\n                continuation.resume(returning: block())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestLocaleManager.swift",
    "content": "\n@testable public import AirshipCore\npublic import Foundation\n\npublic class TestLocaleManager: AirshipLocaleManager, @unchecked Sendable {\n\n    public var _locale: Locale? = nil\n\n    public func clearLocale() {\n        self._locale = nil\n    }\n\n    public var currentLocale: Locale {\n        get {\n            return self._locale ?? Locale.autoupdatingCurrent\n        }\n        set {\n            self._locale = newValue\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestNetworkMonitor.swift",
    "content": "import AirshipCore\nimport Foundation\n\nactor TestNetworkChecker: AirshipNetworkCheckerProtocol {\n    private let _isConnected = AirshipMainActorValue(false)\n\n    @MainActor\n    var connectionUpdates: AsyncStream<Bool> {\n        return _isConnected.updates\n    }\n\n    init() {}\n\n    @MainActor\n    public func setConnected(_ connected: Bool) {\n        self._isConnected.set(connected)\n    }\n\n    @MainActor\n    var isConnected: Bool {\n        return _isConnected.value\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestPermissionPrompter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable import AirshipCore\n\nfinal class TestPermissionPrompter: PermissionPrompter, @unchecked Sendable {\n\n    var onPrompt:\n        (\n            (\n                AirshipPermission, Bool, Bool\n            ) ->\n            AirshipPermissionResult\n        )?\n\n    init() {}\n\n    func prompt(\n        permission: AirshipPermission,\n        enableAirshipUsage: Bool,\n        fallbackSystemSettings: Bool) async ->  AirshipPermissionResult  {\n\n        if let onPrompt = self.onPrompt {\n            return onPrompt(\n                permission,\n                enableAirshipUsage,\n                fallbackSystemSettings\n            )\n        } else {\n            return AirshipPermissionResult.notDetermined\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestPrivacyManager.swift",
    "content": "\n\n@testable import AirshipCore\nimport Foundation\n\nfinal class TestPrivacyManager: InternalAirshipPrivacyManager, @unchecked Sendable {\n    func isAnyFeatureEnabled() -> Bool {\n        return isAnyFeatureEnabled(ignoringRemoteConfig: false)\n    }\n\n    private static let enabledFeaturesKey = \"com.urbanairship.privacymanager.enabledfeatures\"\n\n    private let dataStore: PreferenceDataStore\n    private let config: RuntimeConfig\n    let notificationCenter: AirshipNotificationCenter\n\n    private let defaultEnabledFeatures: AirshipFeature\n\n    private let lock: AirshipLock = AirshipLock()\n    private var lastUpdated: AirshipFeature = []\n\n    private var _enabledFeatures: AirshipFeature = []\n\n    private var localEnabledFeatures: AirshipFeature {\n        get {\n            guard let fromStore = self.dataStore.unsignedInteger(forKey: TestPrivacyManager.enabledFeaturesKey) else {\n                return self.defaultEnabledFeatures\n            }\n\n            return AirshipFeature(\n                rawValue:(fromStore & AirshipFeature.all.rawValue)\n            )\n        }\n        set {\n            self.dataStore.setValue(\n                newValue.rawValue,\n                forKey: TestPrivacyManager.enabledFeaturesKey\n            )\n        }\n    }\n\n    init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        defaultEnabledFeatures: AirshipFeature = [],\n        notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter()\n    ) {\n        self.dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n        self.config = RuntimeConfig.testConfig()\n        self.defaultEnabledFeatures = defaultEnabledFeatures\n        self.notificationCenter = notificationCenter\n\n        self.notifyUpdate()\n    }\n\n    /// The current set of enabled features.\n    public var enabledFeatures: AirshipFeature {\n        get {\n            self.localEnabledFeatures.subtracting(self.config.remoteConfig.disabledFeatures ?? [])\n        }\n        set {\n            lock.sync {\n                self.localEnabledFeatures = newValue\n                notifyUpdate()\n            }\n        }\n    }\n\n    func enableFeatures(_ features: AirshipFeature) {\n        self.enabledFeatures.insert(features)\n    }\n\n    func disableFeatures(_ features: AirshipFeature) {\n        self.enabledFeatures.remove(features)\n    }\n\n    func isEnabled(_ feature: AirshipFeature) -> Bool {\n        guard feature == [] else {\n            return (enabledFeatures.rawValue & feature.rawValue) == feature.rawValue\n        }\n        return enabledFeatures == []\n    }\n\n    func isAnyFeatureEnabled(ignoringRemoteConfig: Bool) -> Bool {\n        if ignoringRemoteConfig {\n            return localEnabledFeatures != []\n        } else {\n            return enabledFeatures != []\n        }\n    }\n\n    private func notifyUpdate() {\n        lock.sync {\n            let enabledFeatures = self.enabledFeatures\n            guard enabledFeatures != lastUpdated else { return }\n            self.lastUpdated = enabledFeatures\n            self.notificationCenter.postOnMain(\n                name: AirshipNotifications.PrivacyManagerUpdated.name\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestPush.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@testable\nimport AirshipCore\n\nimport UserNotifications\nimport UIKit\nimport Foundation\nimport Combine\n\nfinal class TestPush: NSObject, InternalAirshipPush, AirshipPush, AirshipComponent, @unchecked Sendable {\n    var onAPNSRegistrationFinished: (@MainActor @Sendable (AirshipCore.APNSRegistrationResult) -> Void)?\n    \n    var onNotificationRegistrationFinished: (@MainActor @Sendable (AirshipCore.NotificationRegistrationResult) -> Void)?\n    \n    var onNotificationAuthorizedSettingsDidChange: (@MainActor @Sendable (AirshipCore.AirshipAuthorizedNotificationSettings) -> Void)?\n    \n    func enableUserPushNotifications(fallback: AirshipCore.PromptPermissionFallback) async -> Bool {\n        return true\n    }\n    \n    \n    override init() {\n        (self.notificationStatusUpdates, self.statusUpdateContinuation) = AsyncStream<AirshipNotificationStatus>.airshipMakeStreamWithContinuation()\n        \n        super.init()\n    }\n    \n    var quietTime: QuietTimeSettings?\n    \n    func enableUserPushNotifications() async -> Bool {\n        return true\n    }\n\n    func setBadgeNumber(_ newBadgeNumber: Int) async {\n\n    }\n\n    func resetBadge() async {\n\n    }\n\n    var autobadgeEnabled: Bool = false\n\n    var timeZone: NSTimeZone?\n\n    var quietTimeEnabled: Bool = false\n\n    func setQuietTimeStartHour(_ startHour: Int, startMinute: Int, endHour: Int, endMinute: Int) {\n\n    }\n\n    let notificationStatusSubject: PassthroughSubject<AirshipNotificationStatus, Never> = PassthroughSubject()\n    \n    let notificationStatusUpdates: AsyncStream<AirshipNotificationStatus>\n    let statusUpdateContinuation: AsyncStream<AirshipNotificationStatus>.Continuation\n\n    var notificationStatusPublisher: AnyPublisher<AirshipNotificationStatus, Never> {\n        notificationStatusSubject.removeDuplicates().eraseToAnyPublisher()\n    }\n\n    var notificationStatus: AirshipNotificationStatus = AirshipNotificationStatus(\n        isUserNotificationsEnabled: false,\n        areNotificationsAllowed: false,\n        isPushPrivacyFeatureEnabled: false,\n        isPushTokenRegistered: false,\n        displayNotificationStatus: .denied\n    )\n\n\n    var isPushNotificationsOptedIn: Bool = false\n\n    var backgroundPushNotificationsEnabled: Bool = false\n\n    var userPushNotificationsEnabled: Bool = false\n\n    var extendedPushNotificationPermissionEnabled: Bool = false\n\n    var requestExplicitPermissionWhenEphemeral: Bool = false\n\n    var notificationOptions: UNAuthorizationOptions  = []\n\n    var customCategories: Set<UNNotificationCategory> = Set()\n\n    var accengageCategories: Set<UNNotificationCategory> = Set()\n\n    var requireAuthorizationForDefaultCategories: Bool = false\n\n    var pushNotificationDelegate: PushNotificationDelegate?\n\n    var registrationDelegate: RegistrationDelegate?\n\n    var launchNotificationResponse: UNNotificationResponse?\n\n    var authorizedNotificationSettings: AirshipAuthorizedNotificationSettings = []\n\n    var authorizationStatus: UNAuthorizationStatus = .notDetermined\n\n    var userPromptedForNotifications: Bool = false\n\n    var defaultPresentationOptions: UNNotificationPresentationOptions = []\n\n    var badgeNumber: Int = 0\n\n    var deviceToken: String?\n\n    // Notification callbacks\n    var onForegroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> Void)?\n\n#if !os(watchOS)\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> UIBackgroundFetchResult)?\n#else\n    var onBackgroundNotificationReceived: (@MainActor @Sendable ([AnyHashable: Any]) async -> WKBackgroundFetchResult)?\n#endif\n\n#if !os(tvOS)\n    var onNotificationResponseReceived: (@MainActor @Sendable (UNNotificationResponse) async -> Void)?\n#endif\n\n    var onExtendPresentationOptions: (@MainActor @Sendable (UNNotificationPresentationOptions, UNNotification) async -> UNNotificationPresentationOptions)?\n\n    var updateAuthorizedNotificationTypesCalled = false\n    var registrationError: Error?\n    var didReceiveRemoteNotificationCallback: (\n        ([AnyHashable: Any], Bool) -> UIBackgroundFetchResult\n    )?\n    var combinedCategories: Set<UNNotificationCategory> = Set()\n\n    func dispatchUpdateAuthorizedNotificationTypes() {\n        self.updateAuthorizedNotificationTypesCalled = true\n    }\n\n    func didRegisterForRemoteNotifications(_ deviceToken: Data) {\n        self.deviceToken = String(data: deviceToken, encoding: .utf8)\n    }\n\n    func didFailToRegisterForRemoteNotifications(_ error: Error) {\n        self.registrationError = error\n    }\n\n    func didReceiveRemoteNotification(\n        _ userInfo: [AnyHashable: Any],\n        isForeground: Bool\n    ) async -> UABackgroundFetchResult {\n        let result = self.didReceiveRemoteNotificationCallback!(\n            userInfo,\n            isForeground\n        )\n\n        return .init(from: result)\n    }\n\n\n    func presentationOptionsForNotification(_ notification: UNNotification) async -> UNNotificationPresentationOptions {\n        return []\n    }\n\n\n    func didReceiveNotificationResponse(_ response: UNNotificationResponse) async {\n        assertionFailure(\"Unable to create UNNotificationResponse in tests.\")\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestRemoteData.swift",
    "content": "\n@testable\nimport AirshipCore\nimport Foundation\nimport Combine\n\n\nfinal class TestRemoteData: NSObject, RemoteDataProtocol, @unchecked Sendable {\n    func statusUpdates<T>(sources: [AirshipCore.RemoteDataSource], map: @escaping (@Sendable ([AirshipCore.RemoteDataSource : AirshipCore.RemoteDataSourceStatus]) -> T)) -> AsyncStream<T> where T : Sendable {\n        return AsyncStream<T> { _ in }\n    }\n    \n    func forceRefresh() async {\n    }\n\n    var waitForRefreshAttemptBlock: ((RemoteDataSource, TimeInterval?) -> Void)?\n    var waitForRefreshBlock: ((RemoteDataSource, TimeInterval?) -> Void)?\n\n    var notifiedOutdatedInfos: [RemoteDataInfo] = []\n\n    let updatesSubject = PassthroughSubject<[RemoteDataPayload], Never>()\n    var isCurrent = true\n    var payloads: [RemoteDataPayload] = [] {\n        didSet {\n            updatesSubject.send(payloads)\n        }\n    }\n\n    var status: [RemoteDataSource : RemoteDataSourceStatus] = [:]\n\n\n    var remoteDataRefreshInterval: TimeInterval = 0\n    var isContactSourceEnabled: Bool = false\n    func setContactSourceEnabled(enabled: Bool) {\n        isContactSourceEnabled = enabled\n    }\n\n\n    func isCurrent(remoteDataInfo: RemoteDataInfo) async -> Bool {\n        return isCurrent\n    }\n\n    func notifyOutdated(remoteDataInfo: RemoteDataInfo) async {\n        self.notifiedOutdatedInfos.append(remoteDataInfo)\n    }\n\n    func status(source: RemoteDataSource) async -> RemoteDataSourceStatus {\n        return self.status[source] ?? .outOfDate\n    }\n\n    func publisher(types: [String]) -> AnyPublisher<[RemoteDataPayload], Never> {\n        updatesSubject\n            .prepend(payloads)\n            .map{ payloads in\n                return payloads.filter { payload in\n                    types.contains(payload.type)\n                }\n                .sorted { first, second in\n                    let firstIndex = types.firstIndex(of: first.type) ?? 0\n                    let secondIndex = types.firstIndex(of: second.type) ?? 0\n                    return firstIndex < secondIndex\n                }\n            }\n            .eraseToAnyPublisher()\n    }\n\n    func payloads(types: [String]) async -> [RemoteDataPayload] {\n        return payloads.filter { payload in\n            types.contains(payload.type)\n        }\n        .sorted { first, second in\n            let firstIndex = types.firstIndex(of: first.type) ?? 0\n            let secondIndex = types.firstIndex(of: second.type) ?? 0\n            return firstIndex < secondIndex\n        }\n    }\n\n\n    func waitRefresh(\n        source: AirshipCore.RemoteDataSource,\n        maxTime: TimeInterval?\n    ) async {\n        self.waitForRefreshBlock?(source, maxTime)\n    }\n\n    func waitRefreshAttempt(\n        source: AirshipCore.RemoteDataSource,\n        maxTime: TimeInterval?\n    ) async {\n        self.waitForRefreshAttemptBlock?(source, maxTime)\n    }\n\n    func waitRefresh(source: AirshipCore.RemoteDataSource) async {\n        await self.waitRefresh(source: source, maxTime: nil)\n    }\n\n    func waitRefreshAttempt(source: AirshipCore.RemoteDataSource) async {\n        await self.waitRefreshAttempt(source: source, maxTime: nil)\n    }\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestRemoteDataAPIClient.swift",
    "content": "import Foundation\n\n@testable\nimport AirshipCore\n\nfinal class TestRemoteDataAPIClient: RemoteDataAPIClientProtocol, @unchecked Sendable {\n\n    public var fetchData: (\n        (URL, AirshipRequestAuth, String?, RemoteDataInfo) async throws -> AirshipHTTPResponse<RemoteDataResult>\n    )?\n\n    public var lastModified: String? = nil\n\n    func fetchRemoteData(\n        url: URL,\n        auth: AirshipRequestAuth,\n        lastModified: String?,\n        remoteDataInfoBlock: @escaping @Sendable (String?) throws -> AirshipCore.RemoteDataInfo\n    ) async throws -> AirshipCore.AirshipHTTPResponse<AirshipCore.RemoteDataResult> {\n        try await fetchData!(url, auth, lastModified, try remoteDataInfoBlock(self.lastModified))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestSubscriptionListAPIClient.swift",
    "content": "import Foundation\n\n@testable public import AirshipCore\n\npublic class TestSubscriptionListAPIClient: SubscriptionListAPIClientProtocol, @unchecked Sendable {\n    var getCallback:\n        ((String) async throws -> AirshipHTTPResponse<[String]>)?\n\n    init() {}\n\n    public func get(\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<[String]> {\n        return try await getCallback!(channelID)\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestThomasLayoutEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@testable import AirshipCore\n\nstruct TestThomasLayoutEvent: ThomasLayoutEvent {\n    var name: EventType\n    var data: (any Encodable & Sendable)?\n\n    init(name: EventType = .customEvent, data: (Encodable & Sendable)? = nil) {\n        self.name = name\n        self.data = data\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestURLAllowList.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import AirshipCore\npublic import Foundation\n\npublic final class TestURLAllowList: AirshipURLAllowList, @unchecked Sendable {\n    public var delegate: (any URLAllowListDelegate)?\n\n    public var onAllowURL: (@MainActor @Sendable (URL, URLAllowListScope) -> Bool)?\n\n    public var isAllowedReturnValue: Bool = true\n    public var addEntryReturnValue: Bool = true\n\n    public func isAllowed(_ url: URL?) -> Bool {\n        return isAllowedReturnValue\n    }\n\n    public func isAllowed(_ url: URL?, scope: URLAllowListScope)\n        -> Bool\n    {\n        return isAllowedReturnValue\n    }\n\n    public func addEntry(_ patternString: String) -> Bool {\n        return addEntryReturnValue\n    }\n\n    public func addEntry(\n        _ patternString: String,\n        scope: URLAllowListScope\n    ) -> Bool {\n        return addEntryReturnValue\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestURLOpener.swift",
    "content": "\nimport Foundation\n\n@testable\nimport AirshipCore\n\nfinal class TestURLOpener: URLOpenerProtocol, @unchecked Sendable {\n    @MainActor\n    var returnValue: Bool = true\n\n    @MainActor\n    var lastURL: URL?\n\n    @MainActor\n    var lastOpenSettingsCalled: Bool = false\n\n    @MainActor\n    func reset() {\n        lastURL = nil\n        lastOpenSettingsCalled = false\n    }\n\n    @MainActor\n    func openURL(_ url: URL) async -> Bool {\n        lastURL = url\n        return returnValue\n    }\n\n    @MainActor\n    func openURL(_ url: URL, completionHandler: (@MainActor @Sendable (Bool) -> Void)?) {\n        lastURL = url\n        completionHandler?(returnValue)\n    }\n\n    @MainActor\n    func openSettings() async -> Bool {\n        lastOpenSettingsCalled = true\n        return returnValue\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestWorkManager.swift",
    "content": "import Combine\nimport Foundation\n\n@testable import AirshipCore\n\nclass TestWorkManager: AirshipWorkManagerProtocol, @unchecked Sendable {\n\n    \n    struct Worker {\n        let workID: String\n        let workHandler: (AirshipWorkRequest) async throws -> AirshipWorkResult\n    }\n    \n    var backgroundWorkRequests: [AirshipWorkRequest] = []\n\n    var rateLimits: [String: RateLimit] = [:]\n    var workRequests: [AirshipWorkRequest] = []\n    private var workHandler: ((AirshipWorkRequest) async throws -> AirshipWorkResult?)? = nil\n    var onNewWorkRequestAdded: ((AirshipWorkRequest) -> Void)? = nil\n\n    var autoLaunchRequests: Bool = false\n    var workers: [Worker] = []\n    func registerWorker(\n        _ workID: String,\n        workHandler: @escaping (AirshipWorkRequest) async throws -> AirshipWorkResult\n    ) {\n        self.workers.append(\n            Worker(\n                workID: workID,\n                workHandler: workHandler\n            )\n        )\n        self.workHandler = workHandler\n    }\n        \n    func setRateLimit(_ limitID: String, rate: Int, timeInterval: TimeInterval) {\n        rateLimits[limitID] = RateLimit(rate: rate, timeInterval: timeInterval)\n    }\n\n    func dispatchWorkRequest(_ request: AirshipWorkRequest) {\n        workRequests.append(request)\n        onNewWorkRequestAdded?(request)\n        if (autoLaunchRequests) {\n            Task {\n                try await workHandler?(request)\n            }\n        }\n\n    }\n    \n    func autoDispatchWorkRequestOnBackground(_ request: AirshipWorkRequest) {\n        backgroundWorkRequests.append(request)\n    }\n\n    func launchTask(request: AirshipWorkRequest) async throws -> AirshipWorkResult? {\n        return try await workHandler?(request)\n    }\n\n    struct RateLimit {\n        let rate: Int\n        let timeInterval: TimeInterval\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/TestWorkRateLimiterActor.swift",
    "content": "import Foundation\n\n@testable import AirshipCore\n\nactor TestWorkRateLimiter {\n    struct RateLimitRule {\n        let rate: Int\n        let timeInterval: TimeInterval\n    }\n\n    enum Status {\n        case overLimit(TimeInterval)\n        case withinLimit(Int)\n    }\n\n    var hits: [String: [Date]] = [:]\n    var rules: [String: RateLimitRule] = [:]\n    private let date: AirshipDateProtocol\n\n    init(date: AirshipDate = AirshipDate()) {\n        self.date = date\n    }\n\n    func set(_ key: String, rate: Int, timeInterval: TimeInterval) throws {\n        guard rate > 0, timeInterval > 0 else {\n            throw AirshipErrors.error(\n                \"Rate and time interval must be greater than 0\"\n            )\n        }\n\n        self.rules[key] = RateLimitRule(rate: rate, timeInterval: timeInterval)\n        self.hits[key] = []\n    }\n\n    func nextAvailable(_ keys: [String]) -> TimeInterval {\n        return\n            keys.map { key in\n                guard case .overLimit(let delay) = status(key) else {\n                    return 0.0\n                }\n                return delay\n            }\n            .max() ?? 0.0\n    }\n\n    func trackIfWithinLimit(_ keys: [String]) -> Bool {\n        let overLimit = keys.contains {\n            if let status = status($0) {\n                if case .overLimit(_) = status {\n                    return true\n                }\n            }\n\n            return false\n        }\n\n        guard !overLimit else {\n            return false\n        }\n        keys.forEach { track($0) }\n        return true\n    }\n\n    private func status(_ key: String) -> Status? {\n        guard let rule = rules[key] else {\n            AirshipLogger.debug(\"No rule for key \\(key)\")\n            return nil\n        }\n\n        let date = date.now\n\n        let filtered = filter(self.hits[key], rule: rule, date: date) ?? []\n        let count = filtered.count\n\n        guard count >= rule.rate else {\n            return .withinLimit(rule.rate - count)\n        }\n        let nextAvailable =\n            rule.timeInterval\n            - date.timeIntervalSince(filtered[count - rule.rate])\n        return .overLimit(nextAvailable)\n    }\n\n    private func track(_ key: String) {\n        guard let rule = rules[key] else {\n            AirshipLogger.debug(\"No rule for key \\(key)\")\n            return\n        }\n\n        var keyHits = hits[key] ?? []\n        keyHits.append(self.date.now)\n        hits[key] = filter(keyHits, rule: rule, date: self.date.now)\n    }\n\n    private func filter(_ hits: [Date]?, rule: RateLimitRule, date: Date)\n        -> [Date]?\n    {\n        guard let hits = hits else { return nil }\n        return hits.filter { hit in\n            return hit.advanced(by: rule.timeInterval) > date\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ThomasPresentationModelCodingTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class ThomasPresentationModelCodingTest: XCTestCase {\n    \n    private let simpleContentViewJson = \"\"\"\n    {\n       \"type\":\"label\",\n       \"text\":\"Sup Buddy\",\n       \"text_appearance\":{\n          \"font_size\":14,\n          \"color\":{\n             \"type\": \"hex\",\n             \"default\":{\n                \"hex\":\"#333333\"\n             }\n          },\n          \"alignment\":\"start\",\n          \"styles\":[\n             \"italic\"\n          ],\n          \"font_families\":[\n             \"permanent_marker\",\n             \"casual\"\n          ]\n       }\n    }\n    \"\"\"\n    \n    func testBannerPresentationModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"banner\",\n          \"placement_selectors\": [\n            {\n              \"orientation\": \"landscape\",\n              \"placement\": {\n                \"position\": \"top\",\n                \"size\": {\n                  \"width\": \"50%\",\n                  \"height\": 500\n                },\n                \"border\": {\n                  \"radius\": 20\n                }\n              }\n            }\n          ],\n          \"default_placement\": {\n            \"size\": {\n              \"width\": \"100%\",\n              \"height\": 500\n            },\n            \"position\": \"top\"\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasPresentationInfo.self)\n    }\n    \n    func testModalPresentationModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"default_placement\": {\n            \"size\": {\n              \"min_width\": \"100%\",\n              \"min_height\": \"100%\",\n              \"max_height\": \"100%\",\n              \"height\": \"100%\",\n              \"width\": \"100%\",\n              \"max_width\": \"100%\"\n            },\n            \"device\": {\n              \"lock_orientation\": \"portrait\"\n            },\n            \"shade_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"alpha\": 0.2,\n                \"hex\": \"#000000\"\n              }\n            },\n            \"ignore_safe_area\": false,\n            \"position\": {\n              \"horizontal\": \"center\",\n              \"vertical\": \"top\"\n            }\n          },\n          \"type\": \"modal\",\n          \"dismiss_on_touch_outside\": false\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasPresentationInfo.self)\n    }\n    \n    func testEmbeddedPresentationModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"embedded\",\n          \"embedded_id\": \"home_banner\",\n          \"default_placement\": {\n            \"size\": {\n              \"width\": \"100%\",\n              \"height\": \"100%\"\n            },\n            \"margin\": {\n              \"top\": 16,\n              \"bottom\": 16,\n              \"start\": 16,\n              \"end\": 16\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasPresentationInfo.Embedded.self)\n    }\n    \n    func testPresentationModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"default_placement\": {\n            \"size\": {\n              \"min_width\": \"100%\",\n              \"min_height\": \"100%\",\n              \"max_height\": \"100%\",\n              \"height\": \"100%\",\n              \"width\": \"100%\",\n              \"max_width\": \"100%\"\n            },\n            \"device\": {\n              \"lock_orientation\": \"portrait\"\n            },\n            \"shade_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"alpha\": 0.2,\n                \"hex\": \"#000000\"\n              }\n            },\n            \"ignore_safe_area\": false,\n            \"position\": {\n              \"horizontal\": \"center\",\n              \"vertical\": \"top\"\n            }\n          },\n          \"type\": \"modal\",\n          \"dismiss_on_touch_outside\": false\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasPresentationInfo.self)\n    }\n    \n    func testAirshipLayoutCodable() throws {\n        let json = \"\"\"\n        {\n          \"version\": 1,\n          \"presentation\": {\n            \"type\": \"embedded\",\n            \"embedded_id\": \"home_banner\",\n            \"default_placement\": {\n              \"size\": {\n                \"width\": \"50%\",\n                \"height\": \"50%\"\n              },\n              \"margin\": {\n                \"top\": 16,\n                \"bottom\": 16,\n                \"start\": 16,\n                \"end\": 16\n              }\n            }\n          },\n          \"view\": {\n            \"type\": \"container\",\n            \"border\": {\n              \"stroke_color\": {\n                \"default\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#FF0D49\",\n                  \"alpha\": 1\n                }\n              },\n              \"stroke_width\": 2\n            },\n            \"background_color\": {\n              \"selectors\": [\n                {\n                  \"platform\": \"ios\",\n                  \"dark_mode\": false,\n                  \"color\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#FFFFFF\",\n                    \"alpha\": 1\n                  }\n                },\n                {\n                  \"platform\": \"ios\",\n                  \"dark_mode\": true,\n                  \"color\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#000000\",\n                    \"alpha\": 1\n                  }\n                }\n              ],\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#FF00FF\",\n                \"alpha\": 1\n              }\n            },\n            \"items\": [\n              {\n                \"position\": {\n                  \"horizontal\": \"center\",\n                  \"vertical\": \"center\"\n                },\n                \"size\": {\n                  \"width\": \"100%\",\n                  \"height\": \"auto\"\n                },\n                \"view\": {\n                  \"type\": \"label\",\n                  \"text\": \"50% x 50%.\",\n                  \"text_appearance\": {\n                    \"font_size\": 14,\n                    \"color\": {\n                      \"selectors\": [\n                        {\n                          \"platform\": \"ios\",\n                          \"dark_mode\": true,\n                          \"color\": {\n                            \"type\": \"hex\",\n                            \"hex\": \"#FFFFFF\",\n                            \"alpha\": 1\n                          }\n                        },\n                        {\n                          \"platform\": \"ios\",\n                          \"dark_mode\": false,\n                          \"color\": {\n                            \"type\": \"hex\",\n                            \"hex\": \"#000000\",\n                            \"alpha\": 1\n                          }\n                        }\n                      ],\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#FF00FF\",\n                        \"alpha\": 1\n                      }\n                    }\n                  }\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: AirshipLayout.self)\n    }\n    \n    \n    // MARK: - KeyboardAvoidanceMethod Tests\n    func testModalPresentationWithKeyboardAvoidanceSafeArea() throws {\n        let json = \"\"\"\n        {\n          \"default_placement\": {\n            \"size\": {\n              \"width\": \"100%\",\n              \"height\": \"100%\"\n            }\n          },\n          \"type\": \"modal\",\n          \"ios\": {\n            \"keyboard_avoidance\": \"safe_area\"\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasPresentationInfo.self)\n        \n        let decoded = try JSONDecoder().decode(ThomasPresentationInfo.self, from: json.data(using: .utf8)!)\n        if case .modal(let modal) = decoded {\n            XCTAssertEqual(modal.ios?.keyboardAvoidance, .safeArea)\n        } else {\n            XCTFail(\"Expected modal presentation\")\n        }\n    }\n    \n    private func decodeEncodeCompare<T: Codable & Equatable>(source: String, type: T.Type) throws {\n        let decoder = JSONDecoder()\n        let encoder = JSONEncoder()\n        \n        let decoded = try decoder.decode(type, from: source.data(using: .utf8)!)\n        let json = try encoder.encode(decoded)\n        let restored = try decoder.decode(type, from: json)\n        \n        XCTAssertEqual(restored, decoded)\n        \n        let inputJson = try JSONSerialization.jsonObject(with: source.data(using: .utf8)!) as! [String: Any]\n        let encodedJson = try JSONSerialization.jsonObject(with: json) as! [String: Any]\n        \n        XCTAssertEqual(try AirshipJSON.wrap(inputJson), try AirshipJSON.wrap(encodedJson))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ThomasValidationTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ThomasValidationTests: XCTestCase {\n\n    func testValidVersions() throws {\n        try (AirshipLayout.minLayoutVersion...AirshipLayout.maxLayoutVersion)\n            .map { self.layout(version: $0).data(using: .utf8)! }\n            .forEach {\n                let layout = try JSONDecoder().decode(AirshipLayout.self, from: $0)\n\n                XCTAssertTrue(\n                    layout.validate()\n                )\n            }\n    }\n\n    func testInvalidVersions() throws {\n        try ([AirshipLayout.minLayoutVersion - 1, AirshipLayout.maxLayoutVersion + 1])\n            .map { self.layout(version: $0).data(using: .utf8)! }\n            .forEach {\n                let layout = try JSONDecoder().decode(AirshipLayout.self, from: $0)\n                XCTAssertFalse(\n                    layout.validate()\n                )\n            }\n    }\n\n    func layout(version: Int) -> String {\n        \"\"\"\n        {\n            \"presentation\": {\n                \"type\": \"modal\",\n                \"default_placement\": {\n                    \"size\": {\n                        \"width\": \"60%\",\n                        \"height\": \"60%\"\n                    },\n                    \"placement\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                    }\n                }\n            },\n            \"version\": \\(version),\n            \"view\": {\n              \"type\": \"empty_view\",\n            }\n        }\n        \"\"\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/ThomasViewModelTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nfinal class ThomasViewModelTest: XCTestCase {\n\n    func testShapeModelCoding() throws {\n        let rectangle = \"\"\"\n        {\n          \"type\": \"rectangle\",\n          \"scale\": 0.5,\n          \"aspect_ratio\": 1,\n          \"color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#66FF66\",\n              \"alpha\": 1\n            }\n          },\n          \"border\": {\n            \"stroke_width\": 2,\n            \"radius\": 5,\n            \"stroke_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#333333\",\n                \"alpha\": 1\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: rectangle, type: ThomasShapeInfo.self)\n\n        let ellipse = \"\"\"\n        {\n          \"border\": {\n            \"radius\": 2,\n            \"stroke_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"alpha\": 1,\n                \"hex\": \"#000000\",\n              }\n            },\n            \"stroke_width\": 1\n          },\n          \"color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"alpha\": 1,\n              \"hex\": \"#DDDDDD\",\n            }\n          },\n          \"scale\": 1,\n          \"type\": \"ellipse\"\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: ellipse, type: ThomasShapeInfo.self)\n    }\n\n\n    func testLabelInfo() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"label\",\n          \"text\": \"You'll love these\",\n          \"content_description\": \"Love it\",\n          \"border\": {\n            \"radius\": 15,\n            \"stroke_width\": 1,\n            \"stroke_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#FFFFFF\",\n                \"alpha\": 0\n              }\n            }\n          },\n          \"text_appearance\": {\n            \"font_size\": 44,\n            \"color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#000000\",\n                \"alpha\": 1\n              }\n            },\n            \"alignment\": \"start\",\n            \"styles\": [],\n            \"font_families\": [\"sans-serif\"]\n          },\n          \"view_overrides\": {\n            \"background_color\": [{\n            }],\n            \"text\": [{\n              \"when_state_matches\": {\n                \"scope\": [\"some-id:error\"],\n                \"value\": {\n                  \"equals\": true\n                }\n              },\n              \"value\": \"neat\"\n            }]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.Label.self)\n\n\n    }\n    func testSizeCoding() throws {\n        let autoPercent = \"{\\\"width\\\": \\\"auto\\\", \\\"height\\\":\\\"101%\\\"}\"\n        let percentToPoints = \"{\\\"width\\\":\\\"101%\\\", \\\"height\\\":45}\"\n\n        try decodeEncodeCompare(source: autoPercent, type: ThomasSize.self)\n        try decodeEncodeCompare(source: percentToPoints, type: ThomasSize.self)\n    }\n\n    func testVisibilityInfoCoding() throws {\n        let json = \"\"\"\n        {\n          \"default\": false,\n          \"invert_when_state_matches\": {\n            \"or\": [\n              {\n                \"key\": \"neat\",\n                \"value\": {\n                  \"equals\": \"dissatisfied\"\n                }\n              },\n              {\n                \"key\": \"neat\",\n                \"value\": {\n                  \"equals\": \"very_dissatisfied\"\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasVisibilityInfo.self)\n    }\n\n    func testEventHandlerCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"form_input\",\n          \"state_actions\": [\n            {\n              \"type\": \"clear\",\n            },\n            {\n              \"type\": \"set_form_value\",\n              \"key\": \"neat\"\n            },\n            {\n              \"type\": \"set\",\n              \"key\": \"label_tapped\",\n              \"value\": true\n            }\n          ]\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasEventHandler.self)\n    }\n\n    func testWebViewModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"web_view\",\n          \"url\": \"https://example.com\",\n          \"event_handlers\": [\n            {\n              \"type\": \"tap\",\n              \"state_actions\": [\n                {\n                  \"type\": \"set\",\n                  \"key\": \"web_view_tapped\",\n                  \"value\": true\n                }\n              ]\n            }\n          ]\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testCustomViewModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"custom_view\",\n          \"name\": \"ad_custom_view\",\n          \"properties\": {\n            \"ad_type\": \"fashion\"\n          },\n          \"background_color\": {\n            \"selectors\": [\n              {\n                \"platform\": \"ios\",\n                \"dark_mode\": false,\n                \"color\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#FFFFFF\",\n                  \"alpha\": 1\n                }\n              },\n              {\n                \"platform\": \"ios\",\n                \"dark_mode\": true,\n                \"color\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#000000\",\n                  \"alpha\": 1\n                }\n              }\n            ],\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#FF00FF\",\n              \"alpha\": 1\n            }\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testMediaViewModelCodable() throws {\n        let image = \"\"\"\n        {\n          \"media_fit\": \"center_inside\",\n          \"media_type\": \"image\",\n          \"type\": \"media\",\n          \"url\": \"https://example.com\"\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: image, type: ThomasViewInfo.self)\n\n        let video = \"\"\"\n        {\n          \"type\": \"media\",\n          \"media_type\": \"video\",\n          \"video\": {\n            \"aspect_ratio\": 0.56,\n            \"show_controls\": true,\n            \"autoplay\": true,\n            \"muted\": true,\n            \"loop\": true\n          },\n          \"media_fit\": \"center_inside\",\n          \"url\": \"https://hangar-dl.urbanairshi.com\"\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: video, type: ThomasViewInfo.self)\n\n        let youtube = \"\"\"\n        {\n          \"media_fit\": \"center_inside\",\n          \"media_type\": \"youtube\",\n          \"type\": \"media\",\n          \"url\": \"https://www.youtube.com/embed/xUOQZeN8A7o\",\n          \"video\": {\n            \"aspect_ratio\": 1.77777777777778,\n            \"autoplay\": false,\n            \"loop\": true,\n            \"muted\": true,\n            \"show_controls\": true\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: youtube, type: ThomasViewInfo.self)\n    }\n\n    func testLabelModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"label\",\n          \"text\": \"Sup Buddy\",\n          \"text_appearance\": {\n            \"font_size\": 14,\n            \"color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#333333\"\n              }\n            },\n            \"alignment\": \"start\",\n            \"styles\": [\n              \"italic\"\n            ],\n            \"font_families\": [\n              \"permanent_marker\",\n              \"casual\"\n            ]\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testLabelButtonModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"label_button\",\n          \"identifier\": \"button1\",\n          \"background_color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#D32F2F\",\n              \"alpha\": 1\n            }\n          },\n          \"label\": {\n            \"type\": \"label\",\n            \"text\": \"start|top\",\n            \"text_appearance\": {\n              \"font_size\": 10,\n              \"alignment\": \"center\",\n              \"color\": {\n                \"default\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#000000\",\n                  \"alpha\": 1\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testButtonImageModelCodable() throws {\n        let icon = \"\"\"\n        {\n          \"scale\": 0.4,\n          \"type\": \"icon\",\n          \"icon\": \"close\",\n          \"color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#000000\",\n              \"alpha\": 1\n            },\n            \"selectors\": [\n              {\n                \"platform\": \"ios\",\n                \"dark_mode\": true,\n                \"color\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#FFFFFF\",\n                  \"alpha\": 1\n                }\n              },\n              {\n                \"platform\": \"android\",\n                \"dark_mode\": true,\n                \"color\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#FFFFFF\",\n                  \"alpha\": 1\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: icon, type:  ThomasViewInfo.ImageButton.ButtonImage.self)\n\n        let url = \"\"\"\n        {\n          \"type\": \"url\",\n          \"url\": \"https://upload.wikimedia.org/wikipedia/en/thumb/8/8b/Airship_2019_logo.png\"\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: url, type: ThomasViewInfo.ImageButton.ButtonImage.self)\n    }\n\n    func testValidationInfoCoding() throws {\n        let json = \"\"\"\n    {\n        \"required\": true,\n        \"on_error\": {\n            \"state_actions\": [\n                {\n                  \"type\": \"set\",\n                  \"key\": \"is_valid\",\n                  \"value\": false\n                }\n            ]\n        },\n       \"on_edit\": {\n            \"state_actions\": [\n                {\n                    \"type\": \"clear\"\n                }\n            ]\n        },\n        \"on_valid\": {\n            \"state_actions\": [\n             {\n                  \"type\": \"set\",\n                  \"key\": \"is_valid\",\n                  \"value\": false\n                }\n            ]\n        }\n    }\n    \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasValidationInfo.self)\n\n        // Test optional fields\n        let minimalJson = \"\"\"\n    {\n    }\n    \"\"\"\n\n        try decodeEncodeCompare(source: minimalJson, type: ThomasValidationInfo.self)\n    }\n\n    func testImageButtonCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"image_button\",\n          \"image\": {\n            \"scale\": 0.4,\n            \"type\": \"icon\",\n            \"icon\": \"close\",\n            \"color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#000000\",\n                \"alpha\": 1\n              },\n              \"selectors\": [\n                {\n                  \"platform\": \"ios\",\n                  \"dark_mode\": true,\n                  \"color\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#FFFFFF\",\n                    \"alpha\": 1\n                  }\n                },\n                {\n                  \"platform\": \"android\",\n                  \"dark_mode\": true,\n                  \"color\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#FFFFFF\",\n                    \"alpha\": 1\n                  }\n                }\n              ]\n            }\n          },\n          \"identifier\": \"dismiss_button\",\n          \"button_click\": [\n            \"dismiss\"\n          ]\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testEmptyViewModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"empty_view\",\n          \"background_color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#00FF00\",\n              \"alpha\": 0.5\n            }\n          }\n        }\n        \"\"\"\n\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testPagerGestureModelCodable() throws {\n        let swipe = \"\"\"\n        {\n          \"identifier\": \"63a41161-9322-4425-a940-fa928665459e_swipe_up\",\n          \"type\": \"swipe\",\n          \"direction\": \"up\",\n          \"behavior\": {\n            \"behaviors\": [\n              \"dismiss\"\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: swipe, type: ThomasViewInfo.Pager.Gesture.self)\n\n        let tap = \"\"\"\n        {\n          \"identifier\": \"63a41161-9322-4425-a940-fa928665459e_tap_start\",\n          \"type\": \"tap\",\n          \"location\": \"start\",\n          \"behavior\": {\n            \"behaviors\": [\n              \"pager_previous\"\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: tap, type: ThomasViewInfo.Pager.Gesture.self)\n\n        let hold = \"\"\"\n        {\n          \"type\": \"hold\",\n          \"identifier\": \"hold-gesture-any-id\",\n          \"press_behavior\": {\n            \"behaviors\": [\n              \"pager_pause\"\n            ]\n          },\n          \"release_behavior\": {\n            \"behaviors\": [\n              \"pager_resume\"\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: hold, type: ThomasViewInfo.Pager.Gesture.self)\n    }\n\n    func testPagerIndicatorModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"pager_indicator\",\n          \"border\": {\n            \"radius\": 8\n          },\n          \"spacing\": 4,\n          \"bindings\": {\n            \"selected\": {\n              \"shapes\": [\n                {\n                  \"type\": \"ellipse\",\n                  \"aspect_ratio\": 1,\n                  \"scale\": 0.75,\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#000000\",\n                      \"alpha\": 1\n                    }\n                  }\n                }\n              ]\n            },\n            \"unselected\": {\n              \"shapes\": [\n                {\n                  \"type\": \"ellipse\",\n                  \"aspect_ratio\": 1,\n                  \"scale\": 0.75,\n                  \"border\": {\n                    \"stroke_width\": 1,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#333333\",\n                        \"alpha\": 1\n                      }\n                    }\n                  },\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#ffffff\",\n                      \"alpha\": 1\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testStoryIndicatorModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"story_indicator\",\n          \"source\": {\n            \"type\": \"pager\"\n          },\n          \"style\": {\n            \"type\": \"linear_progress\",\n            \"direction\": \"horizontal\",\n            \"sizing\": \"equal\",\n            \"spacing\": 4,\n            \"progress_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#AAAAAA\",\n                \"alpha\": 1\n              }\n            },\n            \"track_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#AAAAAA\",\n                \"alpha\": 0.5\n              }\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testToggleStyleModelCodable() throws {\n        let switchStyle = \"\"\"\n        {\n          \"type\": \"switch\",\n          \"toggle_colors\": {\n            \"on\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#00FF00\",\n                \"alpha\": 1\n              }\n            },\n            \"off\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#FF0000\",\n                \"alpha\": 1\n              }\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: switchStyle, type: ThomasToggleStyleInfo.self)\n\n        let checkbox = \"\"\"\n        {\n          \"bindings\": {\n            \"selected\": {\n              \"shapes\": [\n                {\n                  \"border\": {\n                    \"radius\": 2,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                      }\n                    },\n                    \"stroke_width\": 1\n                  },\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"alpha\": 1,\n                      \"hex\": \"#DDDDDD\",\n                    }\n                  },\n                  \"scale\": 1,\n                  \"type\": \"ellipse\"\n                }\n              ]\n            },\n            \"unselected\": {\n              \"shapes\": [\n                {\n                  \"border\": {\n                    \"radius\": 2,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                      }\n                    },\n                    \"stroke_width\": 1\n                  },\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"alpha\": 1,\n                      \"hex\": \"#FFFFFF\",\n                    }\n                  },\n                  \"scale\": 1,\n                  \"type\": \"ellipse\"\n                }\n              ]\n            }\n          },\n          \"type\": \"checkbox\"\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: checkbox, type: ThomasToggleStyleInfo.self)\n    }\n\n    func testCheckboxModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"checkbox\",\n          \"reporting_value\": \"moving boxes\",\n          \"style\": {\n            \"type\": \"checkbox\",\n            \"bindings\": {\n              \"selected\": {\n                \"shapes\": [\n                  {\n                    \"type\": \"rectangle\",\n                    \"scale\": 0.5,\n                    \"aspect_ratio\": 1,\n                    \"color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#66FF66\",\n                        \"alpha\": 1\n                      }\n                    },\n                    \"border\": {\n                      \"stroke_width\": 2,\n                      \"radius\": 5,\n                      \"stroke_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#333333\",\n                          \"alpha\": 1\n                        }\n                      }\n                    }\n                  }\n                ],\n                \"icon\": {\n                  \"type\": \"icon\",\n                  \"icon\": \"checkmark\",\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#333333\",\n                      \"alpha\": 1\n                    }\n                  },\n                  \"scale\": 0.4\n                }\n              },\n              \"unselected\": {\n                \"shapes\": [\n                  {\n                    \"type\": \"rectangle\",\n                    \"scale\": 0.5,\n                    \"aspect_ratio\": 1,\n                    \"color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#FF6666\",\n                        \"alpha\": 1\n                      }\n                    },\n                    \"border\": {\n                      \"stroke_width\": 2,\n                      \"radius\": 5,\n                      \"stroke_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#333333\",\n                          \"alpha\": 1\n                        }\n                      }\n                    }\n                  }\n                ],\n                \"icon\": {\n                  \"type\": \"icon\",\n                  \"icon\": \"close\",\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#333333\",\n                      \"alpha\": 1\n                    }\n                  },\n                  \"scale\": 0.4\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testRadioModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"reporting_value\": \"very_satisfied\",\n          \"attribute_value\": \"VerySatisfied\",\n          \"style\": {\n            \"bindings\": {\n              \"selected\": {\n                \"shapes\": [\n                  {\n                    \"border\": {\n                      \"radius\": 2,\n                      \"stroke_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"alpha\": 1,\n                          \"hex\": \"#000000\",\n                        }\n                      },\n                      \"stroke_width\": 1\n                    },\n                    \"color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"alpha\": 1,\n                        \"hex\": \"#DDDDDD\",\n                      }\n                    },\n                    \"scale\": 1,\n                    \"type\": \"ellipse\"\n                  }\n                ]\n              },\n              \"unselected\": {\n                \"shapes\": [\n                  {\n                    \"border\": {\n                      \"radius\": 2,\n                      \"stroke_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"alpha\": 1,\n                          \"hex\": \"#000000\",\n                        }\n                      },\n                      \"stroke_width\": 1\n                    },\n                    \"color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"alpha\": 1,\n                        \"hex\": \"#FFFFFF\",\n                      }\n                    },\n                    \"scale\": 1,\n                    \"type\": \"ellipse\"\n                  }\n                ]\n              }\n            },\n            \"type\": \"checkbox\"\n          },\n          \"type\": \"radio_input\"\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testTextInputModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"background_color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#eae9e9\",\n              \"alpha\": 1\n            }\n          },\n          \"border\": {\n            \"radius\": 2,\n            \"stroke_width\": 1,\n            \"stroke_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#63656b\",\n                \"alpha\": 1\n              }\n            }\n          },\n          \"type\": \"text_input\",\n          \"text_appearance\": {\n            \"alignment\": \"start\",\n            \"font_size\": 14,\n            \"color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#000000\",\n                \"alpha\": 1\n              }\n            }\n          },\n          \"identifier\": \"4e1a5c5f-a4cb-4599-a612-199e06aeaebd\",\n          \"input_type\": \"text_multiline\",\n          \"required\": false\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testTextInputModelWithRegistrationCodable() throws {\n        let json = \"\"\"\n        {\n          \"background_color\": {\n            \"default\": {\n              \"type\": \"hex\",\n              \"hex\": \"#eae9e9\",\n              \"alpha\": 1\n            }\n          },\n          \"border\": {\n            \"radius\": 2,\n            \"stroke_width\": 1,\n            \"stroke_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#63656b\",\n                \"alpha\": 1\n              }\n            }\n          },\n          \"type\": \"text_input\",\n          \"text_appearance\": {\n            \"alignment\": \"start\",\n            \"font_size\": 14,\n            \"color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#000000\",\n                \"alpha\": 1\n              }\n            }\n          },\n          \"identifier\": \"4e1a5c5f-a4cb-4599-a612-199e06aeaebd\",\n          \"input_type\": \"email\",\n          \"email_registration\": {\n            \"type\": \"double_opt_in\",\n            \"properties\": {\n               \"from\": \"iax\"\n            }\n          },\n          \"required\": false\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testScoreStyleModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"number_range\",\n          \"start\": 0,\n          \"end\": 10,\n          \"spacing\": 4,\n          \"bindings\": {\n            \"selected\": {\n              \"shapes\": [\n                {\n                  \"type\": \"rectangle\",\n                  \"aspect_ratio\": 1,\n                  \"scale\": 1,\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#000000\",\n                      \"alpha\": 1\n                    }\n                  }\n                },\n                {\n                  \"type\": \"ellipse\",\n                  \"aspect_ratio\": 1.5,\n                  \"scale\": 1,\n                  \"border\": {\n                    \"stroke_width\": 2,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#999999\",\n                        \"alpha\": 1\n                      }\n                    }\n                  },\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#FFFFFF\",\n                      \"alpha\": 0\n                    }\n                  }\n                }\n              ],\n              \"text_appearance\": {\n                \"font_size\": 14,\n                \"color\": {\n                  \"default\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#FFFFFF\",\n                    \"alpha\": 1\n                  }\n                },\n                \"font_families\": [\n                  \"permanent_marker\"\n                ]\n              }\n            },\n            \"unselected\": {\n              \"shapes\": [\n                {\n                  \"type\": \"ellipse\",\n                  \"aspect_ratio\": 1.5,\n                  \"scale\": 1,\n                  \"border\": {\n                    \"stroke_width\": 2,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#999999\",\n                        \"alpha\": 1\n                      }\n                    }\n                  },\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#FFFFFF\",\n                      \"alpha\": 1\n                    }\n                  }\n                }\n              ],\n              \"text_appearance\": {\n                \"font_size\": 14,\n                \"styles\": [\n                  \"bold\"\n                ],\n                \"color\": {\n                  \"default\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#333333\",\n                    \"alpha\": 1\n                  }\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.Score.ScoreStyle.self)\n    }\n\n    func testScoreModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"score\",\n          \"identifier\": \"nps_zero_to_ten\",\n          \"required\": true,\n          \"style\": {\n            \"type\": \"number_range\",\n            \"spacing\": 2,\n            \"start\": 0,\n            \"end\": 10,\n            \"bindings\": {\n              \"selected\": {\n                \"shapes\": [\n                  {\n                    \"type\": \"rectangle\",\n                    \"color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#000000\",\n                        \"alpha\": 1\n                      }\n                    }\n                  }\n                ],\n                \"text_appearance\": {\n                  \"font_size\": 12,\n                  \"styles\": [\n                    \"bold\"\n                  ],\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#ffffff\",\n                      \"alpha\": 1\n                    }\n                  }\n                }\n              },\n              \"unselected\": {\n                \"shapes\": [\n                  {\n                    \"type\": \"rectangle\",\n                    \"border\": {\n                      \"stroke_width\": 1,\n                      \"stroke_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#999999\",\n                          \"alpha\": 1\n                        }\n                      }\n                    },\n                    \"color\": {\n                      \"default\": {\n                        \"type\": \"hex\",\n                        \"hex\": \"#dedede\",\n                        \"alpha\": 1\n                      }\n                    }\n                  }\n                ],\n                \"text_appearance\": {\n                  \"font_size\": 12,\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#666666\",\n                      \"alpha\": 1\n                    }\n                  }\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.Score.self)\n    }\n\n    func testToggleModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"toggle\",\n          \"identifier\": \"hide\",\n          \"event_handlers\": [\n            {\n              \"type\": \"form_input\",\n              \"state_actions\": [\n                {\n                  \"type\": \"set_form_value\",\n                  \"key\": \"hide\"\n                }\n              ]\n            }\n          ],\n          \"style\": {\n            \"type\": \"switch\",\n            \"toggle_colors\": {\n              \"on\": {\n                \"default\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#00FF00\",\n                  \"alpha\": 1\n                }\n              },\n              \"off\": {\n                \"default\": {\n                  \"type\": \"hex\",\n                  \"hex\": \"#FF0000\",\n                  \"alpha\": 1\n                }\n              }\n            }\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.Toggle.self)\n    }\n\n    func testContainerModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"container\",\n          \"items\": [\n            {\n              \"position\": {\n                \"horizontal\": \"center\",\n                \"vertical\": \"center\"\n              },\n              \"size\": {\n                \"width\": \"100%\",\n                \"height\": \"auto\"\n              },\n              \"margin\": {\n                \"top\": 75,\n                \"bottom\": 50,\n                \"start\": 50,\n                \"end\": 50\n              },\n              \"view\": {\n                \"type\": \"label\",\n                \"text\": \"Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. In arcu cursus euismod quis viverra nibh. Lobortis feugiat vivamus at augue eget arcu dictum. Imperdiet dui accumsan sit amet nulla. Ultrices neque ornare aenean euismod elementum. Tincidunt id aliquet risus feugiat in ante metus dictum.\",\n                \"text_appearance\": {\n                  \"font_size\": 14,\n                  \"color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#333333\"\n                    }\n                  },\n                  \"alignment\": \"start\",\n                  \"styles\": [\n                    \"italic\"\n                  ],\n                  \"font_families\": [\n                    \"permanent_marker\",\n                    \"casual\"\n                  ]\n                }\n              }\n            }\n          ]\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.Container.self)\n    }\n\n    func testLinearLayoutModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"linear_layout\",\n          \"items\": [\n            {\n              \"margin\": {\n                \"start\": 0,\n                \"end\": 0,\n                \"top\": 0,\n                \"bottom\": 0\n              },\n              \"size\": {\n                \"width\": \"100%\",\n                \"height\": \"auto\"\n              },\n              \"view\": {\n                \"media_type\": \"image\",\n                \"url\": \"https://media3.giphy.com/media/tBvPFCFQHSpEI/giphy.gif\",\n                \"media_fit\": \"center_inside\",\n                \"type\": \"media\"\n              }\n            },\n            {\n              \"margin\": {\n                \"bottom\": 0,\n                \"end\": 0,\n                \"top\": 0,\n                \"start\": 0\n              },\n              \"view\": {\n                \"media_fit\": \"center_inside\",\n                \"type\": \"media\",\n                \"video\": {\n                  \"muted\": true,\n                  \"aspect_ratio\": 1.7777777777777777,\n                  \"autoplay\": false,\n                  \"show_controls\": true,\n                  \"loop\": false\n                },\n                \"url\": \"https://www.youtube.com/embed/a3ICNMQW7Ok/?autoplay=0&controls=1&loop=0&mute=1\",\n                \"media_type\": \"youtube\"\n              },\n              \"size\": {\n                \"width\": \"100%\",\n                \"height\": \"auto\"\n              }\n            },\n            {\n              \"size\": {\n                \"width\": \"100%\",\n                \"height\": \"100%\"\n              },\n              \"view\": {\n                \"type\": \"linear_layout\",\n                \"items\": [],\n                \"direction\": \"horizontal\"\n              }\n            }\n          ],\n          \"direction\": \"vertical\"\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testScrollLayoutModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"scroll_layout\",\n          \"direction\": \"vertical\",\n          \"view\": {\n            \"type\": \"linear_layout\",\n            \"direction\": \"vertical\",\n            \"items\": [\n              {\n                \"size\": {\n                  \"height\": \"auto\",\n                  \"width\": \"100%\"\n                },\n                \"view\": {\n                  \"type\": \"media\",\n                  \"media_fit\": \"fit_crop\",\n                  \"position\": {\n                    \"horizontal\": \"center\",\n                    \"vertical\": \"center\"\n                  },\n                  \"url\": \"https://hangar-dl.urbanairshi.com/binary/public/Hx7SIqHqQDmFj6aruaAFcQ/34be6e8d-31d0-499b-886e-2b29459cb472\",\n                  \"media_type\": \"image\"\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testPagerModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"pager\",\n          \"items\": [\n            {\n              \"identifier\": \"page1\",\n              \"view\": {\n                \"type\": \"empty_view\",\n                \"background_color\": {\n                  \"default\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#00FF00\",\n                    \"alpha\": 0.5\n                  }\n                }\n              }\n            },\n            {\n              \"identifier\": \"page2\",\n              \"view\": {\n                \"type\": \"empty_view\",\n                \"background_color\": {\n                  \"default\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#FFFF00\",\n                    \"alpha\": 0.5\n                  }\n                }\n              }\n            },\n            {\n              \"identifier\": \"page2\",\n              \"view\": {\n                \"type\": \"empty_view\",\n                \"background_color\": {\n                  \"default\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#FF00FF\",\n                    \"alpha\": 0.5\n                  }\n                }\n              }\n            }\n          ]\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testPagerControllerModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"pager_controller\",\n          \"identifier\": \"6ab1531a-fcb3-44b4-91d7-52db73ae7cd9\",\n          \"view\": {\n            \"type\": \"linear_layout\",\n            \"direction\": \"vertical\",\n            \"items\": [\n              {\n                \"size\": {\n                  \"height\": \"100%\",\n                  \"width\": \"100%\"\n                },\n                \"view\": {\n                  \"type\": \"container\",\n                  \"items\": [\n                    {\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                      },\n                      \"size\": {\n                        \"width\": \"100%\",\n                        \"height\": \"100%\"\n                      },\n                      \"view\": {\n                        \"type\": \"pager\",\n                        \"disable_swipe\": true,\n                        \"items\": [\n                          {\n                            \"identifier\": \"c36a5103-0a8d-4e34-b7b7-331ec1cbc87e\",\n                            \"view\": {\n                              \"type\": \"container\",\n                              \"items\": [\n                                {\n                                  \"size\": {\n                                    \"width\": \"100%\",\n                                    \"height\": \"100%\"\n                                  },\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"view\": {\n                                    \"type\": \"container\",\n                                    \"items\": [\n                                      {\n                                        \"margin\": {\n                                          \"bottom\": 16\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"width\": \"100%\",\n                                          \"height\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"type\": \"linear_layout\",\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"size\": {\n                                                \"width\": \"100%\",\n                                                \"height\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"type\": \"scroll_layout\",\n                                                \"direction\": \"vertical\",\n                                                \"view\": {\n                                                  \"type\": \"linear_layout\",\n                                                  \"direction\": \"vertical\",\n                                                  \"items\": [\n                                                    {\n                                                      \"size\": {\n                                                        \"width\": \"100%\",\n                                                        \"height\": \"auto\"\n                                                      },\n                                                      \"margin\": {\n                                                        \"top\": 48,\n                                                        \"bottom\": 8,\n                                                        \"start\": 16,\n                                                        \"end\": 16\n                                                      },\n                                                      \"view\": {\n                                                        \"type\": \"label\",\n                                                        \"text\": \"This is test\",\n                                                        \"text_appearance\": {\n                                                          \"font_size\": 30,\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"type\": \"hex\",\n                                                              \"hex\": \"#000000\",\n                                                              \"alpha\": 1\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"platform\": \"ios\",\n                                                                \"dark_mode\": true,\n                                                                \"color\": {\n                                                                  \"type\": \"hex\",\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"alpha\": 1\n                                                                }\n                                                              },\n                                                              {\n                                                                \"platform\": \"android\",\n                                                                \"dark_mode\": true,\n                                                                \"color\": {\n                                                                  \"type\": \"hex\",\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"alpha\": 1\n                                                                }\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"alignment\": \"center\",\n                                                          \"styles\": [],\n                                                          \"font_families\": [\n                                                            \"serif\"\n                                                          ]\n                                                        }\n                                                      }\n                                                    },\n                                                    {\n                                                      \"size\": {\n                                                        \"width\": \"100%\",\n                                                        \"height\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"type\": \"linear_layout\",\n                                                        \"direction\": \"horizontal\",\n                                                        \"items\": []\n                                                      }\n                                                    }\n                                                  ]\n                                                }\n                                              }\n                                            }\n                                          ]\n                                        }\n                                      }\n                                    ]\n                                  }\n                                }\n                              ]\n                            }\n                          }\n                        ]\n                      },\n                      \"ignore_safe_area\": false\n                    },\n                    {\n                      \"position\": {\n                        \"horizontal\": \"end\",\n                        \"vertical\": \"top\"\n                      },\n                      \"size\": {\n                        \"width\": 48,\n                        \"height\": 48\n                      },\n                      \"view\": {\n                        \"type\": \"image_button\",\n                        \"image\": {\n                          \"scale\": 0.4,\n                          \"type\": \"icon\",\n                          \"icon\": \"close\",\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#000000\",\n                              \"alpha\": 1\n                            },\n                            \"selectors\": [\n                              {\n                                \"platform\": \"ios\",\n                                \"dark_mode\": true,\n                                \"color\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#FFFFFF\",\n                                  \"alpha\": 1\n                                }\n                              },\n                              {\n                                \"platform\": \"android\",\n                                \"dark_mode\": true,\n                                \"color\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#FFFFFF\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            ]\n                          }\n                        },\n                        \"identifier\": \"dismiss_button\",\n                        \"button_click\": [\n                          \"dismiss\"\n                        ]\n                      }\n                    }\n                  ]\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testFormControllerModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"form_controller\",\n          \"identifier\": \"parent_form\",\n          \"submit\": \"submit_event\",\n          \"view\": {\n            \"type\": \"linear_layout\",\n            \"direction\": \"vertical\",\n            \"background_color\": {\n              \"default\": {\n                \"type\": \"hex\",\n                \"hex\": \"#ffffff\",\n                \"alpha\": 1\n              }\n            },\n            \"items\": [\n              {\n                \"size\": {\n                  \"width\": \"auto\",\n                  \"height\": 40\n                },\n                \"margin\": {\n                  \"top\": 8,\n                  \"bottom\": 8,\n                  \"start\": 16,\n                  \"end\": 16\n                },\n                \"view\": {\n                  \"type\": \"nps_form_controller\",\n                  \"identifier\": \"nps_zero_to_ten_form\",\n                  \"nps_identifier\": \"nps_zero_to_ten\",\n                  \"view\": {\n                    \"type\": \"score\",\n                    \"identifier\": \"nps_zero_to_ten\",\n                    \"required\": true,\n                    \"style\": {\n                      \"type\": \"number_range\",\n                      \"spacing\": 2,\n                      \"start\": 0,\n                      \"end\": 10,\n                      \"bindings\": {\n                        \"selected\": {\n                          \"shapes\": [\n                            {\n                              \"type\": \"rectangle\",\n                              \"color\": {\n                                \"default\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#000000\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            }\n                          ],\n                          \"text_appearance\": {\n                            \"font_size\": 12,\n                            \"styles\": [\n                              \"bold\"\n                            ],\n                            \"color\": {\n                              \"default\": {\n                                \"type\": \"hex\",\n                                \"hex\": \"#ffffff\",\n                                \"alpha\": 1\n                              }\n                            }\n                          }\n                        },\n                        \"unselected\": {\n                          \"shapes\": [\n                            {\n                              \"type\": \"rectangle\",\n                              \"border\": {\n                                \"stroke_width\": 1,\n                                \"stroke_color\": {\n                                  \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#999999\",\n                                    \"alpha\": 1\n                                  }\n                                }\n                              },\n                              \"color\": {\n                                \"default\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#dedede\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            }\n                          ],\n                          \"text_appearance\": {\n                            \"font_size\": 12,\n                            \"color\": {\n                              \"default\": {\n                                \"type\": \"hex\",\n                                \"hex\": \"#666666\",\n                                \"alpha\": 1\n                              }\n                            }\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              },\n              {\n                \"size\": {\n                  \"width\": \"auto\",\n                  \"height\": 24\n                },\n                \"margin\": {\n                  \"top\": 8,\n                  \"bottom\": 8,\n                  \"start\": 16,\n                  \"end\": 16\n                },\n                \"view\": {\n                  \"type\": \"nps_form_controller\",\n                  \"identifier\": \"nps_zero_to_ten_form\",\n                  \"nps_identifier\": \"nps_zero_to_ten\",\n                  \"view\": {\n                    \"type\": \"score\",\n                    \"identifier\": \"nps_zero_to_ten\",\n                    \"required\": true,\n                    \"style\": {\n                      \"type\": \"number_range\",\n                      \"spacing\": 8,\n                      \"start\": 1,\n                      \"end\": 5,\n                      \"bindings\": {\n                        \"selected\": {\n                          \"shapes\": [\n                            {\n                              \"type\": \"ellipse\",\n                              \"color\": {\n                                \"default\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#FFDD33\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            }\n                          ],\n                          \"text_appearance\": {\n                            \"font_size\": 14,\n                            \"color\": {\n                              \"default\": {\n                                \"type\": \"hex\",\n                                \"hex\": \"#000000\",\n                                \"alpha\": 1\n                              }\n                            }\n                          }\n                        },\n                        \"unselected\": {\n                          \"shapes\": [\n                            {\n                              \"type\": \"ellipse\",\n                              \"color\": {\n                                \"default\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#3333ff\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            }\n                          ],\n                          \"text_appearance\": {\n                            \"font_size\": 14,\n                            \"color\": {\n                              \"default\": {\n                                \"type\": \"hex\",\n                                \"hex\": \"#ffffff\",\n                                \"alpha\": 1\n                              }\n                            }\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              },\n              {\n                \"size\": {\n                  \"width\": \"auto\",\n                  \"height\": 32\n                },\n                \"margin\": {\n                  \"top\": 8,\n                  \"bottom\": 8,\n                  \"start\": 16,\n                  \"end\": 16\n                },\n                \"view\": {\n                  \"type\": \"nps_form_controller\",\n                  \"identifier\": \"nps_zero_to_ten_form\",\n                  \"nps_identifier\": \"nps_zero_to_ten\",\n                  \"view\": {\n                    \"type\": \"score\",\n                    \"identifier\": \"nps_zero_to_ten\",\n                    \"required\": true,\n                    \"style\": {\n                      \"type\": \"number_range\",\n                      \"spacing\": 8,\n                      \"start\": 97,\n                      \"end\": 105,\n                      \"bindings\": {\n                        \"selected\": {\n                          \"shapes\": [\n                            {\n                              \"type\": \"ellipse\",\n                              \"color\": {\n                                \"default\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#FF0000\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            }\n                          ],\n                          \"text_appearance\": {\n                            \"font_size\": 14,\n                            \"color\": {\n                              \"default\": {\n                                \"type\": \"hex\",\n                                \"hex\": \"#000000\",\n                                \"alpha\": 1\n                              }\n                            }\n                          }\n                        },\n                        \"unselected\": {\n                          \"shapes\": [\n                            {\n                              \"type\": \"ellipse\",\n                              \"color\": {\n                                \"default\": {\n                                  \"type\": \"hex\",\n                                  \"hex\": \"#0000FF\",\n                                  \"alpha\": 1\n                                }\n                              }\n                            }\n                          ],\n                          \"text_appearance\": {\n                            \"font_size\": 14,\n                            \"color\": {\n                              \"default\": {\n                                \"type\": \"hex\",\n                                \"hex\": \"#ffffff\",\n                                \"alpha\": 1\n                              }\n                            }\n                          }\n                        }\n                      }\n                    }\n                  }\n                }\n              },\n              {\n                \"size\": {\n                  \"width\": \"100%\",\n                  \"height\": \"auto\"\n                },\n                \"margin\": {\n                  \"top\": 16,\n                  \"bottom\": 16,\n                  \"start\": 16,\n                  \"end\": 16\n                },\n                \"view\": {\n                  \"type\": \"label_button\",\n                  \"identifier\": \"SUBMIT_BUTTON\",\n                  \"background_color\": {\n                    \"default\": {\n                      \"type\": \"hex\",\n                      \"hex\": \"#000000\",\n                      \"alpha\": 1\n                    }\n                  },\n                  \"button_click\": [\n                    \"form_submit\",\n                    \"cancel\"\n                  ],\n                  \"enabled\": [\n                    \"form_validation\"\n                  ],\n                  \"label\": {\n                    \"type\": \"label\",\n                    \"text\": \"SEND IT!\",\n                    \"text_appearance\": {\n                      \"font_size\": 14,\n                      \"alignment\": \"center\",\n                      \"color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#ffffff\",\n                          \"alpha\": 1\n                        }\n                      }\n                    }\n                  }\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testCheckboxControllerModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"type\": \"checkbox_controller\",\n          \"identifier\": \"checkboxes\",\n          \"view\": {\n            \"type\": \"linear_layout\",\n            \"direction\": \"vertical\",\n            \"items\": [\n              {\n                \"size\": {\n                  \"width\": \"100%\",\n                  \"height\": \"auto\"\n                },\n                \"view\": {\n                  \"type\": \"linear_layout\",\n                  \"direction\": \"horizontal\",\n                  \"items\": [\n                    {\n                      \"size\": {\n                        \"width\": \"auto\",\n                        \"height\": \"auto\"\n                      },\n                      \"margin\": {\n                        \"top\": 0\n                      },\n                      \"view\": {\n                        \"type\": \"checkbox\",\n                        \"reporting_value\": \"check_cyan\",\n                        \"event_handlers\": [\n                          {\n                            \"type\": \"tap\",\n                            \"state_actions\": [\n                              {\n                                \"type\": \"set\",\n                                \"key\": \"last_check\",\n                                \"value\": \"cyan\"\n                              }\n                            ]\n                          }\n                        ],\n                        \"style\": {\n                          \"type\": \"checkbox\",\n                          \"bindings\": {\n                            \"selected\": {\n                              \"icon\": {\n                                \"type\": \"icon\",\n                                \"icon\": \"checkmark\",\n                                \"color\": {\n                                  \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#000000\",\n                                    \"alpha\": 1\n                                  }\n                                },\n                                \"scale\": 0.5\n                              },\n                              \"shapes\": [\n                                {\n                                  \"border\": {\n                                    \"radius\": 5,\n                                    \"stroke_color\": {\n                                      \"default\": {\n                                        \"type\": \"hex\",\n                                        \"hex\": \"#000000\",\n                                        \"alpha\": 1\n                                      }\n                                    },\n                                    \"stroke_width\": 2\n                                  },\n                                  \"color\": {\n                                    \"default\": {\n                                      \"type\": \"hex\",\n                                      \"hex\": \"#00ffff\",\n                                      \"alpha\": 1\n                                    }\n                                  },\n                                  \"scale\": 1,\n                                  \"type\": \"rectangle\"\n                                }\n                              ]\n                            },\n                            \"unselected\": {\n                              \"shapes\": [\n                                {\n                                  \"border\": {\n                                    \"radius\": 5,\n                                    \"stroke_color\": {\n                                      \"default\": {\n                                        \"type\": \"hex\",\n                                        \"hex\": \"#000000\",\n                                        \"alpha\": 0.5\n                                      }\n                                    },\n                                    \"stroke_width\": 1\n                                  },\n                                  \"color\": {\n                                    \"default\": {\n                                      \"type\": \"hex\",\n                                      \"hex\": \"#00ffff\",\n                                      \"alpha\": 0.5\n                                    }\n                                  },\n                                  \"scale\": 1,\n                                  \"type\": \"rectangle\"\n                                }\n                              ]\n                            }\n                          }\n                        }\n                      }\n                    },\n                    {\n                      \"size\": {\n                        \"width\": \"auto\",\n                        \"height\": \"auto\"\n                      },\n                      \"margin\": {\n                        \"start\": 8\n                      },\n                      \"view\": {\n                        \"type\": \"label\",\n                        \"text\": \"<-- Check it\",\n                        \"text_appearance\": {\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#000000\",\n                              \"alpha\": 1\n                            }\n                          },\n                          \"font_size\": 14,\n                          \"alignment\": \"start\"\n                        },\n                        \"visibility\": {\n                          \"default\": true,\n                          \"invert_when_state_matches\": {\n                            \"key\": \"last_check\",\n                            \"value\": {\n                              \"is_present\": true\n                            }\n                          }\n                        }\n                      }\n                    },\n                    {\n                      \"size\": {\n                        \"width\": \"auto\",\n                        \"height\": \"auto\"\n                      },\n                      \"margin\": {\n                        \"start\": 8\n                      },\n                      \"view\": {\n                        \"type\": \"label\",\n                        \"text\": \"<-- Tapped last\",\n                        \"text_appearance\": {\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#000000\",\n                              \"alpha\": 1\n                            }\n                          },\n                          \"font_size\": 14,\n                          \"alignment\": \"start\"\n                        },\n                        \"visibility\": {\n                          \"default\": false,\n                          \"invert_when_state_matches\": {\n                            \"key\": \"last_check\",\n                            \"value\": {\n                              \"equals\": \"cyan\"\n                            }\n                          }\n                        }\n                      }\n                    }\n                  ]\n                }\n              },\n              {\n                \"size\": {\n                  \"width\": \"100%\",\n                  \"height\": \"auto\"\n                },\n                \"margin\": {\n                  \"top\": 4\n                },\n                \"view\": {\n                  \"type\": \"linear_layout\",\n                  \"direction\": \"horizontal\",\n                  \"items\": [\n                    {\n                      \"size\": {\n                        \"width\": \"auto\",\n                        \"height\": \"auto\"\n                      },\n                      \"view\": {\n                        \"type\": \"checkbox\",\n                        \"reporting_value\": \"check_magenta\",\n                        \"event_handlers\": [\n                          {\n                            \"type\": \"tap\",\n                            \"state_actions\": [\n                              {\n                                \"type\": \"set\",\n                                \"key\": \"last_check\",\n                                \"value\": \"magenta\"\n                              }\n                            ]\n                          }\n                        ],\n                        \"style\": {\n                          \"type\": \"checkbox\",\n                          \"bindings\": {\n                            \"selected\": {\n                              \"icon\": {\n                                \"type\": \"icon\",\n                                \"icon\": \"checkmark\",\n                                \"color\": {\n                                  \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#000000\",\n                                    \"alpha\": 1\n                                  }\n                                },\n                                \"scale\": 0.5\n                              },\n                              \"shapes\": [\n                                {\n                                  \"border\": {\n                                    \"radius\": 5,\n                                    \"stroke_color\": {\n                                      \"default\": {\n                                        \"type\": \"hex\",\n                                        \"hex\": \"#000000\",\n                                        \"alpha\": 1\n                                      }\n                                    },\n                                    \"stroke_width\": 2\n                                  },\n                                  \"color\": {\n                                    \"default\": {\n                                      \"type\": \"hex\",\n                                      \"hex\": \"#ff00ff\",\n                                      \"alpha\": 1\n                                    }\n                                  },\n                                  \"scale\": 1,\n                                  \"type\": \"rectangle\"\n                                }\n                              ]\n                            },\n                            \"unselected\": {\n                              \"shapes\": [\n                                {\n                                  \"border\": {\n                                    \"radius\": 5,\n                                    \"stroke_color\": {\n                                      \"default\": {\n                                        \"type\": \"hex\",\n                                        \"hex\": \"#000000\",\n                                        \"alpha\": 0.5\n                                      }\n                                    },\n                                    \"stroke_width\": 1\n                                  },\n                                  \"color\": {\n                                    \"default\": {\n                                      \"type\": \"hex\",\n                                      \"hex\": \"#ff00ff\",\n                                      \"alpha\": 0.5\n                                    }\n                                  },\n                                  \"scale\": 1,\n                                  \"type\": \"rectangle\"\n                                }\n                              ]\n                            }\n                          }\n                        }\n                      }\n                    },\n                    {\n                      \"size\": {\n                        \"width\": \"auto\",\n                        \"height\": \"auto\"\n                      },\n                      \"margin\": {\n                        \"start\": 8\n                      },\n                      \"view\": {\n                        \"type\": \"label\",\n                        \"text\": \"<-- Check it\",\n                        \"text_appearance\": {\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#000000\",\n                              \"alpha\": 1\n                            }\n                          },\n                          \"font_size\": 14,\n                          \"alignment\": \"start\"\n                        },\n                        \"visibility\": {\n                          \"default\": true,\n                          \"invert_when_state_matches\": {\n                            \"key\": \"last_check\",\n                            \"value\": {\n                              \"is_present\": true\n                            }\n                          }\n                        }\n                      }\n                    },\n                    {\n                      \"size\": {\n                        \"width\": \"auto\",\n                        \"height\": \"auto\"\n                      },\n                      \"margin\": {\n                        \"start\": 8\n                      },\n                      \"view\": {\n                        \"type\": \"label\",\n                        \"text\": \"<-- Tapped last\",\n                        \"text_appearance\": {\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#000000\",\n                              \"alpha\": 1\n                            }\n                          },\n                          \"font_size\": 14,\n                          \"alignment\": \"start\"\n                        },\n                        \"visibility\": {\n                          \"default\": false,\n                          \"invert_when_state_matches\": {\n                            \"key\": \"last_check\",\n                            \"value\": {\n                              \"equals\": \"magenta\"\n                            }\n                          }\n                        }\n                      }\n                    }\n                  ]\n                }\n              }\n            ]\n          }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testRadioInputControllerModelCodable() throws {\n        let json = \"\"\"\n        {\n          \"identifier\": \"52fd50d9-c899-4887-8210-9669cb27188c\",\n          \"type\": \"radio_input_controller\",\n          \"attribute_name\": {\n            \"channel\": \"HowSatisfiedAreYou\"\n          },\n          \"event_handlers\": [\n            {\n              \"type\": \"form_input\",\n              \"state_actions\": [\n                {\n                  \"type\": \"set_form_value\",\n                  \"key\": \"neat\"\n                }\n              ]\n            }\n          ],\n          \"view\": {\n              \"type\": \"label\",\n              \"text\": \"Sup Buddy\",\n              \"text_appearance\": {\n                \"font_size\": 14,\n                \"color\": {\n                  \"default\": {\n                    \"type\": \"hex\",\n                    \"hex\": \"#333333\"\n                  }\n                },\n                \"alignment\": \"start\",\n                \"styles\": [\n                  \"italic\"\n                ],\n                \"font_families\": [\n                  \"permanent_marker\",\n                  \"casual\"\n                ]\n              }\n            }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n    \n\n    func testStateControllerModelCodable() throws {\n        let json = \"\"\"\n        {\n           \"type\":\"state_controller\",\n           \"view\":{\n              \"type\":\"linear_layout\",\n              \"direction\":\"vertical\",\n              \"items\":[\n                 {\n                    \"size\": {\n                      \"width\": \"auto\",\n                      \"height\": \"auto\"\n                    },\n                    \"view\":{\n                       \"type\":\"label\",\n                       \"text\":\"Sup Buddy\",\n                       \"text_appearance\":{\n                          \"font_size\":14,\n                          \"color\":{\n                             \"default\":{\n                                \"type\": \"hex\",\n                                \"hex\":\"#333333\"\n                             }\n                          },\n                          \"alignment\":\"start\",\n                          \"styles\":[\n                             \"italic\"\n                          ],\n                          \"font_families\":[\n                             \"permanent_marker\",\n                             \"casual\"\n                          ]\n                       }\n                    }\n                 }\n              ]\n           }\n        }\n        \"\"\"\n        try decodeEncodeCompare(source: json, type: ThomasViewInfo.self)\n    }\n\n    func testActionPayload() throws {\n        let payload = ThomasActionsPayload(value: try AirshipJSON.wrap([\"foo\": \"bar\"]))\n        let encoded = try JSONEncoder().encode(payload)\n        let decoded = try JSONDecoder().decode(ThomasActionsPayload.self, from: encoded)\n        XCTAssertEqual(payload, decoded)\n    }\n\n    func testActionPayloadPlatformOverrides() throws {\n        let payload = ThomasActionsPayload(value: try AirshipJSON.wrap([\n            \"foo\": \"bar\",\n            \"shouldnt_change\": \"value\",\n            \"platform_action_overrides\": [\n                \"ios\": [\n                    \"foo\": \"bar2\",\n                    \"added\": \"override\"\n                ]\n            ]\n        ]))\n        \n        let encoded = try JSONEncoder().encode(payload)\n        let decoded = try JSONDecoder().decode(ThomasActionsPayload.self, from: encoded)\n        XCTAssertEqual(payload, decoded)\n\n        XCTAssertEqual(\"bar2\", payload.value.object?[\"foo\"]?.string)\n        XCTAssertEqual(\"value\", payload.value.object?[\"shouldnt_change\"]?.string)\n        XCTAssertEqual(\"override\", payload.value.object?[\"added\"]?.string)\n    }\n\n    private func decodeEncodeCompare<T: Codable & Equatable>(source: String, type: T.Type) throws {\n        let decoder = JSONDecoder()\n        let encoder = JSONEncoder()\n\n        let decoded = try decoder.decode(type, from: source.data(using: .utf8)!)\n        let json = try encoder.encode(decoded)\n        let restored = try decoder.decode(type, from: json)\n\n        XCTAssertEqual(restored, decoded)\n\n        let inputJson = try JSONSerialization.jsonObject(with: source.data(using: .utf8)!) as! [String: Any]\n        let encodedJson = try JSONSerialization.jsonObject(with: json) as! [String: Any]\n\n        XCTAssertEqual(try AirshipJSON.wrap(inputJson), try AirshipJSON.wrap(encodedJson))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Types/PagerDisableSwipeSelectorTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\nimport Foundation\n\nstruct PagerDisableSwipeSelectorTest {\n    \n    @Test(\"Parsing test\")\n    func testParsing() async throws {\n        let json = \"\"\"\n        {\n          \"directions\": {\n            \"type\": \"horizontal\"\n          },\n          \"when_state_matches\": {\n            \"scope\": [\n              \"test\"\n            ],\n            \"value\": {\n              \"equals\": [\n                \"is-complete\"\n              ]\n            }\n          }\n        }\n        \"\"\"\n        \n        let expected = try AirshipJSON.from(json: json)\n        let decoded: ThomasViewInfo.Pager.DisableSwipeSelector = try expected.decode()\n        \n        #expect(decoded.predicate != nil)\n        #expect(decoded.direction == .horizontal)\n        \n        let encodedData = try JSONEncoder().encode(decoded)\n        let encoded = try AirshipJSON.from(data: encodedData)\n        #expect(expected == encoded)\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/Types/ThomasEmailRegistrationOptionsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n\nclass ThomasEmailRegistrationOptionsTest: XCTestCase {\n\n    private let date: Date = Date.now\n\n    func testCommercialFromJSON() throws {\n        let json = \"\"\"\n        {\n           \"type\": \"commercial\",\n           \"commercial_opted_in\": true,\n           \"properties\": {\n              \"cool\": \"prop\"\n           }\n        }\n        \"\"\"\n\n        let options = try JSONDecoder().decode(ThomasEmailRegistrationOption.self, from: json.data(using: .utf8)!)\n\n        let expected: ThomasEmailRegistrationOption = .commercial(\n            ThomasEmailRegistrationOption.Commercial(\n                optedIn: true,\n                properties: try AirshipJSON.wrap([\"cool\": \"prop\"])\n            )\n        )\n        XCTAssertEqual(expected, options)\n    }\n\n    func testCommercialNoPropertiesFromJSON() throws {\n        let json = \"\"\"\n        {\n           \"type\": \"commercial\",\n           \"commercial_opted_in\": false\n        }\n        \"\"\"\n\n        let options = try JSONDecoder().decode(ThomasEmailRegistrationOption.self, from: json.data(using: .utf8)!)\n\n        let expected: ThomasEmailRegistrationOption = .commercial(\n            ThomasEmailRegistrationOption.Commercial(\n                optedIn: false,\n                properties: nil\n            )\n        )\n        XCTAssertEqual(expected, options)\n    }\n\n    func testTransactionalFromJSON() throws {\n        let json = \"\"\"\n        {\n           \"type\": \"transactional\",\n           \"properties\": {\n              \"cool\": \"prop\"\n           }\n        }\n        \"\"\"\n\n        let options = try JSONDecoder().decode(ThomasEmailRegistrationOption.self, from: json.data(using: .utf8)!)\n\n        let expected: ThomasEmailRegistrationOption = .transactional(\n            ThomasEmailRegistrationOption.Transactional(\n                properties: try AirshipJSON.wrap([\"cool\": \"prop\"])\n            )\n        )\n        XCTAssertEqual(expected, options)\n    }\n\n    func testTransactionalNoPropertiesFromJSON() throws {\n        let json = \"\"\"\n        {\n           \"type\": \"transactional\"\n        }\n        \"\"\"\n\n        let options = try JSONDecoder().decode(ThomasEmailRegistrationOption.self, from: json.data(using: .utf8)!)\n\n        let expected: ThomasEmailRegistrationOption = .transactional(\n            ThomasEmailRegistrationOption.Transactional(\n                properties: nil\n            )\n        )\n        XCTAssertEqual(expected, options)\n    }\n\n    func testDoubleOptInFromJSON() throws {\n        let json = \"\"\"\n        {\n           \"type\": \"double_opt_in\",\n           \"properties\": {\n              \"cool\": \"prop\"\n           }\n        }\n        \"\"\"\n\n        let options = try JSONDecoder().decode(ThomasEmailRegistrationOption.self, from: json.data(using: .utf8)!)\n\n        let expected: ThomasEmailRegistrationOption = .doubleOptIn(\n            ThomasEmailRegistrationOption.DoubleOptIn(\n                properties: try AirshipJSON.wrap([\"cool\": \"prop\"])\n            )\n        )\n        XCTAssertEqual(expected, options)\n    }\n\n    func testDoubleOptInNoPropertiesFromJSON() throws {\n        let json = \"\"\"\n        {\n           \"type\": \"double_opt_in\"\n        }\n        \"\"\"\n\n        let options = try JSONDecoder().decode(ThomasEmailRegistrationOption.self, from: json.data(using: .utf8)!)\n\n        let expected: ThomasEmailRegistrationOption = .doubleOptIn(\n            ThomasEmailRegistrationOption.DoubleOptIn(\n                properties: nil\n            )\n        )\n        XCTAssertEqual(expected, options)\n    }\n\n    func testCommercialToContactOptions() throws {\n        let options: ThomasEmailRegistrationOption = .commercial(\n            ThomasEmailRegistrationOption.Commercial(\n                optedIn: true,\n                properties: try AirshipJSON.wrap([\"cool\": \"prop\"])\n            )\n        )\n\n        let expected = EmailRegistrationOptions.commercialOptions(\n            transactionalOptedIn: nil,\n            commercialOptedIn: date,\n            properties: [\"cool\": \"prop\"]\n        )\n        XCTAssertEqual(options.makeContactOptions(date: date), expected)\n    }\n\n    func testCommercialNoPropertiesToContactOptions() {\n        let options: ThomasEmailRegistrationOption = .commercial(\n            ThomasEmailRegistrationOption.Commercial(\n                optedIn: false,\n                properties: nil\n            )\n        )\n\n        let expected = EmailRegistrationOptions.commercialOptions(\n            transactionalOptedIn: nil,\n            commercialOptedIn: nil,\n            properties: nil\n        )\n        XCTAssertEqual(options.makeContactOptions(date: date), expected)\n    }\n\n    func testTransactionalToContactOptions() throws {\n        let options: ThomasEmailRegistrationOption = .transactional(\n            ThomasEmailRegistrationOption.Transactional(\n                properties: try AirshipJSON.wrap([\"cool\": \"prop\"])\n            )\n        )\n\n        let expected = EmailRegistrationOptions.options(\n            transactionalOptedIn: nil,\n            properties: [\"cool\": \"prop\"],\n            doubleOptIn: false\n        )\n        XCTAssertEqual(options.makeContactOptions(date: date), expected)\n    }\n\n    func testTransactionalNoPropertiesToContactOptions() {\n        let options: ThomasEmailRegistrationOption = .transactional(\n            ThomasEmailRegistrationOption.Transactional(\n                properties: nil\n            )\n        )\n\n        let expected = EmailRegistrationOptions.options(\n            transactionalOptedIn: nil,\n            properties: nil,\n            doubleOptIn: false\n        )\n        XCTAssertEqual(options.makeContactOptions(date: date), expected)\n    }\n\n    func testDoubleOptInToContactOptions() throws {\n        let options: ThomasEmailRegistrationOption = .doubleOptIn(\n            ThomasEmailRegistrationOption.DoubleOptIn(\n                properties: try AirshipJSON.wrap([\"cool\": \"prop\"])\n            )\n        )\n\n        let expected = EmailRegistrationOptions.options(properties: [\"cool\": \"prop\"], doubleOptIn: true)\n        XCTAssertEqual(options.makeContactOptions(date: date), expected)\n    }\n\n    func testDoubleOptInNoPropertiesToContactOptions() {\n        let options: ThomasEmailRegistrationOption = .doubleOptIn(\n            ThomasEmailRegistrationOption.DoubleOptIn(\n                properties: nil\n            )\n        )\n\n        let expected = EmailRegistrationOptions.options(properties: nil, doubleOptIn: true)\n        XCTAssertEqual(options.makeContactOptions(date: date), expected)\n    }\n\n\n}\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/VideoMediaWebViewTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(watchOS)\n\nimport Testing\n@testable import AirshipCore\n\n@Suite\n@MainActor\nstruct VideoMediaWebViewTests {\n\n    // MARK: - Standard embed URLs\n\n    @Test\n    func testExtractsIDFromStandardEmbedURL() {\n        let url = \"https://www.youtube.com/embed/dQw4w9WgXcQ\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == \"dQw4w9WgXcQ\")\n    }\n\n    @Test\n    func testExtractsIDFromEmbedURLWithQueryParams() {\n        let url = \"https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1&mute=1\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == \"dQw4w9WgXcQ\")\n    }\n\n    @Test\n    func testExtractsIDWithHyphensAndUnderscores() {\n        let url = \"https://www.youtube.com/embed/a1B2-c3_D4e\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == \"a1B2-c3_D4e\")\n    }\n\n    // MARK: - Edge cases\n\n    @Test\n    func testReturnsNilForNonEmbedURL() {\n        let url = \"https://www.youtube.com/watch?v=dQw4w9WgXcQ\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == nil)\n    }\n\n    @Test\n    func testReturnsNilForEmptyString() {\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: \"\") == nil)\n    }\n\n    @Test\n    func testReturnsNilForUnrelatedURL() {\n        let url = \"https://vimeo.com/123456789\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == nil)\n    }\n\n    @Test\n    func testReturnsNilForEmbedWithNoID() {\n        let url = \"https://www.youtube.com/embed/\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == nil)\n    }\n\n    @Test\n    func testExtractsIDFromEmbedWithTrailingSlash() {\n        let url = \"https://www.youtube.com/embed/dQw4w9WgXcQ/\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == \"dQw4w9WgXcQ\")\n    }\n\n    @Test\n    func testExtractsIDFromEmbedWithFragment() {\n        let url = \"https://www.youtube.com/embed/dQw4w9WgXcQ#t=30\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == \"dQw4w9WgXcQ\")\n    }\n\n    @Test\n    func testExtractsIDFromEmbedWithTrailingSlashAndQueryParams() {\n        let url = \"https://www.youtube.com/embed/7sxVHYZ_PnA/?autoplay=1&controls=0&loop=1&mute=1\"\n        #expect(VideoMediaWebView.retrieveYoutubeVideoID(url: url) == \"7sxVHYZ_PnA\")\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipCore/Tests/WorkManager/WorkRateLimiterTests.swift",
    "content": "import Foundation\nimport Testing\n@testable import AirshipCore\n\n@Suite(\"WorkRateLimiter\")\nstruct WorkRateLimiterTests {\n\n    // Helper to make a limiter with a test clock\n    private func makeLimiter(start: TimeInterval = 1_000_000) -> (WorkRateLimiter, UATestDate) {\n        let clock = UATestDate(dateOverride: Date(timeIntervalSince1970: start))\n        let limiter = WorkRateLimiter(date: clock)\n        return (limiter, clock)\n    }\n\n    @Test(\"First N hits succeed, (N+1)th blocks\")\n    func blocksAfterRate() async throws {\n        let (limiter, _) = makeLimiter()\n        try await limiter.set(\"foo\", rate: 2, timeInterval: 10)\n\n        #expect(await limiter.trackIfWithinLimit([\"foo\"]))\n        #expect(await limiter.trackIfWithinLimit([\"foo\"]))\n        #expect(!(await limiter.trackIfWithinLimit([\"foo\"])))  // third should block\n\n        let wait = await limiter.nextAvailable([\"foo\"])\n        #expect(wait > 0 && wait <= 10)\n    }\n\n    @Test(\"nextAvailable is the max across keys\")\n    func nextAvailableMaxAcrossKeys() async throws {\n        let (limiter, clock) = makeLimiter()\n        try await limiter.set(\"foo\", rate: 2, timeInterval: 10)\n        try await limiter.set(\"bar\", rate: 1, timeInterval: 30)\n\n        #expect(await limiter.trackIfWithinLimit([\"foo\"]))\n        #expect(await limiter.trackIfWithinLimit([\"foo\"]))\n        #expect(await limiter.trackIfWithinLimit([\"bar\"]))\n\n        // Immediately, bar drives the wait (~30)\n        let w0 = await limiter.nextAvailable([\"foo\", \"bar\"])\n        #expect((29.999...30.001).contains(w0), \"Expected ~30s, got \\(w0)\")\n\n        // After 25s, max should be ~5s\n        clock.advance(by: 25)\n        let w1 = await limiter.nextAvailable([\"foo\", \"bar\"])\n        #expect((4.999...5.001).contains(w1), \"Expected ~5s, got \\(w1)\")\n    }\n\n    @Test(\"Pruning on read/write unblocks after window\")\n    func pruningUnblocks() async throws {\n        let (limiter, clock) = makeLimiter()\n        try await limiter.set(\"k\", rate: 3, timeInterval: 5)\n\n        #expect(await limiter.trackIfWithinLimit([\"k\"]))\n        #expect(await limiter.trackIfWithinLimit([\"k\"]))\n        #expect(await limiter.trackIfWithinLimit([\"k\"]))\n        #expect(!(await limiter.trackIfWithinLimit([\"k\"]))) // blocked\n\n        clock.advance(by: 6)\n        #expect(await limiter.nextAvailable([\"k\"]) == 0)\n        #expect(await limiter.trackIfWithinLimit([\"k\"]))\n    }\n\n    @Test(\"Negative waits clamp to 0\")\n    func clampNegativeToZero() async throws {\n        let (limiter, clock) = makeLimiter()\n        try await limiter.set(\"k\", rate: 1, timeInterval: 10)\n\n        #expect(await limiter.trackIfWithinLimit([\"k\"])) // consume 1\n        clock.advance(by: 12)\n        let w = await limiter.nextAvailable([\"k\"])\n        #expect(w >= 0)\n        #expect((0.0...0.001).contains(w))\n    }\n\n    @Test(\"Empty key set is a no-op success\")\n    func emptyKeys() async throws {\n        let (limiter, _) = makeLimiter()\n        #expect(await limiter.trackIfWithinLimit([]))\n        #expect(await limiter.nextAvailable([]) == 0)\n    }\n\n    @Test(\"Unknown key behaves as within limit (no rule)\")\n    func unknownKeyNoRule() async throws {\n        let (limiter, _) = makeLimiter()\n        #expect(await limiter.trackIfWithinLimit([\"unknown\"]))\n        #expect(await limiter.nextAvailable([\"unknown\"]) == 0)\n    }\n\n    @Test(\"Set<String> API does not double-count\")\n    func setKeysNoDoubleCount() async throws {\n        let (limiter, _) = makeLimiter()\n        try await limiter.set(\"foo\", rate: 1, timeInterval: 10)\n        #expect(await limiter.trackIfWithinLimit(Set([\"foo\"])))\n        #expect(!(await limiter.trackIfWithinLimit(Set([\"foo\"]))))\n    }\n\n    @Test(\"All-or-nothing tracking across multiple keys\")\n    func allOrNothingAcrossKeys() async throws {\n        let (limiter, _) = makeLimiter()\n        try await limiter.set(\"a\", rate: 1, timeInterval: 10)\n        try await limiter.set(\"b\", rate: 1, timeInterval: 10)\n\n        #expect(await limiter.trackIfWithinLimit([\"a\"]))\n        // Should fail for (a,b) because a is already at limit; and must not track either key\n        #expect(!(await limiter.trackIfWithinLimit([\"a\",\"b\"])))\n        // b should still be free to use\n        #expect(await limiter.trackIfWithinLimit([\"b\"]))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipDebug/Resources/AirshipDebugEventData.xcdatamodeld/AirshipEventData.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"20086\" systemVersion=\"21G83\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"EventData\" representedClassName=\"EventData\" syncable=\"YES\">\n        <attribute name=\"eventBody\" attributeType=\"String\"/>\n        <attribute name=\"eventDate\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"eventID\" attributeType=\"String\"/>\n        <attribute name=\"eventType\" attributeType=\"String\"/>\n    </entity>\n    <elements>\n        <element name=\"EventData\" positionX=\"2555.89453125\" positionY=\"-5715.18359375\" width=\"128\" height=\"89\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipDebug/Resources/AirshipDebugPushData.xcdatamodeld/AirshipDebugPushData.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"23231\" systemVersion=\"23H124\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"PushData\" representedClassName=\"PushData\" syncable=\"YES\">\n        <attribute name=\"alert\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"data\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"pushID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"time\" optional=\"YES\" attributeType=\"Double\" defaultValueString=\"0.0\" usesScalarValueType=\"YES\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipDebug/Source/AirshipDebugManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\n@preconcurrency import Combine\nimport SwiftUI\npublic import AirshipCore\nimport UserNotifications\n\n/// A protocol that provides access to Airship's debug interface functionality.\n///\n/// The `AirshipDebugManager` allows developers to display a comprehensive debug interface\n/// that provides insights into various Airship SDK components including push notifications,\n/// analytics events, channel information, contact data, and more.\n///\n/// ## Usage\n///\n/// ```swift\n/// // Display the debug interface\n/// Airship.debugManager.display()\n/// ```\n///\n/// The debug interface will be presented as an overlay window that can be dismissed\n/// by the user. It provides real-time monitoring and debugging capabilities for:\n/// - Push notification history and details\n/// - Analytics events and associated identifiers\n/// - Channel tags, attributes, and subscription lists\n/// - Contact information and channel management\n/// - In-app experiences and automations\n/// - Feature flags and experiments\n/// - Preference centers\n/// - App and SDK information\n/// - Privacy manager settings\n///\n/// - Note: This protocol is thread-safe and can be called from any thread.\n/// - Important: `Airship.takeOff` must be called before accessing the debug manager.\npublic protocol AirshipDebugManager: Sendable {\n    /// Displays the Airship debug interface as an overlay window.\n    ///\n    /// This method presents a comprehensive debug interface that allows developers\n    /// to inspect and monitor various aspects of the Airship SDK in real-time.\n    /// The interface includes navigation to different debug sections and provides\n    /// detailed information about push notifications, analytics events, channel\n    /// data, and other SDK components.\n    ///\n    /// The debug interface will be displayed as a modal overlay window that can\n    /// be dismissed by the user. If a debug interface is already displayed,\n    /// calling this method will replace the current interface.\n    ///\n    /// - Note: This method must be called from the main thread.\n    /// - Important: The debug interface requires an active scene to display properly.\n    ///   If no active scene is available, an error will be logged and the interface\n    ///   will not be displayed.\n    @MainActor\n    func display()\n}\n\nprotocol InternalAirshipDebugManager: AirshipDebugManager {\n    var preferenceFormsPublisher: AnyPublisher<[String], Never>  { get }\n\n    var inAppAutomationsPublisher: AnyPublisher<[[String: AnyHashable]], Never> { get }\n\n    var experimentsPublisher: AnyPublisher<[[String: AnyHashable]], Never> { get }\n\n    var featureFlagPublisher: AnyPublisher<[[String: AnyHashable]], Never> { get }\n\n    var pushNotificationReceivedPublisher: AnyPublisher<PushNotification, Never> { get }\n\n    var eventReceivedPublisher: AnyPublisher<AirshipEvent, Never> { get }\n\n\n    func pushNotifications() async -> [PushNotification]\n    func events(searchString: String?) async -> [AirshipEvent]\n    func events() async -> [AirshipEvent]\n\n    @MainActor\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON\n    ) async -> UABackgroundFetchResult\n\n#if !os(tvOS)\n    func receivedNotificationResponse(\n        _ response: UNNotificationResponse\n    ) async\n#endif\n\n}\n\nfinal class DefaultAirshipDebugManager: InternalAirshipDebugManager {\n\n    @MainActor\n    private var currentDisplay: (any AirshipMainActorCancellable)?\n    private let pushDataManager: PushDataManager\n    private let eventDataManager: EventDataManager\n    private let remoteData: any RemoteDataProtocol\n\n    @MainActor\n    private var eventUpdates: AnyCancellable? = nil\n\n    var preferenceFormsPublisher: AnyPublisher<[String], Never> {\n        self.remoteData.publisher(types: [\"preference_forms\"])\n            .map { payloads -> [String] in\n                return payloads.compactMap { payload in\n                    if let data = payload.data(key: \"preference_forms\") as? [[String: Any]] {\n                        return data.compactMap { $0[\"form\"] as? [String: Any] }\n                            .compactMap { $0[\"id\"] as? String }\n                    } else {\n                        return []\n                    }\n                }.reduce([], +)\n            }\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    var inAppAutomationsPublisher: AnyPublisher<[[String: AnyHashable]], Never> {\n        self.remoteData.publisher(types: [\"in_app_messages\"])\n            .map { payloads -> [[String: AnyHashable]] in\n                return payloads.compactMap { payload in\n                    payload.data(key: \"in_app_messages\") as? [[String: AnyHashable]]\n                }.reduce([], +)\n            }\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    var experimentsPublisher: AnyPublisher<[[String: AnyHashable]], Never> {\n        self.remoteData.publisher(types: [\"experiments\"])\n            .map { payloads -> [[String: AnyHashable]] in\n                return payloads.compactMap { payload in\n                    payload.data(key: \"experiments\") as? [[String: AnyHashable]]\n                }.reduce([], +)\n            }\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    var featureFlagPublisher: AnyPublisher<[[String: AnyHashable]], Never> {\n        self.remoteData.publisher(types: [\"feature_flags\"])\n            .map { payloads -> [[String: AnyHashable]] in\n                return payloads.compactMap { payload in\n                    payload.data(key: \"feature_flags\") as? [[String: AnyHashable]]\n                }.reduce([], +)\n            }\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    private let pushNotificationReceivedSubject = PassthroughSubject<PushNotification, Never>()\n    var pushNotificationReceivedPublisher: AnyPublisher<PushNotification, Never> {\n        return pushNotificationReceivedSubject.eraseToAnyPublisher()\n    }\n\n    private let eventReceivedSubject = PassthroughSubject<AirshipEvent, Never>()\n    var eventReceivedPublisher: AnyPublisher<AirshipEvent, Never> {\n        return eventReceivedSubject.eraseToAnyPublisher()\n    }\n\n    private let isEnabled: Bool\n\n    @MainActor\n    init(\n        config: RuntimeConfig,\n        analytics: any AirshipAnalytics,\n        remoteData: any RemoteDataProtocol\n    ) {\n        self.remoteData = remoteData\n        self.pushDataManager = PushDataManager(appKey: config.appCredentials.appKey)\n        self.eventDataManager = EventDataManager(appKey: config.appCredentials.appKey)\n        self.isEnabled = config.airshipConfig.isAirshipDebugEnabled\n\n        guard self.isEnabled else { return }\n\n        self.eventUpdates = analytics.eventPublisher\n            .sink { incoming in\n                let encoder = JSONEncoder()\n                encoder.outputFormatting = .prettyPrinted\n\n                guard\n                    let body = try? incoming.body.toString(\n                        encoder: encoder\n                    )\n                else {\n                    return\n                }\n\n                let airshipEvent = AirshipEvent(\n                    identifier: incoming.id,\n                    type: incoming.type.reportingName,\n                    date: incoming.date,\n                    body: body\n                )\n\n                Task { @MainActor in\n                    await self.eventDataManager.saveEvent(airshipEvent)\n                    self.eventReceivedSubject.send(airshipEvent)\n                }\n            }\n    }\n\n    func pushNotifications() async -> [PushNotification] {\n        return await self.pushDataManager.pushNotifications()\n    }\n\n    func events(searchString: String?) async -> [AirshipEvent] {\n        return await self.eventDataManager.events(searchString: searchString)\n    }\n\n    func events() async -> [AirshipEvent] {\n        return await events(searchString: nil)\n    }\n\n    @MainActor\n    public func display() {\n        let displayable = AirshipDisplayTarget().prepareDisplay(for: .modal)\n\n        currentDisplay?.cancel()\n\n        let rootView = AirshipDebugView {\n            displayable.dismiss()\n        }\n\n        do {\n            try displayable.display { _ in\n                return AirshipNativeHostingController(rootView: rootView)\n            }\n            self.currentDisplay = AirshipMainActorCancellableBlock(block: {\n                displayable.dismiss()\n            })\n        } catch {\n            AirshipLogger.error(\"Unable to display AirshipDebug \\(error)\")\n        }\n    }\n\n    @MainActor\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON\n    ) async -> UABackgroundFetchResult {\n\n        guard self.isEnabled else {\n            return .noData\n        }\n\n        do {\n            let push = try PushNotification(userInfo: notification)\n            try await savePush(push)\n        } catch {\n            AirshipLogger.error(\"Failed to save push \\(error)\")\n        }\n        return .noData\n    }\n\n#if !os(tvOS)\n    func receivedNotificationResponse(\n        _ response: UNNotificationResponse\n    ) async {\n        guard self.isEnabled else {\n            return\n        }\n\n        do {\n            let push = try PushNotification(\n                userInfo: try AirshipJSON.wrap(\n                    response.notification.request.content.userInfo\n                )\n            )\n            try await savePush(push)\n        } catch {\n            AirshipLogger.error(\"Failed to save push \\(error)\")\n        }\n    }\n#endif\n\n    private func savePush(\n        _ push: PushNotification\n    ) async throws {\n        await self.pushDataManager.savePushNotification(push)\n        Task { @MainActor in\n            self.pushNotificationReceivedSubject.send(push)\n        }\n    }\n}\n\n\npublic extension Airship {\n    /// The shared AirshipDebugManager instance.\n    ///\n    /// This property provides access to the Airship debug interface functionality,\n    /// allowing developers to display a comprehensive debug UI for monitoring\n    /// and debugging various aspects of the Airship SDK.\n    ///\n    /// ## Usage\n    ///\n    /// ```swift\n    /// // Display the debug interface\n    /// Airship.debugManager.display()\n    /// ```\n    ///\n    /// The debug manager provides access to:\n    /// - Push notification history and details\n    /// - Analytics events and associated identifiers  \n    /// - Channel tags, attributes, and subscription lists\n    /// - Contact information and channel management\n    /// - In-app experiences and automations\n    /// - Feature flags and experiments\n    /// - Preference centers\n    /// - App and SDK information\n    /// - Privacy manager settings\n    ///\n    /// - Note: `Airship.takeOff` must be called before accessing this instance.\n    /// - Important: This property will crash if accessed before Airship initialization.\n    static var debugManager: any AirshipDebugManager {\n        return Airship.requireComponent(ofType: DebugComponent.self).debugManager\n    }\n}\n\n\nextension Airship {\n    static var internalDebugManager: any InternalAirshipDebugManager {\n        return Airship.requireComponent(ofType: DebugComponent.self).debugManager\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/AirshipDebugResources.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Resources for AirshipDebug\npublic final class AirshipDebugResources {\n    /// Module bundle\n    public static let bundle: Bundle = resolveBundle()\n\n    private static func resolveBundle() -> Bundle {\n#if SWIFT_PACKAGE\n        AirshipLogger.trace(\"Using Bundle.module for AirshipDebug\")\n        let bundle = Bundle.module\n#if DEBUG\n        if bundle.resourceURL == nil {\n            assertionFailure(\"\"\"\n            AirshipDebug module was built with SWIFT_PACKAGE\n            but no resources were found. Check your build configuration.\n            \"\"\")\n        }\n#endif\n        return bundle\n#endif\n\n        return Bundle.airshipFindModule(\n            moduleName: \"AirshipDebug\",\n            sourceBundle: Bundle(for: Self.self)\n        )\n    }\n\n}\n\nextension String {\n    func localized(\n        bundle: Bundle = AirshipDebugResources.bundle,\n        tableName: String = \"AirshipDebug\",\n        comment: String = \"\"\n    ) -> String {\n        return NSLocalizedString(\n            self,\n            tableName: tableName,\n            bundle: bundle,\n            comment: comment\n        )\n    }\n\n    func localizedWithFormat(count: Int) -> String {\n        return String.localizedStringWithFormat(localized(), count)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/DebugComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n@preconcurrency import UserNotifications\nimport AirshipCore\n\n/// Actual airship component for AirshipDebugManager. Used to hide AirshipComponent methods.\nfinal class DebugComponent : AirshipComponent, AirshipPushableComponent {\n    final let debugManager: any InternalAirshipDebugManager\n\n    init(debugManager: any InternalAirshipDebugManager) {\n        self.debugManager = debugManager\n    }\n\n    @MainActor\n    func receivedRemoteNotification(\n        _ notification: AirshipCore.AirshipJSON\n    ) async -> AirshipCore.UABackgroundFetchResult {\n        return await self.debugManager.receivedRemoteNotification(notification)\n    }\n\n#if !os(tvOS)\n    @MainActor\n    func receivedNotificationResponse(\n        _ response: UNNotificationResponse\n    ) async {\n        return await self.debugManager.receivedNotificationResponse(response)\n    }\n#endif\n\n}\n\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/DebugSDKModule.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\npublic import AirshipCore\n\n/// - Note: For internal use only. :nodoc:\n@objc(UADebugSDKModule)\npublic class DebugSDKModule: NSObject, AirshipSDKModule {\n\n    public var actionsManifest: (any ActionsManifest)? = nil\n\n    public let components: [any AirshipComponent]\n\n    public static func load(_ args: AirshiopModuleLoaderArgs) -> (any AirshipSDKModule)? {\n        let debugManager = DefaultAirshipDebugManager(\n            config: args.config,\n            analytics: args.analytics,\n            remoteData: args.remoteData\n        )\n        return DebugSDKModule(debugManager)\n    }\n\n    private init(_ debugManager: any InternalAirshipDebugManager) {\n        self.components = [DebugComponent(debugManager: debugManager)]\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Events/AirshipEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport AirshipCore\n\nstruct AirshipEvent: Equatable, Hashable, Sendable {\n    /// The unique identifier of the event.\n    var identifier: String\n    \n    /// The type of the event (e.g., \"custom_event\", \"app_foreground\").\n    var type: String\n    \n    /// The date and time when the event occurred.\n    var date: Date\n    \n    /// The JSON body of the event as a formatted string.\n    var body: String\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Events/EventData.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import CoreData\nimport Foundation\n\n/// - Note: For internal use only. :nodoc:\n@objc(EventData)\npublic class EventData: NSManagedObject {\n\n    @nonobjc public class func fetchRequest() -> NSFetchRequest<EventData> {\n        return NSFetchRequest<EventData>(entityName: \"EventData\")\n    }\n\n    @NSManaged public var eventBody: String?\n    @NSManaged public var eventID: String?\n    @NSManaged public var eventType: String?\n    @NSManaged public var eventDate: Date?\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Events/EventDataManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\nimport AirshipCore\n\nfinal class EventDataManager: Sendable {\n\n    private let maxAge = TimeInterval(172800)  // 2 days\n    private let appKey: String\n    private let coreData: UACoreData\n\n    public init(appKey: String) {\n        self.appKey = appKey\n        self.coreData = UACoreData(\n            name: \"AirshipDebugEventData\",\n            modelURL: AirshipDebugResources.bundle\n                .url(\n                    forResource: \"AirshipDebugEventData\",\n                    withExtension: \"momd\"\n                )!,\n            inMemory: false,\n            stores: [\"AirshipDebugEventData-\\(appKey).sqlite\"]\n        )\n        \n        Task {\n            await self.trimDatabase()\n        }\n    }\n\n    private func trimDatabase() async {\n        \n        let cutOffDate = Date().advanced(by: -self.maxAge)\n        \n        do {\n            try await coreData.perform(skipIfStoreNotCreated: true) { context in\n                let fetchRequest: NSFetchRequest<any NSFetchRequestResult> = EventData.fetchRequest()\n                fetchRequest.predicate = NSPredicate(format: \"eventDate < %@\", cutOffDate as NSDate)\n\n                let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)\n                try context.execute(batchDeleteRequest)\n            }\n        } catch {\n            print(\"Failed to execute request: \\(error)\")\n        }\n    }\n\n    func saveEvent(_ event: AirshipEvent) async {\n        \n        do {\n            try await self.coreData.perform { context in\n                let persistedEvent = EventData(\n                    entity: EventData.entity(),\n                    insertInto: context\n                )\n\n                persistedEvent.eventBody = event.body\n                persistedEvent.eventType = event.type\n                persistedEvent.eventDate = event.date\n                persistedEvent.eventID = event.identifier\n            }\n        } catch {\n            print(\"Failed to save event: \\(error)\")\n\n        }\n    }\n\n    func events(searchString: String? = nil) async -> [AirshipEvent] {\n        do {\n            return try await coreData.performWithResult { context in\n                let fetchRequest: NSFetchRequest = EventData.fetchRequest()\n                fetchRequest.fetchLimit = 200\n                fetchRequest.sortDescriptors = [\n                    NSSortDescriptor(\n                        key: #keyPath(EventData.eventDate),\n                        ascending: false\n                    )\n                ]\n\n                if let searchString = searchString, !searchString.isEmpty {\n                    fetchRequest.predicate = NSPredicate(\n                        format:\n                            \"eventID CONTAINS[cd] %@ OR eventType CONTAINS[cd] %@\",\n                        searchString,\n                        searchString\n                    )\n                }\n\n                let result = try context.fetch(fetchRequest)\n                let events = result.compactMap { data -> AirshipEvent? in\n                    if let eventType = data.eventType,\n                       let eventBody = data.eventBody,\n                       let eventDate = data.eventDate,\n                       let eventID = data.eventID\n                    {\n                        return AirshipEvent(\n                            identifier: eventID,\n                            type: eventType,\n                            date: eventDate,\n                            body: eventBody\n                        )\n                    }\n                    return nil\n                }\n\n                return events\n            }\n\n        } catch {\n            print(\n                \"ERROR: error fetching events list - \\(error)\"\n            )\n            return []\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Push/PushData+CoreDataClass.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import CoreData\n\n/// - Note: For internal use only. :nodoc:\n@objc(PushData)\npublic class PushData: NSManagedObject {}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Push/PushData+CoreDataProperties.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import CoreData\n\n//Had to generate it manually as coredata codegen doesn't work well with swift 6\nextension PushData {\n\n    @nonobjc public class func fetchRequest() -> NSFetchRequest<PushData> {\n        return NSFetchRequest<PushData>(entityName: \"PushData\")\n    }\n\n    @NSManaged public var alert: String?\n    @NSManaged public var data: String?\n    @NSManaged public var pushID: String?\n    @NSManaged public var time: Double\n\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Push/PushDataManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#elseif canImport(AirshipKit)\nimport AirshipKit\n#endif\n\nfinal class PushDataManager: Sendable {\n\n    private let maxAge = TimeInterval(172800)  // 2 days\n    private let appKey: String\n    private let coreData: UACoreData\n\n    public init(appKey: String) {\n        self.appKey = appKey\n        self.coreData = UACoreData(\n            name: \"AirshipDebugPushData\",\n            modelURL: AirshipDebugResources.bundle\n                .url(\n                    forResource: \"AirshipDebugPushData\",\n                    withExtension: \"momd\"\n                )!,\n            inMemory: false,\n            stores: [\"AirshipDebugPushData-\\(appKey).sqlite\"]\n        )\n\n        Task {\n            await self.trimDatabase()\n        }\n    }\n\n    private func trimDatabase() async {\n        \n        let storageDaysInterval = Date()\n            .advanced(by: -self.maxAge)\n            .timeIntervalSince1970\n\n        do {\n            try await coreData.perform(skipIfStoreNotCreated: true) { context in\n                let fetchRequest: NSFetchRequest<any NSFetchRequestResult> = PushData.fetchRequest()\n                fetchRequest.predicate = NSPredicate(format: \"time < %f\", storageDaysInterval)\n\n                let batchDeleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest)\n                try context.execute(batchDeleteRequest)\n            }\n        } catch {\n            print(\"Failed to execute request: \\(error)\")\n        }\n\n    }\n\n    public func savePushNotification(_ push: PushNotification) async {\n        try? await coreData.perform { context in\n            guard !self.pushExists(id: push.pushID, context: context) else {\n                return\n            }\n\n            let persistedPush = PushData(\n                entity: PushData.entity(),\n                insertInto: context\n            )\n            persistedPush.pushID = push.pushID\n            persistedPush.alert = push.alert\n            persistedPush.data = push.description\n            persistedPush.time = push.time\n        }\n    }\n\n    private func pushExists(id: String, context: NSManagedObjectContext) -> Bool\n    {\n        let fetchRequest: NSFetchRequest = PushData.fetchRequest()\n        fetchRequest.predicate = NSPredicate(format: \"pushID = %@\", id)\n        var results: [NSManagedObject] = []\n        do {\n            results = try context.fetch(fetchRequest)\n        } catch {\n            print(\"error executing fetch request: \\(error)\")\n        }\n        return results.count > 0\n    }\n\n    func pushNotifications() async -> [PushNotification] {\n        let result = try? await coreData.performWithResult { (context) -> [PushNotification] in\n            let fetchRequest: NSFetchRequest = PushData.fetchRequest()\n            fetchRequest.sortDescriptors = [\n                NSSortDescriptor(key: \"time\", ascending: false)\n            ]\n            \n            let result = try context.fetch(fetchRequest)\n            let notifications = result.map {\n                PushNotification(pushData: $0)\n            }\n            return notifications\n        } \n\n        return result ?? []\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/Push/PushNotification.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\n\n/// A wrapper for representing an Airship push notification in the Debug UI.\n///\n/// `PushNotification` encapsulates push notification data for display in the debug interface.\n/// It provides a simplified representation of push notifications with the essential\n/// information needed for debugging and monitoring.\n///\n/// ## Usage\n///\n/// ```swift\n/// // Access push notifications through the debug manager\n/// let pushes = await Airship.internalDebugManager.pushNotifications()\n/// for push in pushes {\n///     print(\"Push: \\(push.alert ?? \"No alert\") at \\(Date(timeIntervalSince1970: push.time))\")\n/// }\n/// ```\n///\n/// - Note: This struct is thread-safe and can be used across different threads.\nstruct PushNotification: Equatable, Hashable, CustomStringConvertible, Sendable {\n\n    /// The unique push ID.\n    var pushID: String\n\n    /// The push alert message.\n    var alert: String?\n\n    /// The time the push was created (as a TimeInterval since 1970).\n    var time: TimeInterval\n\n    /// The push data description as a JSON string.\n    public var description: String\n\n    var payload: AirshipJSON {\n        return (try? AirshipJSON.from(json: self.description)) ?? AirshipJSON.string(description)\n    }\n\n    init(userInfo: AirshipJSON) throws {\n        self.description = try userInfo.toString()\n        self.time = Date().timeIntervalSince1970\n        self.alert = PushNotification.parseAlert(userInfo)\n        self.pushID = userInfo.object?[\"_\"]?.string ?? \"MISSING_PUSH_ID\"\n    }\n\n    init(pushData: PushData) {\n        self.description = pushData.data ?? \"\"\n        self.time = pushData.time\n        self.alert = pushData.alert\n        self.pushID = pushData.pushID ?? \"\"\n    }\n\n    private static func parseAlert(_ push: AirshipJSON) -> String? {\n        guard let alert = push.object?[\"aps\"]?.object?[\"alert\"] else { return nil }\n\n        if let string = alert.string {\n            return string\n        }\n\n        return alert.object?[\"body\"]?.string\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/ShakeUtils.swift",
    "content": ""
  },
  {
    "path": "Airship/AirshipDebug/Source/View/AirshipDebugContentView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\nimport AirshipCore\nimport AirshipAutomation\n\n/// A SwiftUI view that provides the main content of the Airship debug interface.\n///\n/// `AirshipDebugContentView` contains the primary debug interface content without\n/// navigation wrapper, making it suitable for embedding in existing navigation contexts.\n/// It displays the main menu of debug sections that users can navigate to.\n///\n/// ## Usage\n///\n/// When embedding in existing navigation, you must handle the navigation destinations\n/// using the `navigationDestination` helper from `AirshipDebugRoute`:\n///\n/// ```swift\n/// // Embed in existing navigation with proper destination handling\n/// NavigationStack(path: $path) {\n///     AirshipDebugContentView()\n///         .navigationDestination(for: AirshipDebugRoute.self) { route in\n///             route.navigationDestination\n///         }\n/// }\n/// ```\n///\n/// - Important: When using `AirshipDebugContentView` in a `NavigationStack`, you must\n///   add the `.navigationDestination(for: AirshipDebugRoute.self)` modifier to handle\n///   navigation to debug sections. Use `route.navigationDestination` to get the\n///   appropriate view for each route.\n@MainActor\npublic struct AirshipDebugContentView: View {\n    private static let sections = [\n        DebugSection(\n            icon: \"hand.raised.square.fill\",\n            title: \"Privacy Manager\",\n            route: .privacyManager\n        ),\n        DebugSection(\n            icon: \"arrow.left.arrow.right.square.fill\",\n            title: \"Channel\",\n            route: .channel\n        ),\n        DebugSection(\n            icon: \"person.crop.square.fill\",\n            title: \"Contacts\",\n            route: .contact\n        ),\n        DebugSection(\n            icon: \"checkmark.bubble.fill\",\n            title: \"Push\",\n            route: .push\n        ),\n        DebugSection(\n            icon: \"calendar.badge.checkmark\",\n            title: \"Analytics\",\n            route: .analytics\n        ),\n        DebugSection(\n            icon: \"bolt.square.fill\",\n            title: \"In-App Experiences\",\n            route: .inAppExperience\n        ),\n        DebugSection(\n            icon: \"flag.square.fill\",\n            title: \"Feature Flags\",\n            route: .featureFlags\n        ),\n        DebugSection(\n            icon: \"list.bullet.rectangle.fill\",\n            title: \"Preference Centers\",\n            route: .preferenceCenters\n        ),\n        DebugSection(\n            icon: \"iphone.homebutton\",\n            title: \"App Info\",\n            route: .appInfo\n        )\n    ]\n\n    public init() {\n\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        Form {\n            ForEach(Self.sections, id: \\.self) { item in\n                NavigationLink(value: item.route) {\n                    HStack {\n                        Image(systemName: item.icon)\n                            .resizable()\n                            .aspectRatio(contentMode: .fit)\n                            .frame(width: 24, height: 24)\n                            .foregroundColor(.blue)\n\n                        Text(item.title.localized())\n                            .foregroundColor(.primary)\n\n                        Spacer()\n                    }\n                }\n            }\n        }\n    }\n}\n\nfileprivate struct DebugSection: Sendable, Hashable {\n    let icon: String\n    let title: String\n    let route: AirshipDebugRoute\n}\n\n#Preview {\n    AirshipDebugContentView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/AirshipDebugRoute.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\nimport AirshipCore\nimport AirshipPreferenceCenter\n\n\n/// An enum that defines all possible navigation routes within the Airship debug interface.\n///\n/// `AirshipDebugRoute` provides type-safe navigation for the debug interface.\n/// Each route case maps to a specific SwiftUI view through the `navigationDestination`\n/// computed property, enabling seamless navigation between different debug sections.\n///\n/// ## Usage\n///\n/// Routes are used internally by the `AirshipDebugView` for navigation. The\n/// `navigationDestination` property automatically resolves each route to its\n/// corresponding SwiftUI view.\n///\n/// ## Route Categories\n///\n/// The routes are organized into main categories with optional sub-routes:\n/// - **Privacy Manager**: Privacy settings and controls\n/// - **Channel**: Channel management with sub-routes for tags, attributes, etc.\n/// - **Contact**: Contact management with sub-routes for different channel types\n/// - **Push**: Push notification management with sub-routes for details\n/// - **Analytics**: Analytics data with sub-routes for events and identifiers\n/// - **In-App Experience**: In-app experiences with sub-routes for automations and experiments\n/// - **Feature Flags**: Feature flag management with sub-routes for details\n/// - **Preference Centers**: Preference center management with sub-routes for specific centers\n/// - **App Info**: General app and SDK information\n///\n/// - Note: This enum is thread-safe and can be used across different threads.\npublic enum AirshipDebugRoute: Sendable, Equatable, Hashable {\n    /// Navigate to the privacy manager section.\n    case privacyManager\n    \n    /// Navigate to the main channel section.\n    case channel\n    \n    /// Navigate to a specific channel sub-section.\n    case channelSub(ChannelRoute)\n    \n    /// Navigate to the main contact section.\n    case contact\n    \n    /// Navigate to a specific contact sub-section.\n    case contactSub(ContactRoute)\n    \n    /// Navigate to the main push notifications section.\n    case push\n    \n    /// Navigate to a specific push notifications sub-section.\n    case pushSub(PushRoute)\n    \n    /// Navigate to the main analytics section.\n    case analytics\n    \n    /// Navigate to a specific analytics sub-section.\n    case analyticsSub(AnalyticsRoute)\n    \n    /// Navigate to the main in-app experiences section.\n    case inAppExperience\n    \n    /// Navigate to a specific in-app experiences sub-section.\n    case inAppExperienceSub(InAppExperienceRoute)\n    \n    /// Navigate to the main feature flags section.\n    case featureFlags\n    \n    /// Navigate to a specific feature flag sub-section.\n    case featureFlagsSub(FeatureFlagRoute)\n    \n    /// Navigate to the main preference centers section.\n    case preferenceCenters\n    \n    /// Navigate to a specific preference center sub-section.\n    case preferenceCentersSub(PrefenceCenterRoute)\n    \n    /// Navigate to the app information section.\n    case appInfo\n\n    /// Sub-routes for the channel management section.\n    public enum ChannelRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the channel tags management view.\n        case tags\n        \n        /// Navigate to the channel tag groups management view.\n        case tagGroups\n        \n        /// Navigate to the channel attributes management view.\n        case attributes\n        \n        /// Navigate to the channel subscription lists management view.\n        case subscriptionLists\n    }\n\n    /// Sub-routes for the contact management section.\n    public enum ContactRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the contact tag groups management view.\n        case tagGroups\n        \n        /// Navigate to the contact attributes management view.\n        case attributes\n        \n        /// Navigate to the contact subscription lists management view.\n        case subscriptionLists\n        \n        /// Navigate to the add open channel view.\n        case addOpenChannel\n        \n        /// Navigate to the add SMS channel view.\n        case addSMSChannel\n        \n        /// Navigate to the add email channel view.\n        case addEmailChannel\n        \n        /// Navigate to the named user ID management view.\n        case namedUserID\n    }\n\n    /// Sub-routes for the analytics section.\n    public enum AnalyticsRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the analytics events list view.\n        case events\n        \n        /// Navigate to the details view for a specific analytics event.\n        /// - Parameter identifier: The unique identifier of the event to display.\n        case eventDetails(identifier: String)\n        \n        /// Navigate to the add analytics event view.\n        case addEvent\n        \n        /// Navigate to the associated identifiers management view.\n        case associatedIdentifiers\n    }\n\n    /// Sub-routes for the in-app experiences section.\n    public enum InAppExperienceRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the in-app automations view.\n        case automations\n        \n        /// Navigate to the experiments view.\n        case experiments\n    }\n\n    /// Sub-routes for the feature flags section.\n    public enum FeatureFlagRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the details view for a specific feature flag.\n        /// - Parameter name: The name of the feature flag to display.\n        case featureFlagDetails(name: String)\n    }\n\n    /// Sub-routes for the preference centers section.\n    public enum PrefenceCenterRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the details view for a specific preference center.\n        /// - Parameter identifier: The identifier of the preference center to display.\n        case preferenceCenter(identifier: String)\n    }\n\n    /// Sub-routes for the push notifications section.\n    public enum PushRoute: Sendable, Equatable, Hashable {\n        /// Navigate to the received push notifications list view.\n        case recievedPushes\n        \n        /// Navigate to the details view for a specific push notification.\n        /// - Parameter identifier: The unique identifier of the push notification to display.\n        case pushDetails(identifier: String)\n    }\n}\n\npublic extension AirshipDebugRoute {\n    /// Returns the SwiftUI view that corresponds to this route.\n    ///\n    /// This computed property provides the view that should be displayed when\n    /// navigating to this route. It uses a switch statement to map each route\n    /// case to its corresponding SwiftUI view.\n    ///\n    /// - Returns: The SwiftUI view that should be displayed for this route.\n    @ViewBuilder\n    @MainActor\n    var navigationDestiation: some View {\n        switch(self) {\n        case .privacyManager: AirshipDebugPrivacyManagerView()\n        case .channel: AirshipDebugChannelView()\n        case .channelSub(let subRoute):\n            switch(subRoute) {\n            case .tags: AirshipDebugChannelTagView()\n            case .attributes: AirshipDebugAttributesEditorView(for: .channel)\n            case .subscriptionLists: AirshipDebugChannelSubscriptionsView()\n            case .tagGroups: AirshipDebugTagGroupsEditorView(for: .channel)\n            }\n\n        case .contact: AirshipDebugContactsView()\n        case .contactSub(let subRoute):\n            switch(subRoute) {\n            case .attributes: AirshipDebugAttributesEditorView(for: .contact)\n            case .subscriptionLists: AirshipDebugContactSubscriptionEditorView()\n            case .tagGroups: AirshipDebugTagGroupsEditorView(for: .contact)\n            case .addSMSChannel: AirshipDebugAddSMSChannelView()\n            case .addOpenChannel: AirshipDebugAddOpenChannelView()\n            case .addEmailChannel: AirshipDebugAddEmailChannelView()\n            case .namedUserID: AirshipDebugNamedUserView()\n            }\n            \n        case .push: AirshipDebugPushView()\n        case .pushSub(let subRoute):\n            switch(subRoute) {\n            case .recievedPushes: AirshipDebugReceivedPushView()\n            case .pushDetails(let identifier): AirshipDebugPushDetailsView(identifier: identifier)\n            }\n\n        case .analytics: AirshipDebugAnalyticsView()\n        case .analyticsSub(let subRoute):\n            switch(subRoute) {\n            case .associatedIdentifiers: AirshipDebugAnalyticIdentifierEditorView()\n            case .events: AirshipDebugEventsView()\n            case .addEvent: AirshipDebugAddEventView()\n            case .eventDetails(let identifier): AirshipDebugEventDetailsView(identifier: identifier)\n            }\n\n        case .inAppExperience: AirshipDebugInAppExperiencesView()\n        case .inAppExperienceSub(let subRoute):\n            switch(subRoute) {\n            case .experiments: AirshipDebugExperimentsView()\n            case .automations: AirshipDebugAutomationsView()\n            }\n        case .featureFlags: AirshipDebugFeatureFlagView()\n        case .featureFlagsSub(let subRoute):\n            switch(subRoute) {\n            case .featureFlagDetails(let name): AirshipDebugFeatureFlagDetailsView(name: name)\n            }\n\n        case .preferenceCenters: AirshipDebugPreferenceCentersView()\n        case .preferenceCentersSub(let subRoute):\n            switch(subRoute) {\n            case .preferenceCenter(let identifier): AirshipDebugPreferencCenterItemView(preferenceCenterID: identifier)\n            }\n\n        case .appInfo: AirshipDebugAppInfoView()\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/AirshipDebugView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\nimport AirshipCore\n\n/// A SwiftUI view that provides the main Airship debug interface.\n///\n/// `AirshipDebugView` presents a comprehensive debug interface for monitoring\n/// and debugging various aspects of the Airship SDK. The view uses a navigation\n/// stack to organize different debug sections and provides real-time access to\n/// push notifications, analytics events, channel data, and other SDK components.\n///\n/// ## Usage\n///\n/// ```swift\n/// // Basic usage\n/// AirshipDebugView()\n///\n/// // With custom dismissal handling\n/// AirshipDebugView {\n///     // Handle dismissal\n///     print(\"Debug view dismissed\")\n/// }\n/// ```\n///\n/// ## Features\n///\n/// The debug view provides access to:\n/// - **Privacy Manager**: Privacy settings and controls\n/// - **Channel**: Channel tags, attributes, and subscription lists\n/// - **Contacts**: Contact information and channel management\n/// - **Push**: Push notification history and details\n/// - **Analytics**: Analytics events and associated identifiers\n/// - **In-App Experiences**: Automations and experiments\n/// - **Feature Flags**: Feature flag details and status\n/// - **Preference Centers**: Preference center management\n/// - **App Info**: General app and SDK information\n///\n/// - Note: This view must be used on the main thread.\n@MainActor\npublic struct AirshipDebugView: View {\n    private let onDismiss: (@MainActor () -> Void)?\n\n    @State\n    private var path: [AirshipDebugRoute] = []\n\n    /// Creates a new AirshipDebugView.\n    ///\n    /// - Parameter onDismiss: Optional callback that will be called when the\n    ///   debug view is dismissed. The callback will be executed on the main thread.\n    public init(onDismiss: (@MainActor () -> Void)? = nil) {\n        self.onDismiss = onDismiss\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        NavigationStack(path: self.$path) {\n            AirshipDebugContentView()\n                .toolbar {\n                    if let onDismiss {\n                        ToolbarItem(placement: .cancellationAction) {\n                            Button(action: {\n                                onDismiss()\n                            }) {\n                                Image(systemName: \"chevron.backward\")\n                                    .scaleEffect(0.68)\n                                    .font(Font.title.weight(.medium))\n                            }\n                        }\n                    }\n                }\n                .navigationTitle(\"Airship Debug\")\n                .navigationDestination(for: AirshipDebugRoute.self) { route in\n                    route.navigationDestiation\n                }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/AirshipoDebugTriggers.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\nimport AirshipCore\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n/// Defines the triggers available for the Airship Debug interface.\npublic struct AirshipDebugTrigger: OptionSet, Sendable {\n    public let rawValue: Int\n    public init(rawValue: Int) { self.rawValue = rawValue }\n\n    /// Detects device shake (iOS only).\n    public static let shake = AirshipDebugTrigger(rawValue: 1 << 0)\n    /// Detects Cmd + Shift + D (iOS with hardware keyboard & macOS).\n    public static let cmdShiftD = AirshipDebugTrigger(rawValue: 1 << 1)\n}\n\npublic extension View {\n    /// Enables the Airship Debug interface based on specified interaction triggers.\n    ///\n    /// This modifier provides a unified way to access the Airship Debug console across platforms.\n    ///\n    /// The debug interface will only be attached if `Airship.isFlying` is true and\n    /// `isAirshipDebugEnabled` is set to `true` in the Airship configuration.\n    ///\n    /// ### Usage\n    /// ```swift\n    /// // Standard behavior (Shake & Hotkey)\n    /// ContentView()\n    ///     .airshipDebug(triggers: [.shake, .cmdShiftD)\n    ///\n    /// // Discrete mode (keyboard only)\n    /// ContentView()\n    ///     .airshipDebug(triggers: [.cmdShiftD])\n    /// ```\n    ///\n    /// - Parameter triggers: A set of ``AirshipDebugTrigger`` options determining how the\n    ///   debug interface is invoked. Defaults to ``AirshipDebugTrigger/defaultTriggers``.\n    /// - Returns: A view modified to detect the specified debug triggers.\n    @ViewBuilder\n    func airshipDebug(triggers: AirshipDebugTrigger) -> some View {\n        if Airship.isFlying, Airship.config.airshipConfig.isAirshipDebugEnabled {\n            self.modifier(AirshipDebugModifier(triggers: triggers))\n        } else {\n            self\n        }\n    }\n\n    /// Enables the Airship Debug interface via a device shake gesture.\n    ///\n    /// This is a legacy convenience method for iOS that specifically enables the\n    /// shake gesture trigger. For more control, use ``airshipDebug(triggers:)``.\n    @available(*, deprecated, renamed: \"airshipDebug(triggers:)\", message: \"Use airshipDebug(triggers: .shake) instead.\")\n    func airshipDebugOnShake() -> some View {\n        self.airshipDebug(triggers: .shake)\n    }\n}\n\nstruct AirshipDebugModifier: ViewModifier {\n    let triggers: AirshipDebugTrigger\n\n    func body(content: Content) -> some View {\n        content\n            // --- Gestures (iOS) ---\n#if canImport(UIKit)\n            .onReceive(NotificationCenter.default.publisher(for: UIDevice.airshipDeviceDidShakeNotification)) { _ in\n                if triggers.contains(.shake) { displayDebug() }\n            }\n#endif\n            .airshipApplyIf(triggers.contains(.cmdShiftD)) { view in\n                view.background {\n                    Button(\"\") {\n                        displayDebug()\n                    }\n                    .keyboardShortcut(\"d\", modifiers: [.command, .shift])\n                    .opacity(0)\n                    .allowsHitTesting(false)\n                    .accessibilityHidden(true)\n                }\n            }\n    }\n\n    private func displayDebug() {\n        Airship.debugManager.display()\n    }\n}\n\n// MARK: - iOS Specific Shake Logic\n#if canImport(UIKit)\nextension UIDevice {\n    static let airshipDeviceDidShakeNotification = Notification.Name(rawValue: \"AirshipDeviceDidShakeNotification\")\n}\n\nextension UIWindow {\n    open override func motionEnded(_ motion: UIEvent.EventSubtype, with event: UIEvent?) {\n        if motion == .motionShake {\n            NotificationCenter.default.post(name: UIDevice.airshipDeviceDidShakeNotification, object: nil)\n        }\n        super.motionEnded(motion, with: event)\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Analytics/AirshipDebugAddEventView.swift",
    "content": "import AirshipCore\nimport Foundation\nimport SwiftUI\nimport Combine\n\nstruct AirshipDebugAddEventView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    @State var shouldPresentPropetySheet = false\n\n    public init() {}\n\n    @ViewBuilder\n    func makeTextInput(title: String, binding: Binding<String>) -> some View {\n        HStack {\n            Text(title.lowercased())\n            Spacer()\n            TextField(title.lowercased(), text: binding.preventWhiteSpace())\n                .freeInput()\n        }\n    }\n\n    @ViewBuilder\n    func makeNumberInput(title: String, binding: Binding<Double>) -> some View {\n        HStack {\n            Text(title.lowercased())\n            Spacer()\n            TextField(\n                title.lowercased(),\n                value: binding,\n                formatter: NumberFormatter()\n            )\n#if !os(macOS)\n            .keyboardType(.numberPad)\n#endif\n        }\n    }\n\n    var body: some View {\n        Form {\n            Section(header: Text(\"Event Properties\".localized())) {\n                makeTextInput(\n                    title: \"Event Name\",\n                    binding: self.$viewModel.eventName\n                )\n                makeNumberInput(\n                    title: \"Event Value\",\n                    binding: self.$viewModel.eventValue\n                )\n                makeTextInput(\n                    title: \"Transaction ID\",\n                    binding: self.$viewModel.transactionID\n                )\n                makeTextInput(\n                    title: \"Interaction ID\",\n                    binding: self.$viewModel.interactionID\n                )\n                makeTextInput(\n                    title: \"Interaction Type\",\n                    binding: self.$viewModel.interactionType\n                )\n            }\n\n            Section(header: Text(\"Properties\".localized())) {\n                Button(\"Add Property\".localized()) {\n                    self.shouldPresentPropetySheet = true\n                }\n                .sheet(isPresented: self.$shouldPresentPropetySheet) {\n                    NavigationStack {\n                        AirshipDebugAddPropertyView {\n                            self.viewModel.properties[$0] = $1\n                        }\n                        .navigationTitle(\"New Property\")\n#if !os(tvOS) && !os(macOS)\n                        .navigationBarTitleDisplayMode(.inline)\n#endif\n                    }\n                    .presentationDetents([.medium])\n                }\n                List {\n                    let keys = [String](self.viewModel.properties.keys)\n                    ForEach(keys, id: \\.self) { key in\n                        HStack {\n                            Text(\"\\(key):\")\n                            Text(\n                                self.viewModel.properties[key]?.prettyString ?? \"-\"\n                            )\n                        }\n                    }\n                    .onDelete {\n                        $0.forEach { index in\n                            self.viewModel.properties[keys[index]] = nil\n                        }\n                    }\n                }\n            }\n\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    self.viewModel.createEvent()\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Text(\"Create\".localized())\n                }\n                .disabled(!self.viewModel.isEnabled)\n            }\n        }\n        .navigationTitle(\"Custom Event\".localized())\n    }\n\n    @MainActor\n    fileprivate class ViewModel: ObservableObject {\n        @Published var eventName: String = \"\"\n        @Published var eventValue: Double = 1.0\n        @Published var interactionID: String = \"\"\n        @Published var interactionType: String = \"\"\n        @Published var transactionID: String = \"\"\n\n        var isEnabled: Bool {\n            return !self.eventName.isEmpty && Airship.isFlying\n        }\n\n        var properties: [String: AirshipJSON] = [:]\n\n        func createEvent() {\n            guard\n                Airship.isFlying,\n                !self.eventName.isEmpty\n            else {\n                return\n            }\n\n            var event = CustomEvent(\n                name: self.eventName,\n                value: self.eventValue\n            )\n            if !self.transactionID.isEmpty {\n                event.transactionID = self.transactionID\n            }\n            if !self.interactionID.isEmpty && !self.interactionType.isEmpty {\n                event.interactionID = self.interactionID\n                event.interactionType = self.interactionType\n            }\n            try? event.setProperties(self.properties)\n\n            event.track()\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugAddEventView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Analytics/AirshipDebugAnalyticIdentifierEditorView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugAnalyticIdentifierEditorView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @State\n    private var shouldPresentPropetySheet: Bool = false\n\n    var body: some View {\n        Form {\n            Section(header: Text(\"Identifiers\".localized())) {\n                Button(\"Add Identifier\".localized()) {\n                    self.shouldPresentPropetySheet = true\n                }\n                .sheet(isPresented: self.$shouldPresentPropetySheet) {\n                    NavigationStack {\n                        AirshipDebugAddStringPropertyView {\n                            self.viewModel.identifiers[$0] = $1\n                        }\n                        .navigationTitle(\"New Identifier\")\n#if !os(tvOS) && !os(macOS)\n                        .navigationBarTitleDisplayMode(.inline)\n#endif\n                    }\n                    .presentationDetents([.medium])\n                }\n\n                List {\n                    let keys = [String](self.viewModel.identifiers.keys)\n                    ForEach(keys, id: \\.self) { key in\n                        HStack {\n                            Text(\"\\(key):\")\n                            Text(self.viewModel.identifiers[key] ?? \"\")\n                        }\n                    }\n                    .onDelete {\n                        $0.forEach { index in\n                            self.viewModel.identifiers[keys[index]] = nil\n                        }\n                    }\n                }\n            }\n        }\n        .navigationTitle(\"Analytic Identifiers\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published var identifiers: [String: String] {\n            didSet {\n                save()\n            }\n        }\n\n        init() {\n            if Airship.isFlying {\n                self.identifiers =\n                Airship.analytics.currentAssociatedDeviceIdentifiers().allIDs\n            } else {\n                self.identifiers = [:]\n            }\n        }\n\n        func save() {\n            guard Airship.isFlying else { return }\n            Airship.analytics.associateDeviceIdentifiers(\n                AssociatedIdentifiers(identifiers: self.identifiers)\n            )\n        }\n    }\n}\n\nprivate struct AddIdentifierView: View {\n\n    @State\n    private var key: String = \"\"\n\n    @State\n    private var value: String = \"\"\n\n    let onAdd: (String, String) -> Void\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    var body: some View {\n        Form {\n            HStack {\n                Text(\"Key\".localized())\n                Spacer()\n                TextField(\n                    \"Key\".localized(),\n                    text: self.$key.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n            HStack {\n                Text(\"Value\".localized())\n                Spacer()\n                TextField(\n                    \"Value\".localized(),\n                    text: self.$value.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    onAdd(key, value)\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Text(\"Add\".localized())\n                }\n                .disabled(key.isEmpty || value.isEmpty)\n            }\n        }\n        .navigationTitle(\"Identifier\".localized())\n\n    }\n}\n\n#Preview {\n    AirshipDebugAnalyticIdentifierEditorView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Analytics/AirshipDebugAnalyticsView.swift",
    "content": "// Copyright Airship and Contributors\n\nimport SwiftUI\n\nstruct AirshipDebugAnalyticsView: View {\n\n    var body: some View {\n        Form {\n            CommonItems.navigationLink(\n                title: \"Events\".localized(),\n                route: .analyticsSub(.events)\n            )\n            CommonItems.navigationLink(\n                title: \"Add Custom Event\".localized(),\n                route: .analyticsSub(.addEvent)\n            )\n            CommonItems.navigationLink(\n                title: \"Associated Identifiers\".localized(),\n                route: .analyticsSub(.associatedIdentifiers)\n            )\n        }\n        .navigationTitle(\"Analytics\".localized())\n    }\n}\n\n#Preview {\n    AirshipDebugAnalyticsView()\n}\n\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Analytics/AirshipDebugEventDetailsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugEventDetailsView: View {\n\n    @State\n    private var toastMessage: AirshipToast.Message? = nil\n\n    @StateObject\n    private var viewModel: ViewModel\n\n    public init(identifier: String) {\n        _viewModel = .init(wrappedValue: .init(identifier: identifier))\n    }\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            if let event = self.viewModel.event {\n                Section(header: Text(\"Event Details\".localized())) {\n                    makeInfoItem(\"Type\", event.type)\n                    makeInfoItem(\"ID\", event.identifier)\n                    makeInfoItem(\n                        \"Date\",\n                        AirshipDateFormatter.string(fromDate: event.date, format: .iso)\n                    )\n                }\n\n                Section(header: Text(\"Event body\".localized())) {\n                    Button(action: {\n                        copyToClipboard(value: event.body)\n                    }) {\n                        Text(event.body)\n                            .foregroundColor(.primary)\n                    }\n                }\n            } else {\n                ProgressView()\n            }\n        }\n        .overlay(AirshipToast(message: self.$toastMessage))\n        .navigationTitle(\"Event\".localized())\n    }\n\n    @ViewBuilder\n    func makeInfoItem(_ title: String, _ value: String?) -> some View {\n        Button(action: {\n            if let value = value {\n                copyToClipboard(value: value)\n            }\n        }) {\n            HStack {\n                Text(title)\n                    .foregroundColor(.primary)\n                Spacer()\n                Text(value ?? \"\")\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n\n    func copyToClipboard(value: String?) {\n        guard let value = value else {\n            return\n        }\n        value.pastleboard()\n        self.toastMessage = AirshipToast.Message(\n            id: UUID().uuidString,\n            text: \"Copied to pasteboard!\".localized(),\n            duration: 1.0\n        )\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var event: AirshipEvent?\n\n        init(identifier: String) {\n            Task { @MainActor [weak self] in\n                self?.event = await Airship.internalDebugManager.events().first(\n                    where: { event in\n                        event.identifier == identifier\n                    }\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Analytics/AirshipDebugEventsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugEventsView: View {\n    \n    @StateObject\n    private var viewModel = ViewModel()\n    \n    var body: some View {\n        Form {\n            Section {\n                ForEach(self.viewModel.events, id: \\.identifier) { event in\n                    \n                    NavigationLink(\n                        value: AirshipDebugRoute.analyticsSub(.eventDetails(identifier: event.identifier))\n                    ) {\n                        VStack(alignment: .leading) {\n                            Text(event.type)\n                            Text(event.identifier)\n                            HStack {\n                                Text(event.date, style: .date)\n                                Text(event.date, style: .time)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        .navigationTitle(\"Events\".localized())\n    }\n    \n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var events: [AirshipEvent] = []\n        \n        @Published\n        var searchString: String = \"\" {\n            didSet {\n                refreshEvents()\n            }\n        }\n        \n        private var cancellable: AnyCancellable? = nil\n        \n        init() {\n            if Airship.isFlying {\n                self.cancellable = Airship.internalDebugManager\n                    .eventReceivedPublisher\n                    .sink { [weak self] incoming in\n                        self?.refreshEvents()\n                    }\n            }\n            \n            refreshEvents()\n        }\n        \n        private func refreshEvents() {\n            if !Airship.isFlying { return }\n            \n            Task { @MainActor in\n                let events = await Airship.internalDebugManager.events(\n                    searchString: self.searchString\n                )\n                self.events = events\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugEventsView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/AppInfo/AirshipDebugAppInfoView.swift",
    "content": "// Copyright Airship and Contributors\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nfileprivate struct AppInfo: Sendable {\n    let bundleId: String\n    let timeZone: String\n    let sdkVersion: String\n    let appVersion: String\n    let appCodeVersion: String\n    let applicationLocale: String\n}\n\n@MainActor\nstruct AirshipDebugAppInfoView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @State\n    private var toast: AirshipToast.Message? = nil\n\n\n    var body: some View {\n        Form {\n            CommonItems.infoRow(\n                title: \"Airship SDK Version\".localized(),\n                value: viewModel.appInfo.sdkVersion,\n                onTap: { copyValue(viewModel.appInfo.sdkVersion) }\n            )\n\n            CommonItems.infoRow(\n                title: \"App Version\".localized(),\n                value: viewModel.appInfo.appVersion,\n                onTap: { copyValue(viewModel.appInfo.appVersion) }\n            )\n\n            CommonItems.infoRow(\n                title: \"App Code Version\".localized(),\n                value: viewModel.appInfo.appCodeVersion,\n                onTap: { copyValue(viewModel.appInfo.appCodeVersion) }\n            )\n\n            CommonItems.infoRow(\n                title: \"Model\".localized(),\n                value: AirshipDevice.modelIdentifier,\n                onTap: { copyValue(AirshipDevice.modelIdentifier) }\n            )\n\n            CommonItems.infoRow(\n                title: \"Bundle ID\".localized(),\n                value: viewModel.appInfo.bundleId,\n                onTap: { copyValue(viewModel.appInfo.bundleId) }\n            )\n\n            CommonItems.infoRow(\n                title: \"Time Zone\".localized(),\n                value: viewModel.appInfo.timeZone,\n                onTap: { copyValue(viewModel.appInfo.timeZone) }\n            )\n\n            CommonItems.infoRow(\n                title: \"App Locale\".localized(),\n                value: viewModel.appInfo.applicationLocale,\n                onTap: { copyValue(viewModel.appInfo.applicationLocale) }\n            )\n\n            Picker(\n                selection: self.$viewModel.airshipLocaleIdentifier,\n                label: Text(\"Airship Locale\".localized())\n            ) {\n                let allIDs = Locale.availableIdentifiers\n                ForEach(allIDs, id: \\.self) { localeID in  // <1>\n                    Text(localeID)\n                }\n            }\n            .foregroundColor(.primary)\n            .frame(height: CommonItems.rowHeight)\n\n            Button(\n                \"Clear Locale Override\".localized(),\n                role: .destructive\n            ) { [weak viewModel] in\n                viewModel?.clearLocaleOverride()\n            }\n            .frame(height: CommonItems.rowHeight)\n        }\n        .toastable($toast)\n        .navigationTitle(\"App Info\".localized())\n    }\n\n    private func copyValue(_ value: String) {\n        value.pastleboard()\n        toast = .init(text: \"Copied to clipboard\")\n    }\n    \n    @MainActor\n    fileprivate final class ViewModel: ObservableObject {\n        @Published\n        var appInfo: AppInfo\n\n        @Published\n        var airshipLocaleIdentifier: String {\n            didSet {\n                guard Airship.isFlying else { return }\n\n                Airship.localeManager.currentLocale = Locale(\n                    identifier: airshipLocaleIdentifier\n                )\n            }\n        }\n\n        @MainActor\n        init() {\n            self.appInfo = .init(\n                bundleId: Bundle.main.bundleIdentifier ?? \"\",\n                timeZone: TimeZone.autoupdatingCurrent.identifier,\n                sdkVersion: AirshipVersion.version,\n                appVersion: (Bundle.main.infoDictionary?[\"CFBundleShortVersionString\"] as? String)\n                ?? \"\",\n                appCodeVersion: (Bundle.main.infoDictionary?[\"CFBundleVersion\"] as? String) ?? \"\",\n                applicationLocale: Locale.autoupdatingCurrent.identifier\n            )\n\n            self.airshipLocaleIdentifier = \"\"\n            Airship.onReady { [weak self] in\n                self?.airshipLocaleIdentifier = Airship.localeManager.currentLocale.identifier\n            }\n        }\n\n        func clearLocaleOverride() {\n            if Airship.isFlying {\n                self.airshipLocaleIdentifier = Locale.autoupdatingCurrent.identifier\n                Airship.localeManager.clearLocale()\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugAppInfoView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Automations/AirshipDebugAutomationsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugAutomationsView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    var body: some View {\n        Form {\n            Section(header: Text(\"\")) {\n                List(self.viewModel.messagePayloads, id: \\.self) { payload in\n                    let title = parseTitle(payload: payload)\n                    VStack(alignment: .leading) {\n                        Text(title)\n                        Text(parseID(payload: payload))\n                    }\n                }\n            }\n        }\n        .navigationTitle(\"Automations\".localized())\n    }\n\n    func parseTitle(payload: [String: AnyHashable]) -> String {\n        let message = payload[\"message\"] as? [String: AnyHashable]\n        return message?[\"name\"] as? String ?? parseType(payload: payload)\n    }\n\n    func parseType(payload: [String: AnyHashable]) -> String {\n        return payload[\"type\"] as? String ?? \"Unknown\"\n    }\n\n    func parseID(payload: [String: AnyHashable]) -> String {\n        return payload[\"id\"] as? String ?? \"MISSING_ID\"\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var messagePayloads: [[String: AnyHashable]] = []\n        private var cancellable: AnyCancellable? = nil\n\n        init() {\n            Airship.onReady { [weak self] in\n                self?.cancellable = Airship.internalDebugManager\n                    .inAppAutomationsPublisher\n                    .receive(on: RunLoop.main)\n                    .sink { incoming in\n                        self?.messagePayloads = incoming\n                    }\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugAutomationsView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Automations/AirshipDebugExperimentsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugExperimentsView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    var body: some View {\n        Form {\n            Section(header: Text(\"\")) {\n                ForEach(self.viewModel.payloads, id: \\.self) { payload in\n                    VStack(alignment: .leading) {\n                        Text(parseID(payload: payload))\n                    }\n                }\n            }\n        }\n        .navigationTitle(\"Experiments\".localized())\n    }\n\n    func parseID(payload: [String: AnyHashable]) -> String {\n        return payload[\"experiment_id\"] as? String ?? \"MISSING_ID\"\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var payloads: [[String: AnyHashable]] =\n            []\n        private var cancellable: AnyCancellable? = nil\n\n        init() {\n            if Airship.isFlying {\n                self.cancellable = Airship.internalDebugManager\n                    .experimentsPublisher\n                    .receive(on: RunLoop.main)\n                    .sink { incoming in\n                        self.payloads = incoming\n                    }\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugExperimentsView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Automations/AirshipDebugInAppExperiencesView.swift",
    "content": "// Copyright Airship and Contributors\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\nimport AirshipAutomation\n\nstruct AirshipDebugInAppExperiencesView: View {\n\n    @StateObject\n    private var viewModel: ViewModel = .init()\n\n    var body: some View {\n        Form {\n            CommonItems.navigationLink(\n                title: \"Automations\".localized(),\n                route: .inAppExperienceSub(.automations)\n            )\n            \n            CommonItems.navigationLink(\n                title: \"Experiments\".localized(),\n                route: .inAppExperienceSub(.experiments)\n            )\n            \n            displayIntervalRow()\n        }\n        .navigationTitle(\"In-App Experiences\".localized())\n    }\n    \n    private var displayIntervalString: String {\n        viewModel.displayInterval.formatted(.number.precision(.fractionLength(1)))\n    }\n    \n    @ViewBuilder\n    private func displayIntervalRow() -> some View {\n        VStack {\n            HStack {\n                Text(\"Display Interval\".localized())\n                    .foregroundColor(.primary)\n                Spacer()\n                Text(\"\\(displayIntervalString) seconds\")\n                    .foregroundColor(.secondary)\n            }\n#if os(tvOS)\n            TVSlider(\n                displayInterval: self.$viewModel.displayInterval,\n                range: 0.0...200.0,\n                step: 1.0\n            )\n#else\n            Slider(\n                value: self.$viewModel.displayInterval,\n                in: 0.0...200.0,\n                step: 1.0\n            )\n#endif\n        }\n    }\n\n    @MainActor\n    fileprivate final class ViewModel: ObservableObject {\n        @Published\n        var displayInterval: TimeInterval = 0.0 {\n            didSet {\n                guard Airship.isFlying else { return }\n                Airship.inAppAutomation.inAppMessaging.displayInterval = self.displayInterval\n            }\n        }\n        \n        @MainActor\n        init() {\n            Airship.onReady { [weak self] in\n                self?.displayInterval = Airship.inAppAutomation.inAppMessaging.displayInterval\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugInAppExperiencesView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Channel/AirshipDebugChannelSubscriptionsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugChannelSubscriptionsView: View {\n    private enum SubscriptionListAction: String, Equatable, CaseIterable {\n        case subscribe = \"Subscribe\"\n        case unsubscribe = \"Unsubscribe\"\n    }\n\n    @State\n    private var listID: String = \"\"\n\n    @State\n    private var action: SubscriptionListAction = .subscribe\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            Section(\n                header: Text(\"Subscription Info\".localized())\n            ) {\n                Picker(\"Action\".localized(), selection: $action) {\n                    ForEach(SubscriptionListAction.allCases, id: \\.self) {\n                        value in\n                        Text(value.rawValue.localized())\n                    }\n                }\n                .pickerStyle(.segmented)\n\n                HStack {\n                    Text(\"List ID\".localized())\n                    Spacer()\n                    TextField(\"\", text: self.$listID.preventWhiteSpace())\n                        .freeInput()\n                }\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    apply()\n                } label: {\n                    Text(\"Apply\".localized())\n                }\n                .disabled(listID.isEmpty)\n            }\n        }\n        .navigationTitle(\"Subscription Lists\".localized())\n    }\n\n    private func apply() {\n        defer {\n            self.listID = \"\"\n        }\n\n        guard Airship.isFlying else { return }\n\n        Airship.channel.editSubscriptionLists { editor in\n            switch self.action {\n            case .subscribe:\n                editor.subscribe(self.listID)\n            case .unsubscribe:\n                editor.unsubscribe(self.listID)\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugChannelSubscriptionsView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Channel/AirshipDebugChannelTagView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugChannelTagView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @State\n    private var tag: String = \"\"\n\n    var body: some View {\n        Form {\n            Section {\n                HStack {\n                    Text(\"New Tag\".localized())\n\n                    TextField(\"\", text: self.$tag.preventWhiteSpace()).freeInput()\n                        .frame(maxWidth: .infinity)\n\n                    Button {\n                        self.viewModel.addTag(self.tag)\n                        self.tag = \"\"\n                    } label: {\n                        Text(\"Save\".localized())\n                    }\n                    .disabled(tag.isEmpty)\n                }\n            }\n\n            Section(\"Current Tags\".localized()) {\n                List {\n                    ForEach(self.viewModel.tags, id: \\.self) { tag in\n                        Text(tag)\n                    }\n                    .onDelete {\n                        $0.forEach { index in\n                            let tag = self.viewModel.tags[index]\n                            self.viewModel.removeTag(tag)\n                        }\n                    }\n                }\n            }\n        }\n        .navigationTitle(\"Tags\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n\n        @Published\n        private(set) var tags: [String]\n\n        init() {\n            if Airship.isFlying {\n                self.tags = Airship.channel.tags\n            } else {\n                self.tags = []\n            }\n        }\n\n        func addTag(_ tag: String) {\n            if Airship.isFlying {\n                Airship.channel.editTags {\n                    $0.add(tag)\n                }\n                self.tags = Airship.channel.tags\n            }\n        }\n\n        func removeTag(_ tag: String) {\n            if Airship.isFlying {\n                Airship.channel.editTags {\n                    $0.remove(tag)\n                }\n                self.tags = Airship.channel.tags\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugChannelTagView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Channel/AirshipDebugChannelView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugChannelView: View {\n\n    @State\n    private var toastMessage: AirshipToast.Message? = nil\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    var body: some View {\n        Form {\n            let channelID = viewModel.channelID\n            CommonItems.infoRow(\n                title: \"Channel ID\".localized(),\n                value: channelID,\n                onTap: { copyChananelId(channelID) }\n            )\n\n            CommonItems.navigationLink(\n                title: \"Tags\".localized(),\n                route: .channelSub(.tags)\n            )\n\n            CommonItems.navigationLink(\n                title: \"Tag Groups\".localized(),\n                route: .channelSub(.tagGroups)\n            )\n\n            CommonItems.navigationLink(\n                title: \"Attributes\".localized(),\n                route: .channelSub(.attributes)\n            )\n\n            CommonItems.navigationLink(\n                title: \"Subscription Lists\".localized(),\n                route: .channelSub(.subscriptionLists)\n            )\n        }\n        .toastable($toastMessage)\n        .navigationTitle(\"Channel\".localized())\n    }\n    \n    private func copyChananelId(_ channelId: String?) {\n        guard let channelId else { return }\n        \n        channelId.pastleboard()\n        self.toastMessage = .init(text: \"Channel ID copied to clipboard\")\n    }\n\n    @MainActor\n    fileprivate final class ViewModel: ObservableObject {\n        @Published\n        var channelID: String?\n\n        private var task: Task<Void, Never>? = nil\n\n        @MainActor\n        init() {\n            self.task = Task { [weak self] in\n                await Airship.waitForReady()\n\n                for await _ in Airship.channel.identifierUpdates {\n                    self?.updateChannelID()\n                }\n            }\n            updateChannelID()\n        }\n\n        deinit {\n            task?.cancel()\n        }\n\n        private func updateChannelID() {\n            guard Airship.isFlying else { return }\n            if self.channelID != Airship.channel.identifier {\n                channelID = Airship.channel.identifier\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugChannelView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipDebugAddPropertyView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugAddPropertyView: View {\n\n    enum PropertyType: String, Equatable, CaseIterable {\n        case bool = \"Bool\"\n        case string = \"String\"\n        case number = \"Number\"\n        case json = \"JSON\"\n    }\n\n    @State\n    private var key: String = \"\"\n\n    @State\n    private var boolValue: Bool = false\n\n    @State\n    private var numberValue: Double = 0\n\n    @State\n    private var stringValue: String = \"\"\n\n    @State\n    private var jsonValue: String = \"\"\n\n    @State\n    private var propertyType: PropertyType = .string\n\n    let onAdd: (String, AirshipJSON) -> Void\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    var body: some View {\n        Form {\n            HStack {\n                Text(\"Name\".localized())\n                Spacer()\n                TextField(\n                    \"Name\".localized(),\n                    text: self.$key.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n\n            Picker(\"Type\".localized(), selection: self.$propertyType) {\n                ForEach(PropertyType.allCases, id: \\.self) { value in\n                    Text(value.rawValue.localized())\n                }\n            }\n            .pickerStyle(.segmented)\n\n            makeValue()\n        }\n        .toolbar {\n            ToolbarItem(placement: .cancellationAction) {\n                Button {\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Image(systemName: \"xmark\")\n                }\n            }\n\n            ToolbarItem(placement: .confirmationAction) {\n                Button(\"Add\".localized()) {\n                    guard let value = self.value else { return }\n                    onAdd(self.key, value)\n                    presentationMode.wrappedValue.dismiss()\n                }\n                .disabled(!self.isValid)\n            }\n        }\n    }\n\n    private var value: AirshipJSON? {\n        return switch self.propertyType {\n        case .bool: .bool(self.boolValue)\n        case .number: .number(self.numberValue)\n        case .string: .string(self.stringValue)\n        case .json: try? AirshipJSON.from(json: self.jsonValue)\n        }\n    }\n\n    private var isValid: Bool {\n        guard !self.key.isEmpty else {\n            return false\n        }\n        return switch self.propertyType {\n        case .bool: true\n        case .number: true\n        case .string: !self.stringValue.isEmpty\n        case .json: self.value != nil\n        }\n    }\n\n    @ViewBuilder\n    private func makeValue() -> some View {\n        switch self.propertyType {\n        case .bool:\n            Toggle(\"\\(self.boolValue ? \"true\": \"false\")\", isOn: self.$boolValue)\n        case .string:\n            HStack {\n                Text(\"String\".localized())\n                Spacer()\n                TextField(\n                    \"String\".localized(),\n                    text: self.$stringValue.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n        case .json:\n            VStack {\n                Text(\"JSON\".localized())\n                Spacer()\n#if !os(tvOS)\n                TextEditor(text: self.$jsonValue.preventWhiteSpace())\n                    .frame(maxWidth: .infinity, minHeight: 300)\n#endif\n            }\n        case .number:\n            HStack {\n                Text(\"Number\".localized())\n                Spacer()\n                TextField(\n                    \"Number\".localized(),\n                    value: self.$numberValue,\n                    formatter: NumberFormatter()\n                )\n#if !os(macOS)\n                .keyboardType(.numberPad)\n#endif\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipDebugAddStringPropertyView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugAddStringPropertyView: View {\n\n    @State\n    private var key: String = \"\"\n\n    @State\n    private var value: String = \"\"\n\n    let onAdd: (String, String) -> Void\n\n    private var isValid: Bool {\n        return !key.isEmpty && !value.isEmpty\n    }\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    var body: some View {\n        Form {\n            HStack {\n                Text(\"Key\".localized())\n                Spacer()\n                TextField(\n                    \"Key\".localized(),\n                    text: self.$key.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n            HStack {\n                Text(\"Value\".localized())\n                Spacer()\n                TextField(\n                    \"Value\".localized(),\n                    text: self.$value.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .cancellationAction) {\n                Button {\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Image(systemName: \"xmark\")\n                }\n            }\n\n            ToolbarItem(placement: .confirmationAction) {\n                Button(\"Add\".localized()) {\n                    onAdd(self.key, value)\n                    presentationMode.wrappedValue.dismiss()\n                }\n                .disabled(!self.isValid)\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipDebugAttributesEditorView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugAttributesEditorView: View {\n\n    private enum AttributeAction: String, Equatable, CaseIterable {\n        case add = \"Add\"\n        case remove = \"Remove\"\n    }\n\n    private enum AttributeType: String, Equatable, CaseIterable {\n        case text = \"Text\"\n        case number = \"Number\"\n        case date = \"Date\"\n        case json = \"JSON\"\n    }\n\n    private let subject: AirshipDebugAudienceSubject?\n\n\n    init() {\n        self.subject = nil\n    }\n\n    public init(for subject: AirshipDebugAudienceSubject) {\n        self.subject = subject\n    }\n\n    @State\n    private var attribute: String = \"\"\n\n    @State\n    private var action: AttributeAction = .add\n\n    @State\n    private var type: AttributeType = .text\n\n    @State\n    private var date = Date()\n\n    @State\n    private var text: String = \"\"\n\n    @State\n    private var number: Double = 0.0\n\n    @State\n    private var jsonText: String = \"\"\n\n    @State\n    private var instanceID: String = \"\"\n\n    @State\n    private var expiryEnabled: Bool = false\n\n    @State\n    private var expiryDate = Date()\n\n    @ViewBuilder\n    func makeValue() -> some View {\n        switch self.type {\n        case .date:\n            #if os(tvOS)\n            TVDatePicker(\n                \"Date\".localized(),\n                selection: $date,\n                displayedComponents: .all\n            )\n            #else\n            DatePicker(\n                \"Date\".localized(),\n                selection: $date,\n                displayedComponents: [.date, .hourAndMinute]\n            )\n            #endif\n        case .text:\n            HStack {\n                Text(\"Text\")\n                Spacer()\n                TextField(\n                    \"Value\".localized(),\n                    text: self.$text.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n        case .number:\n            HStack {\n                Text(\"Number\")\n                Spacer()\n                TextField(\n                    \"Value\".localized(),\n                    value: self.$number,\n                    formatter: NumberFormatter()\n                )\n#if !os(macOS)\n                .keyboardType(.numberPad)\n#endif\n            }\n        case .json:\n            VStack(alignment: .leading, spacing: 8) {\n                Text(\"JSON\")\n                Group {\n                    #if !os(tvOS)\n                    TextEditor(text: $jsonText)\n                        .font(.system(.caption, design: .monospaced))\n                        .frame(height: 100)\n                    #else\n                    MultilineTextView(text: $jsonText)\n                        .frame(height: 100)\n                    #endif\n                }.overlay(\n                    RoundedRectangle(cornerRadius: 4)\n                        .stroke(\n                            jsonText.isEmpty || jsonIsValid\n                            ? Color.secondary.opacity(0.5)\n                            : Color.red\n                        )\n                )\n                .airshipOnChangeOf(jsonText) { newValue in\n                    guard let data = newValue.data(using: .utf8) else { return }\n                    do {\n                        let obj = try JSONSerialization.jsonObject(with: data)\n                        let prettyData = try JSONSerialization.data(\n                            withJSONObject: obj,\n                            options: .prettyPrinted\n                        )\n                        if let prettyString = String(data: prettyData, encoding: .utf8),\n                           prettyString != newValue {\n                            jsonText = prettyString\n                        }\n                    } catch {}\n                }\n                HStack {\n                    Text(\"Instance ID\")\n                    Spacer()\n                    TextField(\"ID\".localized(), text: $instanceID.preventWhiteSpace())\n                        .freeInput()\n                }\n                Toggle(\"Expiry\".localized(), isOn: $expiryEnabled)\n                if expiryEnabled {\n                    #if os(tvOS)\n                    TVDatePicker(\n                        \"Date\".localized(),\n                        selection: $expiryDate,\n                        displayedComponents: .all\n                    )\n                    #else\n                    DatePicker(\n                        \"Date\".localized(),\n                        selection: $expiryDate,\n                        displayedComponents: [.date, .hourAndMinute]\n                    )\n                    #endif\n                }\n            }\n        }\n    }\n\n    var body: some View {\n        Form {\n            Section(header: Text(\"Attribute Info\".localized())) {\n                Picker(\"Action\".localized(), selection: $action) {\n                    ForEach(AttributeAction.allCases, id: \\.self) { value in\n                        Text(value.rawValue.localized())\n                    }\n                }\n                .pickerStyle(.segmented)\n\n                HStack {\n                    Text(\"Attribute\".localized())\n                    Spacer()\n                    TextField(\n                        \"Attribute Name\".localized(),\n                        text: self.$attribute.preventWhiteSpace()\n                    )\n                    .freeInput()\n                }\n\n                if self.action == .add {\n                    Picker(\"Type\".localized(), selection: $type) {\n                        ForEach(AttributeType.allCases, id: \\.self) { value in\n                            Text(value.rawValue.localized())\n                        }\n                    }\n                    .pickerStyle(.segmented)\n\n                    makeValue()\n                }\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    apply()\n                } label: {\n                    Text(\"Apply\".localized())\n                }\n                .disabled(!isValid())\n            }\n        }\n        .navigationTitle(\"Attributes\".localized())\n    }\n\n    private func isValid() -> Bool {\n        guard !attribute.isEmpty else { return false }\n\n        switch self.action {\n        case .add:\n            switch self.type {\n            case .number:\n                return true\n            case .text:\n                return !self.text.isEmpty\n            case .date:\n                return true\n            case .json: return !jsonText.isEmpty && !instanceID.isEmpty\n            }\n        case .remove:\n            return true\n        }\n    }\n\n    private func apply() {\n        defer {\n            attribute = \"\"\n            text = \"\"\n            number = 0\n            jsonText = \"\"\n            instanceID = \"\"\n            expiryEnabled = false\n            expiryDate = Date()\n        }\n\n        guard Airship.isFlying, let subject else { return }\n\n        subject.editAttributes { editor in\n            switch self.action {\n            case .add:\n                switch self.type {\n                case .number:\n                    editor.set(double: self.number, attribute: self.attribute)\n                case .text:\n                    editor.set(string: self.text, attribute: self.attribute)\n                case .date:\n                    editor.set(date: self.date, attribute: self.attribute)\n                case .json:\n                    do {\n                        let root = try AirshipJSON.from(json: jsonText)\n\n                        guard case let .object(dict) = root else {\n                            throw AirshipErrors.error(\"Top‑level JSON must be an object\")\n                        }\n\n                        if expiryEnabled {\n                            try editor.set(\n                                json: dict,\n                                attribute: attribute,\n                                instanceID: instanceID,\n                                expiration: expiryDate\n                            )\n                        } else {\n                            try editor.set(\n                                json: dict,\n                                attribute: attribute,\n                                instanceID: instanceID\n                            )\n                        }\n                    } catch {\n                        AirshipLogger.error(\"JSON attribute error: \\(error)\")\n                    }\n                }\n            case .remove:\n                editor.remove(self.attribute)\n            }\n        }\n    }\n\n    private var jsonIsValid: Bool {\n        guard let data = jsonText.data(using: .utf8) else {\n            return false\n        }\n        return (try? JSONSerialization.jsonObject(with: data, options: [])) != nil\n    }\n}\n\n/// For tvOS\n#if os(tvOS)\nstruct MultilineTextView: UIViewRepresentable {\n    @Binding var text: String\n\n    class Coordinator: NSObject, UITextViewDelegate {\n        var parent: MultilineTextView\n        init(_ parent: MultilineTextView) { self.parent = parent }\n        func textViewDidChange(_ textView: UITextView) { parent.text = textView.text }\n    }\n\n    func makeCoordinator() -> Coordinator { Coordinator(self) }\n\n    func makeUIView(context: Context) -> UITextView {\n        let tv = UITextView()\n        tv.font = .monospacedSystemFont(ofSize: 14, weight: .regular)\n        tv.delegate = context.coordinator\n        tv.textColor = .label\n        tv.backgroundColor = .clear\n        tv.isScrollEnabled = true\n        tv.text = text\n        return tv\n    }\n\n    func updateUIView(_ uiView: UITextView, context: Context) {\n        if uiView.text != text { uiView.text = text }\n    }\n}\n#endif\n\n#Preview {\n    AirshipDebugAttributesEditorView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipDebugAudienceSubject.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\n\nenum AirshipDebugAudienceSubject {\n    case channel\n    case contact\n\n    func editTagGroups(_ editorBlock: (TagGroupsEditor) -> Void) {\n        switch(self) {\n        case .channel: Airship.channel.editTagGroups(editorBlock)\n        case .contact: Airship.channel.editTagGroups(editorBlock)\n        }\n    }\n\n    func editAttributes(_ editorBlock: (AttributesEditor) -> Void) {\n        switch(self) {\n        case .channel: Airship.channel.editAttributes(editorBlock)\n        case .contact: Airship.channel.editAttributes(editorBlock)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipDebugExtensions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nextension Binding where Value == String {\n    func preventWhiteSpace() -> Binding<String> {\n        return Binding<String>(\n            get: { self.wrappedValue },\n            set: {\n                self.wrappedValue = $0.trimmingCharacters(\n                    in: .whitespacesAndNewlines\n                )\n            }\n        )\n    }\n}\n\nextension View {\n    @ViewBuilder\n    func freeInput() -> some View {\n#if os(macOS)\n        self.disableAutocorrection(true)\n#else\n        \n        self.textInputAutocapitalization(.never)\n            .disableAutocorrection(true)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipDebugTagGroupsEditorView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport AirshipCore\nimport Combine\n\nstruct AirshipDebugTagGroupsEditorView: View {\n    enum TagAction: String, Equatable, CaseIterable {\n        case add = \"Add\"\n        case remove = \"Remove\"\n    }\n\n    @State\n    private var tag: String = \"\"\n\n    @State\n    private var group: String = \"\"\n\n    @State\n    private var action: TagAction = .add\n\n    private let subject: AirshipDebugAudienceSubject?\n\n    init() {\n        self.subject = nil\n    }\n\n    init(for subject: AirshipDebugAudienceSubject) {\n        self.subject = subject\n    }\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            Section(header: Text(\"Tag Info\".localized())) {\n                Picker(\"Action\".localized(), selection: $action) {\n                    ForEach(TagAction.allCases, id: \\.self) { value in\n                        Text(value.rawValue.localized())\n                    }\n                }\n                .pickerStyle(.segmented)\n\n                HStack {\n                    Text(\"Tag\")\n                    Spacer()\n                    TextField(\"\", text: self.$tag.preventWhiteSpace())\n                        .freeInput()\n                }\n\n                HStack {\n                    Text(\"Group\")\n                    Spacer()\n                    TextField(\"\", text: self.$group.preventWhiteSpace())\n                        .freeInput()\n                }\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    apply()\n                } label: {\n                    Text(\"Apply\".localized())\n                }\n                .disabled(tag.isEmpty || group.isEmpty)\n            }\n        }\n        .navigationTitle(\"Tag Groups\".localized())\n    }\n\n    private func apply() {\n        defer {\n            self.tag = \"\"\n            self.group = \"\"\n        }\n\n        guard Airship.isFlying, let subject else { return }\n\n        subject.editTagGroups { editor in\n            switch self.action {\n            case .add:\n                editor.add([tag], group: group)\n            case .remove:\n                editor.remove([tag], group: group)\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugTagGroupsEditorView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipJSONDetailsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipJSONDetailsView: View {\n    let payload: AirshipJSON\n    let title: String\n\n    @State\n    private var toastMessage: AirshipToast.Message? = nil\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            Section(header: Text(\"Details\".localized())) {\n                AirshipJSONView(json: payload)\n            }\n        }\n        .navigationTitle(title)\n        .toolbar {\n            ToolbarItem(placement: .primaryAction) {\n                Button(\"Copy\") {\n                    copyToClipboard(value: payload.prettyString)\n                }\n            }\n        }\n        .toastable($toastMessage)\n    }\n\n    func copyToClipboard(value: String?) {\n        guard let value = value else {\n            return\n        }\n\n        value.pastleboard()\n        self.toastMessage = AirshipToast.Message(\n            id: UUID().uuidString,\n            text: \"Copied to pasteboard!\".localized(),\n            duration: 1.0\n        )\n    }\n}\n\n#Preview {\n    AirshipJSONDetailsView(\n        payload: [\"key\": \"value\"],\n        title: \"Preview\")\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipJSONView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Foundation\nimport AirshipCore\n\nstruct AirshipJSONView: View {\n    let json: AirshipJSON\n\n    init(json: AirshipJSON) {\n        self.json = json\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        switch (json) {\n        case .object(let object):\n            List(Array(object.keys), id: \\.self) { key in\n                if let value = object[key] {\n                    ObjectEntry(key: key, value: value)\n                }\n            }\n        default:\n            Text(json.prettyString)\n        }\n\n    }\n}\n\nextension String {\n    var quoted: String {\n        return \"\\\"\\(self)\\\"\"\n    }\n}\n\nextension AirshipJSON {\n    static let prettyEncoder = {\n        let encoder = JSONEncoder()\n        encoder.outputFormatting = .prettyPrinted\n        return encoder\n    }()\n\n    static func wrapSafe(_ any: Any?) -> AirshipJSON {\n        do {\n            return try AirshipJSON.wrap(any)\n        } catch {\n            return AirshipJSON.string(\"Error \\(error)\")\n        }\n    }\n\n    var prettyString: String {\n        do {\n            return try self.toString(encoder: AirshipJSON.prettyEncoder)\n        } catch {\n            return \"Error: \\(error)\"\n        }\n    }\n\n    var typeString: String {\n        switch(self) {\n        case .object: return \"object\"\n        case .string: return \"string\"\n        case .number: return \"number\"\n        case .array: return \"array\"\n        case .bool: return \"boolean\"\n        case .null: return \"null\"\n        @unknown default: return \"unknown\"\n        }\n    }\n}\n\nfileprivate struct ObjectEntry: View {\n    let key: String\n    let value: AirshipJSON\n\n    @State private var collapsed: Bool = false\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            Button(\n                action: { self.collapsed.toggle() },\n                label: {\n                    HStack {\n                        Text(key.quoted) + Text(\": \") + Text(value.typeString).italic().font(.system(size: 12))\n                        Spacer()\n                        Image(systemName: self.collapsed ? \"chevron.down\" : \"chevron.up\")\n                    }\n                    .contentShape(Rectangle())\n                }\n            )\n            .buttonStyle(PlainButtonStyle())\n            .frame(minHeight: 36)\n\n            VStack(alignment: .leading) {\n                Text(value.prettyString)\n                    .font(.subheadline)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n\n            }\n            .padding(8)\n            .frame(minWidth: 0, maxWidth: .infinity, minHeight: 0, maxHeight: collapsed ? 0 : .none)\n            .clipped()\n            .animation(.easeOut, value: self.collapsed)\n            .transition(.slide)\n        }\n    }\n}\n\n#Preview {\n    AirshipJSONView(json: [\"preview\": true])\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/AirshipToast.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipToast: View {\n    struct Message: Equatable {\n        let id: String\n        let text: String\n        let duration: TimeInterval\n        \n        init(id: String = UUID().uuidString, text: String, duration: TimeInterval = 1) {\n            self.id = id\n            self.text = text\n            self.duration = duration\n        }\n    }\n\n    @Binding\n    var message: Message?\n\n    @State\n    private var toastTask: Task<(), Never>? = nil\n\n    @State\n    private var toastVisible: Bool = false\n\n    @ViewBuilder\n    private func makeView() -> some View {\n        Text(message?.text ?? \"\")\n            .padding()\n            .background(\n                .ultraThinMaterial,\n                in: RoundedRectangle(cornerRadius: 16, style: .continuous)\n            )\n    }\n\n    @ViewBuilder\n    var body: some View {\n        makeView()\n            .airshipOnChangeOf( self.message) { incoming in\n                if incoming != nil {\n                    showToast()\n                }\n            }\n            .hideOpt(self.toastVisible == false || self.message == nil)\n    }\n\n    private func showToast() {\n        self.toastTask?.cancel()\n\n        guard let message = message else {\n            return\n        }\n\n        let waitTask = Task {\n            try? await Task.sleep(\n                nanoseconds: UInt64(message.duration * 1_000_000_000)\n            )\n            return\n        }\n\n        Task {\n            let _ = await waitTask.result\n            await MainActor.run {\n                if !waitTask.isCancelled {\n                    self.toastVisible = false\n                    self.message = nil\n                }\n            }\n        }\n\n        self.toastTask = waitTask\n        self.toastVisible = true\n    }\n}\n\nextension View {\n    @ViewBuilder\n    func hideOpt(_ shouldHide: Bool) -> some View {\n        if shouldHide {\n            self.hidden()\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Common/Extensions.swift",
    "content": "// Copyright Airship and Contributors\n\nimport AirshipCore\nimport SwiftUI\n\nprivate struct ToastModifier: ViewModifier {\n    @Binding\n    var message: AirshipToast.Message?\n    \n    func body(content: Content) -> some View {\n        content.overlay(AirshipToast(message: $message), alignment: .bottom)\n    }\n}\n\nextension View {\n    func toastable(_ message: Binding<AirshipToast.Message?>) -> some View {\n        modifier(ToastModifier(message: message))\n    }\n}\n\nextension String {\n    func pastleboard() {\n#if os(macOS)\n        let pasteboard = NSPasteboard.general\n        pasteboard.declareTypes([.string], owner: nil)\n        pasteboard.setString(self, forType: .string)\n#elseif !os(tvOS)\n        UIPasteboard.general.string = self\n#endif\n    }\n}\n\n\nstruct CommonItems {\n    \n    static let rowHeight = 34.0\n    \n    @ViewBuilder\n    @MainActor\n    static func navigationLink(\n        title: String,\n        trailingView: (() -> some View) = { EmptyView() },\n        showDivider: Bool = false,\n        route: AirshipDebugRoute\n    ) -> some View {\n        NavigationLink(value: route) {\n            VStack {\n                HStack {\n                    Text(title)\n                        .foregroundColor(.primary)\n\n                    Spacer()\n\n                    trailingView()\n                }\n                .frame(height: Self.rowHeight)\n\n                if showDivider {\n                    Divider()\n                }\n            }\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    static func infoRow(\n        title: String,\n        value: String?,\n        onTap: (() -> Void)? = nil,\n        showDivider: Bool = false\n    ) -> some View {\n        \n        VStack(alignment: .leading) {\n            HStack {\n                Text(title)\n                    .foregroundColor(.primary)\n                \n                Spacer()\n                \n                if let value {\n                    Text(value)\n                        .foregroundColor(.secondary)\n                }\n            }\n            \n            if showDivider {\n                Divider()\n            }\n        }\n        .frame(height: Self.rowHeight)\n        .onTapGesture { onTap?() }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Contact/AirshipDebugAddEmailChannelView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugAddEmailChannelView: View {\n\n    enum RegistrationType: String, Equatable, CaseIterable {\n        case transactional = \"Transactional\"\n        case commercial = \"Commercial\"\n    }\n\n    public init() {}\n    \n    @StateObject\n    private var viewModel = ViewModel()\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    @State\n    private var shouldPresentPropetySheet: Bool = false\n    \n    var body: some View {\n        Form {\n            Section(header: Text(\"Channel Info\".localized())) {\n                HStack {\n                    Text(\"Email\")\n                    Spacer()\n                    TextField(\n                        \"Email\",\n                        text: self.$viewModel.emailAddress.preventWhiteSpace()\n                    )\n                    .freeInput()\n                }\n\n                Picker(\n                    \"Registration Type\".localized(),\n                    selection: self.$viewModel.registrationType\n                ) {\n                    ForEach(RegistrationType.allCases, id: \\.self) { value in\n                        Text(value.rawValue.localized())\n                    }\n                }\n                .pickerStyle(.segmented)\n            }\n\n            if self.viewModel.registrationType == .commercial {\n                Section(header: Text(\"Commercial Options\".localized())) {\n                    Toggle(\"Double Opt-In\", isOn: self.$viewModel.doubleOptIn)\n                }\n            }\n\n            Section(header: Text(\"Properties\".localized())) {\n\n                Button(\"Add Property\".localized()) {\n                    self.shouldPresentPropetySheet = true\n                }\n                .sheet(isPresented: self.$shouldPresentPropetySheet) {\n                    NavigationStack {\n                        AirshipDebugAddPropertyView {\n                            self.viewModel.properties[$0] = $1\n                        }\n                        .navigationTitle(\"New Property\")\n#if !os(tvOS) && !os(macOS)\n                        .navigationBarTitleDisplayMode(.inline)\n#endif\n                    }\n                    .presentationDetents([.medium])\n                }\n\n                List {\n                    let keys = [String](self.viewModel.properties.keys)\n                    ForEach(keys, id: \\.self) { key in\n                        HStack {\n                            Text(\"\\(key):\")\n                            Text(\n                                self.viewModel.properties[key]?.prettyString\n                                    ?? \"\"\n                            )\n                        }\n                    }\n                    .onDelete {\n                        $0.forEach { index in\n                            self.viewModel.properties[keys[index]] = nil\n                        }\n                    }\n                }\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    self.viewModel.createChannel()\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Text(\"Create\".localized())\n                }\n                .disabled(self.viewModel.emailAddress.isEmpty)\n            }\n        }\n        .navigationTitle(\"Email Channel\".localized())\n    }\n\n    @MainActor\n    fileprivate class ViewModel: ObservableObject {\n        @Published var emailAddress: String = \"\"\n        @Published var commercial: Bool = false\n        @Published var registrationType: RegistrationType = .transactional\n        @Published var doubleOptIn: Bool = false\n        @Published var properties: [String: AirshipJSON] = [:]\n\n        func createChannel() {\n            guard\n                Airship.isFlying,\n                !self.emailAddress.isEmpty\n            else {\n                return\n            }\n\n            var options: EmailRegistrationOptions!\n            let date = Date()\n\n            switch self.registrationType {\n            case .commercial:\n                if doubleOptIn {\n                    options = EmailRegistrationOptions.options(\n                        transactionalOptedIn: date,\n                        properties: properties,\n                        doubleOptIn: true\n                    )\n                } else {\n                    options = EmailRegistrationOptions.commercialOptions(\n                        transactionalOptedIn: date,\n                        commercialOptedIn: date,\n                        properties: properties,\n                    )\n                }\n            case .transactional:\n                options = EmailRegistrationOptions.options(\n                    transactionalOptedIn: date,\n                    properties: properties,\n                    doubleOptIn: false\n                )\n            }\n\n            Airship.contact.registerEmail(\n                self.emailAddress,\n                options: options\n            )\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugAddEmailChannelView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Contact/AirshipDebugAddOpenChannelView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\nimport Combine\n\nstruct AirshipDebugAddOpenChannelView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    @State\n    private var shouldPresentPropetySheet: Bool = false\n\n    var body: some View {\n        Form {\n\n            Section(header: Text(\"Channel Info\".localized())) {\n                HStack {\n                    Text(\"Platform\")\n                    Spacer()\n                    TextField(\n                        \"Platform\",\n                        text: self.$viewModel.platformName.preventWhiteSpace()\n                    )\n                    .freeInput()\n                }\n\n                HStack {\n                    Text(\"Address\")\n                    Spacer()\n                    TextField(\n                        \"Address\",\n                        text: self.$viewModel.address.preventWhiteSpace()\n                    )\n                    .freeInput()\n                }\n            }\n\n            Section(header: Text(\"Identifiers\".localized())) {\n                Button(\"Add Identifier\".localized()) {\n                    self.shouldPresentPropetySheet = true\n                }\n                .sheet(isPresented: self.$shouldPresentPropetySheet) {\n                    NavigationStack {\n                        AirshipDebugAddStringPropertyView {\n                            self.viewModel.identifiers[$0] = $1\n                        }\n                        .navigationTitle(\"New Identifier\")\n#if !os(tvOS) && !os(macOS)\n                        .navigationBarTitleDisplayMode(.inline)\n#endif\n                    }\n                    .presentationDetents([.medium])\n                }\n\n                List {\n                    let keys = [String](self.viewModel.identifiers.keys)\n                    ForEach(keys, id: \\.self) { key in\n                        HStack {\n                            Text(\"\\(key):\")\n                            Text(self.viewModel.identifiers[key] ?? \"\")\n                        }\n                    }\n                    .onDelete {\n                        $0.forEach { index in\n                            self.viewModel.identifiers[keys[index]] = nil\n                        }\n                    }\n                }\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    self.viewModel.createChannel()\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Text(\"Create\".localized())\n                }\n                .disabled(\n                    self.viewModel.address.isEmpty\n                        || self.viewModel.platformName.isEmpty\n                )\n            }\n        }\n        .navigationTitle(\"Open Channel\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published var identifiers: [String: String] = [:]\n        @Published var address: String = \"\"\n        @Published var platformName: String = \"\"\n\n        func createChannel() {\n            guard\n                Airship.isFlying,\n                !self.address.isEmpty,\n                !self.platformName.isEmpty\n            else {\n                return\n            }\n\n            let options = OpenRegistrationOptions.optIn(\n                platformName: self.platformName,\n                identifiers: identifiers\n            )\n\n            Airship.contact.registerOpen(\n                self.address,\n                options: options\n            )\n\n        }\n    }\n}\n\nprivate struct AddIdentifierView: View {\n\n    @State\n    private var key: String = \"\"\n\n    @State\n    private var value: String = \"\"\n\n    let onAdd: (String, String) -> Void\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    var body: some View {\n        Form {\n            HStack {\n                Text(\"Key\".localized())\n                Spacer()\n                TextField(\n                    \"Key\".localized(),\n                    text: self.$key.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n            HStack {\n                Text(\"Value\".localized())\n                Spacer()\n                TextField(\n                    \"Value\".localized(),\n                    text: self.$value.preventWhiteSpace()\n                )\n                .freeInput()\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    onAdd(key, value)\n                    presentationMode.wrappedValue.dismiss()\n                } label: {\n                    Text(\"Add\".localized())\n                }\n                .disabled(key.isEmpty || value.isEmpty)\n            }\n        }\n        .navigationTitle(\"Identifier\".localized())\n\n    }\n}\n\n#Preview {\n    AirshipDebugAddOpenChannelView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Contact/AirshipDebugAddSMSChannelView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugAddSMSChannelView: View {\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    @State \n    private var phoneNumber: String = \"\"\n\n    @State\n    private var senderID: String = \"\"\n\n    var body: some View {\n        Form {\n            Section(header: Text(\"Channel Info\".localized())) {\n                HStack {\n                    Text(\"MSISDN\".localized())\n                    Spacer()\n                    TextField(\n                        \"MSISDN\".localized(),\n                        text: self.$phoneNumber\n                    )\n#if !os(macOS)\n                    .keyboardType(.phonePad)\n#endif\n                    .freeInput()\n                }\n\n                HStack {\n                    Text(\"Sender ID\")\n                    Spacer()\n                    TextField(\n                        \"Sender ID\",\n                        text: self.$senderID.preventWhiteSpace()\n                    )\n                    .freeInput()\n                }\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    createChannel()\n                } label: {\n                    Text(\"Create\".localized())\n                }\n                .disabled(senderID.isEmpty || phoneNumber.isEmpty)\n            }\n        }\n        .navigationTitle(\"SMS Channel\".localized())\n    }\n\n    func createChannel() {\n        guard\n            Airship.isFlying,\n            !senderID.isEmpty,\n            !phoneNumber.isEmpty\n        else {\n            return\n        }\n        let options = SMSRegistrationOptions.optIn(senderID: senderID)\n        Airship.contact.registerSMS(phoneNumber, options: options)\n    }\n}\n\n#Preview {\n    AirshipDebugAddSMSChannelView()\n}\n\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Contact/AirshipDebugContactSubscriptionEditorView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugContactSubscriptionEditorView: View {\n    private enum SubscriptionListAction: String, Equatable, CaseIterable {\n        case subscribe = \"Subscribe\"\n        case unsubscribe = \"Unsubscribe\"\n    }\n\n    private enum Scope: String, Equatable, CaseIterable {\n        case app = \"App\"\n        case web = \"Web\"\n        case email = \"Email\"\n        case sms = \"SMS\"\n\n        var channelScope: ChannelScope {\n            switch self {\n            case .app: return .app\n            case .web: return .web\n            case .email: return .email\n            case .sms: return .sms\n            }\n        }\n    }\n\n    @State\n    private var listID: String = \"\"\n\n    @State\n    private var action: SubscriptionListAction = .subscribe\n\n    @State\n    private var scope: Scope = .app\n\n    @ViewBuilder\n    public var body: some View {\n        Form {\n            Section(header: Text(\"Subscription Info\".localized())) {\n                HStack {\n                    Text(\"List ID\".localized())\n                    Spacer()\n                    TextField(\"\", text: self.$listID.preventWhiteSpace())\n                        .freeInput()\n                }\n\n                Picker(\"Scope\".localized(), selection: $scope) {\n                    ForEach(Scope.allCases, id: \\.self) { value in\n                        Text(value.rawValue.localized())\n                    }\n                }\n                .pickerStyle(.segmented)\n\n                Picker(\"Action\".localized(), selection: $action) {\n                    ForEach(SubscriptionListAction.allCases, id: \\.self) {\n                        value in\n                        Text(value.rawValue.localized())\n                    }\n                }\n                .pickerStyle(.segmented)\n            }\n        }\n        .toolbar {\n            ToolbarItem(placement: .confirmationAction) {\n                Button {\n                    apply()\n                } label: {\n                    Text(\"Apply\".localized())\n                }\n                .disabled(listID.isEmpty)\n            }\n        }\n        .navigationTitle(\"Subscription Lists\".localized())\n    }\n\n    private func apply() {\n        defer {\n            self.listID = \"\"\n        }\n        guard Airship.isFlying else { return }\n\n        Airship.contact.editSubscriptionLists { editor in\n            switch self.action {\n            case .subscribe:\n                editor.subscribe(self.listID, scope: self.scope.channelScope)\n            case .unsubscribe:\n                editor.unsubscribe(self.listID, scope: self.scope.channelScope)\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugContactSubscriptionEditorView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Contact/AirshipDebugContactView.swift",
    "content": "// Copyright Airship and Contributors\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugContactsView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            Section {\n                CommonItems.navigationLink(\n                    title: \"Named User\".localized(),\n                    trailingView: {\n                        HStack {\n                            if let namedUserID = viewModel.namedUserID {\n                                Text(namedUserID)\n                                    .foregroundColor(.secondary)\n                            }\n                        }\n                    },\n                    route: .contactSub(.namedUserID)\n                )\n\n                CommonItems.navigationLink(\n                    title: \"Tag Groups\".localized(),\n                    route: .contactSub(.tagGroups)\n                )\n\n                CommonItems.navigationLink(\n                    title: \"Attributes\".localized(),\n                    route: .contactSub(.attributes)\n                )\n\n                CommonItems.navigationLink(\n                    title: \"Subscription Lists\".localized(),\n                    route: .contactSub(.subscriptionLists)\n                )\n            }\n\n            Section(\"Channels\".localized()) {\n                CommonItems.navigationLink(\n                    title: \"Add Email Channel\".localized(),\n                    route: .contactSub(.addEmailChannel)\n                )\n                CommonItems.navigationLink(\n                    title: \"Add SMS Channel\".localized(),\n                    route: .contactSub(.addSMSChannel)\n                )\n                CommonItems.navigationLink(\n                    title: \"Add Open Channel\".localized(),\n                    route: .contactSub(.addOpenChannel)\n                )\n            }\n        }\n        .navigationTitle(\"Contact\".localized())\n    }\n\n\n    @MainActor\n    fileprivate final class ViewModel: ObservableObject {\n        @Published\n        var namedUserID: String?\n\n        private var task: Task<Void, Never>? = nil\n        private var subscription: AnyCancellable? = nil\n\n        @MainActor\n        init() {\n            self.task = Task { [weak self] in\n                await Airship.waitForReady()\n                self?.update(\n                    namedUserID: await Airship.contact.namedUserID\n                )\n\n                for await namedUserID in Airship.contact.namedUserIDPublisher.values {\n                    self?.update(namedUserID: namedUserID)\n                }\n            }\n        }\n\n        deinit {\n            task?.cancel()\n        }\n\n        private func update(namedUserID: String?) {\n            if self.namedUserID != namedUserID {\n                self.namedUserID = namedUserID\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugContactsView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Contact/AirshipDebugNamedUserView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugNamedUserView: View {\n\n    @StateObject\n    private var viewModel: ViewModel = ViewModel()\n\n    private func updateNamedUser() {\n        let normalized = self.viewModel.namedUserID.trimmingCharacters(\n            in: .whitespacesAndNewlines\n        )\n\n        if !normalized.isEmpty {\n            Airship.contact.identify(normalized)\n        } else {\n            Airship.contact.reset()\n        }\n    }\n\n    var body: some View {\n        let title = \"Named User\".localized()\n\n        Form {\n            Section(\n                header: Text(\"\"),\n                footer: Text(\n                    \"An empty value does not indicate the device does not have a named user. The SDK only knows about the Named User ID if set through the SDK.\"\n                        .localized()\n                )\n            ) {\n                TextField(title, text: self.$viewModel.namedUserID)\n                    .onSubmit {\n                        updateNamedUser()\n                    }\n                    .freeInput()\n            }\n        }\n        .navigationTitle(title)\n    }\n\n\n    @MainActor\n    private class ViewModel: ObservableObject {\n        @Published\n        public var namedUserID: String = \"\"\n\n        init() {\n            Task { @MainActor in\n                if !Airship.isFlying { return }\n                self.namedUserID = await Airship.contact.namedUserID ?? \"\"\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugNamedUserView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/FeatureFlags/AirshipDebugFeatureFlagDetailsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\nimport AirshipFeatureFlags\n\nstruct AirshipDebugFeatureFlagDetailsView: View {\n\n    @StateObject\n    private var viewModel: ViewModel\n\n    @State\n    private var toastMessage: AirshipToast.Message? = nil\n\n    init(name: String) {\n        _viewModel = .init(wrappedValue: .init(name: name))\n    }\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            Section(header: Text(\"Details\".localized())) {\n                makeInfoItem(\"Name\".localized(), viewModel.name)\n            }\n\n            Section(\n                content: {\n                    if let result = self.viewModel.result {\n                        makeInfoItem(\"Elegible\".localized(), result.isEligible ? \"true\" : \"false\")\n                        makeInfoItem(\"Exists\".localized(), result.exists ? \"true\" : \"false\")\n\n                        if let variables = result.variables {\n                            Section(header: Text(\"Variables: \".localized())) {\n                                AirshipJSONView(json: variables)\n                                    .padding(.leading, 8)\n                            }\n                        }\n\n                        Button(\"Track Interaction\") {\n                            Airship.featureFlagManager.trackInteraction(flag: result)\n                        }.frame(maxWidth: .infinity, alignment: .center)\n                    } else {\n                        VStack {\n                            ProgressView()\n                                .padding()\n                            Text(\"Resolving flag\")\n                        }.frame(maxWidth: .infinity)\n                    }\n                },\n                header: {\n                    HStack {\n                        Text(\"Result\".localized())\n                        Spacer()\n\n                        if let result = self.viewModel.result {\n                            makeShareLink(AirshipJSON.wrapSafe(result).prettyString)\n                        }\n\n                        if self.viewModel.result != nil || self.viewModel.error != nil {\n                            Button {\n                                self.viewModel.evaluateFlag()\n                            } label: {\n                                Image(systemName: \"arrow.clockwise\")\n                            }\n                        }\n                    }\n                }\n            )\n        }\n        .navigationTitle(viewModel.name)\n        .onAppear {\n            self.viewModel.evaluateFlag()\n        }\n        .overlay(AirshipToast(message: self.$toastMessage))\n    }\n\n    @ViewBuilder\n    func makeShareLink(_ string: String) -> some View {\n#if !os(tvOS)\n        ShareLink(item: string) {\n            Image(systemName: \"square.and.arrow.up\")\n        }\n#else\n        Button {\n            copyToClipboard(value: string)\n        } label: {\n            Image(systemName: \"doc.on.clipboard.fill\")\n        }\n#endif\n    }\n\n    @ViewBuilder\n    func makeInfoItem(_ title: String, _ value: String?) -> some View {\n        HStack {\n            Text(title)\n                .foregroundColor(.primary)\n            Spacer()\n            Text(value ?? \"\")\n                .foregroundColor(.secondary)\n        }\n    }\n\n    func copyToClipboard(value: String?) {\n        guard let value = value else {\n            return\n        }\n        value.pastleboard()\n        self.toastMessage = AirshipToast.Message(\n            id: UUID().uuidString,\n            text: \"Copied to pasteboard!\".localized(),\n            duration: 1.0\n        )\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n\n        @Published private(set) var result: FeatureFlag?\n        @Published private(set) var error: String?\n\n        let name: String\n\n        init(name: String) {\n            self.name = name\n        }\n\n        func evaluateFlag() {\n            self.result = nil\n            self.error = nil\n            Task { @MainActor in\n                do {\n                    self.result = try await Airship.featureFlagManager.flag(name: name)\n                } catch {\n                    self.error = error.localizedDescription\n                }\n            }\n        }\n    }\n}\n\n\n#Preview {\n    AirshipDebugFeatureFlagDetailsView(name: \"some flag name\")\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/FeatureFlags/AirshipDebugFeatureFlagView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\nimport AirshipFeatureFlags\n\nstruct AirshipDebugFeatureFlagView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    var body: some View {\n        Form {\n            Section {\n                ForEach(self.viewModel.entries, id: \\.self) { name in\n                    CommonItems.navigationLink(\n                        title: name,\n                        route: .featureFlagsSub(.featureFlagDetails(name: name))\n                    )\n                }\n            }\n        }\n        .navigationTitle(\"Feature Flags\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var entries: [String] = []\n        private var cancellable: AnyCancellable? = nil\n\n        init() {\n            if Airship.isFlying {\n                self.cancellable = Airship.internalDebugManager\n                    .featureFlagPublisher\n                    .receive(on: RunLoop.main)\n                    .map { result in\n                        return result.compactMap { element in\n                            let flag = element[\"flag\"] as? [String : AnyHashable]\n                            return flag?[\"name\"] as? String\n                        }\n                    }\n                    .sink { incoming in\n                        self.entries = Array(Set(incoming)).sorted()\n                    }\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugFeatureFlagView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/PreferenceCenter/AirshipDebugPreferencCenterItemView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\nimport AirshipCore\nimport AirshipPreferenceCenter\n\nstruct AirshipDebugPreferencCenterItemView: View {\n\n    private let preferenceCenterID: String\n\n    @State private var title: String? = nil\n\n    init(preferenceCenterID: String) {\n        self.preferenceCenterID = preferenceCenterID\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        PreferenceCenterContent(\n            preferenceCenterID: preferenceCenterID,\n            onPhaseChange: { phase in\n                guard case .loaded(let state) = phase else { return }\n\n                let title = state.config.display?.title\n                if let title, title.isEmpty == false {\n                    self.title = title\n                }\n            }\n        )\n        .frame(maxWidth: .infinity, maxHeight: .infinity)\n        .navigationTitle(self.title ?? preferenceCenterID)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/PreferenceCenter/AirshipDebugPreferenceCenterView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\nimport AirshipPreferenceCenter\n\nstruct AirshipDebugPreferenceCentersView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    var body: some View {\n        Form {\n            ForEach(self.viewModel.identifiers, id: \\.self) { identifier in\n                CommonItems.navigationLink(\n                    title: identifier,\n                    route: .preferenceCentersSub(.preferenceCenter(identifier: identifier))\n                )\n            }\n        }\n        .navigationTitle(\"Preference Centers\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var identifiers: [String] = []\n        private var cancellable: AnyCancellable? = nil\n\n        init() {\n            if Airship.isFlying {\n                self.cancellable = Airship.internalDebugManager\n                    .preferenceFormsPublisher\n                    .receive(on: RunLoop.main)\n                    .sink { incoming in\n                        self.identifiers = incoming.sorted()\n                    }\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugPreferenceCentersView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/PrivacyManager/AirshipDebugPrivacyManagerView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugPrivacyManagerView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @ViewBuilder\n    private func makeFeatureToggle(\n        _ title: String,\n        _ isOn: Binding<Bool>\n    ) -> some View {\n        Toggle(title.localized(), isOn: isOn).frame(height: CommonItems.rowHeight)\n    }\n\n    var body: some View {\n        Form {\n            makeFeatureToggle(\"Contacts\", self.$viewModel.contactsEnabled)\n            \n            makeFeatureToggle(\n                \"Tags & Attributes\",\n                self.$viewModel.tagsAndAttributesEnabled\n            )\n            \n            makeFeatureToggle(\"Analytics\", self.$viewModel.analyticsEnabled)\n            \n            makeFeatureToggle(\"Push\", self.$viewModel.pushEnabled)\n            \n            makeFeatureToggle(\n                \"In App Automation\",\n                self.$viewModel.iaaEnabled\n            )\n            \n            makeFeatureToggle(\n                \"Message Center\",\n                self.$viewModel.messageCenterEnabled\n            )\n            \n            makeFeatureToggle(\n                \"Feature Flags\",\n                self.$viewModel.featureFlagEnabled\n            )\n        }\n        .navigationTitle(\"Privacy Manager\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n\n        @Published\n        public var iaaEnabled: Bool {\n            didSet {\n                update(.inAppAutomation, enable: self.iaaEnabled)\n            }\n        }\n\n        @Published\n        public var messageCenterEnabled: Bool {\n            didSet {\n                update(.messageCenter, enable: self.messageCenterEnabled)\n            }\n        }\n        \n        @Published\n        public var featureFlagEnabled: Bool {\n            didSet {\n                update(.featureFlags, enable: self.featureFlagEnabled)\n            }\n        }\n\n        @Published\n        public var pushEnabled: Bool {\n            didSet {\n                update(.push, enable: self.pushEnabled)\n            }\n        }\n\n        @Published\n        public var analyticsEnabled: Bool {\n            didSet {\n                update(.analytics, enable: self.analyticsEnabled)\n            }\n        }\n\n        @Published\n        public var tagsAndAttributesEnabled: Bool {\n            didSet {\n                update(\n                    .tagsAndAttributes,\n                    enable: self.tagsAndAttributesEnabled\n                )\n            }\n        }\n\n        @Published\n        public var contactsEnabled: Bool {\n            didSet {\n                update(.contacts, enable: self.contactsEnabled)\n            }\n        }\n\n        init() {\n            if Airship.isFlying {\n                let privacyManager = Airship.privacyManager\n                self.iaaEnabled = privacyManager.isEnabled(.inAppAutomation)\n                self.messageCenterEnabled = privacyManager.isEnabled(\n                    .messageCenter\n                )\n                self.pushEnabled = privacyManager.isEnabled(.push)\n                self.analyticsEnabled = privacyManager.isEnabled(.analytics)\n                self.contactsEnabled = privacyManager.isEnabled(.contacts)\n                self.tagsAndAttributesEnabled = privacyManager.isEnabled(\n                    .tagsAndAttributes)\n                self.featureFlagEnabled = privacyManager.isEnabled(.featureFlags)\n            } else {\n                self.iaaEnabled = false\n                self.messageCenterEnabled = false\n                self.pushEnabled = false\n                self.analyticsEnabled = false\n                self.contactsEnabled = false\n                self.tagsAndAttributesEnabled = false\n                self.featureFlagEnabled = false\n            }\n        }\n\n        private func update(_ features: AirshipFeature, enable: Bool) {\n            guard Airship.isFlying else { return }\n\n            if enable {\n                Airship.privacyManager.enableFeatures(features)\n            } else {\n                Airship.privacyManager.disableFeatures(features)\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugPrivacyManagerView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Push/AirshipDebugPushDetailsView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\n\nstruct AirshipDebugPushDetailsView: View {\n\n    @StateObject\n    private var viewModel: ViewModel\n\n    init(identifier: String) {\n        _viewModel = .init(wrappedValue: .init(identifier: identifier))\n    }\n\n    var body: some View {\n        if let push = viewModel.pushNotification {\n            AirshipJSONDetailsView(payload: push.payload, title: push.alert ?? \"Silent Push\".localized())\n        } else {\n            ProgressView()\n        }\n    }\n\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var pushNotification: PushNotification?\n\n        init(identifier: String) {\n            Task { @MainActor [weak self] in\n                self?.pushNotification = await Airship.internalDebugManager.pushNotifications().first(where: {\n                    $0.pushID == identifier\n                })\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Push/AirshipDebugPushView.swift",
    "content": "// Copyright Airship and Contributors\n\nimport SwiftUI\nimport Combine\nimport AirshipCore\nimport AirshipAutomation\n\nstruct AirshipDebugPushView: View {\n\n    @State\n    private var toast: AirshipToast.Message? = nil\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n    @ViewBuilder\n    var body: some View {\n        Form {\n            Section {\n                Toggle(\n                    \"User Notifications\".localized(),\n                    isOn: $viewModel.isPushNotificationsOptedIn\n                )\n                .frame(height: 34)\n\n                Toggle(\n                    \"Background Push Enabled\".localized(),\n                    isOn: $viewModel.backgroundPushNotificationsEnabled\n                )\n                .frame(height: 34)\n\n                CommonItems.navigationLink(\n                    title: \"Received Pushes\",\n                    route: .pushSub(.recievedPushes)\n                )\n            }\n\n            Section(\"Notification Opt-In Status\") {\n                if let status = self.viewModel.notificationStatus {\n                    CommonItems.infoRow(\n                        title: \"Channel Opted-In\".localized(),\n                        value: status.isOptedIn.description\n                    )\n\n                    CommonItems.infoRow(\n                        title: \"Airship Enabled\".localized(),\n                        value: status.isUserNotificationsEnabled.description\n                    )\n\n                    CommonItems.infoRow(\n                        title: \"Push PrivacyManager Enabled\".localized(),\n                        value: status.isPushPrivacyFeatureEnabled.description\n                    )\n\n                    CommonItems.infoRow(\n                        title: \"Permission Status\".localized(),\n                        value: status.displayNotificationStatus.rawValue\n                    )\n\n                    CommonItems.infoRow(\n                        title: \"Push Token\".localized(),\n                        value: self.viewModel.deviceToken ?? \"Not Available\",\n                        onTap: {\n                            if let token = self.viewModel.deviceToken {\n                                copyToClipboard(token)\n                            }\n                        }\n                    )\n                } else {\n                    ProgressView()\n                }\n            }\n        }\n        .toastable($toast)\n        .navigationTitle(\"Push\".localized())\n    }\n    \n    private func copyToClipboard(_ value: String?) {\n        guard let value else { return }\n        value.pastleboard()\n        \n        self.toast = .init(text: \"Copied to clipboard\".localized())\n    }\n\n    @MainActor\n    fileprivate final class ViewModel: ObservableObject {\n        @Published\n        private(set) var deviceToken: String? = nil\n\n        @Published\n        private(set) var notificationStatus: AirshipNotificationStatus? = nil\n\n        @Published\n        public var isPushNotificationsOptedIn: Bool = false {\n            didSet {\n                guard\n                    Airship.isFlying,\n                    Airship.push.isPushNotificationsOptedIn != self.isPushNotificationsOptedIn\n                else {\n                    return\n                }\n\n                self.enableNotificationsTask?.cancel()\n\n                if self.isPushNotificationsOptedIn {\n                    Airship.privacyManager.enableFeatures(.push)\n                    self.enableNotificationsTask = Task { [weak self] in\n                        await Airship.push.enableUserPushNotifications(fallback: .systemSettings)\n                        guard !Task.isCancelled else { return }\n                        self?.isPushNotificationsOptedIn = Airship.push.isPushNotificationsOptedIn\n                    }\n                } else {\n                    Airship.push.userPushNotificationsEnabled = false\n                }\n            }\n        }\n\n        @Published\n        public var backgroundPushNotificationsEnabled: Bool = false {\n            didSet {\n                guard\n                    Airship.isFlying,\n                    Airship.push.backgroundPushNotificationsEnabled != self.backgroundPushNotificationsEnabled\n                else {\n                    return\n                }\n\n                Airship.push.backgroundPushNotificationsEnabled = self.backgroundPushNotificationsEnabled\n            }\n        }\n\n        private var task: Task<Void, Never>? = nil\n        private var enableNotificationsTask: Task<Void, Never>? = nil\n\n        deinit {\n            self.task?.cancel()\n            self.enableNotificationsTask?.cancel()\n        }\n\n        @MainActor\n        init() {\n            if Airship.isFlying {\n                self.deviceToken = Airship.push.deviceToken\n                self.isPushNotificationsOptedIn = Airship.push.isPushNotificationsOptedIn\n                self.backgroundPushNotificationsEnabled = Airship.push.backgroundPushNotificationsEnabled\n            }\n\n            self.task = Task { @MainActor [weak self] in\n                await Airship.waitForReady()\n                self?.deviceToken = Airship.push.deviceToken\n                self?.isPushNotificationsOptedIn = Airship.push.isPushNotificationsOptedIn\n                self?.backgroundPushNotificationsEnabled = Airship.push.backgroundPushNotificationsEnabled\n\n                self?.notificationStatus = await Airship.push.notificationStatus\n\n                for await update in await Airship.push.notificationStatusUpdates {\n                    self?.notificationStatus = update\n                    self?.deviceToken = Airship.push.deviceToken\n                    self?.isPushNotificationsOptedIn = Airship.push.isPushNotificationsOptedIn\n                }\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugPushView()\n}\n\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/Push/AirshipDebugReceivedPushView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipCore\n\nstruct AirshipDebugReceivedPushView: View {\n\n    @StateObject\n    private var viewModel = ViewModel()\n\n\n    var body: some View {\n        Form {\n            ForEach(self.viewModel.pushNotifications, id: \\.self) { push in\n                CommonItems.navigationLink(\n                    title: push.alert ?? \"Silent Push\".localized(),\n                    route: .pushSub(.pushDetails(identifier: push.pushID))\n                )\n            }\n        }\n        .navigationTitle(\"Received Notifications\".localized())\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published private(set) var pushNotifications: [PushNotification] = []\n        private var cancellable: AnyCancellable? = nil\n\n        init() {\n            if Airship.isFlying {\n                self.refreshPush()\n                self.cancellable = Airship.internalDebugManager\n                    .pushNotificationReceivedPublisher\n                    .sink { [weak self] _ in\n                        self?.refreshPush()\n                    }\n            }\n        }\n\n        private func refreshPush() {\n            Task { @MainActor in\n                self.pushNotifications = await Airship.internalDebugManager.pushNotifications()\n            }\n        }\n    }\n}\n\n#Preview {\n    AirshipDebugReceivedPushView()\n}\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/TvOSComponents/TVDatePicker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\n/// Represents the available components of the tv date picker view.\nstruct TvDatePickerComponents: OptionSet {\n    /// Displays day, month, and year based on the locale\n    static var date: TvDatePickerComponents { TvDatePickerComponents(rawValue: 1 << 3) }\n    /// Displays hour and minute components based on the locale\n    static var hourAndMinute: TvDatePickerComponents { TvDatePickerComponents(rawValue: 1) }\n    /// Displays all components based on the locale\n    static var all: TvDatePickerComponents { [.date, .hourAndMinute] }\n    \n    var rawValue: Int8\n    \n    init(rawValue: Int8) {\n        self.rawValue = rawValue\n    }\n}\n\n#if os(tvOS)\n/// A `SwiftUI` tvOS date picker view\nstruct TVDatePicker: View {\n    \n    var titleKey: String\n    var displayedComponents: TvDatePickerComponents\n    \n    private let pickerStyle = SegmentedPickerStyle()\n    private var minimumDate = Date.distantPast\n    private var calendar: Calendar = .current\n    \n    @State\n    private var isSheetPresented = false\n    \n    @Binding\n    var selection: Date\n    \n    @State\n    private var selectedYear: Int = 0\n    \n    @State\n    private var selectedMonth: Int = 0\n    \n    @State\n    private var selectedDay: Int = 0\n    \n    @State\n    private var selectedHour: Int = 0\n    \n    @State\n    private var selectedMinute: Int = 0\n    \n    private var currentSelectedYear: Int {\n        calendar.component(.year, from: selection)\n    }\n    \n    private var minimumYear: Int {\n        calendar.component(.year, from: minimumDate)\n    }\n    \n    private var months: Range<Int> {\n        calendar.range(of: .month, in: .year, for: minimumDate) ?? Range(0...0)\n    }\n    \n    private var daysInSelectedMonth: Range<Int> {\n        calendar.range(of: .day, in: .month, for: selection) ?? Range(0...0)\n    }\n    \n    private var hours: Range<Int> {\n        calendar.range(of: .hour, in: .day, for: minimumDate) ?? Range(0...0)\n    }\n    \n    private var minutes: Array<Int> { Array(stride(from: 0, to: 60, by: 5)) }\n    \n    /// Initializes the date picker view with the given values.\n    ///\n    /// - Parameters:\n    ///   - titleKey: The key for the localized title of self, describing its purpose.\n    ///   - selection: The date value being displayed and selected.\n    ///   - displayedComponents: The date components that user is able to view and edit. Defaults to [.hourAndMinute, .date].\n    init<S>(\n        _ titleKey: S,\n        selection: Binding<Date>,\n        displayedComponents: TvDatePickerComponents = [.hourAndMinute, .date]\n    ) where S : StringProtocol {\n        self.titleKey = titleKey as! String\n        self.displayedComponents = displayedComponents\n        _selection = selection\n    }\n    \n    var body: some View {\n        Button {\n            isSheetPresented = true\n        } label: {\n            HStack {\n                Spacer()\n                Text(AirshipDateFormatter.string(\n                    fromDate: selection,\n                    format: .relativeShort\n                ))\n                Image(systemName: \"chevron.right\")\n            }\n        }\n        .background(\n            EmptyView()\n                .sheet(\n                    isPresented: $isSheetPresented,\n                    onDismiss: {\n                        isSheetPresented = false\n                    }) {\n                    NavigationStack {\n                        VStack(\n                            alignment: .leading,\n                            content: content\n                        )\n                        .onAppear(perform: {\n                            updateSelectedDateComponents()\n                        })\n                        .navigationTitle(.init(titleKey))\n                        .navigationBarItems(\n                            trailing: dismissButton()\n                        )\n                    }\n                }\n        )\n        .airshipOnChangeOf(selection, initial: true, { _ in\n            updateSelectedDateComponents()\n        })\n    }\n}\n\nprivate extension TVDatePicker {\n    \n    @ViewBuilder\n    func content() -> some View {\n        HStack {\n            Spacer()\n            Text(AirshipDateFormatter.string(\n                fromDate: selection,\n                format: .relativeShort\n            ))\n            .font(.largeTitle)\n            Spacer()\n        }\n        \n        Divider()\n        \n        Text(\"MM/DD/YYYY\")\n            .foregroundColor(.secondary)\n            .font(.subheadline)\n        \n        if displayedComponents.contains(.date) {\n            Picker(\n                selection: $selectedYear,\n                label: Text(\"Year\")\n            ) {\n                let lowerBound = max(minimumYear, (currentSelectedYear - 5))\n                let upperBound = max(minimumYear + 10, (currentSelectedYear + 5))\n                ForEach(lowerBound...upperBound, id: \\.self) { year in\n                    Text(String(year))\n                        .tag(year)\n                }\n            }\n            .pickerStyle(pickerStyle)\n            .airshipOnChangeOf(selectedYear) { value in\n                updateComponent(.year, value: value)\n            }\n        }\n        \n        if displayedComponents.contains(.date) {\n            Picker(\n                selection: $selectedMonth,\n                label: Text(\"Month\")\n            ) {\n                ForEach(months, id: \\.self) { month in\n                    Text(DateFormatter().shortMonthSymbols[month - 1])\n                        .tag(month)\n                }\n            }\n            .pickerStyle(pickerStyle)\n            .airshipOnChangeOf(selectedMonth) { value in\n                updateComponent(.month, value: value)\n            }\n        }\n        \n        if displayedComponents.contains(.date) {\n            Picker(\n                selection: $selectedDay,\n                label: Text(\"Day\")\n            ) {\n                ForEach(daysInSelectedMonth, id: \\.self) { day in\n                    Text(\"\\(day)\")\n                        .tag(day)\n                }\n            }\n            .pickerStyle(pickerStyle)\n            .airshipOnChangeOf(selectedDay) { value in\n                updateComponent(.day, value: value)\n            }\n        }\n        \n        if displayedComponents.contains(.hourAndMinute) {\n            Text(\"HH:mm\")\n                .foregroundColor(.secondary)\n                .font(.subheadline)\n            \n            Picker(\n                selection: $selectedHour,\n                label: Text(\"Hour\")\n            ) {\n                ForEach(hours, id: \\.self) { hour in\n                    Text(\"\\(hour)\")\n                        .tag(hour)\n                }\n            }\n            .pickerStyle(pickerStyle)\n            .airshipOnChangeOf(selectedHour) { value in\n                updateComponent(.hour, value: value)\n            }\n            \n            Picker(\n                selection: $selectedMinute,\n                label: Text(\"Minute\")\n            ) {\n                ForEach(Array(minutes), id: \\.self) { minute in\n                    Text(\"\\(minute)\")\n                        .tag(minute)\n                }\n            }\n            .pickerStyle(pickerStyle)\n            .airshipOnChangeOf(selectedMinute) { value in\n                updateComponent(.minute, value: value)\n            }\n        }\n        \n    }\n    \n    func dismissButton() -> some View {\n        Button(action: {\n            isSheetPresented = false\n        }, label: {\n            Image(systemName: \"xmark\")\n        })\n    }\n    \n    func updateComponent(_ component: Calendar.Component, value: Int) {\n        let dateComponents = DateComponents(\n            calendar: calendar,\n            year: (component == .year) ? value : selectedYear,\n            month: (component == .month) ? value : selectedMonth,\n            day: (component == .day) ? value : selectedDay,\n            hour: (component == .hour) ? value : selectedHour,\n            minute: (component == .minute) ? value : selectedMinute\n        )\n        \n        if let selectedDate = calendar.date(from: dateComponents) {\n            self.selection = selectedDate\n            updateSelectedDateComponents()\n        }\n    }\n    \n    func updateSelectedDateComponents() {\n        selectedYear = calendar.component(.year, from: selection)\n        selectedMonth = calendar.component(.month, from: selection)\n        selectedDay = calendar.component(.day, from: selection)\n        selectedHour = calendar.component(.hour, from: selection)\n        \n        let currentMinute = calendar.component(.minute, from: selection)\n        selectedMinute = currentMinute - (currentMinute % 5)\n    }\n}\n\n@available(iOS 17.0, *)\n#Preview {\n    \n    @Previewable @State var date = Date.now\n    \n    TVDatePicker(\n        \"Date\".localized(),\n        selection: $date,\n        displayedComponents: .all\n    )\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipDebug/Source/View/TvOSComponents/TVSlider.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nstruct TVSlider: View {\n    @Binding var displayInterval: Double\n    var range: ClosedRange<Double>\n    var step: Double = 1.0\n    static private let height = 50.0\n    static private let width = 500.0\n    \n    var body: some View {\n        HStack {\n            Button(\"-\") {\n                guard self.$displayInterval.wrappedValue - step >= range.upperBound else { return }\n                self.$displayInterval.wrappedValue -= step\n            }\n            \n            ZStack(alignment: .leading) {\n                Rectangle()\n                    .fill(Color.accentColor.opacity(0.5))\n                    .frame(width: TVSlider.width, height: TVSlider.height)\n                Rectangle()\n                    .fill(Color.accentColor)\n                    .frame(width: TVSlider.width, height: TVSlider.height)\n                \n                HStack {\n                    Spacer()\n                    Text(\"\\(Int($displayInterval.wrappedValue)) / \\(Int(range.upperBound))\")\n                        .padding()\n                }\n                .foregroundStyle(.white)\n            }\n            .frame(width: TVSlider.width, height: TVSlider.height)\n            .clipShape(\n                RoundedRectangle(cornerRadius: 10)\n                    .size(CGSize(width: TVSlider.width, height: TVSlider.height))\n            )\n            \n            Button(\"+\") {\n                guard self.$displayInterval.wrappedValue + step <= range.upperBound else { return }\n                self.$displayInterval.wrappedValue += step\n            }\n            \n        }\n        .padding()\n    }\n}\n\n@available(iOS 17.0, *)\n#Preview {\n    \n    @Previewable @State var interval: Double = 50.0\n    \n    TVSlider(\n        displayInterval: $interval,\n        range: 0.0...200.0,\n        step: 1.0\n    )\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/AirshipFeatureFlagsSDKModule.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\npublic import Foundation\n\n/// AirshipFeatureFlags module loader.\n/// @note For internal use only. :nodoc:\n\n@objc(UAFeatureFlagsSDKModule)\npublic class AirshipFeatureFlagsSDKModule: NSObject, AirshipSDKModule {\n    public let actionsManifest: (any ActionsManifest)? = nil\n\n    public let components: [any AirshipComponent]\n\n    public static func load(_ args: AirshiopModuleLoaderArgs) -> (any AirshipSDKModule)? {\n        let manager = DefaultFeatureFlagManager(\n            dataStore: args.dataStore,\n            remoteDataAccess: FeatureFlagRemoteDataAccess(remoteData: args.remoteData),\n            remoteData: args.remoteData,\n            analytics: FeatureFlagAnalytics(airshipAnalytics: args.analytics),\n            audienceChecker: args.audienceChecker,\n            deferredResolver: FeatureFlagDeferredResolver(\n                cache: args.cache,\n                deferredResolver: args.deferredResolver\n            ),\n            privacyManager: args.privacyManager,\n            resultCache: DefaultFeatureFlagResultCache(cache: args.cache)\n        )\n\n        let component = FeatureFlagComponent(featureFlagManager: manager)\n        return AirshipFeatureFlagsSDKModule(components: [component])\n    }\n\n    init(components: [any AirshipComponent]) {\n        self.components = components\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/DeferredFlagResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol FeatureFlagDeferredResolverProtocol: Actor {\n    func resolve(\n        request: DeferredRequest,\n        flagInfo: FeatureFlagInfo\n    ) async throws -> DeferredFlagResponse\n}\n\nactor FeatureFlagDeferredResolver: FeatureFlagDeferredResolverProtocol {\n    private static let minCacheTime: TimeInterval = 60.0\n\n    private static let defaultBackoff: TimeInterval = 30.0\n    private static let immediateBackoffRetryCutoff: TimeInterval = 5.0\n\n    private let cache: any AirshipCache\n    private let deferredResolver: any AirshipDeferredResolverProtocol\n    private let date: any AirshipDateProtocol\n    private let taskSleeper: any AirshipTaskSleeper\n\n    private var pendingTasks: [String: Task<DeferredFlagResponse, any Error>] = [:]\n    private var backOffDates: [String: Date] = [:]\n\n    init(\n        cache: any AirshipCache,\n        deferredResolver: any AirshipDeferredResolverProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        taskSleeper: any AirshipTaskSleeper = .shared\n    ) {\n        self.cache = cache\n        self.deferredResolver = deferredResolver\n        self.date = date\n        self.taskSleeper = taskSleeper\n    }\n\n    func resolve(\n        request: DeferredRequest,\n        flagInfo: FeatureFlagInfo\n    ) async throws -> DeferredFlagResponse {\n\n        let requestID = [\n            flagInfo.name,\n            flagInfo.id,\n            \"\\(flagInfo.lastUpdated.timeIntervalSince1970)\",\n            request.contactID ?? \"\",\n            request.url.absoluteString,\n        ].joined(separator: \":\")\n\n        _ = try? await pendingTasks[requestID]?.value\n\n        let task = Task {\n            if let cached: DeferredFlagResponse = await self.cache.getCachedValue(key: requestID) {\n                return cached\n            }\n\n            let result = try await self.fetchFlag(\n                request: request,\n                requestID: requestID,\n                flagInfo: flagInfo,\n                allowRetry: true\n            )\n\n            var ttl: TimeInterval = FeatureFlagDeferredResolver.minCacheTime\n            if let ttlMs = flagInfo.evaluationOptions?.ttlMS {\n                let ttlSeconds = Double(ttlMs)/1000\n                ttl = max(ttl, ttlSeconds)\n            }\n\n            await self.cache.setCachedValue(result, key: requestID, ttl: ttl)\n            return result\n        }\n\n        pendingTasks[requestID] = task\n        return try await task.value\n    }\n\n    private func fetchFlag(\n        request: DeferredRequest,\n        requestID: String,\n        flagInfo: FeatureFlagInfo,\n        allowRetry: Bool\n    ) async throws -> DeferredFlagResponse {\n        let now = self.date.now\n        if let backOffDate = backOffDates[requestID], backOffDate > now {\n            try await self.taskSleeper.sleep(\n                timeInterval: backOffDate.timeIntervalSince(now)\n            )\n        }\n        \n        let result = await deferredResolver.resolve(request: request) { data in\n            return try JSONDecoder().decode(DeferredFlag.self, from: data)\n        }\n\n        switch(result) {\n        case .success(let flag):\n            return .found(flag)\n\n        case .notFound:\n            return .notFound\n\n        case .retriableError(let retryAfter, let statusCode):\n            let backoff = retryAfter ?? FeatureFlagDeferredResolver.defaultBackoff\n\n            guard allowRetry, backoff <= FeatureFlagDeferredResolver.immediateBackoffRetryCutoff else {\n                backOffDates[requestID] = self.date.now.advanced(by: backoff)\n                if let statusCode = statusCode {\n                    throw FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag. Status code: \\(statusCode)\")\n                }\n\n                throw FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\")\n            }\n\n            if (backoff > 0) {\n                AirshipLogger.debug(statusCode == nil ? \"Backing off deferred flag request \\(requestID) for \\(backoff) seconds\" : \"Backing off deferred flag request \\(requestID) for \\(backoff) seconds with status code: \\(statusCode!)\")\n\n                try await self.taskSleeper.sleep(timeInterval: backoff)\n            }\n\n            AirshipLogger.error(statusCode == nil ? \"Retrying deferred flag request \\(requestID)\" : \"Retrying deferred flag request \\(requestID) with status code: \\(statusCode!)\")\n\n            return try await self.fetchFlag(\n                request: request,\n                requestID: requestID,\n                flagInfo: flagInfo,\n                allowRetry: false\n            )\n\n        case .outOfDate:\n            throw FeatureFlagEvaluationError.outOfDate\n\n        default:\n            throw FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\")\n        }\n    }\n}\n\nenum DeferredFlagResponse: Codable, Equatable {\n    case notFound\n    case found(DeferredFlag)\n}\n\nstruct DeferredFlag: Codable, Equatable {\n    let isEligible: Bool\n    let variables: FeatureFlagVariables?\n    let reportingMetadata: AirshipJSON\n    enum CodingKeys: String, CodingKey {\n        case isEligible = \"is_eligible\"\n        case variables = \"variables\"\n        case reportingMetadata = \"reporting_metadata\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlag.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Feature flag\npublic struct FeatureFlag: Equatable, Sendable, Codable {\n\n    /// The name of the flag\n    public let name: String\n\n    /// If the device is elegible or not for the flag.\n    public var isEligible: Bool\n\n    /// If the flag exists in the current flag listing or not\n    public let exists: Bool\n\n    /// Optional variables associated with the flag\n    public var variables: AirshipJSON?\n\n    var reportingInfo: ReportingInfo?\n\n    init(\n        name: String,\n        isEligible: Bool,\n        exists: Bool, \n        variables: AirshipJSON? = nil,\n        reportingInfo: ReportingInfo? = nil,\n        supersededReportingMetadata: AirshipJSON? = nil\n    ) {\n        self.name = name\n        self.isEligible = isEligible\n        self.exists = exists\n        self.variables = variables\n        self.reportingInfo = reportingInfo\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case name\n        case isEligible = \"is_eligible\"\n        case exists\n        case variables\n        case reportingInfo = \"_reporting_info\"\n    }\n\n    struct ReportingInfo: Codable, Sendable, Equatable {\n        // Reporting info\n        var reportingMetadata: AirshipJSON\n        \n        var supersededReportingMetadata: [AirshipJSON]?\n\n        // Evaluated contact ID\n        let contactID: String?\n\n        // Evaluated channel ID\n        let channelID: String?\n\n        init(\n            reportingMetadata: AirshipJSON,\n            supersededReportingMetadata: [AirshipJSON]? = nil,\n            contactID: String? = nil,\n            channelID: String? = nil) {\n                self.reportingMetadata = reportingMetadata\n                self.supersededReportingMetadata = supersededReportingMetadata\n                self.contactID = contactID\n                self.channelID = channelID\n            }\n\n        enum CodingKeys: String, CodingKey {\n            case reportingMetadata = \"reporting_metadata\"\n            case supersededReportingMetadata = \"superseded_reporting_metadata\"\n            case contactID = \"contact_id\"\n            case channelID = \"channel_id\"\n        }\n        \n        mutating func addSuperseded(metadata: AirshipJSON?) {\n            guard let metadata = metadata else {\n                return\n            }\n            \n            var mutable = supersededReportingMetadata ?? []\n            mutable.append(metadata)\n            \n            supersededReportingMetadata = mutable\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\nprotocol FeatureFlagAnalyticsProtocol: Sendable {\n    func trackInteraction(flag: FeatureFlag)\n}\n\nfinal class FeatureFlagAnalytics: FeatureFlagAnalyticsProtocol {\n    private let airshipAnalytics: any InternalAirshipAnalytics\n\n    private enum FlagKeys {\n        static let name = \"flag_name\"\n        static let metadata = \"reporting_metadata\"\n        static let supersededMetadata = \"superseded_reporting_metadata\"\n        static let eligible = \"eligible\"\n        static let device = \"device\"\n    }\n\n    private enum DeviceKeys {\n        static let channelID = \"channel_id\"\n        static let contactID = \"contact_id\"\n    }\n\n    init(\n        airshipAnalytics: any InternalAirshipAnalytics\n    ) {\n        self.airshipAnalytics = airshipAnalytics\n    }\n\n    func trackInteraction(flag: FeatureFlag) {\n        guard flag.exists else { return }\n\n        guard let reportingInfo = flag.reportingInfo else {\n            AirshipLogger.error(\"Missing reportingInfo, unable to track flag interaction \\(flag)\")\n            return\n        }\n\n        let eventBody = AirshipJSON.makeObject{ object in\n            object.set(string: flag.name, key: FlagKeys.name)\n            object.set(json: reportingInfo.reportingMetadata, key: FlagKeys.metadata)\n            object.set(bool: flag.isEligible, key: FlagKeys.eligible)\n            \n            if let superseded = reportingInfo.supersededReportingMetadata {\n                object.set(json: .array(superseded), key: FlagKeys.supersededMetadata)\n            }\n\n            let device = AirshipJSON.makeObject { object in\n                object.set(string: reportingInfo.channelID, key: DeviceKeys.channelID)\n                object.set(string: reportingInfo.contactID, key: DeviceKeys.contactID)\n            }\n\n            if (device.object?.isEmpty != true) {\n                object.set(json: device, key: FlagKeys.device)\n            }\n        }\n\n        let airshipEvent = AirshipEvent(\n            eventType: .featureFlagInteraction,\n            eventData: eventBody\n        )\n\n        airshipAnalytics.recordEvent(airshipEvent)\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Actual airship component for FeatureFlags. Used to hide AirshipComponent methods.\nfinal class FeatureFlagComponent : AirshipComponent {\n    final let featureFlagManager: DefaultFeatureFlagManager\n\n    init(featureFlagManager: DefaultFeatureFlagManager) {\n        self.featureFlagManager = featureFlagManager\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nenum FeatureFlagEvaluationError: Error, Equatable {\n    case outOfDate\n    case connectionError(errorMessage: String)\n}\n\n/// Airship feature flag manager\nfinal class DefaultFeatureFlagManager: FeatureFlagManager {\n\n    private let remoteDataAccess: any FeatureFlagRemoteDataAccessProtocol\n    private let audienceChecker: any DeviceAudienceChecker\n    private let analytics: any FeatureFlagAnalyticsProtocol\n    private let deviceInfoProviderFactory: @Sendable () -> any AudienceDeviceInfoProvider\n    private let deferredResolver: any FeatureFlagDeferredResolverProtocol\n    private let remoteData: any RemoteDataProtocol\n    private let privacyManager: any AirshipPrivacyManager\n\n    let resultCache: any FeatureFlagResultCache\n\n    var featureFlagStatusUpdates: AsyncStream<any Sendable> {\n        get async {\n            return await self.remoteData.statusUpdates(sources: [RemoteDataSource.app]) { statuses in\n                return self.toFeatureFlagUpdateStatus(status: statuses.values.first ?? .upToDate)\n            }\n        }\n    }\n    \n    var featureFlagStatus: FeatureFlagUpdateStatus {\n        get async {\n            return await self.toFeatureFlagUpdateStatus(status: self.remoteDataAccess.status)\n        }\n    }\n    \n    private var enabled: Bool {\n        return self.privacyManager.isEnabled(.featureFlags)\n    }\n    \n    init(\n        dataStore: PreferenceDataStore,\n        remoteDataAccess: any FeatureFlagRemoteDataAccessProtocol,\n        remoteData: any RemoteDataProtocol,\n        analytics: any FeatureFlagAnalyticsProtocol,\n        audienceChecker: any DeviceAudienceChecker,\n        deviceInfoProviderFactory: @escaping @Sendable () -> any AudienceDeviceInfoProvider = { CachingAudienceDeviceInfoProvider() },\n        deferredResolver: any FeatureFlagDeferredResolverProtocol,\n        privacyManager: any AirshipPrivacyManager,\n        resultCache: any FeatureFlagResultCache\n    ) {\n        self.remoteDataAccess = remoteDataAccess\n        self.audienceChecker = audienceChecker\n        self.analytics = analytics\n        self.deviceInfoProviderFactory = deviceInfoProviderFactory\n        self.deferredResolver = deferredResolver\n        self.privacyManager = privacyManager\n        self.resultCache = resultCache\n        self.remoteData = remoteData\n    }\n\n    func trackInteraction(flag: FeatureFlag) {\n        guard self.enabled else {\n            AirshipLogger.warn(\"Feature flags disabled.\")\n            return\n        }\n        analytics.trackInteraction(flag: flag)\n    }\n\n    func flag(name: String) async throws -> FeatureFlag {\n        try await self.flag(name: name, useResultCache: true)\n    }\n\n    func flag(name: String, useResultCache: Bool) async throws -> FeatureFlag {\n        guard self.enabled else {\n            throw AirshipErrors.error(\"Feature flags disabled.\")\n        }\n\n        do {\n            let flag = try await resolveFlag(name: name)\n            if !flag.exists, useResultCache {\n                if let fromCache = await resultCache.flag(name: name) {\n                    return fromCache\n                }\n            }\n            return flag\n        } catch {\n            guard\n                useResultCache,\n                let fromCache = await resultCache.flag(name: name)\n            else {\n                throw error\n            }\n            return fromCache\n        }\n    }\n\n    func waitRefresh(maxTime: TimeInterval) async {\n        await self.remoteData.waitRefresh(source: RemoteDataSource.app, maxTime: maxTime)\n    }\n\n    func waitRefresh() async {\n        await self.remoteData.waitRefresh(source: RemoteDataSource.app, maxTime: nil)\n    }\n\n    func resolveFlag(name: String) async throws -> FeatureFlag {\n        let remoteDataFeatureFlagInfo = try await remoteDataFeatureFlagInfo(name: name)\n\n        do {\n            // Attempt to evaluate\n            return try await self.evaluate(\n                remoteDataFeatureFlagInfo: remoteDataFeatureFlagInfo\n            )\n        } catch {\n            // If it's not an outOfDate evaluation error, throw the error\n            guard case FeatureFlagEvaluationError.outOfDate = error else {\n                throw mapError(error)\n            }\n\n            // Notify out of date\n            await self.remoteDataAccess.notifyOutdated(\n                remoteDateInfo: remoteDataFeatureFlagInfo.remoteDataInfo\n            )\n\n            // Best effort refresh again\n            await remoteDataAccess.bestEffortRefresh()\n\n            // Only continue if we actually have updated the status\n            guard await remoteDataAccess.status == .upToDate else {\n                throw mapError(error)\n            }\n\n            // Try one more time\n            do {\n                return try await self.evaluate(\n                    remoteDataFeatureFlagInfo: remoteDataFeatureFlagInfo\n                )\n            } catch {\n                throw mapError(error)\n            }\n        }\n    }\n\n    private func remoteDataFeatureFlagInfo(\n        name: String\n    ) async throws -> RemoteDataFeatureFlagInfo {\n\n        switch(await remoteDataAccess.status) {\n        case .upToDate:\n            return await self.remoteDataAccess.remoteDataFlagInfo(name: name)\n        case .stale, .outOfDate:\n            let info = await self.remoteDataAccess.remoteDataFlagInfo(name: name)\n            if info.disallowStale || info.flagInfos.isEmpty {\n                await self.remoteDataAccess.bestEffortRefresh()\n                let updatedStatus = await self.remoteDataAccess.status\n                switch(updatedStatus) {\n                case .upToDate:\n                    return await self.remoteDataAccess.remoteDataFlagInfo(name: name)\n                case .outOfDate:\n                    throw FeatureFlagError.outOfDate\n                case .stale:\n                    throw FeatureFlagError.staleData\n                @unknown default:\n                    throw AirshipErrors.error(\"Unexpected state\")\n                }\n            } else {\n                return info\n            }\n        @unknown default:\n            throw AirshipErrors.error(\"Unexpected state\")\n        }\n    }\n\n    private func mapError(_ error: any Error) -> any Error {\n        return switch (error) {\n        case FeatureFlagEvaluationError.connectionError(let errorMessage):\n            FeatureFlagError.connectionError(errorMessage: errorMessage)\n        case FeatureFlagEvaluationError.outOfDate:\n            FeatureFlagError.outOfDate\n        default:\n            FeatureFlagError.failedToFetchData\n        }\n    }\n\n    private func evaluate(\n        remoteDataFeatureFlagInfo: RemoteDataFeatureFlagInfo\n    ) async throws -> FeatureFlag {\n        let name = remoteDataFeatureFlagInfo.name\n        let flagInfos = remoteDataFeatureFlagInfo.flagInfos\n        let deviceInfoProvider = deviceInfoProviderFactory()\n\n        for (index, flagInfo) in flagInfos.enumerated() {\n            let isLast = index == (flagInfos.count - 1)\n            let isLocallyEligible = try await self.isLocallyEligible(\n                flagInfo: flagInfo,\n                deviceInfoProvider: deviceInfoProvider\n            )\n\n            // We are not locally eligible and have other flags skip\n            if !isLast, !isLocallyEligible {\n                continue\n            }\n\n            let flag: FeatureFlag = switch (flagInfo.flagPayload) {\n            case .deferredPayload(let deferredInfo):\n                try await evaluateDeferred(\n                    flagInfo: flagInfo,\n                    isLocallyEligible: isLocallyEligible,\n                    deferredInfo: deferredInfo,\n                    deviceInfoProvider: deviceInfoProvider\n                )\n            case .staticPayload(let staticInfo):\n                try await evaluateStatic(\n                    flagInfo: flagInfo,\n                    isLocallyEligible: isLocallyEligible,\n                    staticInfo: staticInfo,\n                    deviceInfoProvider: deviceInfoProvider\n                )\n            }\n\n            /// If the flag is eligible or the last flag return\n            if flag.isEligible || isLast {\n                return try await self.flag(flag, applyingControlFrom: flagInfo, deviceInfoProvider: deviceInfoProvider)\n            }\n        }\n\n        return FeatureFlag.makeNotFound(name: name)\n    }\n    \n    private func flag(\n        _ flag: FeatureFlag,\n        applyingControlFrom info: FeatureFlagInfo,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> FeatureFlag {\n        guard\n            flag.isEligible,\n            let control = info.controlOptins\n        else {\n            return flag\n        }\n        \n        let isAudienceMatch = try await self.audienceChecker.evaluate(\n            audienceSelector: control.compoundAudience?.selector,\n            newUserEvaluationDate: info.created,\n            deviceInfoProvider: deviceInfoProvider\n        )\n        \n        if !isAudienceMatch.isMatch {\n            return flag\n        }\n        \n        var result = flag\n        \n        switch control.controlType {\n        case .flag:\n            result.isEligible = false\n        case .variables(let override):\n            result.variables = override\n        }\n        \n        guard var reportingInfo = flag.reportingInfo else {\n            return result\n        }\n        \n        reportingInfo.addSuperseded(metadata: reportingInfo.reportingMetadata)\n        reportingInfo.reportingMetadata = control.reportingMetadata\n        \n        result.reportingInfo = reportingInfo\n        \n        return result\n    }\n\n    private func isLocallyEligible(\n        flagInfo: FeatureFlagInfo,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> Bool {\n        let result = try await self.audienceChecker.evaluate(\n            audienceSelector: .combine(\n                compoundSelector: flagInfo.compoundAudience?.selector,\n                deviceSelector: flagInfo.audienceSelector\n            ),\n            newUserEvaluationDate: flagInfo.created,\n            deviceInfoProvider: deviceInfoProvider\n        )\n        \n        return result.isMatch\n    }\n\n    private func evaluateDeferred(\n        flagInfo: FeatureFlagInfo,\n        isLocallyEligible: Bool,\n        deferredInfo: FeatureFlagPayload.DeferredInfo,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> FeatureFlag {\n\n        guard isLocallyEligible else {\n            return try await FeatureFlag.makeFound(\n                name: flagInfo.name,\n                isEligible: false,\n                deviceInfoProvider: deviceInfoProvider,\n                reportingMetadata: flagInfo.reportingMetadata,\n                variables: nil\n            )\n        }\n\n        let request = DeferredRequest(\n            url: deferredInfo.deferred.url,\n            channelID: try await deviceInfoProvider.channelID,\n            contactID: await deviceInfoProvider.stableContactInfo.contactID,\n            locale: deviceInfoProvider.locale,\n            notificationOptIn: await deviceInfoProvider.isUserOptedInPushNotifications\n        )\n\n        let deferredFlagResult = try await deferredResolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        switch(deferredFlagResult) {\n        case .notFound:\n            return FeatureFlag.makeNotFound(name: flagInfo.name)\n\n        case .found(let deferredFlag):\n            let variables = await evaluateVariables(\n                deferredFlag.variables,\n                flagInfo: flagInfo,\n                isEligible: deferredFlag.isEligible,\n                deviceInfoProvider: deviceInfoProvider\n            )\n\n            return try await FeatureFlag.makeFound(\n                name: flagInfo.name,\n                isEligible: deferredFlag.isEligible,\n                deviceInfoProvider: deviceInfoProvider,\n                reportingMetadata: deferredFlag.reportingMetadata,\n                variables: variables\n            )\n        }\n    }\n\n    private func evaluateStatic(\n        flagInfo: FeatureFlagInfo,\n        isLocallyEligible: Bool,\n        staticInfo: FeatureFlagPayload.StaticInfo,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async throws -> FeatureFlag {\n        let variables = await evaluateVariables(\n            staticInfo.variables,\n            flagInfo: flagInfo,\n            isEligible: isLocallyEligible,\n            deviceInfoProvider: deviceInfoProvider\n        )\n\n        return try await FeatureFlag.makeFound(\n            name: flagInfo.name,\n            isEligible: isLocallyEligible,\n            deviceInfoProvider: deviceInfoProvider,\n            reportingMetadata: flagInfo.reportingMetadata,\n            variables: variables\n        )\n    }\n\n    private func evaluateVariables(\n        _ variables: FeatureFlagVariables?,\n        flagInfo: FeatureFlagInfo,\n        isEligible: Bool,\n        deviceInfoProvider: any AudienceDeviceInfoProvider\n    ) async -> VariableResult? {\n        guard let variables = variables, isEligible else {\n            return nil\n        }\n\n        switch (variables) {\n        case .fixed(let data):\n            return VariableResult(data: data, reportingMetadata: nil)\n        case .variant(let variants):\n            for variant in variants {\n\n                let result = try? await self.audienceChecker.evaluate(\n                    audienceSelector: .combine(\n                        compoundSelector: variant.compoundAudience?.selector,\n                        deviceSelector: variant.audienceSelector\n                    ),\n                    newUserEvaluationDate: flagInfo.created,\n                    deviceInfoProvider: deviceInfoProvider\n                )\n                \n                if (result?.isMatch != true) {\n                    continue\n                }\n\n                return VariableResult(\n                    data: variant.data,\n                    reportingMetadata: variant.reportingMetadata\n                )\n            }\n\n            return nil\n        }\n    }\n    \n    private func toFeatureFlagUpdateStatus(status: RemoteDataSourceStatus) -> FeatureFlagUpdateStatus {\n        \n        switch(status) {\n            \n        case .upToDate:\n            return FeatureFlagUpdateStatus.upToDate\n            \n        case .stale:\n            return FeatureFlagUpdateStatus.stale\n            \n        case .outOfDate:\n            return FeatureFlagUpdateStatus.outOfDate\n            \n        @unknown default:\n            return FeatureFlagUpdateStatus.upToDate\n        }\n        \n    }\n}\n\nfileprivate struct VariableResult {\n    let data: AirshipJSON?\n    let reportingMetadata: AirshipJSON?\n}\n\nfileprivate extension FeatureFlag {\n    static func makeNotFound(name: String) -> FeatureFlag {\n        return FeatureFlag(\n            name: name,\n            isEligible: false,\n            exists: false,\n            variables: nil,\n            reportingInfo: nil\n        )\n    }\n\n    static func makeFound(\n        name: String,\n        isEligible: Bool,\n        deviceInfoProvider: any AudienceDeviceInfoProvider,\n        reportingMetadata: AirshipJSON,\n        variables: VariableResult?\n    ) async throws -> FeatureFlag {\n        return FeatureFlag(\n            name: name,\n            isEligible: isEligible,\n            exists: true,\n            variables: variables?.data,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: variables?.reportingMetadata ?? reportingMetadata,\n                contactID: await deviceInfoProvider.stableContactInfo.contactID,\n                channelID: try await deviceInfoProvider.channelID\n            )\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagManagerProtocol.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Feature flag errors\npublic enum FeatureFlagError: Error, Equatable {\n    case failedToFetchData\n    case staleData\n    case outOfDate\n    case connectionError(errorMessage: String)\n}\n\n/// Airship feature flag manager\npublic protocol FeatureFlagManager: AnyObject, Sendable {\n\n    /// Feature flag result cache. This can be used to return a cached result for `flag(name:useResultCache:)`\n    /// if the flag fails to resolve or it does not exist.\n    var resultCache: any FeatureFlagResultCache { get }\n\n    /// Feature flag status updates. Possible values are upToDate, stale and outOfDate.\n    var featureFlagStatusUpdates: AsyncStream<any Sendable> { get async }\n\n    /// Current feature flag status. Possible values are upToDate, stale and outOfDate.\n    var featureFlagStatus: FeatureFlagUpdateStatus { get async }\n\n    /// Tracks a feature flag interaction event.\n    /// - Parameter flag: The flag.\n    func trackInteraction(flag: FeatureFlag)\n\n    /// Gets and evaluates a feature flag.\n    /// - Parameters\n    ///     - name: The flag name\n    ///     - useResultCache: `true` to use the `FeatureFlagResultCache` if the flag fails to resolve or if the resolved flag does not exist,`false` to ignore the result cache.\n    /// - Returns: The feature flag.\n    /// - Throws: Throws `FeatureFlagError` if the flag fails to resolve.\n    func flag(name: String, useResultCache: Bool ) async throws -> FeatureFlag\n\n    /// Gets and evaluates a feature flag using a result cache.\n    /// - Parameters\n    ///     - name: The flag name\n    ///     - useResultCache: `true` to use the `FeatureFlagResultCache` if the flag fails to resolve or if the resolved flag does not exist,`false` to ignore the result cache.\n    /// - Returns: The feature flag.\n    /// - Throws: Throws `FeatureFlagError` if the flag fails to resolve.\n    func flag(name: String) async throws -> FeatureFlag\n\n    /// Waits for the refresh of the Feature Flag rules.\n    func waitRefresh() async\n\n    /// Waits for the refresh of the Feature Flag rules.\n    /// - Parameters maxTime: Timeout in seconds.\n    func waitRefresh(maxTime: TimeInterval) async\n}\n\npublic extension Airship {\n    /// The shared `FeatureFlagManager` instance. `Airship.takeOff` must be called before accessing this instance.\n    static var featureFlagManager: any FeatureFlagManager {\n        return Airship.requireComponent(\n            ofType: FeatureFlagComponent.self\n        ).featureFlagManager\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagPayload.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct FeatureFlagCompoundAudience: Codable, Sendable, Equatable {\n    var selector: CompoundDeviceAudienceSelector\n\n    init(selector: CompoundDeviceAudienceSelector) {\n        self.selector = selector\n    }\n}\n\nstruct FeatureFlagInfo: Decodable, Equatable {\n    /**\n     * Unique id of the flag, a UUID\n     */\n    let id: String\n\n    /**\n     * Date of the object's creation\n     */\n    let created: Date\n\n    /**\n     * Date of the last update to the flag definition\n     */\n    let lastUpdated: Date\n\n    /**\n     * The flag name\n     */\n    let name: String\n\n    /**\n     * The flag name\n     */\n    let reportingMetadata: AirshipJSON\n\n    /**\n      * Optional audience selector\n      */\n    let audienceSelector: DeviceAudienceSelector?\n\n    /// Optional compound audience\n    let compoundAudience: FeatureFlagCompoundAudience?\n\n    /**\n     * Optional time span in which the flag should be active\n     */\n    let timeCriteria: AirshipTimeCriteria?\n\n    /**\n     * Flag payload\n     */\n    let flagPayload: FeatureFlagPayload\n\n    /**\n     * Evaluation options.\n     */\n    let evaluationOptions: EvaluationOptions?\n    \n    /**\n     * Control options\n     */\n    let controlOptins: ControlOptions?\n\n    private enum FeatureFlagObjectCodingKeys: String, CodingKey {\n        case id = \"flag_id\"\n        case created\n        case lastUpdated = \"last_updated\"\n        case flagPayload = \"flag\"\n    }\n\n    private enum FlagPayloadKeys: String, CodingKey {\n        case type\n        case reportingMetadata = \"reporting_metadata\"\n        case audienceSelector = \"audience_selector\"\n        case compoundAudience = \"compound_audience\"\n        case timeCriteria = \"time_criteria\"\n        case variables\n        case evaluationOptions = \"evaluation_options\"\n        case control\n        case name\n    }\n    \n    private enum CompoundAudienceKeys: String, CodingKey {\n        case selector\n    }\n\n    init(\n        id: String,\n        created: Date,\n        lastUpdated: Date,\n        name: String,\n        reportingMetadata: AirshipJSON,\n        audienceSelector: DeviceAudienceSelector? = nil,\n        compoundAudience: FeatureFlagCompoundAudience? = nil,\n        timeCriteria: AirshipTimeCriteria? = nil,\n        flagPayload: FeatureFlagPayload,\n        evaluationOptions: EvaluationOptions? = nil,\n        controlOptions: ControlOptions? = nil\n    ) {\n        self.id = id\n        self.created = created\n        self.lastUpdated = lastUpdated\n        self.name = name\n        self.reportingMetadata = reportingMetadata\n        self.audienceSelector = audienceSelector\n        self.compoundAudience = compoundAudience\n        self.timeCriteria = timeCriteria\n        self.flagPayload = flagPayload\n        self.evaluationOptions = evaluationOptions\n        self.controlOptins = controlOptions\n    }\n\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: FeatureFlagObjectCodingKeys.self)\n        self.id = try container.decode(String.self, forKey: .id)\n\n        guard let created = AirshipDateFormatter.date(fromISOString: try container.decode(String.self, forKey: .created)) else {\n            throw DecodingError.typeMismatch(\n                FeatureFlagInfo.self,\n                DecodingError.Context(\n                    codingPath: container.codingPath,\n                    debugDescription: \"Invalid created date string.\",\n                    underlyingError: nil\n                )\n            )\n        }\n        self.created = created\n\n        guard let lastUpdated = AirshipDateFormatter.date(fromISOString: try container.decode(String.self, forKey: .lastUpdated)) else {\n            throw DecodingError.typeMismatch(\n                FeatureFlagInfo.self,\n                DecodingError.Context(\n                    codingPath: container.codingPath,\n                    debugDescription: \"Invalid updated date string.\",\n                    underlyingError: nil\n                )\n            )\n        }\n        self.lastUpdated = lastUpdated\n\n\n        self.flagPayload = try container.decode(FeatureFlagPayload.self, forKey: .flagPayload)\n\n        let payloadContainer = try container.nestedContainer(keyedBy: FlagPayloadKeys.self, forKey: .flagPayload)\n\n        self.name = try payloadContainer.decode(String.self, forKey: .name)\n        self.audienceSelector = try payloadContainer.decodeIfPresent(DeviceAudienceSelector.self, forKey: .audienceSelector)\n        self.compoundAudience = try payloadContainer.decodeIfPresent(FeatureFlagCompoundAudience.self, forKey: .compoundAudience)\n        self.timeCriteria = try payloadContainer.decodeIfPresent(AirshipTimeCriteria.self, forKey: .timeCriteria)\n        self.reportingMetadata = try payloadContainer.decode(AirshipJSON.self, forKey: .reportingMetadata)\n        self.evaluationOptions = try payloadContainer.decodeIfPresent(EvaluationOptions.self, forKey: .evaluationOptions)\n        self.controlOptins = try payloadContainer.decodeIfPresent(ControlOptions.self, forKey: .control)\n    }\n}\n\nstruct EvaluationOptions: Decodable, Equatable {\n    let disallowStaleValue: Bool?\n    let ttlMS: UInt64?\n\n    init(disallowStaleValue: Bool? = nil, ttlMS: UInt64? = nil) {\n        self.disallowStaleValue = disallowStaleValue\n        self.ttlMS = ttlMS\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case disallowStaleValue = \"disallow_stale_value\"\n        case ttlMS = \"ttl\"\n    }\n}\n\nenum FeatureFlagPayload: Decodable, Equatable {\n    case staticPayload(StaticInfo)\n    case deferredPayload(DeferredInfo)\n\n    struct DeferredInfo: Decodable, Equatable {\n        let deferred: Deferred\n    }\n\n    struct Deferred: Decodable, Equatable {\n        let url: URL\n\n        enum CodingKeys: String, CodingKey {\n            case url\n        }\n    }\n\n    struct StaticInfo: Decodable, Equatable {\n        let variables: FeatureFlagVariables?\n    }\n\n    private enum CodingKeys: CodingKey {\n        case type\n    }\n\n    private enum FeatureFlagPayloadType: String, Decodable {\n        case staticType = \"static\"\n        case deferredType = \"deferred\"\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(FeatureFlagPayloadType.self, forKey: .type)\n        let singleValueContainer = try decoder.singleValueContainer()\n\n        switch type {\n        case .staticType:\n            self = .staticPayload(\n                try singleValueContainer.decode(StaticInfo.self)\n            )\n        case .deferredType:\n            self = .deferredPayload(\n                try singleValueContainer.decode(DeferredInfo.self)\n            )\n        }\n    }\n}\n\nenum FeatureFlagVariables: Codable, Equatable {\n    case fixed(AirshipJSON?)\n    case variant([VariablesVariant])\n\n\n    struct VariablesVariant: Codable, Equatable {\n        let id: String\n        let audienceSelector: DeviceAudienceSelector?\n        let compoundAudience: FeatureFlagCompoundAudience?\n        let reportingMetadata: AirshipJSON\n        let data: AirshipJSON?\n\n        enum CodingKeys: String, CodingKey {\n            case id\n            case audienceSelector = \"audience_selector\"\n            case compoundAudience = \"compound_audience\"\n            case reportingMetadata = \"reporting_metadata\"\n            case data\n        }\n\n        init(\n            id: String,\n            audienceSelector: DeviceAudienceSelector? = nil,\n            compoundAudience: FeatureFlagCompoundAudience? = nil,\n            reportingMetadata: AirshipJSON,\n            data: AirshipJSON?\n        ) {\n            self.id = id\n            self.audienceSelector = audienceSelector\n            self.compoundAudience = compoundAudience\n            self.reportingMetadata = reportingMetadata\n            self.data = data\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.id = try container.decode(String.self, forKey: .id)\n            self.audienceSelector = try container.decodeIfPresent(DeviceAudienceSelector.self, forKey: .audienceSelector)\n            self.compoundAudience = try container.decodeIfPresent(FeatureFlagCompoundAudience.self, forKey: .compoundAudience)\n            self.reportingMetadata = try container.decode(AirshipJSON.self, forKey: .reportingMetadata)\n            self.data = try container.decodeIfPresent(AirshipJSON.self, forKey: .data)\n        }\n        \n        func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            try container.encode(id, forKey: .id)\n            try container.encodeIfPresent(audienceSelector, forKey: .audienceSelector)\n            try container.encodeIfPresent(compoundAudience, forKey: .compoundAudience)\n            try container.encode(reportingMetadata, forKey: .reportingMetadata)\n            try container.encodeIfPresent(data, forKey: .data)\n            \n        }\n    }\n\n    private enum FeatureFlagVariableType: String, Codable {\n        case fixed\n        case variant\n    }\n\n    private enum CodingKeys: CodingKey {\n        case type\n        case variants\n        case data\n    }\n\n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        let type = try container.decode(FeatureFlagVariableType.self, forKey: .type)\n\n        switch type {\n        case .fixed:\n            self = .fixed(\n                try container.decodeIfPresent(AirshipJSON.self, forKey: .data)\n            )\n        case .variant:\n            self = .variant(\n                try container.decode([VariablesVariant].self, forKey: .variants)\n            )\n        }\n    }\n\n    func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n\n        switch self {\n        case .fixed(let data):\n            try container.encode(FeatureFlagVariableType.fixed, forKey: .type)\n            try container.encodeIfPresent(data, forKey: .data)\n        case .variant(let variants):\n            try container.encode(FeatureFlagVariableType.variant, forKey: .type)\n            try container.encode(variants, forKey: .variants)\n        }\n    }\n}\n\n\nextension FeatureFlagInfo {\n    var isDeferred: Bool {\n        if case .deferredPayload(_) = self.flagPayload {\n            return true\n        }\n        return false\n    }\n}\n\nstruct ControlOptions: Codable, Equatable {\n    let compoundAudience: FeatureFlagCompoundAudience?\n    let reportingMetadata: AirshipJSON\n    let controlType: ControlType\n    \n    private enum CodingKeys: String, CodingKey {\n        case compoundAudience = \"compound_audience\"\n        case reportintMetadata = \"reporting_metadata\"\n    }\n    \n    init(\n        compoundAudience: FeatureFlagCompoundAudience?,\n        reportingMetadata: AirshipJSON,\n        controlType: ControlType\n    ) {\n        self.compoundAudience = compoundAudience\n        self.reportingMetadata = reportingMetadata\n        self.controlType = controlType\n    }\n    \n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        self.compoundAudience = try container.decodeIfPresent(FeatureFlagCompoundAudience.self, forKey: .compoundAudience)\n        self.reportingMetadata = try container.decode(AirshipJSON.self, forKey: .reportintMetadata)\n        self.controlType = try ControlType(from: decoder)\n    }\n    \n    func encode(to encoder: any Encoder) throws {\n        try controlType.encode(to: encoder)\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encodeIfPresent(compoundAudience, forKey: .compoundAudience)\n        try container.encode(reportingMetadata, forKey: .reportintMetadata)\n    }\n    \n    enum ControlType: Codable, Equatable {\n        case flag\n        case variables(AirshipJSON?)\n        \n        private enum CodingKeys: CodingKey {\n            case type\n            case data\n        }\n        \n        enum OptionType: String, Codable {\n            case flag\n            case variables\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            \n            let type = try container.decode(OptionType.self, forKey: .type)\n            \n            switch type {\n            case .flag:\n                self = .flag\n            case .variables:\n                self = .variables(\n                    try container.decodeIfPresent(AirshipJSON.self, forKey: .data)\n                )\n            }\n        }\n        \n        func encode(to encoder: any Encoder) throws {\n            var container = encoder.container(keyedBy: CodingKeys.self)\n            \n            switch self {\n            case .flag:\n                try container.encode(OptionType.flag, forKey: .type)\n                \n            case .variables(let variables):\n                try container.encode(OptionType.variables, forKey: .type)\n                try container.encodeIfPresent(variables, forKey: .data)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagResultCache.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Feature Flag result cache\npublic protocol FeatureFlagResultCache: Actor {\n    /// Caches a flag for the given cachTTL.\n    /// - Parameters:\n    ///     - flag: The flag to cache\n    ///     - ttl: The time to cache the value for.\n    func cache(flag: FeatureFlag, ttl: TimeInterval) async\n\n    /// Gets a flag from the cache.\n    /// - Parameters:\n    ///     - name: The flag name.\n    /// - Returns: The flag if its in the cache, otherwise nil.\n    func flag(name: String) async -> FeatureFlag?\n\n    /// Removes a flag from the cache.\n    /// - Parameters:\n    ///     - name: The flag name.\n    func removeCachedFlag(name: String) async\n}\n\nactor DefaultFeatureFlagResultCache: FeatureFlagResultCache {\n    private static let cacheKeyPrefix: String = \"FeatureFlagResultCache:\"\n    private let airshipCache: any AirshipCache\n\n    init(cache: any AirshipCache) {\n        self.airshipCache = cache\n    }\n\n    func cache(flag: FeatureFlag, ttl: TimeInterval) async {\n        guard let key = Self.makeKey(flag.name) else {\n            return\n        }\n\n        await airshipCache.setCachedValue(flag, key: key, ttl: ttl)\n    }\n\n    func flag(name: String) async -> FeatureFlag? {\n        guard let key = Self.makeKey(name) else {\n            return nil\n        }\n        return await airshipCache.getCachedValue(key: key)\n    }\n\n    func removeCachedFlag(name: String) async {\n        guard let key = Self.makeKey(name) else {\n            return\n        }\n\n        return await airshipCache.deleteCachedValue(key: key)\n    }\n\n    private static func makeKey(_ name: String) -> String? {\n        guard !name.isEmpty else {\n            AirshipLogger.error(\"Flag cache key is empty.\")\n            return nil\n        }\n        return \"\\(cacheKeyPrefix)\\(name)\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagUpdateStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Feature flag status. Possible values are upToDate, stale and outOfDate.\npublic enum FeatureFlagUpdateStatus: Sendable {\n    case upToDate\n    case stale\n    case outOfDate\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Source/FeatureFlagsRemoteDataAccess.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol FeatureFlagRemoteDataAccessProtocol: Sendable {\n    func remoteDataFlagInfo(name: String) async -> RemoteDataFeatureFlagInfo\n    var status: RemoteDataSourceStatus { get async }\n\n    func bestEffortRefresh() async\n    func notifyOutdated(remoteDateInfo: RemoteDataInfo?) async\n}\n\nfinal class FeatureFlagRemoteDataAccess: FeatureFlagRemoteDataAccessProtocol {\n\n    private let remoteData: any RemoteDataProtocol\n    private let date: any AirshipDateProtocol\n\n    init(\n        remoteData: any RemoteDataProtocol,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.remoteData = remoteData\n        self.date = date\n    }\n\n    var status: RemoteDataSourceStatus {\n        get async {\n            return await remoteData.status(source: RemoteDataSource.app)\n        }\n    }\n\n    func bestEffortRefresh() async {\n        await remoteData.waitRefresh(source: RemoteDataSource.app, maxTime: 15.0)\n    }\n\n    func notifyOutdated(remoteDateInfo: RemoteDataInfo?) async {\n        if let remoteDateInfo = remoteDateInfo {\n            await remoteData.notifyOutdated(remoteDataInfo: remoteDateInfo)\n        }\n    }\n\n    func remoteDataFlagInfo(name: String) async -> RemoteDataFeatureFlagInfo {\n        let appPayload: RemoteDataPayload? = await remoteData.payloads(types: [\"feature_flags\"])\n            .first { $0.remoteDataInfo?.source == .app }\n\n\n        let parsedFlagInfo: [FeatureFlagInfo] = appPayload?.data.object?[\"feature_flags\"]?.array?.compactMap { json in\n            do {\n                let flag: FeatureFlagInfo = try json.decode()\n                return flag\n            } catch {\n                AirshipLogger.error(\"Unable to parse feature flag \\(json), error: \\(error)\")\n                return nil\n            }\n        } ?? []\n\n        let flagInfos: [FeatureFlagInfo] = parsedFlagInfo\n            .filter { $0.name == name }\n            .filter { $0.timeCriteria?.isActive(date: self.date.now) ?? true }\n\n        return RemoteDataFeatureFlagInfo(\n            name: name,\n            flagInfos: flagInfos,\n            remoteDataInfo: appPayload?.remoteDataInfo\n        )\n    }\n}\n\nstruct RemoteDataFeatureFlagInfo {\n    let name: String\n    let flagInfos: [FeatureFlagInfo]\n    let remoteDataInfo: RemoteDataInfo?\n\n\n    var disallowStale: Bool {\n        return flagInfos.contains { flagInfo in\n            flagInfo.evaluationOptions?.disallowStaleValue == true\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagAnalyticsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipFeatureFlags\n@testable import AirshipCore\n\nfinal class FeatureFlagAnalyticsTest: XCTestCase {\n    private let airshipAnalytics: TestAnalytics = TestAnalytics()\n    private var analytics: FeatureFlagAnalytics!\n\n    override func setUp() {\n        self.analytics = FeatureFlagAnalytics(airshipAnalytics: airshipAnalytics)\n    }\n\n    func testTrackInteractionDoesNotExist() {\n        let flag = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: false,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting two\",\n                contactID: \"some contactID\",\n                channelID: \"some channel ID\"\n            )\n        )\n\n        self.analytics.trackInteraction(flag: flag)\n        XCTAssertEqual(0, self.airshipAnalytics.events.count)\n    }\n\n    func testTrackInteractionNoReportingInfo() {\n        let flag = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: nil\n        )\n\n        self.analytics.trackInteraction(flag: flag)\n        XCTAssertEqual(0, self.airshipAnalytics.events.count)\n    }\n\n    func testTrackInteraction() throws {\n        let flag = FeatureFlag(\n            name: \"some_flag\",\n            isEligible: true,\n            exists: true,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reportingMetadata\",\n                contactID: \"some_contact\",\n                channelID: \"some_channel\"\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n            \"flag_name\": \"some_flag\",\n            \"reporting_metadata\": \"reportingMetadata\",\n            \"eligible\": true,\n            \"device\": {\n                \"channel_id\": \"some_channel\",\n                \"contact_id\": \"some_contact\"\n            }\n        }\n        \"\"\"\n\n        self.analytics.trackInteraction(flag: flag)\n        XCTAssertEqual(1, self.airshipAnalytics.events.count)\n\n        let event = self.airshipAnalytics.events.first!\n        XCTAssertEqual(\"feature_flag_interaction\", event.eventType.reportingName)\n        XCTAssertEqual(AirshipEventPriority.normal, event.priority)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n    \n    func testTrackInteractionSupersede() throws {\n        let flag = FeatureFlag(\n            name: \"some_flag\",\n            isEligible: true,\n            exists: true,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reportingMetadata\",\n                supersededReportingMetadata: [\"supersede\"],\n                contactID: \"some_contact\",\n                channelID: \"some_channel\"\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n            \"flag_name\": \"some_flag\",\n            \"reporting_metadata\": \"reportingMetadata\",\n            \"superseded_reporting_metadata\": [\"supersede\"],\n            \"eligible\": true,\n            \"device\": {\n                \"channel_id\": \"some_channel\",\n                \"contact_id\": \"some_contact\"\n            }\n        }\n        \"\"\"\n\n        self.analytics.trackInteraction(flag: flag)\n        XCTAssertEqual(1, self.airshipAnalytics.events.count)\n\n        let event = self.airshipAnalytics.events.first!\n        XCTAssertEqual(\"feature_flag_interaction\", event.eventType.reportingName)\n        XCTAssertEqual(AirshipEventPriority.normal, event.priority)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    func testTrackInteractionNoDeviceInfo() throws {\n        let flag = FeatureFlag(\n            name: \"some_flag\",\n            isEligible: true,\n            exists: true,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reportingMetadata\"\n            )\n        )\n\n        let expectedBody = \"\"\"\n        {\n            \"flag_name\": \"some_flag\",\n            \"reporting_metadata\": \"reportingMetadata\",\n            \"eligible\": true\n        }\n        \"\"\"\n\n        self.analytics.trackInteraction(flag: flag)\n        XCTAssertEqual(1, self.airshipAnalytics.events.count)\n\n        let event = self.airshipAnalytics.events.first!\n        XCTAssertEqual(\"feature_flag_interaction\", event.eventType.reportingName)\n        XCTAssertEqual(AirshipEventPriority.normal, event.priority)\n        XCTAssertEqual(try AirshipJSON.from(json: expectedBody), event.eventData)\n    }\n\n    func testTrackInteractionEventFeed() async throws {\n        let flag = FeatureFlag(\n            name: \"some_flag\",\n            isEligible: true,\n            exists: true,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reportingMetadata\"\n            )\n        )\n\n        var feed = await self.airshipAnalytics.eventFeed.updates.makeAsyncIterator()\n        \n        self.analytics.trackInteraction(flag: flag)\n\n        let event = self.airshipAnalytics.events.first!\n        XCTAssertEqual(1, self.airshipAnalytics.events.count)\n        \n        let next = await feed.next()\n        XCTAssertEqual(next, .analytics(eventType: .featureFlagInteraction, body: event.eventData))\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagDeferredResolverTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n@testable\nimport AirshipFeatureFlags\n\nimport AirshipCore\n\nfinal class FeatureFlagDeferredResolverTest: XCTestCase {\n\n    private let cache: TestCache = TestCache()\n    private let deferredResolver: TestDeferredResolver = TestDeferredResolver()\n    private let date: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n    private let sleeper: TestTaskSleeper = TestTaskSleeper()\n\n    private var resolver: FeatureFlagDeferredResolver!\n\n    private let request = DeferredRequest(\n        url: URL(string: \"example://example\")!,\n        channelID: \"some channel id\",\n        contactID: \"some contact id\",\n        locale: Locale.current,\n        notificationOptIn: true\n    )\n\n    private let flagInfo = FeatureFlagInfo(\n        id: \"some-id\",\n        created: Date(),\n        lastUpdated: Date(),\n        name: \"flag name\",\n        reportingMetadata: \"reporting\",\n        flagPayload: .deferredPayload(\n            .init(\n                deferred: .init(url: URL(string: \"example://example\")!)\n            )\n        )\n    )\n\n    override func setUpWithError() throws {\n        resolver = FeatureFlagDeferredResolver(\n            cache: cache,\n            deferredResolver: deferredResolver,\n            date: date,\n            taskSleeper: sleeper\n        )\n    }\n\n    func testResolve() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n\n        self.deferredResolver.onData = { request in\n            expectation.fulfill()\n            XCTAssertEqual(request, self.request)\n            let data = try! AirshipJSON.wrap([\n                \"is_eligible\": false,\n                \"reporting_metadata\": [\"reporting\": \"reporting\"]\n            ]).toData()\n            return .success(data)\n        }\n\n        let result = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        let expected = DeferredFlagResponse.found(\n            DeferredFlag(\n                isEligible: false,\n                variables: nil,\n                reportingMetadata: try! AirshipJSON.wrap([\"reporting\": \"reporting\"])\n            )\n        )\n\n\n        XCTAssertEqual(expected, result)\n\n        await fulfillment(of: [expectation])\n    }\n\n    func testResolveVariables() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n\n        self.deferredResolver.onData = { request in\n            expectation.fulfill()\n            XCTAssertEqual(request, self.request)\n            let data = try! AirshipJSON.wrap([\n                \"is_eligible\": true,\n                \"variables\": [\n                    \"type\": \"fixed\",\n                    \"data\": [\n                        \"var\": \"one\"\n                    ]\n                ],\n                \"reporting_metadata\": [\"reporting\": \"reporting\"]\n            ]).toData()\n            return .success(data)\n        }\n\n        let result = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        let expected = DeferredFlagResponse.found(\n            DeferredFlag(\n                isEligible: true,\n                variables: .fixed(try! AirshipJSON.wrap([\"var\": \"one\"])),\n                reportingMetadata: try! AirshipJSON.wrap([\"reporting\": \"reporting\"])\n            )\n        )\n\n        XCTAssertEqual(expected, result)\n\n        await fulfillment(of: [expectation])\n    }\n\n    func testResolveNotFound() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n        self.deferredResolver.onData = { _ in\n            expectation.fulfill()\n            return .notFound\n        }\n\n\n        let result = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        XCTAssertEqual(DeferredFlagResponse.notFound, result)\n\n        await fulfillment(of: [expectation])\n\n    }\n\n    func testResolveOutOfDate() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n        self.deferredResolver.onData = { _ in\n            expectation.fulfill()\n            return .outOfDate\n        }\n\n        do {\n            _ = try await self.resolver.resolve(\n                request: request,\n                flagInfo: flagInfo\n            )\n            XCTFail()\n        } catch {\n            XCTAssertEqual(FeatureFlagEvaluationError.outOfDate, error as! FeatureFlagEvaluationError)\n        }\n\n        await fulfillment(of: [expectation])\n    }\n\n    func testResolveTimedOut() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n        self.deferredResolver.onData = { _ in\n            expectation.fulfill()\n            return .timedOut\n        }\n\n        do {\n            _ = try await self.resolver.resolve(\n                request: request,\n                flagInfo: flagInfo\n            )\n            XCTFail()\n        } catch {\n            XCTAssertEqual(FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\"), error as! FeatureFlagEvaluationError)\n        }\n\n        await fulfillment(of: [expectation])\n    }\n\n    func testResolveConnectionErrorNoRetryAfter() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n        self.deferredResolver.onData = { _ in\n            expectation.fulfill()\n            return .retriableError(statusCode: nil)\n        }\n\n        do {\n            _ = try await self.resolver.resolve(\n                request: request,\n                flagInfo: flagInfo\n            )\n            XCTFail()\n        } catch {\n            XCTAssertEqual(FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\"), error as! FeatureFlagEvaluationError)\n        }\n\n        await fulfillment(of: [expectation])\n\n        XCTAssertTrue(sleeper.sleeps.isEmpty)\n    }\n\n    func testResolveConnectionErrorShortRetryAfter() async throws {\n        let expectation = expectation(description: \"flag resolved\")\n        expectation.expectedFulfillmentCount = 2\n        self.deferredResolver.onData = { _ in\n            expectation.fulfill()\n            return .retriableError(retryAfter: 5)\n        }\n\n        do {\n            _ = try await self.resolver.resolve(\n                request: request,\n                flagInfo: flagInfo\n            )\n            XCTFail()\n        } catch {\n            XCTAssertEqual(FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\"), error as! FeatureFlagEvaluationError)\n        }\n\n        await fulfillment(of: [expectation])\n\n        XCTAssertEqual(sleeper.sleeps, [5])\n    }\n\n    func testResolveConnectionErrorLongRetryAfter() async throws {\n        let expecation = expectation(description: \"flag resolved\")\n        self.deferredResolver.onData = { _ in\n            expecation.fulfill()\n            return .retriableError(retryAfter: 6)\n        }\n\n        do {\n            _ = try await self.resolver.resolve(\n                request: request,\n                flagInfo: flagInfo\n            )\n            XCTFail()\n        } catch {\n            XCTAssertEqual(FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\"), error as! FeatureFlagEvaluationError)\n        }\n\n        await fulfillment(of: [expecation])\n        XCTAssertEqual(sleeper.sleeps, [])\n\n        self.date.offset += 1\n\n        self.deferredResolver.onData = { _ in\n            XCTAssertEqual(self.sleeper.sleeps, [5])\n            return .notFound\n        }\n\n        let result = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        XCTAssertEqual(DeferredFlagResponse.notFound, result)\n    }\n\n    func testCache() async throws {\n        self.deferredResolver.onData = { _ in\n            let data = try! AirshipJSON.wrap([\n                \"is_eligible\": true,\n                \"reporting_metadata\": [\"reporting\": \"reporting\"]\n            ]).toData()\n            return .success(data)\n        }\n\n        let flag = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        let expectedKey = [\n            flagInfo.name,\n            flagInfo.id,\n            \"\\(flagInfo.lastUpdated.timeIntervalSince1970)\",\n            request.contactID ?? \"\",\n            request.url.absoluteString,\n        ].joined(separator: \":\")\n\n        let entry = await self.cache.entry(key: expectedKey)!\n\n\n        let expectedValue = DeferredFlagResponse.found(\n            DeferredFlag(\n                isEligible: true,\n                variables: nil,\n                reportingMetadata: try! AirshipJSON.wrap([\"reporting\": \"reporting\"])\n            )\n        )\n\n        XCTAssertEqual(\n            try JSONDecoder().decode(DeferredFlagResponse.self, from: entry.data),\n            expectedValue\n        )\n\n        XCTAssertEqual(entry.ttl, 60.0)\n\n        self.deferredResolver.onData = { _ in\n            return .notFound\n        }\n\n        let cached = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n        XCTAssertEqual(cached, flag)\n    }\n\n    func testCacheTTL() async throws {\n        self.deferredResolver.onData = { _ in\n            let data = try! AirshipJSON.wrap([\n                \"is_eligible\": true,\n                \"reporting_metadata\": [\"reporting\": \"reporting\"]\n            ]).toData()\n            return .success(data)\n        }\n\n        let flagInfo = FeatureFlagInfo(\n            id: \"some-id\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"flag name\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .deferredPayload(\n                .init(\n                    deferred: .init(url: URL(string: \"example://example\")!)\n                )\n            ),\n            evaluationOptions: EvaluationOptions(ttlMS: 120000)\n        )\n\n        let result = try await self.resolver.resolve(\n            request: request,\n            flagInfo: flagInfo\n        )\n\n        let expectedKey = [\n            flagInfo.name,\n            flagInfo.id,\n            \"\\(flagInfo.lastUpdated.timeIntervalSince1970)\",\n            request.contactID ?? \"\",\n            request.url.absoluteString,\n        ].joined(separator: \":\")\n\n        let entry = await self.cache.entry(key: expectedKey)!\n        XCTAssertEqual(\n            try JSONDecoder().decode(DeferredFlagResponse.self, from: entry.data),\n            result\n        )\n\n        XCTAssertEqual(entry.ttl, 120.0)\n    }\n\n\n}\n\n\nfileprivate final class TestDeferredResolver: AirshipDeferredResolverProtocol, @unchecked Sendable {\n    var onData: ((DeferredRequest) async -> AirshipDeferredResult<Data>)?\n\n    func resolve<T>(\n        request: DeferredRequest,\n        resultParser: @escaping @Sendable (Data) async throws -> T\n    ) async -> AirshipDeferredResult<T> where T : Sendable {\n        switch(await onData?(request) ?? .timedOut) {\n        case .success(let data):\n            do {\n                let value = try await resultParser(data)\n                return .success(value)\n            } catch {\n                return .retriableError()\n            }\n        case .timedOut: return .timedOut\n        case .outOfDate: return .outOfDate\n        case .notFound: return .notFound\n        case .retriableError(retryAfter: let retryAfter, statusCode: let statusCode): return .retriableError(retryAfter: retryAfter, statusCode: statusCode)\n        @unknown default:\n            fatalError()\n        }\n    }\n}\n\n\n\nfileprivate final class TestTaskSleeper : AirshipTaskSleeper, @unchecked Sendable {\n    var sleeps : [TimeInterval] = []\n\n    func sleep(timeInterval: TimeInterval) async throws {\n        sleeps.append(timeInterval)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagInfoTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\n@testable\nimport AirshipFeatureFlags\n\nfinal class FeatureFlagInfoTest: XCTestCase {\n    \n    func testDecode() throws {\n        let json = \"\"\"\n        {\n          \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n          \"created\": \"2023-07-10T18:10:46.203\",\n          \"last_updated\": \"2023-07-10T18:10:46.203\",\n          \"platforms\": [\n            \"web\"\n          ],\n          \"flag\": {\n            \"name\": \"cool_flag\",\n            \"type\": \"static\",\n            \"reporting_metadata\": {\n              \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\"\n            },\n            \"audience_selector\": {\n              \"app_version\": {\n                \"value\": {\n                  \"version_matches\": \"1.6.0+\"\n                }\n              },\n              \"hash\": {\n                \"audience_hash\": {\n                  \"hash_prefix\": \"27f26d85-0550-4df5-85f0-7022fa7a5925:\",\n                  \"num_hash_buckets\": 16384,\n                  \"hash_identifier\": \"contact\",\n                  \"hash_algorithm\": \"farm_hash\"\n                },\n                \"audience_subset\": {\n                  \"min_hash_bucket\": 0,\n                  \"max_hash_bucket\": 1637\n                }\n              }\n            },\n            \"control\": {\n              \"reporting_metadata\": \"superseded\",\n              \"type\": \"flag\"\n            },\n            \"variables\": {\n              \"type\": \"variant\",\n              \"variants\": [\n                {\n                  \"id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\",\n                  \"reporting_metadata\": {\n                    \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                    \"variant_id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\"\n                  },\n                  \"audience_selector\": {\n                    \"hash\": {\n                      \"audience_hash\": {\n                        \"hash_prefix\": \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                        \"num_hash_buckets\": 100,\n                        \"hash_identifier\": \"contact\",\n                        \"hash_algorithm\": \"farm_hash\"\n                      },\n                      \"audience_subset\": {\n                        \"min_hash_bucket\": 0,\n                        \"max_hash_bucket\": 9\n                      }\n                    }\n                  },\n                  \"data\": {\n                    \"arbitrary_key_1\": \"some_value\",\n                    \"arbitrary_key_2\": \"some_other_value\"\n                  }\n                },\n                {\n                  \"id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\",\n                  \"reporting_metadata\": {\n                    \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                    \"variant_id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\"\n                  },\n                  \"audience_selector\": {\n                    \"hash\": {\n                      \"audience_hash\": {\n                        \"hash_prefix\": \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                        \"num_hash_buckets\": 100,\n                        \"hash_identifier\": \"contact\",\n                        \"hash_algorithm\": \"farm_hash\"\n                      },\n                      \"audience_subset\": {\n                        \"min_hash_bucket\": 0,\n                        \"max_hash_bucket\": 19\n                      }\n                    }\n                  },\n                  \"data\": {\n                    \"arbitrary_key_1\": \"different_value\",\n                    \"arbitrary_key_2\": \"different_other_value\"\n                  }\n                },\n                {\n                  \"id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\",\n                  \"reporting_metadata\": {\n                    \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                    \"variant_id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\"\n                  },\n                  \"data\": {\n                    \"arbitrary_key_1\": \"some default value\",\n                    \"arbitrary_key_2\": \"some other default value\"\n                  }\n                }\n              ]\n            }\n          }\n        }\n        \"\"\"\n\n        let decoded: FeatureFlagInfo = try JSONDecoder().decode(\n            FeatureFlagInfo.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = FeatureFlagInfo(\n            id: \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n            created: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n            lastUpdated: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n            name: \"cool_flag\",\n            reportingMetadata: try! AirshipJSON.wrap([\"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"]),\n            audienceSelector: DeviceAudienceSelector(\n                versionPredicate: JSONPredicate(\n                    jsonMatcher: JSONMatcher(\n                        valueMatcher: .matcherWithVersionConstraint(\"1.6.0+\")!\n                    )\n                ),\n                hashSelector: AudienceHashSelector(\n                    hash: .init(\n                        prefix: \"27f26d85-0550-4df5-85f0-7022fa7a5925:\",\n                        property: .contact,\n                        algorithm: .farm,\n                        seed: nil,\n                        numberOfBuckets: 16384,\n                        overrides: nil\n                    ),\n                    bucket: .init(min: 0, max: 1637)\n                )\n            ),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .variant(\n                        [\n                            .init(\n                                id: \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\",\n                                audienceSelector: DeviceAudienceSelector(\n                                    hashSelector: AudienceHashSelector(\n                                        hash: .init(\n                                            prefix: \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                                            property: .contact,\n                                            algorithm: .farm,\n                                            seed: nil,\n                                            numberOfBuckets: 100,\n                                            overrides: nil\n                                        ),\n                                        bucket: .init(min: 0, max: 9)\n                                    )\n                                ),\n                                reportingMetadata: try AirshipJSON.wrap(\n                                    [\n                                        \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                                        \"variant_id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\"\n                                    ]\n                                ),\n                                data: try AirshipJSON.wrap(\n                                    [\n                                        \"arbitrary_key_1\": \"some_value\",\n                                        \"arbitrary_key_2\": \"some_other_value\"\n                                    ]\n                                )\n                            ),\n                            .init(\n                                id: \"15422380-ce8f-49df-a7b1-9755b88ec0ef\",\n                                audienceSelector: DeviceAudienceSelector(\n                                    hashSelector: AudienceHashSelector(\n                                        hash: .init(\n                                            prefix: \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                                            property: .contact,\n                                            algorithm: .farm,\n                                            seed: nil,\n                                            numberOfBuckets: 100,\n                                            overrides: nil\n                                        ),\n                                        bucket: .init(min: 0, max: 19)\n                                    )\n                                ),\n                                compoundAudience: nil,\n                                reportingMetadata: try AirshipJSON.wrap(\n                                    [\n                                        \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                                        \"variant_id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\"\n                                    ]\n                                ),\n                                data: try AirshipJSON.wrap(\n                                    [\n                                        \"arbitrary_key_1\": \"different_value\",\n                                        \"arbitrary_key_2\": \"different_other_value\"\n                                    ]\n                                )\n                            ),\n                            .init(\n                                id: \"40e08a3d-8901-40fc-a01a-e6c263bec895\",\n                                audienceSelector: nil,\n                                compoundAudience: nil,\n                                reportingMetadata: try AirshipJSON.wrap(\n                                    [\n                                        \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                                        \"variant_id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\"\n                                    ]\n                                ),\n                                data: try AirshipJSON.wrap(\n                                    [\n                                        \"arbitrary_key_1\": \"some default value\",\n                                        \"arbitrary_key_2\": \"some other default value\"\n                                    ]\n                                )\n                            )\n                        ]\n                    )\n                )\n            ),\n            controlOptions: ControlOptions(\n                compoundAudience: nil,\n                reportingMetadata: \"superseded\",\n                controlType: .flag\n            )\n        )\n\n        XCTAssertEqual(decoded, expected)\n    }\n\n    func testDecodeWithCompoundAudience() throws {\n        let json = \"\"\"\n        {\n          \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n          \"created\": \"2023-07-10T18:10:46.203\",\n          \"last_updated\": \"2023-07-10T18:10:46.203\",\n          \"platforms\": [\n            \"web\"\n          ],\n          \"flag\": {\n            \"name\": \"cool_flag\",\n            \"type\": \"static\",\n            \"reporting_metadata\": {\n              \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\"\n            },\n            \"audience_selector\": {\n              \"app_version\": {\n                \"value\": {\n                  \"version_matches\": \"1.6.0+\"\n                }\n              },\n              \"hash\": {\n                \"audience_hash\": {\n                  \"hash_prefix\": \"27f26d85-0550-4df5-85f0-7022fa7a5925:\",\n                  \"num_hash_buckets\": 16384,\n                  \"hash_identifier\": \"contact\",\n                  \"hash_algorithm\": \"farm_hash\"\n                },\n                \"audience_subset\": {\n                  \"min_hash_bucket\": 0,\n                  \"max_hash_bucket\": 1637\n                }\n              }\n            },\n            \"compound_audience\": {\n              \"selector\": {\n                \"type\": \"atomic\",\n                \"audience\": {\n                  \"new_user\": true\n                }\n              }\n            },\n            \"control\": {\n              \"audience_selector\": {\n                \"app_version\": {\n                  \"value\": {\n                    \"version_matches\": \"1.6.0+\"\n                  }\n                }\n              },\n              \"reporting_metadata\": \"superseded\",\n              \"type\": \"flag\",\n              \"compound_audience\": {\n                \"selector\": {\n                  \"type\": \"atomic\",\n                  \"audience\": {\n                    \"new_user\": true\n                  }\n                }\n              }\n            },\n            \"variables\": {\n              \"type\": \"variant\",\n              \"variants\": [\n                {\n                  \"id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\",\n                  \"reporting_metadata\": {\n                    \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                    \"variant_id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\"\n                  },\n                  \"audience_selector\": {\n                    \"hash\": {\n                      \"audience_hash\": {\n                        \"hash_prefix\": \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                        \"num_hash_buckets\": 100,\n                        \"hash_identifier\": \"contact\",\n                        \"hash_algorithm\": \"farm_hash\"\n                      },\n                      \"audience_subset\": {\n                        \"min_hash_bucket\": 0,\n                        \"max_hash_bucket\": 9\n                      }\n                    }\n                  },\n                  \"compound_audience\": {\n                    \"selector\": {\n                      \"type\": \"atomic\",\n                      \"audience\": {\n                        \"new_user\": true\n                      }\n                    }\n                  },\n                  \"data\": {\n                    \"arbitrary_key_1\": \"some_value\",\n                    \"arbitrary_key_2\": \"some_other_value\"\n                  }\n                },\n                {\n                  \"id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\",\n                  \"reporting_metadata\": {\n                    \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                    \"variant_id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\"\n                  },\n                  \"audience_selector\": {\n                    \"hash\": {\n                      \"audience_hash\": {\n                        \"hash_prefix\": \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                        \"num_hash_buckets\": 100,\n                        \"hash_identifier\": \"contact\",\n                        \"hash_algorithm\": \"farm_hash\"\n                      },\n                      \"audience_subset\": {\n                        \"min_hash_bucket\": 0,\n                        \"max_hash_bucket\": 19\n                      }\n                    }\n                  },\n                  \"data\": {\n                    \"arbitrary_key_1\": \"different_value\",\n                    \"arbitrary_key_2\": \"different_other_value\"\n                  }\n                },\n                {\n                  \"id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\",\n                  \"reporting_metadata\": {\n                    \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                    \"variant_id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\"\n                  },\n                  \"data\": {\n                    \"arbitrary_key_1\": \"some default value\",\n                    \"arbitrary_key_2\": \"some other default value\"\n                  }\n                }\n              ]\n            }\n          }\n        }\n        \"\"\"\n\n        let decoded: FeatureFlagInfo = try JSONDecoder().decode(\n            FeatureFlagInfo.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = FeatureFlagInfo(\n            id: \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n            created: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n            lastUpdated: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n            name: \"cool_flag\",\n            reportingMetadata: try! AirshipJSON.wrap([\"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"]),\n            audienceSelector: DeviceAudienceSelector(\n                versionPredicate: JSONPredicate(\n                    jsonMatcher: JSONMatcher(\n                        valueMatcher: .matcherWithVersionConstraint(\"1.6.0+\")!\n                    )\n                ),\n                hashSelector: AudienceHashSelector(\n                    hash: .init(\n                        prefix: \"27f26d85-0550-4df5-85f0-7022fa7a5925:\",\n                        property: .contact,\n                        algorithm: .farm,\n                        seed: nil,\n                        numberOfBuckets: 16384,\n                        overrides: nil\n                    ),\n                    bucket: .init(min: 0, max: 1637)\n                )\n            ),\n            compoundAudience: .init(selector: .atomic(DeviceAudienceSelector(newUser: true))),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .variant(\n                        [\n                            .init(\n                                id: \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\",\n                                audienceSelector: DeviceAudienceSelector(\n                                    hashSelector: AudienceHashSelector(\n                                        hash: .init(\n                                            prefix: \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                                            property: .contact,\n                                            algorithm: .farm,\n                                            seed: nil,\n                                            numberOfBuckets: 100,\n                                            overrides: nil\n                                        ),\n                                        bucket: .init(min: 0, max: 9)\n                                    )\n                                ),\n                                compoundAudience: .init(selector: .atomic(DeviceAudienceSelector(newUser: true))),\n                                reportingMetadata: try AirshipJSON.wrap(\n                                    [\n                                        \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                                        \"variant_id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\"\n                                    ]\n                                ),\n                                data: try AirshipJSON.wrap(\n                                    [\n                                        \"arbitrary_key_1\": \"some_value\",\n                                        \"arbitrary_key_2\": \"some_other_value\"\n                                    ]\n                                )\n                            ),\n                            .init(\n                                id: \"15422380-ce8f-49df-a7b1-9755b88ec0ef\",\n                                audienceSelector: DeviceAudienceSelector(\n                                    hashSelector: AudienceHashSelector(\n                                        hash: .init(\n                                            prefix: \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                                            property: .contact,\n                                            algorithm: .farm,\n                                            seed: nil,\n                                            numberOfBuckets: 100,\n                                            overrides: nil\n                                        ),\n                                        bucket: .init(min: 0, max: 19)\n                                    )\n                                ),\n                                compoundAudience: nil,\n                                reportingMetadata: try AirshipJSON.wrap(\n                                    [\n                                        \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                                        \"variant_id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\"\n                                    ]\n                                ),\n                                data: try AirshipJSON.wrap(\n                                    [\n                                        \"arbitrary_key_1\": \"different_value\",\n                                        \"arbitrary_key_2\": \"different_other_value\"\n                                    ]\n                                )\n                            ),\n                            .init(\n                                id: \"40e08a3d-8901-40fc-a01a-e6c263bec895\",\n                                audienceSelector: nil,\n                                compoundAudience: nil,\n                                reportingMetadata: try AirshipJSON.wrap(\n                                    [\n                                        \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                                        \"variant_id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\"\n                                    ]\n                                ),\n                                data: try AirshipJSON.wrap(\n                                    [\n                                        \"arbitrary_key_1\": \"some default value\",\n                                        \"arbitrary_key_2\": \"some other default value\"\n                                    ]\n                                )\n                            )\n                        ]\n                    )\n                )\n            ),\n            controlOptions: ControlOptions(\n                compoundAudience: .init(selector: .atomic(DeviceAudienceSelector(newUser: true))),\n                reportingMetadata: \"superseded\",\n                controlType: .flag\n            )\n        )\n\n        XCTAssertEqual(decoded, expected)\n    }\n\n    func testDecodeControl() throws {\n        let json = \"\"\"\n        {\n           \"compound_audience\":{\n              \"selector\":{\n                 \"type\":\"atomic\",\n                 \"audience\":{\n                    \"app_version\":{\n                       \"value\":{\n                          \"version_matches\":\"1.6.0+\"\n                       }\n                    }\n                 }\n              }\n           },\n           \"reporting_metadata\":\"superseded\",\n           \"type\":\"variables\",\n           \"data\":\"variables_override\"\n        }\n        \"\"\"\n        \n        let decoded = try JSONDecoder().decode(\n            ControlOptions.self,\n            from: json.data(using: .utf8)!\n        )\n        \n        let expected = ControlOptions(\n            compoundAudience: FeatureFlagCompoundAudience(\n                selector: .atomic(\n                    .init(\n                        versionPredicate: JSONPredicate(\n                            jsonMatcher: JSONMatcher(\n                                valueMatcher: .matcherWithVersionConstraint(\"1.6.0+\")!\n                            )\n                        )\n                    )\n                )\n            ),\n            reportingMetadata: \"superseded\",\n            controlType: .variables(.string(\"variables_override\"))\n        )\n\n        XCTAssertEqual(expected, decoded)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagManagerTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\n@testable\nimport AirshipFeatureFlags\n\nfinal class AirshipFeatureFlagsTest: XCTestCase {\n\n    private let remoteDataAccess: TestFeatureFlagRemoteDataAccess = TestFeatureFlagRemoteDataAccess()\n    private let remoteData: TestRemoteData = TestRemoteData()\n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let networkChecker: TestNetworkChecker = TestNetworkChecker()\n    private let audienceChecker: TestAudienceChecker = TestAudienceChecker()\n    private let analytics: TestFeatureFlagAnalytics = TestFeatureFlagAnalytics()\n    private let deviceInfoProvider: TestDeviceInfoProvider = TestDeviceInfoProvider()\n    private let deferredResolver: TestFeatureFlagResolver = TestFeatureFlagResolver()\n    private var privacyManager: TestPrivacyManager!\n    private let notificationCenter: AirshipNotificationCenter = AirshipNotificationCenter(notificationCenter: NotificationCenter())\n    private let resultCache: DefaultFeatureFlagResultCache = DefaultFeatureFlagResultCache(cache: TestCache())\n\n    private var featureFlagManager: DefaultFeatureFlagManager!\n\n    override func setUp() async throws {\n        let config: RuntimeConfig = .testConfig()\n        self.privacyManager = TestPrivacyManager(\n            dataStore: dataStore,\n            config: config,\n            defaultEnabledFeatures: .all,\n            notificationCenter: notificationCenter\n        )\n        self.featureFlagManager = DefaultFeatureFlagManager(\n            dataStore: self.dataStore,\n            remoteDataAccess: self.remoteDataAccess,\n            remoteData: self.remoteData,\n            analytics: self.analytics,\n            audienceChecker: self.audienceChecker,\n            deviceInfoProviderFactory: { self.deviceInfoProvider },\n            deferredResolver: self.deferredResolver,\n            privacyManager: self.privacyManager,\n            resultCache: self.resultCache\n        )\n    }\n\n    func testFlagAccessWaitsForRefreshIfOutOfDateAndStaleNotAllowed() async throws {\n        let expectation = XCTestExpectation()\n        self.remoteDataAccess.bestEffortRefresh = {\n            expectation.fulfill()\n        }\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(variables: nil)\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: true)\n            )\n        ]\n        self.remoteDataAccess.status = .outOfDate\n        let _ = try? await featureFlagManager.flag(name: \"foo\")\n        await self.fulfillment(of: [expectation])\n    }\n\n    func testFlagAccessWaitsForRefreshIfFlagNotFound() async throws {\n        let expectation = XCTestExpectation()\n        self.remoteDataAccess.bestEffortRefresh = {\n            expectation.fulfill()\n        }\n        self.remoteDataAccess.status = .outOfDate\n        let _ = try? await featureFlagManager.flag(name: \"foo\")\n        await self.fulfillment(of: [expectation])\n    }\n\n    func testFlagAccessWaitsForRefreshIfStaleNotAllowed() async throws {\n        let expectation = XCTestExpectation()\n        self.remoteDataAccess.bestEffortRefresh = {\n            self.remoteDataAccess.status = .upToDate\n            expectation.fulfill()\n        }\n\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(variables: nil)\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: true)\n            )\n        ]\n\n        self.remoteDataAccess.status = .stale\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        await self.fulfillment(of: [expectation])\n\n        XCTAssertTrue(flag.exists)\n    }\n\n    func testNoFlags() async throws {\n        self.remoteDataAccess.status = .upToDate\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(name: \"foo\", isEligible: false, exists: false, variables: nil)\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testFlagNoAudience() async throws {\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(variables: nil)\n                )\n            )\n        ]\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testFlagAudienceMatch() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            compoundAudience: .init(selector: .not(.atomic(DeviceAudienceSelector(newUser: false)))),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(variables: nil)\n            )\n        )\n\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { selector, newUserDate, _ in\n            XCTAssertEqual(selector, .combine(compoundSelector: flagInfo.compoundAudience?.selector, deviceSelector: flagInfo.audienceSelector)!)\n            XCTAssertEqual(newUserDate, flagInfo.created)\n            return .match\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testFlagAudienceNoMatch() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(variables: nil)\n            )\n        )\n\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testAudienceMissLastInfoStatic() async throws {\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting 1\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(variables: nil)\n                )\n            ),\n            FeatureFlagInfo(\n                id: \"some other ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting 2\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(.string(\"some variables\"))\n                    )\n                )\n            ),\n\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting 2\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testAudienceMissLastInfoDeferred() async throws {\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting 1\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(variables: nil)\n                )\n            ),\n            FeatureFlagInfo(\n                id: \"some other ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting 2\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .deferredPayload(\n                    FeatureFlagPayload.DeferredInfo(\n                        deferred: .init(url: URL(string: \"some-url://\")!)\n                    )\n                )\n            ),\n\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting 2\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testMultipleFlags() async throws {\n        let flagInfo1 = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(variables: nil)\n            )\n        )\n\n        let flagInfo2 = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            audienceSelector: DeviceAudienceSelector(newUser: false),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .fixed(AirshipJSON.string(\"flagInfo2 variables\"))\n                )\n            )\n        )\n\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo1, flagInfo2\n        ]\n\n        self.audienceChecker.onEvaluate = { selector,  _, _ in\n            return if selector == .atomic(flagInfo2.audienceSelector!) {\n                .match\n            } else {\n                .miss\n            }\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: AirshipJSON.string(\"flagInfo2 variables\"),\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        XCTAssertEqual(expected, flag)\n    }\n    \n    func testMultipleFlagsCompound() async throws {\n        let flagInfo1 = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            audienceSelector: nil,\n            compoundAudience: .init(selector: .atomic(DeviceAudienceSelector(newUser: true))),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(variables: nil)\n            )\n        )\n\n        let flagInfo2 = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            audienceSelector: DeviceAudienceSelector(newUser: false),\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .fixed(AirshipJSON.string(\"flagInfo2 variables\"))\n                )\n            )\n        )\n\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo1, flagInfo2\n        ]\n\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            return if selector == flagInfo1.compoundAudience?.selector {\n                .match\n            } else {\n                .miss\n            }\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testVariantVariables() async throws {\n        let variables: [FeatureFlagVariables.VariablesVariant] = [\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 1\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"1\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant1 variables\")\n            ),\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 2\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"2\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant2 variables\")\n            ),\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 3\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"3\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant3 variables\")\n            )\n        ]\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .variant(variables)\n                )\n            )\n        )\n\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo,\n        ]\n\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            // match second variant\n            return if selector == .atomic(variables[1].audienceSelector!) {\n                .match\n            } else {\n                .miss\n            }\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: variables[1].data,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: variables[1].reportingMetadata,\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        XCTAssertEqual(expected, flag)\n    }\n    \n    func testControlFlag() async throws {\n        \n        let controlAudience = DeviceAudienceSelector(\n            versionPredicate: JSONPredicate(\n                jsonMatcher: JSONMatcher(\n                    valueMatcher: .matcherWithVersionConstraint(\"1.6.0+\")!\n                )\n            )\n        )\n        \n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .variant([])\n                )\n            ),\n            controlOptions: .init(\n                compoundAudience: .init(selector: .atomic(controlAudience)),\n                reportingMetadata: \"supersede\",\n                controlType: .flag)\n        )\n        \n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo,\n        ]\n        \n        var audienceMatched = false\n\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            return if selector == .atomic(controlAudience), audienceMatched {\n                .match\n            } else {\n                .miss\n            }\n        }\n\n        \n        let noControlFlag = try await featureFlagManager.flag(name: \"foo\")\n        \n        var expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        \n        XCTAssertEqual(expected, noControlFlag)\n        \n        audienceMatched = true\n        \n        let controlFlag = try await featureFlagManager.flag(name: \"foo\")\n        \n        expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"supersede\",\n                supersededReportingMetadata: [\"reporting\"],\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        XCTAssertEqual(expected, controlFlag)\n    }\n    \n    func testControlVariables() async throws {\n        \n        let controlAudience = DeviceAudienceSelector(\n            versionPredicate: JSONPredicate(\n                jsonMatcher: JSONMatcher(\n                    valueMatcher: .matcherWithVersionConstraint(\"1.6.0+\")!\n                )\n            )\n        )\n        \n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .variant([])\n                )\n            ),\n            controlOptions: .init(\n                compoundAudience: .init(selector: .atomic(controlAudience)),\n                reportingMetadata: \"supersede\",\n                controlType: .variables(\"variables-overrides\"))\n        )\n        \n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo,\n        ]\n        \n        var audienceMatched = false\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            return if selector == .atomic(controlAudience), audienceMatched {\n                .match\n            } else {\n                .miss\n            }\n        }\n        \n        let noControlFlag = try await featureFlagManager.flag(name: \"foo\")\n        \n        var expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        \n        XCTAssertEqual(expected, noControlFlag)\n        \n        audienceMatched = true\n        \n        let controlFlag = try await featureFlagManager.flag(name: \"foo\")\n        \n        expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: \"variables-overrides\",\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"supersede\",\n                supersededReportingMetadata: [\"reporting\"],\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n        XCTAssertEqual(expected, controlFlag)\n    }\n    \n    func testVariantVariablesDeferred() async throws {\n        let variables: [FeatureFlagVariables.VariablesVariant] = [\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 1\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"1\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant1 variables\")\n            ),\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 2\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"2\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant2 variables\")\n            ),\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 3\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"3\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant3 variables\")\n            )\n        ]\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        let deferredResponse = DeferredFlagResponse.found(\n            DeferredFlag(isEligible: true, variables: .variant(variables), reportingMetadata: \"reporting two\")\n        )\n\n        let expectedFlag = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: variables[1].data,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"Variant reporting\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            return if selector == .atomic(variables[1].audienceSelector!) {\n                .match\n            } else {\n                .miss\n            }\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            return deferredResponse\n        }\n\n        let result = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(result, expectedFlag)\n    }\n\n    func testVariantVariablesDeferredNoMatch() async throws {\n        let variables: [FeatureFlagVariables.VariablesVariant] = [\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 1\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"1\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant1 variables\")\n            ),\n        ]\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        let deferredResponse = DeferredFlagResponse.found(\n            DeferredFlag(isEligible: false, variables: .variant(variables), reportingMetadata: \"reporting two\")\n        )\n\n        let expectedFlag = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting two\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            return if selector == .atomic(variables[1].audienceSelector!) {\n                .match\n            } else {\n                .miss\n            }\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            return deferredResponse\n        }\n\n        let result = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(result, expectedFlag)\n    }\n\n\n    func testVariantVariablesNoMatch() async throws {\n        let variables: [FeatureFlagVariables.VariablesVariant] = [\n            FeatureFlagVariables.VariablesVariant(\n                id: \"variant 1\",\n                audienceSelector: DeviceAudienceSelector(tagSelector: .tag(\"1\")),\n                reportingMetadata: AirshipJSON.string(\"Variant reporting\"),\n                data: AirshipJSON.string(\"variant1 variables\")\n            )\n        ]\n\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting\",\n            flagPayload: .staticPayload(\n                FeatureFlagPayload.StaticInfo(\n                    variables: .variant(variables)\n                )\n            )\n        )\n\n        self.remoteDataAccess.status = .upToDate\n        self.remoteDataAccess.flagInfos = [\n            flagInfo,\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        let expected = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: flagInfo.reportingMetadata,\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        XCTAssertEqual(expected, flag)\n    }\n\n    func testStaleNotDefined() async throws {\n        self.remoteDataAccess.status = .stale\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                )\n            )\n        ]\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(\n            flag,\n            FeatureFlag(\n                name: \"foo\",\n                isEligible: true,\n                exists: true,\n                variables: nil,\n                reportingInfo: FeatureFlag.ReportingInfo(\n                    reportingMetadata: \"reporting\",\n                    contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                    channelID: self.deviceInfoProvider.channelID\n                )\n            )\n        )\n    }\n\n    func testStaleAllowed() async throws {\n        self.remoteDataAccess.status = .stale\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: false)\n            )\n        ]\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(\n            flag,\n            FeatureFlag(\n                name: \"foo\",\n                isEligible: true,\n                exists: true,\n                variables: nil,\n                reportingInfo: FeatureFlag.ReportingInfo(\n                    reportingMetadata: \"reporting\",\n                    contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                    channelID: self.deviceInfoProvider.channelID\n                )\n            )\n        )\n    }\n\n    func testStaleNotAllowed() async throws {\n        self.remoteDataAccess.status = .stale\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: true)\n            )\n        ]\n\n        do {\n            let _ = try await featureFlagManager.flag(name: \"foo\")\n            XCTFail(\"Should throw\")\n        } catch FeatureFlagError.staleData {\n            // No-op\n        } catch {\n            XCTFail(\"Should throw staleData\")\n        }\n    }\n\n    func testStaleNotAllowedMultipleFlags() async throws {\n        self.remoteDataAccess.status = .stale\n\n        // If one flag does not allow we ignore all\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: false)\n            ),\n            FeatureFlagInfo(\n                id: \"some other ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: true)\n            )\n        ]\n\n        do {\n            let _ = try await featureFlagManager.flag(name: \"foo\")\n            XCTFail(\"Should throw\")\n        }catch FeatureFlagError.staleData {\n            // No-op\n        } catch {\n            XCTFail(\"Should throw staleData\")\n        }\n    }\n\n    func testStaleAllowedOutOfDate() async throws {\n        self.remoteDataAccess.status = .outOfDate\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: false)\n            )\n        ]\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(\n            flag,\n            FeatureFlag(\n                name: \"foo\",\n                isEligible: true,\n                exists: true,\n                variables: nil,\n                reportingInfo: FeatureFlag.ReportingInfo(\n                    reportingMetadata: \"reporting\",\n                    contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                    channelID: self.deviceInfoProvider.channelID\n                )\n            )\n        )\n    }\n\n    func testOutOfDate() async throws {\n        self.remoteDataAccess.status = .outOfDate\n\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: false)\n            ),\n            FeatureFlagInfo(\n                id: \"some other ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting\",\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                ),\n                evaluationOptions: EvaluationOptions(disallowStaleValue: true)\n            )\n        ]\n\n        do {\n            let _ = try await featureFlagManager.flag(name: \"foo\")\n            XCTFail(\"Should throw\")\n        } catch FeatureFlagError.outOfDate {\n            // No-op\n        } catch {\n            XCTFail(\"Should throw outOfDate\")\n        }\n    }\n\n    func testMultipleFlagsNotEligible() async throws {\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting one\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                )\n            ),\n            FeatureFlagInfo(\n                id: \"some other ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting two\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                )\n            )\n        ]\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(\n            flag,\n            FeatureFlag(\n                name: \"foo\",\n                isEligible: false,\n                exists: true,\n                variables: nil,\n                reportingInfo: FeatureFlag.ReportingInfo(\n                    reportingMetadata: \"reporting two\",\n                    contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                    channelID: self.deviceInfoProvider.channelID\n                )\n            )\n        )\n    }\n\n    func testTrackInteractive() async throws {\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"some ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting one\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                )\n            ),\n            FeatureFlagInfo(\n                id: \"some other ID\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting two\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: .fixed(nil)\n                    )\n                )\n            )\n        ]\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(\n            flag,\n            FeatureFlag(\n                name: \"foo\",\n                isEligible: false,\n                exists: true,\n                variables: nil,\n                reportingInfo: FeatureFlag.ReportingInfo(\n                    reportingMetadata: \"reporting two\",\n                    contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                    channelID: self.deviceInfoProvider.channelID\n                )\n            )\n        )\n    }\n\n    func testTrackInteraction() {\n        let flag = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting two\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        self.featureFlagManager.trackInteraction(flag: flag)\n        XCTAssertEqual(self.analytics.trackedInteractions, [flag])\n    }\n\n    func testDeferred() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting one\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        let deferredResponse = DeferredFlagResponse.found(\n            DeferredFlag(isEligible: false, variables: nil, reportingMetadata: \"reporting two\")\n        )\n\n        let expectedFlag = FeatureFlag(\n            name: \"foo\",\n            isEligible: false,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting two\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.deferredResolver.setOnResolve { [deviceInfoProvider] request, info in\n            XCTAssertEqual(request.url, URL(string: \"some-url://\"))\n            XCTAssertEqual(request.contactID, deviceInfoProvider.stableContactInfo.contactID)\n            XCTAssertEqual(request.channelID, deviceInfoProvider.channelID)\n            XCTAssertEqual(request.locale, deviceInfoProvider.locale)\n            XCTAssertEqual(request.notificationOptIn, deviceInfoProvider.isUserOptedInPushNotifications)\n            XCTAssertEqual(flagInfo, info)\n            return deferredResponse\n        }\n\n        let result = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(result, expectedFlag)\n    }\n\n    func testDeferredLocalAudience() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting one\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            XCTFail()\n            throw AirshipErrors.error(\"Failed\")\n        }\n\n        let result = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertFalse(result.isEligible)\n    }\n\n    func testMultipleDeferred() async throws {\n        self.remoteDataAccess.flagInfos = [\n            FeatureFlagInfo(\n                id: \"one\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting one\",\n                audienceSelector: DeviceAudienceSelector(newUser: false),\n                flagPayload: .deferredPayload(\n                    FeatureFlagPayload.DeferredInfo(\n                        deferred: .init(url: URL(string: \"some-url://\")!)\n                    )\n                )\n            ),\n            FeatureFlagInfo(\n                id: \"two\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting two\",\n                audienceSelector: DeviceAudienceSelector(newUser: true),\n                flagPayload: .deferredPayload(\n                    FeatureFlagPayload.DeferredInfo(\n                        deferred: .init(url: URL(string: \"some-url://\")!)\n                    )\n                )\n            ),\n            FeatureFlagInfo(\n                id: \"three\",\n                created: Date(),\n                lastUpdated: Date(),\n                name: \"foo\",\n                reportingMetadata: \"reporting three\",\n                flagPayload: .deferredPayload(\n                    FeatureFlagPayload.DeferredInfo(\n                        deferred: .init(url: URL(string: \"some-url://\")!)\n                    )\n                )\n            )\n        ]\n\n        self.audienceChecker.onEvaluate = { selector, _, _ in\n            if selector == .atomic(DeviceAudienceSelector(newUser: true)) {\n                return .match\n            } else {\n                return .miss\n            }\n        }\n\n        await self.deferredResolver.setOnResolve { request, info in\n            DeferredFlagResponse.found(\n                DeferredFlag(\n                    isEligible: info.id == \"three\",\n                    variables: nil,\n                    reportingMetadata: info.reportingMetadata\n                )\n            )\n        }\n\n        let expectedFlag = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true,\n            variables: nil,\n            reportingInfo: FeatureFlag.ReportingInfo(\n                reportingMetadata: \"reporting three\",\n                contactID: self.deviceInfoProvider.stableContactInfo.contactID,\n                channelID: self.deviceInfoProvider.channelID\n            )\n        )\n\n        let result = try await featureFlagManager.flag(name: \"foo\")\n        XCTAssertEqual(expectedFlag, result)\n\n        let resolved = await self.deferredResolver.resolvedFlagInfos\n        XCTAssertEqual(\n            [\n                self.remoteDataAccess.flagInfos[1],\n                self.remoteDataAccess.flagInfos[2]\n            ],\n            resolved\n        )\n    }\n\n\n    func testDeferredOutOfDate() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting one\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        self.remoteDataAccess.remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some://remote-data\")!,\n            lastModifiedTime: \"last modified\",\n            source: .app\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            throw FeatureFlagEvaluationError.outOfDate\n        }\n\n        do {\n            _ = try await featureFlagManager.flag(name: \"foo\")\n        } catch {\n            XCTAssertEqual(error as! FeatureFlagError, FeatureFlagError.outOfDate)\n        }\n\n        XCTAssertEqual(remoteDataAccess.lastOutdatedRemoteInfo, self.remoteDataAccess.remoteDataInfo)\n    }\n\n    func testDeferredConnectionIssue() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting one\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        self.remoteDataAccess.remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some://remote-data\")!,\n            lastModifiedTime: \"last modified\",\n            source: .app\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .miss\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            throw FeatureFlagEvaluationError.connectionError(errorMessage: \"Failed to resolve flag.\")\n        }\n\n        do {\n            _ = try await featureFlagManager.flag(name: \"foo\")\n        } catch {\n            XCTAssertEqual(error as! FeatureFlagError, FeatureFlagError.connectionError(errorMessage: \"Failed to resolve flag.\"))\n        }\n\n        XCTAssertNil(remoteDataAccess.lastOutdatedRemoteInfo)\n    }\n\n    func testDeferredOtherError() async throws {\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting one\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        self.remoteDataAccess.remoteDataInfo = RemoteDataInfo(\n            url: URL(string: \"some://remote-data\")!,\n            lastModifiedTime: \"last modified\",\n            source: .app\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            throw AirshipErrors.error(\"other!\")\n        }\n\n        do {\n            _ = try await featureFlagManager.flag(name: \"foo\")\n        } catch {\n            XCTAssertEqual(error as! FeatureFlagError, FeatureFlagError.failedToFetchData)\n        }\n\n        XCTAssertNil(remoteDataAccess.lastOutdatedRemoteInfo)\n    }\n\n    func testResultCacheFlagDoesNotExist() async throws {\n        let cachedValue = FeatureFlag(\n            name: \"does-not-exist\",\n            isEligible: true,\n            exists: false\n        )\n\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            throw AirshipErrors.error(\"other!\")\n        }\n\n        await featureFlagManager.resultCache.cache(flag: cachedValue, ttl: .infinity)\n\n        let flag = try await featureFlagManager.flag(name: \"does-not-exist\")\n        let flagNoCache = try await featureFlagManager.flag(name: \"does-not-exist\", useResultCache: false)\n\n        XCTAssertEqual(flag, cachedValue)\n        XCTAssertNotEqual(flagNoCache, cachedValue)\n    }\n\n    func testResultCacheThrows() async throws {\n        let cachedValue = FeatureFlag(\n            name: \"foo\",\n            isEligible: true,\n            exists: true\n        )\n        await featureFlagManager.resultCache.cache(flag: cachedValue, ttl: .infinity)\n\n        let flagInfo = FeatureFlagInfo(\n            id: \"some ID\",\n            created: Date(),\n            lastUpdated: Date(),\n            name: \"foo\",\n            reportingMetadata: \"reporting one\",\n            audienceSelector: DeviceAudienceSelector(newUser: true),\n            flagPayload: .deferredPayload(\n                FeatureFlagPayload.DeferredInfo(\n                    deferred: .init(url: URL(string: \"some-url://\")!)\n                )\n            )\n        )\n\n        self.remoteDataAccess.flagInfos = [\n            flagInfo\n        ]\n\n        self.audienceChecker.onEvaluate = { _, _, _ in\n            return .match\n        }\n\n        await self.deferredResolver.setOnResolve { _, _ in\n            throw AirshipErrors.error(\"other!\")\n        }\n\n        let flag = try await featureFlagManager.flag(name: \"foo\")\n        do {\n            _ = try await featureFlagManager.flag(name: \"foo\", useResultCache: false)\n            XCTFail()\n        } catch {}\n\n        XCTAssertEqual(flag, cachedValue)\n    }\n}\n\nfinal class TestFeatureFlagRemoteDataAccess: FeatureFlagRemoteDataAccessProtocol, @unchecked Sendable {\n\n    var lastOutdatedRemoteInfo: RemoteDataInfo?\n    func remoteDataFlagInfo(name: String) async -> RemoteDataFeatureFlagInfo {\n        let flags = flagInfos.filter { info in\n            info.name == name\n        }\n        return RemoteDataFeatureFlagInfo(name: name, flagInfos: flags, remoteDataInfo: self.remoteDataInfo)\n    }\n    \n    func notifyOutdated(remoteDateInfo: RemoteDataInfo?) async {\n        lastOutdatedRemoteInfo = remoteDataInfo;\n    }\n    \n    var bestEffortRefresh: (() -> Void)?\n    func bestEffortRefresh() async {\n        self.bestEffortRefresh?()\n    }\n    \n    var status: RemoteDataSourceStatus = .upToDate\n    var flagInfos: [FeatureFlagInfo] = []\n    var remoteDataInfo: RemoteDataInfo?\n\n}\n\n\nfinal class TestFeatureFlagAnalytics: FeatureFlagAnalyticsProtocol, @unchecked Sendable {\n    func trackInteraction(flag: FeatureFlag) {\n        trackedInteractions.append(flag)\n    }\n\n    var trackedInteractions: [FeatureFlag] = []\n}\n\n\nfinal class TestDeviceInfoProvider: AudienceDeviceInfoProvider, @unchecked Sendable {\n    var sdkVersion: String = \"1.0.0\"\n\n\n    var isAirshipReady: Bool = false\n\n    var tags: Set<String> = Set()\n\n    var isChannelCreated: Bool = true\n\n    var channelID: String = UUID().uuidString\n\n    var locale: Locale = Locale.current\n\n    var appVersion: String?\n    \n    var permissions: [AirshipCore.AirshipPermission : AirshipCore.AirshipPermissionStatus] = [:]\n\n    var isUserOptedInPushNotifications: Bool = false\n\n    var analyticsEnabled: Bool = false\n\n    var installDate: Date = Date()\n\n    var stableContactInfo: StableContactInfo = StableContactInfo(contactID: UUID().uuidString)\n\n}\n\n\nfinal actor TestFeatureFlagResolver: FeatureFlagDeferredResolverProtocol {\n\n    var resolvedFlagInfos: [FeatureFlagInfo] = []\n\n    var onResolve: ((DeferredRequest, FeatureFlagInfo) async throws -> DeferredFlagResponse)?\n\n    func setOnResolve(onResolve: @escaping @Sendable (DeferredRequest, FeatureFlagInfo) async throws -> DeferredFlagResponse) {\n        self.onResolve = onResolve\n    }\n\n    func resolve(request: DeferredRequest, flagInfo: FeatureFlagInfo) async throws -> DeferredFlagResponse {\n        resolvedFlagInfos.append(flagInfo)\n        return try await self.onResolve!(request, flagInfo)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagRemoteDataAccessTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\n@testable\nimport AirshipFeatureFlags\n\nfinal class FeatureFlagRemoteDataAccessTest: XCTestCase {\n\n    private let remoteData: TestRemoteData = TestRemoteData()\n    private let date: UATestDate = UATestDate(offset: 0, dateOverride: Date())\n    private var remoteDataAccess: FeatureFlagRemoteDataAccess!\n\n    override func setUp() {\n        self.remoteDataAccess = FeatureFlagRemoteDataAccess(\n            remoteData: self.remoteData,\n            date: date\n        )\n    }\n\n    func testBestEffortRefresh() async throws {\n        \n        let expectation = XCTestExpectation()\n        self.remoteData.waitForRefreshBlock = { source, time in\n            XCTAssertEqual(source, .app)\n            XCTAssertEqual(time, 15.0)\n            expectation.fulfill()\n        }\n\n        await self.remoteDataAccess.bestEffortRefresh()\n        await self.fulfillment(of: [expectation])\n    }\n\n    func testFeatureFlags() async throws {\n        let json = \"\"\"\n        {\n           \"feature_flags\":[\n              {\n                 \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                 \"created\":\"2023-07-10T18:10:46.203\",\n                 \"last_updated\":\"2023-07-10T18:10:46.203\",\n                 \"platforms\":[\n                    \"web\"\n                 ],\n                 \"flag\":{\n                    \"name\":\"cool_flag\",\n                    \"type\":\"static\",\n                    \"reporting_metadata\":{\n                       \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"\n                    }\n                 }\n              }\n           ]\n        }\n        \"\"\"\n\n        self.remoteData.payloads = [\n            RemoteDataPayload(\n                type: \"feature_flags\",\n                timestamp: Date(),\n                data: try! AirshipJSON.from(json: json),\n                remoteDataInfo: RemoteDataInfo(\n                    url: URL(string: \"some:url\")!,\n                    lastModifiedTime: nil,\n                    source: .app\n                )\n            )\n        ]\n\n        let remoteDataInfo = await self.remoteDataAccess.remoteDataFlagInfo(name: \"cool_flag\")\n        let expected: [FeatureFlagInfo] = [\n            FeatureFlagInfo(\n                id: \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                created: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n                lastUpdated: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n                name: \"cool_flag\",\n                reportingMetadata: try! AirshipJSON.wrap([\"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"]),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: nil\n                    )\n                )\n            )\n        ]\n\n        XCTAssertEqual(remoteDataInfo.flagInfos, expected)\n        XCTAssertEqual(remoteDataInfo.remoteDataInfo, self.remoteData.payloads.first?.remoteDataInfo)\n    }\n\n    func testFeatureFlagsIgnoreInvalid() async throws {\n        let json = \"\"\"\n        {\n           \"feature_flags\":[\n              {\n                 \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                 \"created\":\"2023-07-10T18:10:46.203\",\n                 \"last_updated\":\"2023-07-10T18:10:46.203\",\n                 \"platforms\":[\n                    \"web\"\n                 ],\n                 \"flag\":{\n                    \"name\":\"cool_flag\",\n                    \"type\":\"static\",\n                    \"reporting_metadata\":{\n                       \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"\n                    }\n                 }\n              },\n              {\n                \"something\": \"invalid\"\n              }\n           ]\n        }\n        \"\"\"\n\n        self.remoteData.payloads = [\n            RemoteDataPayload(\n                type: \"feature_flags\",\n                timestamp: Date(),\n                data: try! AirshipJSON.from(json: json),\n                remoteDataInfo: RemoteDataInfo(\n                    url: URL(string: \"some:url\")!,\n                    lastModifiedTime: nil,\n                    source: .app\n                )\n            )\n        ]\n\n        let flagInfos = await self.remoteDataAccess.remoteDataFlagInfo(name: \"cool_flag\").flagInfos\n        let expected: [FeatureFlagInfo] = [\n            FeatureFlagInfo(\n                id: \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                created: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n                lastUpdated: AirshipDateFormatter.date(fromISOString: \"2023-07-10T18:10:46.203\")!,\n                name: \"cool_flag\",\n                reportingMetadata: try! AirshipJSON.wrap([\"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"]),\n                flagPayload: .staticPayload(\n                    FeatureFlagPayload.StaticInfo(\n                        variables: nil\n                    )\n                )\n            )\n        ]\n\n        XCTAssertEqual(flagInfos, expected)\n    }\n\n    \n    func testFeatureFlagsIgnoreContact() async throws {\n        let json = \"\"\"\n        {\n           \"feature_flags\":[\n              {\n                 \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                 \"created\":\"2023-07-10T18:10:46.203\",\n                 \"last_updated\":\"2023-07-10T18:10:46.203\",\n                 \"platforms\":[\n                    \"web\"\n                 ],\n                 \"flag\":{\n                    \"name\":\"cool_flag\",\n                    \"type\":\"static\",\n                    \"reporting_metadata\":{\n                       \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"\n                    }\n                 }\n              }\n           ]\n        }\n        \"\"\"\n\n        self.remoteData.payloads = [\n            RemoteDataPayload(\n                type: \"feature_flags\",\n                timestamp: Date(),\n                data: try! AirshipJSON.from(json: json),\n                remoteDataInfo: RemoteDataInfo(\n                    url: URL(string: \"some:url\")!,\n                    lastModifiedTime: nil,\n                    source: .contact\n                )\n            )\n        ]\n\n        let flagInfos = await self.remoteDataAccess.remoteDataFlagInfo(name: \"cool_flag\").flagInfos\n        XCTAssertTrue(flagInfos.isEmpty)\n    }\n\n    func testFeatureFlagsIgnoreInActive() async throws {\n        let nowMs = self.date.now.millisecondsSince1970\n        let json = \"\"\"\n        {\n           \"feature_flags\":[\n              {\n                 \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                 \"created\":\"2023-07-10T18:10:46.203\",\n                 \"last_updated\":\"2023-07-10T18:10:46.203\",\n                 \"flag\":{\n                    \"name\":\"cool_flag\",\n                    \"type\":\"static\",\n                    \"reporting_metadata\":{\n                       \"flag_id\":\"27f26d85-0550-4df5-85f0-7022fa7a5925\"\n                    },\n                    \"time_criteria\": {\n                      \"start_timestamp\": \\(nowMs),\n                      \"end_timestamp\": \\(nowMs + 5000)\n                    }\n                 },\n              }\n           ]\n        }\n        \"\"\"\n\n        self.remoteData.payloads = [\n            RemoteDataPayload(\n                type: \"feature_flags\",\n                timestamp: Date(),\n                data: try! AirshipJSON.from(json: json),\n                remoteDataInfo: RemoteDataInfo(\n                    url: URL(string: \"some:url\")!,\n                    lastModifiedTime: nil,\n                    source: .app\n                )\n            )\n        ]\n\n        var flagInfos = await self.remoteDataAccess.remoteDataFlagInfo(name: \"cool_flag\").flagInfos\n        XCTAssertFalse(flagInfos.isEmpty)\n\n        self.date.offset = 4.9\n        flagInfos = await self.remoteDataAccess.remoteDataFlagInfo(name: \"cool_flag\").flagInfos\n        XCTAssertFalse(flagInfos.isEmpty)\n\n        self.date.offset = 5.0\n        flagInfos = await self.remoteDataAccess.remoteDataFlagInfo(name: \"cool_flag\").flagInfos\n        XCTAssertTrue(flagInfos.isEmpty)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagResultCacheTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\n@testable\nimport AirshipFeatureFlags\n\nfinal class FeatureFlagResultCacheTest: XCTestCase {\n    private let airshipCache: TestCache = TestCache()\n    private var resultCache: DefaultFeatureFlagResultCache!\n\n    override func setUp() {\n        self.resultCache = DefaultFeatureFlagResultCache(cache: self.airshipCache)\n    }\n\n    public func testSet() async {\n        let flag = FeatureFlag(name: UUID().uuidString, isEligible: true, exists: true)\n        await resultCache.cache(flag: flag, ttl: 100)\n\n        let entry = await airshipCache.entry(key: \"FeatureFlagResultCache:\\(flag.name)\")!\n        XCTAssertEqual(\n            try JSONDecoder().decode(FeatureFlag.self, from: entry.data),\n            flag\n        )\n        XCTAssertEqual(entry.ttl, 100)\n    }\n\n    public func testUpdate() async {\n        var flag = FeatureFlag(name: UUID().uuidString, isEligible: true, exists: true)\n        await resultCache.cache(flag: flag, ttl: 100)\n        flag.isEligible = false\n        await resultCache.cache(flag: flag, ttl: 99)\n\n        let entry = await airshipCache.entry(key: \"FeatureFlagResultCache:\\(flag.name)\")!\n        XCTAssertEqual(\n            try JSONDecoder().decode(FeatureFlag.self, from: entry.data),\n            flag\n        )\n        XCTAssertEqual(entry.ttl, 99)\n    }\n\n    public func testDeleteDoesNotExist() async {\n        await resultCache.removeCachedFlag(name: \"does not exist\")\n    }\n\n    public func testDelete() async {\n        let flag = FeatureFlag(name: UUID().uuidString, isEligible: true, exists: true)\n        await resultCache.cache(flag: flag, ttl: 100)\n        var entry = await airshipCache.entry(key: \"FeatureFlagResultCache:\\(flag.name)\")\n        XCTAssertNotNil(entry)\n\n        await resultCache.removeCachedFlag(name: flag.name)\n        entry = await airshipCache.entry(key: \"FeatureFlagResultCache:\\(flag.name)\")\n        XCTAssertNil(entry)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipFeatureFlags/Tests/FeatureFlagVariablesTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable\nimport AirshipCore\n\n@testable\nimport AirshipFeatureFlags\n\nfinal class FeatureFlagVariablesTest: XCTestCase {\n\n    func testCodableVariant() throws {\n        let json = \"\"\"\n              {\n                \"type\": \"variant\",\n                \"variants\": [\n                  {\n                    \"id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\",\n                    \"reporting_metadata\": {\n                      \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                      \"variant_id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\"\n                    },\n                    \"audience_selector\": {\n                      \"hash\": {\n                        \"audience_hash\": {\n                          \"hash_prefix\": \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                          \"num_hash_buckets\": 100,\n                          \"hash_identifier\": \"contact\",\n                          \"hash_algorithm\": \"farm_hash\"\n                        },\n                        \"audience_subset\": {\n                          \"min_hash_bucket\": 0,\n                          \"max_hash_bucket\": 9\n                        }\n                      }\n                    },\n                    \"compound_audience\": {\n                      \"selector\": {\n                        \"type\": \"atomic\",\n                        \"audience\": {\n                          \"new_user\": true\n                        }\n                      }\n                    },\n                    \"data\": {\n                      \"arbitrary_key_1\": \"some_value\",\n                      \"arbitrary_key_2\": \"some_other_value\"\n                    }\n                  },\n                  {\n                    \"id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\",\n                    \"reporting_metadata\": {\n                      \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                      \"variant_id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\"\n                    },\n                    \"audience_selector\": {\n                      \"hash\": {\n                        \"audience_hash\": {\n                          \"hash_prefix\": \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                          \"num_hash_buckets\": 100,\n                          \"hash_identifier\": \"contact\",\n                          \"hash_algorithm\": \"farm_hash\"\n                        },\n                        \"audience_subset\": {\n                          \"min_hash_bucket\": 0,\n                          \"max_hash_bucket\": 19\n                        }\n                      }\n                    },\n                    \"data\": {\n                      \"arbitrary_key_1\": \"different_value\",\n                      \"arbitrary_key_2\": \"different_other_value\"\n                    }\n                  },\n                  {\n                    \"id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\",\n                    \"reporting_metadata\": {\n                      \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                      \"variant_id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\"\n                    },\n                    \"data\": {\n                      \"arbitrary_key_1\": \"some default value\",\n                      \"arbitrary_key_2\": \"some other default value\"\n                    }\n                  }\n                ]\n              }\n        \"\"\"\n\n        let decoded: FeatureFlagVariables = try JSONDecoder().decode(\n            FeatureFlagVariables.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = FeatureFlagVariables.variant(\n            [\n                .init(\n                    id: \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\",\n                    audienceSelector: DeviceAudienceSelector(\n                        hashSelector: AudienceHashSelector(\n                            hash: .init(\n                                prefix: \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                                property: .contact,\n                                algorithm: .farm,\n                                seed: nil,\n                                numberOfBuckets: 100,\n                                overrides: nil\n                            ),\n                            bucket: .init(min: 0, max: 9)\n                        )\n                    ),\n                    compoundAudience: .init(selector: .atomic(DeviceAudienceSelector(newUser: true))),\n                    reportingMetadata: try AirshipJSON.wrap(\n                        [\n                            \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                            \"variant_id\": \"dda26cb5-e40b-4bc8-abb1-eb88240f7fd7\"\n                        ]\n                    ),\n                    data: try AirshipJSON.wrap(\n                        [\n                            \"arbitrary_key_1\": \"some_value\",\n                            \"arbitrary_key_2\": \"some_other_value\"\n                        ]\n                    )\n                ),\n                .init(\n                    id: \"15422380-ce8f-49df-a7b1-9755b88ec0ef\",\n                    audienceSelector: DeviceAudienceSelector(\n                        hashSelector: AudienceHashSelector(\n                            hash: .init(\n                                prefix: \"686f2c15-cf8c-47a6-ae9f-e749fc792a9d:\",\n                                property: .contact,\n                                algorithm: .farm,\n                                seed: nil,\n                                numberOfBuckets: 100,\n                                overrides: nil\n                            ),\n                            bucket: .init(min: 0, max: 19)\n                        )\n                    ),\n                    compoundAudience: nil,\n                    reportingMetadata: try AirshipJSON.wrap(\n                        [\n                            \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                            \"variant_id\": \"15422380-ce8f-49df-a7b1-9755b88ec0ef\"\n                        ]\n                    ),\n                    data: try AirshipJSON.wrap(\n                        [\n                            \"arbitrary_key_1\": \"different_value\",\n                            \"arbitrary_key_2\": \"different_other_value\"\n                        ]\n                    )\n                ),\n                .init(\n                    id: \"40e08a3d-8901-40fc-a01a-e6c263bec895\",\n                    audienceSelector: nil,\n                    compoundAudience: nil,\n                    reportingMetadata: try AirshipJSON.wrap(\n                        [\n                            \"flag_id\": \"27f26d85-0550-4df5-85f0-7022fa7a5925\",\n                            \"variant_id\": \"40e08a3d-8901-40fc-a01a-e6c263bec895\"\n                        ]\n                    ),\n                    data: try AirshipJSON.wrap(\n                        [\n                            \"arbitrary_key_1\": \"some default value\",\n                            \"arbitrary_key_2\": \"some other default value\"\n                        ]\n                    )\n                )\n            ]\n        )\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n\n    func testCodableFixed() throws {\n        let json = \"\"\"\n          {\n             \"type\":\"fixed\",\n              \"data\":{\n                  \"arbitrary_key_1\":\"some_value\",\n                  \"arbitrary_key_2\":\"some_other_value\"\n               }\n          }\n        \"\"\"\n\n        let decoded: FeatureFlagVariables = try JSONDecoder().decode(\n            FeatureFlagVariables.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = FeatureFlagVariables.fixed(\n            try AirshipJSON.wrap(\n                [\n                    \"arbitrary_key_1\": \"some_value\",\n                    \"arbitrary_key_2\": \"some_other_value\"\n                ]\n            )\n        )\n\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n\n    func testCodableFixedNullData() throws {\n        let json = \"\"\"\n          {\n             \"type\":\"fixed\"\n          }\n        \"\"\"\n\n        let decoded: FeatureFlagVariables = try JSONDecoder().decode(\n            FeatureFlagVariables.self,\n            from: json.data(using: .utf8)!\n        )\n\n        let expected = FeatureFlagVariables.fixed(nil)\n\n        XCTAssertEqual(decoded, expected)\n\n        let encoded = String(data: try JSONEncoder().encode(decoded), encoding: .utf8)\n        XCTAssertEqual(try AirshipJSON.from(json: json), try AirshipJSON.from(json: encoded))\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleSignature</key>\n\t<string>????</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>NSPrincipalClass</key>\n\t<string></string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/TestAssets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/TestAssets.xcassets/testNamedColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.500\",\n          \"green\" : \"0.500\",\n          \"red\" : \"0.500\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"light\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"1.000\",\n          \"green\" : \"1.000\",\n          \"red\" : \"1.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    },\n    {\n      \"appearances\" : [\n        {\n          \"appearance\" : \"luminosity\",\n          \"value\" : \"dark\"\n        }\n      ],\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0.000\",\n          \"green\" : \"0.000\",\n          \"red\" : \"0.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/.xccurrentversion",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>_XCCurrentVersionName</key>\n\t<string>UAInbox 4.xcdatamodel</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 2.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22522\" systemVersion=\"23A344\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAInboxMessage\" representedClassName=\"UAInboxMessageData\" syncable=\"YES\">\n        <attribute name=\"deletedClient\" attributeType=\"Boolean\" defaultValueString=\"NO\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"extra\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAJSONValueTransformer\"/>\n        <attribute name=\"messageBodyURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageExpiration\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"messageReporting\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageSent\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"rawMessageObject\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"title\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"unread\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"unreadClient\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 3.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"22522\" systemVersion=\"23D56\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAInboxMessage\" representedClassName=\"UAInboxMessageData\" syncable=\"YES\">\n        <attribute name=\"deletedClient\" attributeType=\"Boolean\" defaultValueString=\"NO\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"extra\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageBodyURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageExpiration\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"messageReporting\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageSent\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"rawMessageObject\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"title\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"unread\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"unreadClient\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 4.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"24299\" systemVersion=\"24G419\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAInboxMessage\" representedClassName=\"UAInboxMessageData\" syncable=\"YES\">\n        <attribute name=\"associatedData\" optional=\"YES\" attributeType=\"Binary\"/>\n        <attribute name=\"contentType\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"deletedClient\" attributeType=\"Boolean\" defaultValueString=\"NO\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"extra\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageBodyURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageExpiration\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"messageReporting\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"messageSent\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"rawMessageObject\" optional=\"YES\" attributeType=\"Binary\" valueTransformerName=\"NSSecureUnarchiveFromDataTransformer\"/>\n        <attribute name=\"title\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"unread\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"unreadClient\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n    </entity>\n</model>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox.xcdatamodel/contents",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"yes\"?>\n<model type=\"com.apple.IDECoreDataModeler.DataModel\" documentVersion=\"1.0\" lastSavedToolsVersion=\"17192\" systemVersion=\"19H2\" minimumToolsVersion=\"Automatic\" sourceLanguage=\"Swift\" userDefinedModelVersionIdentifier=\"\">\n    <entity name=\"UAInboxMessage\" representedClassName=\"UAInboxMessageData\" syncable=\"YES\">\n        <attribute name=\"deletedClient\" attributeType=\"Boolean\" defaultValueString=\"NO\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"extra\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UAJSONValueTransformer\"/>\n        <attribute name=\"messageBodyURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSURLValueTransformer\"/>\n        <attribute name=\"messageExpiration\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageID\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"messageSent\" optional=\"YES\" attributeType=\"Date\" usesScalarValueType=\"NO\"/>\n        <attribute name=\"messageURL\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSURLValueTransformer\"/>\n        <attribute name=\"rawMessageObject\" optional=\"YES\" attributeType=\"Transformable\" valueTransformerName=\"UANSDictionaryValueTransformer\"/>\n        <attribute name=\"title\" optional=\"YES\" attributeType=\"String\"/>\n        <attribute name=\"unread\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n        <attribute name=\"unreadClient\" attributeType=\"Boolean\" defaultValueString=\"YES\" usesScalarValueType=\"YES\"/>\n    </entity>\n    <elements>\n        <element name=\"UAInboxMessage\" positionX=\"-63\" positionY=\"-18\" width=\"128\" height=\"208\"/>\n    </elements>\n</model>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInboxDataMappingV1toV4.xcmappingmodel/xcmapping.xml",
    "content": "<?xml version=\"1.0\" standalone=\"yes\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n    <databaseInfo>\n        <version>134481920</version>\n        <UUID>746F2920-DF24-481B-B805-EFD92FAD9619</UUID>\n        <nextObjectID>116</nextObjectID>\n        <metadata>\n            <plist version=\"1.0\">\n                <dict>\n                    <key>NSPersistenceFrameworkVersion</key>\n                    <integer>1518</integer>\n                    <key>NSPersistenceMaximumFrameworkVersion</key>\n                    <integer>1518</integer>\n                    <key>NSStoreModelVersionChecksumKey</key>\n                    <string>bMpud663vz0bXQE24C6Rh4MvJ5jVnzsD2sI3njZkKbc=</string>\n                    <key>NSStoreModelVersionHashes</key>\n                    <dict>\n                        <key>XDDevAttributeMapping</key>\n                        <data>\n\t\t0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=\n\t\t</data>\n                        <key>XDDevEntityMapping</key>\n                        <data>\n\t\tqeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=\n\t\t</data>\n                        <key>XDDevMappingModel</key>\n                        <data>\n\t\tEqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=\n\t\t</data>\n                        <key>XDDevPropertyMapping</key>\n                        <data>\n\t\tXN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA=\n\t\t</data>\n                        <key>XDDevRelationshipMapping</key>\n                        <data>\n\t\takYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs=\n\t\t</data>\n                    </dict>\n                    <key>NSStoreModelVersionHashesDigest</key>\n                    <string>+Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A==</string>\n                    <key>NSStoreModelVersionHashesVersion</key>\n                    <integer>3</integer>\n                    <key>NSStoreModelVersionIdentifiers</key>\n                    <array>\n                        <string></string>\n                    </array>\n                </dict>\n            </plist>\n        </metadata>\n    </databaseInfo>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z102\">\n        <attribute name=\"name\" type=\"string\">messageReporting</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z103\">\n        <attribute name=\"name\" type=\"string\">rawMessageObject</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVENTITYMAPPING\" id=\"z104\">\n        <attribute name=\"migrationpolicyclassname\" type=\"string\">UAInboxDataMappingV1toV4</attribute>\n        <attribute name=\"sourcename\" type=\"string\">UAInboxMessage</attribute>\n        <attribute name=\"mappingtypename\" type=\"string\">Undefined</attribute>\n        <attribute name=\"mappingnumber\" type=\"int16\">1</attribute>\n        <attribute name=\"destinationname\" type=\"string\">UAInboxMessage</attribute>\n        <attribute name=\"autogenerateexpression\" type=\"bool\">1</attribute>\n        <relationship name=\"mappingmodel\" type=\"1/1\" destination=\"XDDEVMAPPINGMODEL\" idrefs=\"z108\"></relationship>\n        <relationship name=\"attributemappings\" type=\"0/0\" destination=\"XDDEVATTRIBUTEMAPPING\" idrefs=\"z103 z110 z109 z113 z112 z107 z102 z116 z111 z106 z115 z105 z114\"></relationship>\n        <relationship name=\"relationshipmappings\" type=\"0/0\" destination=\"XDDEVRELATIONSHIPMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z105\">\n        <attribute name=\"name\" type=\"string\">unread</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z106\">\n        <attribute name=\"name\" type=\"string\">contentType</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z107\">\n        <attribute name=\"name\" type=\"string\">extra</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVMAPPINGMODEL\" id=\"z108\">\n        <attribute name=\"sourcemodelpath\" type=\"string\">AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox.xcdatamodel</attribute>\n        <attribute name=\"sourcemodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBXgALAAwAGQA1ADYANwA/AEAAWwBcAF0AYwBkAHAAhgCHAIgAiQCKAIsAjACNAI4AjwCoAKsAsgC4AMcA1gDZAOgA9wD6AFoBCgEZAR0BIQEwATYBNwE/AU4BTwFYAXIBcwF0AXUBdgF3AXgBeQF6AXsBfAF9AZIBkwGbAZwBnQGpAb0BvgG/AcABwQHCAcMBxAHFAdQB4wHyAfYCBQIUAiMCMgJBAk0CXwJgAmECYgJjAmQCZQJmAnUCdgKFApQCowKkArMCwgLRAtkC7gLvAvcDAwMXAyYDNQNEA0gDVwNmA2cDdgOFA5QDoAOyA8ED0APfA+4D7wP+BA0EHAQxBDIEOgRGBFoEaQR4BIcEiwSaBKkEuATHBNYE4gT0BQMFEgUhBTAFMQVABU8FUAVfBXQFdQV9BYkFnQWsBbsFygXOBd0F7AX7BgoGGQYlBjcGRgZHBlYGZQZ0BoMGkgahBrYGtwa/BssG3wbuBv0HDAcQBx8HLgc9B0wHWwdnB3kHiAeXB6YHtQfEB9MH1AfjB/gH+QgBCA0IIQgwCD8ITghSCGEIcAh/CI4InQipCLsIygjZCOgI9wj4CQcJFgklCToJOwlDCU8JYwlyCYEJkAmUCaMJsgnBCdAJ3wnrCf0KDAobCioKOQpIClcKWApnCnwKfQqFCpEKpQq0CsMK0grWCuUK9AsDCxILIQstCz8LTgtdC2wLewuKC5kLmgupC74LvwvHC9ML5wv2DAUMFAwYDCcMNgxFDFQMYwxvDIEMkAyfDK4MvQzMDNsM6gz/DQANCA0UDSgNNw1GDVUNWQ1oDXcNhg2VDaQNsA3CDdEN4A3vDf4ODQ4cDisOQA5BDkkOVQ5pDngOhw6WDpoOqQ64DscO1g7lDvEPAw8SDyEPMA8/D04PXQ9sD20PcA95D30PgQ+FD40PkA+UD5VVJG51bGzWAA0ADgAPABAAEQASABMAFAAVABYAFwAYXxAPX3hkX3Jvb3RQYWNrYWdlViRjbGFzc11feGRfbW9kZWxOYW1lXF94ZF9jb21tZW50c18QFV9jb25maWd1cmF0aW9uc0J5TmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKBAV2AAIEBWoEBW4EBXN4AGgAbABwAHQAeAB8AIAAOACEAIgAjACQAJQAmACcAKAApAAkAJwAVAC0ALgAvADAAMQAnACcAFV8QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABIEBWIEBVoABgASAAIEBV4EBWRAAgAWAA4AEgASAAFBTWUVT0wA4ADkADgA6ADwAPldOUy5rZXlzWk5TLm9iamVjdHOhADuABqEAPYAHgCVeVUFJbmJveE1lc3NhZ2XfEBAAQQBCAEMARAAfAEUARgAhAEcASAAOACMASQBKACYASwBMAE0AJwAnABMAUQBSAC8AJwBMAFUAOwBMAFgAWQBaXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zZHVwbGljYXRlc18QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNzdG9yYWdlW19pc0Fic3RyYWN0gAmALYAEgASAAoAKgQFTgASACYEBVYAGgAmBAVSACAgS5f8u2VdvcmRlcmVk0wA4ADkADgBeAGAAPqEAX4ALoQBhgAyAJV5YRF9QU3RlcmVvdHlwZdkAHwAjAGUADgAmAGYAIQBLAGcAPQBfAEwAawAVACcALwBaAG9fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAB4ALgAmALIAAgAQIgA3TADgAOQAOAHEAewA+qQByAHMAdAB1AHYAdwB4AHkAeoAOgA+AEIARgBKAE4AUgBWAFqkAfAB9AH4AfwCAAIEAggCDAISAF4AbgByAHoAfgCGAI4AmgCqAJV8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAZWERQTVVuaXF1ZW5lc3NDb25zdHJhaW50c18QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAZWERfUFNLX2ZldGNoUmVxdWVzdHNBcnJheV8QEVhEX1BTS19pc0Fic3RyYWN0XxAPWERfUFNLX3VzZXJJbmZvXxATWERfUFNLX2NsYXNzTWFwcGluZ18QFlhEX1BTS19lbnRpdHlDbGFzc05hbWXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQCbABUAYQBaAFoAWgAvAFoAogByAFoAWgAVAFpVX3R5cGVYX2RlZmF1bHRcX2Fzc29jaWF0aW9uW19pc1JlYWRPbmx5WV9pc1N0YXRpY1lfaXNVbmlxdWVaX2lzRGVyaXZlZFpfaXNPcmRlcmVkXF9pc0NvbXBvc2l0ZVdfaXNMZWFmgACAGIAAgAwICAgIgBqADggIgAAI0gA5AA4AqQCqoIAZ0gCsAK0ArgCvWiRjbGFzc25hbWVYJGNsYXNzZXNeTlNNdXRhYmxlQXJyYXmjAK4AsACxV05TQXJyYXlYTlNPYmplY3TSAKwArQCzALRfEBBYRFVNTFByb3BlcnR5SW1wpAC1ALYAtwCxXxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAGEAWgBaAFoALwBaAKIAcwBaAFoAFQBagACAAIAAgAwICAgIgBqADwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAyQAVAGEAWgBaAFoALwBaAKIAdABaAFoAFQBagACAHYAAgAwICAgIgBqAEAgIgAAI0gA5AA4A1wCqoIAZ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAGEAWgBaAFoALwBaAKIAdQBaAFoAFQBagACAAIAAgAwICAgIgBqAEQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA6gAVAGEAWgBaAFoALwBaAKIAdgBaAFoAFQBagACAIIAAgAwICAgIgBqAEggIgAAI0gA5AA4A+ACqoIAZ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAGEAWgBaAFoALwBaAKIAdwBaAFoAFQBagACAIoAAgAwICAgIgBqAEwgIgAAICN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAQwAFQBhAFoAWgBaAC8AWgCiAHgAWgBaABUAWoAAgCSAAIAMCAgICIAagBQICIAACNMAOAA5AA4BGgEbAD6goIAl0gCsAK0BHgEfXxATTlNNdXRhYmxlRGljdGlvbmFyeaMBHgEgALFcTlNEaWN0aW9uYXJ53xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBIwAVAGEAWgBaAFoALwBaAKIAeQBaAFoAFQBagACAJ4AAgAwICAgIgBqAFQgIgAAI1gAjAA4AJgBLAB8AIQExATIAFQBaABUAL4AogCmAAAiAAF8QFFhER2VuZXJpY1JlY29yZENsYXNz0gCsAK0BOAE5XVhEVU1MQ2xhc3NJbXCmAToBOwE8AT0BPgCxXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBQQAVAGEAWgBaAFoALwBaAKIAegBaAFoAFQBagACAK4AAgAwICAgIgBqAFggIgAAIXxASVUFJbmJveE1lc3NhZ2VEYXRh0gCsAK0BUAFRXxASWERVTUxTdGVyZW90eXBlSW1wpwFSAVMBVAFVAVYBVwCxXxASWERVTUxTdGVyZW90eXBlSW1wXVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADgFZAWUAPqsBWgFbAVwBXQFeAV8BYAFhAWIBYwFkgC6AL4AwgDGAMoAzgDSANYA2gDeAOKsBZgFnAWgBaQFqAWsBbAFtAW4BbwFwgDmAZIB9gJaAroDGgN6A9oEBDoEBJYEBPIAlVnVucmVhZFltZXNzYWdlSURabWVzc2FnZVVSTF1kZWxldGVkQ2xpZW50VWV4dHJhW21lc3NhZ2VTZW50Xm1lc3NhZ2VCb2R5VVJMXxAQcmF3TWVzc2FnZU9iamVjdFV0aXRsZV8QEW1lc3NhZ2VFeHBpcmF0aW9uXHVucmVhZENsaWVudN8QEgCQAJEAkgF+AB8AlACVAX8AIQCTAYAAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAYgALwBaAEwAWgGMAVoAWgBaAZAAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIA7CIAJCIBjgC4ICIA6CBMAAAABIXF1TtMAOAA5AA4BlAGXAD6iAZUBloA8gD2iAZgBmYA+gFGAJV8QElhEX1BQcm9wU3RlcmVvdHlwZV8QElhEX1BBdHRfU3RlcmVvdHlwZdkAHwAjAZ4ADgAmAZ8AIQBLAaABZgGVAEwAawAVACcALwBaAahfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAOYA8gAmALIAAgAQIgD/TADgAOQAOAaoBswA+qAGrAawBrQGuAa8BsAGxAbKAQIBBgEKAQ4BEgEWARoBHqAG0AbUBtgG3AbgBuQG6AbuASIBJgEqATIBNgE6AT4BQgCVfEBtYRF9QUFNLX2lzU3RvcmVkSW5UcnV0aEZpbGVfEBtYRF9QUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBBYRF9QUFNLX3VzZXJJbmZvXxARWERfUFBTS19pc0luZGV4ZWRfEBJYRF9QUFNLX2lzT3B0aW9uYWxfEBpYRF9QUFNLX2lzU3BvdGxpZ2h0SW5kZXhlZF8QEVhEX1BQU0tfZWxlbWVudElEXxATWERfUFBTS19pc1RyYW5zaWVudN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGYAFoAWgBaAC8AWgCiAasAWgBaABUAWoAAgCKAAIA+CAgICIAagEAICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGYAFoAWgBaAC8AWgCiAawAWgBaABUAWoAAgACAAIA+CAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAeUAFQGYAFoAWgBaAC8AWgCiAa0AWgBaABUAWoAAgEuAAIA+CAgICIAagEIICIAACNMAOAA5AA4B8wH0AD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZgAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgD4ICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZgAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAIoAAgD4ICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZgAWgBaAFoALwBaAKIBsABaAFoAFQBagACAIoAAgD4ICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZgAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAAIAAgD4ICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZgAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgD4ICAgIgBqARwgIgAAI2QAfACMCQgAOACYCQwAhAEsCRAFmAZYATABrABUAJwAvAFoCTF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA5gD2ACYAsgACABAiAUtMAOAA5AA4CTgJWAD6nAk8CUAJRAlICUwJUAlWAU4BUgFWAVoBXgFiAWacCVwJYAlkCWgJbAlwCXYBagFyAXYBegGCAYYBigCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQJoABUBmQBaAFoAWgAvAFoAogJPAFoAWgAVAFqAAIBbgACAUQgICAiAGoBTCAiAAAhTWUVT3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZkAWgBaAFoALwBaAKICUABaAFoAFQBagACAIoAAgFEICAgIgBqAVAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZkAWgBaAFoALwBaAKICUQBaAFoAFQBagACAAIAAgFEICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUClgAVAZkAWgBaAFoALwBaAKICUgBaAFoAFQBagACAX4AAgFEICAgIgBqAVggIgAAIEQMg3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZkAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgFEICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZkAWgBaAFoALwBaAKICVABaAFoAFQBagACAAIAAgFEICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZkAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgFEICAgIgBqAWQgIgAAI0gCsAK0C0gLTXVhEUE1BdHRyaWJ1dGWmAtQC1QLWAtcC2ACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAtoAHwCUAJUC2wAhAJMC3ACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC5AAvAFoATABaAYwBWwBaAFoC7ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGYIgAkIgGOALwgIgGUIEunC0zjTADgAOQAOAvAC8wA+ogGVAZaAPIA9ogL0AvWAZ4BzgCXZAB8AIwL4AA4AJgL5ACEASwL6AWcBlQBMAGsAFQAnAC8AWgMCXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgGSAPIAJgCyAAIAECIBo0wA4ADkADgMEAw0APqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gDDgMPAxADEQMSAxMDFAMVgGmAaoBrgG2AboBwgHGAcoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvQAWgBaAFoALwBaAKIBqwBaAFoAFQBagACAIoAAgGcICAgIgBqAQAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvQAWgBaAFoALwBaAKIBrABaAFoAFQBagACAAIAAgGcICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDNwAVAvQAWgBaAFoALwBaAKIBrQBaAFoAFQBagACAbIAAgGcICAgIgBqAQggIgAAI0wA4ADkADgNFA0YAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC9ABaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAZwgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQNZABUC9ABaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIBvgACAZwgICAiAGoBECAiAAAgJ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvQAWgBaAFoALwBaAKIBsABaAFoAFQBagACAIoAAgGcICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvQAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAAIAAgGcICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvQAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgGcICAgIgBqARwgIgAAI2QAfACMDlQAOACYDlgAhAEsDlwFnAZYATABrABUAJwAvAFoDn18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBkgD2ACYAsgACABAiAdNMAOAA5AA4DoQOpAD6nAk8CUAJRAlICUwJUAlWAU4BUgFWAVoBXgFiAWacDqgOrA6wDrQOuA68DsIB1gHaAd4B4gHqAe4B8gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC9QBaAFoAWgAvAFoAogJPAFoAWgAVAFqAAIAAgACAcwgICAiAGoBTCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC9QBaAFoAWgAvAFoAogJQAFoAWgAVAFqAAIAigACAcwgICAiAGoBUCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC9QBaAFoAWgAvAFoAogJRAFoAWgAVAFqAAIAAgACAcwgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQPhABUC9QBaAFoAWgAvAFoAogJSAFoAWgAVAFqAAIB5gACAcwgICAiAGoBWCAiAAAgRArzfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC9QBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACAcwgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC9QBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAAgACAcwgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC9QBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACAcwgICAiAGoBZCAiAAAjfEBIAkACRAJIEHQAfAJQAlQQeACEAkwQfAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgQnAC8AWgBMAFoBjAFcAFoAWgQvAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAfwiACQiAY4AwCAiAfggTAAAAASQJCmbTADgAOQAOBDMENgA+ogGVAZaAPIA9ogQ3BDiAgICLgCXZAB8AIwQ7AA4AJgQ8ACEASwQ9AWgBlQBMAGsAFQAnAC8AWgRFXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgH2APIAJgCyAAIAECICB0wA4ADkADgRHBFAAPqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gEUQRSBFMEVARVBFYEVwRYgIKAg4CEgIaAh4CIgImAioAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBDcAWgBaAFoALwBaAKIBqwBaAFoAFQBagACAIoAAgIAICAgIgBqAQAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDcAWgBaAFoALwBaAKIBrABaAFoAFQBagACAAIAAgIAICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUEegAVBDcAWgBaAFoALwBaAKIBrQBaAFoAFQBagACAhYAAgIAICAgIgBqAQggIgAAI0wA4ADkADgSIBIkAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUENwBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAgAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQNZABUENwBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIBvgACAgAgICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUENwBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIAigACAgAgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUENwBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAAgACAgAgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUENwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAigACAgAgICAiAGoBHCAiAAAjZAB8AIwTXAA4AJgTYACEASwTZAWgBlgBMAGsAFQAnAC8AWgThXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgH2APYAJgCyAAIAECICM0wA4ADkADgTjBOsAPqcCTwJQAlECUgJTAlQCVYBTgFSAVYBWgFeAWIBZpwTsBO0E7gTvBPAE8QTygI2AjoCPgJCAkoCTgJWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ4AFoAWgBaAC8AWgCiAk8AWgBaABUAWoAAgACAAICLCAgICIAagFMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ4AFoAWgBaAC8AWgCiAlAAWgBaABUAWoAAgCKAAICLCAgICIAagFQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ4AFoAWgBaAC8AWgCiAlEAWgBaABUAWoAAgACAAICLCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBSMAFQQ4AFoAWgBaAC8AWgCiAlIAWgBaABUAWoAAgJGAAICLCAgICIAagFYICIAACBEHCN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ4AFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAICLCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBUIAFQQ4AFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgJSAAICLCAgICIAagFgICIAACF8QF1VBTlNVUkxWYWx1ZVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDgAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgIsICAgIgBqAWQgIgAAI3xASAJAAkQCSBWAAHwCUAJUFYQAhAJMFYgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoFagAvAFoATABaAYwBXQBaAFoFcgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgJgIgAkIgGOAMQgIgJcIEqyLopzTADgAOQAOBXYFeQA+ogGVAZaAPIA9ogV6BXuAmYCkgCXZAB8AIwV+AA4AJgV/ACEASwWAAWkBlQBMAGsAFQAnAC8AWgWIXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJaAPIAJgCyAAIAECICa0wA4ADkADgWKBZMAPqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gFlAWVBZYFlwWYBZkFmgWbgJuAnICdgJ+AoIChgKKAo4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBXoAWgBaAFoALwBaAKIBqwBaAFoAFQBagACAIoAAgJkICAgIgBqAQAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBXoAWgBaAFoALwBaAKIBrABaAFoAFQBagACAAIAAgJkICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFvQAVBXoAWgBaAFoALwBaAKIBrQBaAFoAFQBagACAnoAAgJkICAgIgBqAQggIgAAI0wA4ADkADgXLBcwAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFegBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAmQgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFegBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAigACAmQgICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFegBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIAigACAmQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFegBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAAgACAmQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFegBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAigACAmQgICAiAGoBHCAiAAAjZAB8AIwYaAA4AJgYbACEASwYcAWkBlgBMAGsAFQAnAC8AWgYkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJaAPYAJgCyAAIAECICl0wA4ADkADgYmBi4APqcCTwJQAlECUgJTAlQCVYBTgFSAVYBWgFeAWIBZpwYvBjAGMQYyBjMGNAY1gKaAqICpgKqAq4CsgK2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBjkAFQV7AFoAWgBaAC8AWgCiAk8AWgBaABUAWoAAgKeAAICkCAgICIAagFMICIAACFJOT98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQV7AFoAWgBaAC8AWgCiAlAAWgBaABUAWoAAgCKAAICkCAgICIAagFQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV7AFoAWgBaAC8AWgCiAlEAWgBaABUAWoAAgACAAICkCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVApYAFQV7AFoAWgBaAC8AWgCiAlIAWgBaABUAWoAAgF+AAICkCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV7AFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAICkCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV7AFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgACAAICkCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV7AFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAICkCAgICIAagFkICIAACN8QEgCQAJEAkgaiAB8AlACVBqMAIQCTBqQAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBqwALwBaAEwAWgGMAV4AWgBaBrQAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICICwCIAJCIBjgDIICICvCBK7OgT90wA4ADkADga4BrsAPqIBlQGWgDyAPaIGvAa9gLGAvIAl2QAfACMGwAAOACYGwQAhAEsGwgFqAZUATABrABUAJwAvAFoGyl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCugDyACYAsgACABAiAstMAOAA5AA4GzAbVAD6oAasBrAGtAa4BrwGwAbEBsoBAgEGAQoBDgESARYBGgEeoBtYG1wbYBtkG2gbbBtwG3YCzgLSAtYC3gLiAuYC6gLuAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQa8AFoAWgBaAC8AWgCiAasAWgBaABUAWoAAgCKAAICxCAgICIAagEAICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQa8AFoAWgBaAC8AWgCiAawAWgBaABUAWoAAgACAAICxCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBv8AFQa8AFoAWgBaAC8AWgCiAa0AWgBaABUAWoAAgLaAAICxCAgICIAagEIICIAACNMAOAA5AA4HDQcOAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBrwAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgLEICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDWQAVBrwAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAb4AAgLEICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBrwAWgBaAFoALwBaAKIBsABaAFoAFQBagACAIoAAgLEICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBrwAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAAIAAgLEICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBrwAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgLEICAgIgBqARwgIgAAI2QAfACMHXAAOACYHXQAhAEsHXgFqAZYATABrABUAJwAvAFoHZl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCugD2ACYAsgACABAiAvdMAOAA5AA4HaAdwAD6nAk8CUAJRAlICUwJUAlWAU4BUgFWAVoBXgFiAWacHcQdyB3MHdAd1B3YHd4C+gL+AwIDBgMKAw4DFgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvQBaAFoAWgAvAFoAogJPAFoAWgAVAFqAAIAAgACAvAgICAiAGoBTCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGvQBaAFoAWgAvAFoAogJQAFoAWgAVAFqAAIAigACAvAgICAiAGoBUCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvQBaAFoAWgAvAFoAogJRAFoAWgAVAFqAAIAAgACAvAgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQUjABUGvQBaAFoAWgAvAFoAogJSAFoAWgAVAFqAAICRgACAvAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvQBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACAvAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQfGABUGvQBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIDEgACAvAgICAiAGoBYCAiAAAhfEBZVQUpTT05WYWx1ZVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBr0AWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgLwICAgIgBqAWQgIgAAI3xASAJAAkQCSB+QAHwCUAJUH5QAhAJMH5gCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoH7gAvAFoATABaAYwBXwBaAFoH9gBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgMgIgAkIgGOAMwgIgMcIEsBLINHTADgAOQAOB/oH/QA+ogGVAZaAPIA9ogf+B/+AyYDUgCXZAB8AIwgCAA4AJggDACEASwgEAWsBlQBMAGsAFQAnAC8AWggMXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgMaAPIAJgCyAAIAECIDK0wA4ADkADggOCBcAPqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gIGAgZCBoIGwgcCB0IHggfgMuAzIDNgM+A0IDRgNKA04Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVB/4AWgBaAFoALwBaAKIBqwBaAFoAFQBagACAIoAAgMkICAgIgBqAQAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVB/4AWgBaAFoALwBaAKIBrABaAFoAFQBagACAAIAAgMkICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUIQQAVB/4AWgBaAFoALwBaAKIBrQBaAFoAFQBagACAzoAAgMkICAgIgBqAQggIgAAI0wA4ADkADghPCFAAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUH/gBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAyQgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQNZABUH/gBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIBvgACAyQgICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUH/gBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIAigACAyQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUH/gBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAAgACAyQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUH/gBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAigACAyQgICAiAGoBHCAiAAAjZAB8AIwieAA4AJgifACEASwigAWsBlgBMAGsAFQAnAC8AWgioXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgMaAPYAJgCyAAIAECIDV0wA4ADkADgiqCLIAPqcCTwJQAlECUgJTAlQCVYBTgFSAVYBWgFeAWIBZpwizCLQItQi2CLcIuAi5gNaA14DYgNmA24DcgN2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQf/AFoAWgBaAC8AWgCiAk8AWgBaABUAWoAAgACAAIDUCAgICIAagFMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQf/AFoAWgBaAC8AWgCiAlAAWgBaABUAWoAAgCKAAIDUCAgICIAagFQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQf/AFoAWgBaAC8AWgCiAlEAWgBaABUAWoAAgACAAIDUCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCOoAFQf/AFoAWgBaAC8AWgCiAlIAWgBaABUAWoAAgNqAAIDUCAgICIAagFYICIAACBEDhN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQf/AFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIDUCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQf/AFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgACAAIDUCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQf/AFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIDUCAgICIAagFkICIAACN8QEgCQAJEAkgkmAB8AlACVCScAIQCTCSgAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaCTAALwBaAEwAWgGMAWAAWgBaCTgAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIDgCIAJCIBjgDQICIDfCBMAAAABGm7ardMAOAA5AA4JPAk/AD6iAZUBloA8gD2iCUAJQYDhgOyAJdkAHwAjCUQADgAmCUUAIQBLCUYBbAGVAEwAawAVACcALwBaCU5fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA3oA8gAmALIAAgAQIgOLTADgAOQAOCVAJWQA+qAGrAawBrQGuAa8BsAGxAbKAQIBBgEKAQ4BEgEWARoBHqAlaCVsJXAldCV4JXwlgCWGA44DkgOWA54DogOmA6oDrgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJQABaAFoAWgAvAFoAogGrAFoAWgAVAFqAAIAigACA4QgICAiAGoBACAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJQABaAFoAWgAvAFoAogGsAFoAWgAVAFqAAIAAgACA4QgICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQmDABUJQABaAFoAWgAvAFoAogGtAFoAWgAVAFqAAIDmgACA4QgICAiAGoBCCAiAAAjTADgAOQAOCZEJkgA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlAAFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIDhCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA1kAFQlAAFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgG+AAIDhCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlAAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgCKAAIDhCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlAAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgACAAIDhCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlAAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgCKAAIDhCAgICIAagEcICIAACNkAHwAjCeAADgAmCeEAIQBLCeIBbAGWAEwAawAVACcALwBaCepfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA3oA9gAmALIAAgAQIgO3TADgAOQAOCewJ9AA+pwJPAlACUQJSAlMCVAJVgFOAVIBVgFaAV4BYgFmnCfUJ9gn3CfgJ+Qn6CfuA7oDvgPCA8YDygPOA9YAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUEAWgBaAFoALwBaAKICTwBaAFoAFQBagACAAIAAgOwICAgIgBqAUwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCUEAWgBaAFoALwBaAKICUABaAFoAFQBagACAIoAAgOwICAgIgBqAVAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUEAWgBaAFoALwBaAKICUQBaAFoAFQBagACAAIAAgOwICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFIwAVCUEAWgBaAFoALwBaAKICUgBaAFoAFQBagACAkYAAgOwICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUEAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgOwICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKSgAVCUEAWgBaAFoALwBaAKICVABaAFoAFQBagACA9IAAgOwICAgIgBqAWAgIgAAIXxAXVUFOU1VSTFZhbHVlVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJQQBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACA7AgICAiAGoBZCAiAAAjfEBIAkACRAJIKaAAfAJQAlQppACEAkwpqAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgpyAC8AWgBMAFoBjAFhAFoAWgp6AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiA+AiACQiAY4A1CAiA9wgSoVCAedMAOAA5AA4KfgqBAD6iAZUBloA8gD2iCoIKg4D5gQEEgCXZAB8AIwqGAA4AJgqHACEASwqIAW0BlQBMAGsAFQAnAC8AWgqQXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgPaAPIAJgCyAAIAECID60wA4ADkADgqSCpsAPqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gKnAqdCp4KnwqgCqEKogqjgPuA/ID9gP+BAQCBAQGBAQKBAQOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqCAFoAWgBaAC8AWgCiAasAWgBaABUAWoAAgCKAAID5CAgICIAagEAICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqCAFoAWgBaAC8AWgCiAawAWgBaABUAWoAAgACAAID5CAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCsUAFQqCAFoAWgBaAC8AWgCiAa0AWgBaABUAWoAAgP6AAID5CAgICIAagEIICIAACNMAOAA5AA4K0wrUAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoIAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgPkICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDWQAVCoIAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAb4AAgPkICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoIAWgBaAFoALwBaAKIBsABaAFoAFQBagACAIoAAgPkICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoIAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAAIAAgPkICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoIAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgPkICAgIgBqARwgIgAAI2QAfACMLIgAOACYLIwAhAEsLJAFtAZYATABrABUAJwAvAFoLLF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYD2gD2ACYAsgACABAiBAQXTADgAOQAOCy4LNgA+pwJPAlACUQJSAlMCVAJVgFOAVIBVgFaAV4BYgFmnCzcLOAs5CzoLOws8Cz2BAQaBAQeBAQiBAQmBAQqBAQuBAQ2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqDAFoAWgBaAC8AWgCiAk8AWgBaABUAWoAAgACAAIEBBAgICAiAGoBTCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKgwBaAFoAWgAvAFoAogJQAFoAWgAVAFqAAIAigACBAQQICAgIgBqAVAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoMAWgBaAFoALwBaAKICUQBaAFoAFQBagACAAIAAgQEECAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBSMAFQqDAFoAWgBaAC8AWgCiAlIAWgBaABUAWoAAgJGAAIEBBAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKgwBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACBAQQICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABULjAAVCoMAWgBaAFoALwBaAKICVABaAFoAFQBagACBAQyAAIEBBAgICAiAGoBYCAiAAAhfEB5VQU5TRGljdGlvbmFyeVZhbHVlVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKgwBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACBAQQICAgIgBqAWQgIgAAI3xASAJAAkQCSC6oAHwCUAJULqwAhAJMLrACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoLtAAvAFoATABaAYwBYgBaAFoLvABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQEQCIAJCIBjgDYICIEBDwgSQSEQUtMAOAA5AA4LwAvDAD6iAZUBloA8gD2iC8QLxYEBEYEBHIAl2QAfACMLyAAOACYLyQAhAEsLygFuAZUATABrABUAJwAvAFoL0l8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBDoA8gAmALIAAgAQIgQES0wA4ADkADgvUC90APqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gL3gvfC+AL4QviC+ML5AvlgQETgQEUgQEVgQEXgQEYgQEZgQEagQEbgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULxABaAFoAWgAvAFoAogGrAFoAWgAVAFqAAIAigACBAREICAgIgBqAQAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8QAWgBaAFoALwBaAKIBrABaAFoAFQBagACAAIAAgQERCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDAcAFQvEAFoAWgBaAC8AWgCiAa0AWgBaABUAWoAAgQEWgACBAREICAgIgBqAQggIgAAI0wA4ADkADgwVDBYAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULxABaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACBAREICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDWQAVC8QAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAb4AAgQERCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvEAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgCKAAIEBEQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxABaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAAgACBAREICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8QAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgQERCAgICIAagEcICIAACNkAHwAjDGQADgAmDGUAIQBLDGYBbgGWAEwAawAVACcALwBaDG5fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAQ6APYAJgCyAAIAECIEBHdMAOAA5AA4McAx4AD6nAk8CUAJRAlICUwJUAlWAU4BUgFWAVoBXgFiAWacMeQx6DHsMfAx9DH4Mf4EBHoEBH4EBIIEBIYEBIoEBI4EBJIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8UAWgBaAFoALwBaAKICTwBaAFoAFQBagACAAIAAgQEcCAgICIAagFMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvFAFoAWgBaAC8AWgCiAlAAWgBaABUAWoAAgCKAAIEBHAgICAiAGoBUCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxQBaAFoAWgAvAFoAogJRAFoAWgAVAFqAAIAAgACBARwICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUD4QAVC8UAWgBaAFoALwBaAKICUgBaAFoAFQBagACAeYAAgQEcCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvFAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIEBHAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxQBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAAgACBARwICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8UAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgQEcCAgICIAagFkICIAACN8QEgCQAJEAkgzrAB8AlACVDOwAIQCTDO0AlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaDPUALwBaAEwAWgGMAWMAWgBaDP0AWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBJwiACQiAY4A3CAiBASYIEwAAAAEasuRo0wA4ADkADg0BDQQAPqIBlQGWgDyAPaINBQ0GgQEogQEzgCXZAB8AIw0JAA4AJg0KACEASw0LAW8BlQBMAGsAFQAnAC8AWg0TXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQElgDyACYAsgACABAiBASnTADgAOQAODRUNHgA+qAGrAawBrQGuAa8BsAGxAbKAQIBBgEKAQ4BEgEWARoBHqA0fDSANIQ0iDSMNJA0lDSaBASqBASuBASyBAS6BAS+BATCBATGBATKAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0FAFoAWgBaAC8AWgCiAasAWgBaABUAWoAAgCKAAIEBKAgICAiAGoBACAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNBQBaAFoAWgAvAFoAogGsAFoAWgAVAFqAAIAAgACBASgICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUNSAAVDQUAWgBaAFoALwBaAKIBrQBaAFoAFQBagACBAS2AAIEBKAgICAiAGoBCCAiAAAjTADgAOQAODVYNVwA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0FAFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIEBKAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQNZABUNBQBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIBvgACBASgICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQUAWgBaAFoALwBaAKIBsABaAFoAFQBagACAIoAAgQEoCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0FAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgACAAIEBKAgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNBQBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAigACBASgICAgIgBqARwgIgAAI2QAfACMNpQAOACYNpgAhAEsNpwFvAZYATABrABUAJwAvAFoNr18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBJYA9gAmALIAAgAQIgQE00wA4ADkADg2xDbkAPqcCTwJQAlECUgJTAlQCVYBTgFSAVYBWgFeAWIBZpw26DbsNvA29Db4Nvw3AgQE1gQE2gQE3gQE4gQE5gQE6gQE7gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNBgBaAFoAWgAvAFoAogJPAFoAWgAVAFqAAIAAgACBATMICAgIgBqAUwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQYAWgBaAFoALwBaAKICUABaAFoAFQBagACAIoAAgQEzCAgICIAagFQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0GAFoAWgBaAC8AWgCiAlEAWgBaABUAWoAAgACAAIEBMwgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQjqABUNBgBaAFoAWgAvAFoAogJSAFoAWgAVAFqAAIDagACBATMICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQYAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgQEzCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0GAFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgACAAIEBMwgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNBgBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACBATMICAgIgBqAWQgIgAAI3xASAJAAkQCSDiwAHwCUAJUOLQAhAJMOLgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoONgAvAFoATABaAYwBZABaAFoOPgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQE+CIAJCIBjgDgICIEBPQgSn10YdNMAOAA5AA4OQg5FAD6iAZUBloA8gD2iDkYOR4EBP4EBSoAl2QAfACMOSgAOACYOSwAhAEsOTAFwAZUATABrABUAJwAvAFoOVF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBPIA8gAmALIAAgAQIgQFA0wA4ADkADg5WDl8APqgBqwGsAa0BrgGvAbABsQGygECAQYBCgEOARIBFgEaAR6gOYA5hDmIOYw5kDmUOZg5ngQFBgQFCgQFDgQFFgQFGgQFHgQFIgQFJgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUORgBaAFoAWgAvAFoAogGrAFoAWgAVAFqAAIAigACBAT8ICAgIgBqAQAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkYAWgBaAFoALwBaAKIBrABaAFoAFQBagACAAIAAgQE/CAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDokAFQ5GAFoAWgBaAC8AWgCiAa0AWgBaABUAWoAAgQFEgACBAT8ICAgIgBqAQggIgAAI0wA4ADkADg6XDpgAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUORgBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACBAT8ICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkYAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAIoAAgQE/CAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5GAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgCKAAIEBPwgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUORgBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAAgACBAT8ICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkYAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgQE/CAgICIAagEcICIAACNkAHwAjDuYADgAmDucAIQBLDugBcAGWAEwAawAVACcALwBaDvBfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBATyAPYAJgCyAAIAECIEBS9MAOAA5AA4O8g76AD6nAk8CUAJRAlICUwJUAlWAU4BUgFWAVoBXgFiAWacO+w78Dv0O/g7/DwAPAYEBTIEBTYEBToEBT4EBUIEBUYEBUoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCaAAVDkcAWgBaAFoALwBaAKICTwBaAFoAFQBagACAW4AAgQFKCAgICIAagFMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5HAFoAWgBaAC8AWgCiAlAAWgBaABUAWoAAgCKAAIEBSggICAiAGoBUCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUORwBaAFoAWgAvAFoAogJRAFoAWgAVAFqAAIAAgACBAUoICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUClgAVDkcAWgBaAFoALwBaAKICUgBaAFoAFQBagACAX4AAgQFKCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5HAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIEBSggICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUORwBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAAgACBAUoICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkcAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgQFKCAgICIAagFkICIAACFpkdXBsaWNhdGVz0gA5AA4PbgCqoIAZ0gCsAK0PcQ9yWlhEUE1FbnRpdHmnD3MPdA91D3YPdw94ALFaWERQTUVudGl0eV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4Peg97AD6goIAl0wA4ADkADg9+D38APqCggCXTADgAOQAOD4IPgwA+oKCAJdIArACtD4YPh15YRE1vZGVsUGFja2FnZaYPiA+JD4oPiw+MALFeWERNb2RlbFBhY2thZ2VfEA9YRFVNTFBhY2thZ2VJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0gA5AA4PjgCqoIAZ0wA4ADkADg+RD5IAPqCggCVQ0gCsAK0Plg+XWVhEUE1Nb2RlbKMPlg+YALFXWERNb2RlbAAIABkAIgAsADEAOgA/AFEAVgBbAF0DHQMjAzwDTgNVA2MDcAOIA6IDpAOnA6kDrAOvA7ID6wQKBCcERgRYBHgEfwSdBKkExQTLBO0FDgUhBSMFJgUpBSsFLQUvBTIFNQU3BTkFOwU9BT8FQQVCBUYFUwVbBWYFaQVrBW4FcAVyBYEFxAXoBgwGLwZWBnYGnQbEBuQHCAcsBzgHOgc8Bz4HQAdCB0QHRwdJB0sHTgdQB1IHVQdXB1gHXQdlB3IHdQd3B3oHfAd+B40HsgfWB/0IIQgjCCUIJwgpCCsILQguCDAIPQhQCFIIVAhWCFgIWghcCF4IYAhiCHUIdwh5CHsIfQh/CIEIgwiFCIcIiQifCLIIzgjrCQcJGwktCUMJXAmbCaEJqgm3CcMJzQnXCeIJ7Qn6CgIKBAoGCggKCgoLCgwKDQoOChAKEgoTChQKFgoXCiAKIQojCiwKNwpACk8KVgpeCmcKcAqDCowKnwq2CsgLBwsJCwsLDQsPCxALEQsSCxMLFQsXCxgLGQsbCxwLWwtdC18LYQtjC2QLZQtmC2cLaQtrC2wLbQtvC3ALeQt6C3wLuwu9C78LwQvDC8QLxQvGC8cLyQvLC8wLzQvPC9AMDwwRDBMMFQwXDBgMGQwaDBsMHQwfDCAMIQwjDCQMLQwuDDAMbwxxDHMMdQx3DHgMeQx6DHsMfQx/DIAMgQyDDIQMhQzEDMYMyAzKDMwMzQzODM8M0AzSDNQM1QzWDNgM2QzmDOcM6AzqDPMNCQ0QDR0NXA1eDWANYg1kDWUNZg1nDWgNag1sDW0Nbg1wDXENig2MDY4NkA2RDZMNqg2zDcENzg3cDfEOBQ4cDi4ObQ5vDnEOcw51DnYOdw54DnkOew59Dn4Ofw6BDoIOlw6gDrUOxA7ZDucO/A8QDycPOQ9GD10PXw9hD2MPZQ9nD2kPaw9tD28PcQ9zD4oPjA+OD5APkg+UD5YPmA+aD50PoA+jD6UPrA+2D8EPzw/VD+EP8BADEAkQHRAqEHUQmBC4ENgQ2hDcEN4Q4BDiEOMQ5BDmEOcQ6RDqEOwQ7hDvEPAQ8hDzEPwRCREOERAREhEXERkRGxEdETIRRxFsEZARtxHbEd0R3xHhEeMR5RHnEegR6hH3EggSChIMEg4SEBISEhQSFhIYEikSKxItEi8SMRIzEjUSNxI5EjsSWRJ3EooSnhKzEtAS5BL6EzkTOxM9Ez8TQRNCE0MTRBNFE0cTSRNKE0sTTRNOE40TjxORE5MTlROWE5cTmBOZE5sTnROeE58ToROiE+ET4xPlE+cT6RPqE+sT7BPtE+8T8RPyE/MT9RP2FAMUBBQFFAcURhRIFEoUTBROFE8UUBRRFFIUVBRWFFcUWBRaFFsUmhScFJ4UoBSiFKMUpBSlFKYUqBSqFKsUrBSuFK8U7hTwFPIU9BT2FPcU+BT5FPoU/BT+FP8VABUCFQMVQhVEFUYVSBVKFUsVTBVNFU4VUBVSFVMVVBVWFVcVlhWYFZoVnBWeFZ8VoBWhFaIVpBWmFacVqBWqFasV0BX0FhsWPxZBFkMWRRZHFkkWSxZMFk4WWxZqFmwWbhZwFnIWdBZ2FngWhxaJFosWjRaPFpEWkxaVFpcWtxbiFvwXFRcvF08XchexF7MXtRe3F7kXuhe7F7wXvRe/F8EXwhfDF8UXxhfKGAkYCxgNGA8YERgSGBMYFBgVGBcYGRgaGBsYHRgeGF0YXxhhGGMYZRhmGGcYaBhpGGsYbRhuGG8YcRhyGLEYsxi1GLcYuRi6GLsYvBi9GL8YwRjCGMMYxRjGGMkZCBkKGQwZDhkQGREZEhkTGRQZFhkYGRkZGhkcGR0ZXBleGWAZYhlkGWUZZhlnGWgZahlsGW0ZbhlwGXEZsBmyGbQZthm4GbkZuhm7GbwZvhnAGcEZwhnEGcUZzhncGekZ9xoEGhcaLhpAGosarhrOGu4a8BryGvQa9hr4Gvka+hr8Gv0a/xsAGwIbBBsFGwYbCBsJGw4bGxsgGyIbJBspGysbLRsvG1QbeBufG8MbxRvHG8kbyxvNG88b0BvSG98b8BvyG/Qb9hv4G/ob/Bv+HAAcERwTHBUcFxwZHBscHRwfHCEcIxxiHGQcZhxoHGocaxxsHG0cbhxwHHIccxx0HHYcdxy2HLgcuhy8HL4cvxzAHMEcwhzEHMYcxxzIHMocyx0KHQwdDh0QHRIdEx0UHRUdFh0YHRodGx0cHR4dHx0sHS0dLh0wHW8dcR1zHXUddx14HXkdeh17HX0dfx2AHYEdgx2EHcMdxR3HHckdyx3MHc0dzh3PHdEd0x3UHdUd1x3YHdkeGB4aHhweHh4gHiEeIh4jHiQeJh4oHikeKh4sHi0ebB5uHnAech50HnUedh53Hngeeh58Hn0efh6AHoEewB7CHsQexh7IHskeyh7LHswezh7QHtEe0h7UHtUe+h8eH0UfaR9rH20fbx9xH3MfdR92H3gfhR+UH5YfmB+aH5wfnh+gH6IfsR+zH7Uftx+5H7sfvR+/H8EgACACIAQgBiAIIAkgCiALIAwgDiAQIBEgEiAUIBUgVCBWIFggWiBcIF0gXiBfIGAgYiBkIGUgZiBoIGkgqCCqIKwgriCwILEgsiCzILQgtiC4ILkguiC8IL0g/CD+IQAhAiEEIQUhBiEHIQghCiEMIQ0hDiEQIREhFCFTIVUhVyFZIVshXCFdIV4hXyFhIWMhZCFlIWchaCGnIakhqyGtIa8hsCGxIbIhsyG1IbchuCG5IbshvCH7If0h/yIBIgMiBCIFIgYiByIJIgsiDCINIg8iECJbIn4iniK+IsAiwiLEIsYiyCLJIsoizCLNIs8i0CLSItQi1SLWItgi2SLiIu8i9CL2Ivgi/SL/IwEjAyMoI0wjcyOXI5kjmyOdI58joSOjI6QjpiOzI8QjxiPII8ojzCPOI9Aj0iPUI+Uj5yPpI+sj7SPvI/Ej8yP1I/ckNiQ4JDokPCQ+JD8kQCRBJEIkRCRGJEckSCRKJEskiiSMJI4kkCSSJJMklCSVJJYkmCSaJJsknCSeJJ8k3iTgJOIk5CTmJOck6CTpJOok7CTuJO8k8CTyJPMlACUBJQIlBCVDJUUlRyVJJUslTCVNJU4lTyVRJVMlVCVVJVclWCWXJZklmyWdJZ8loCWhJaIloyWlJaclqCWpJaslrCXrJe0l7yXxJfMl9CX1JfYl9yX5Jfsl/CX9Jf8mACY/JkEmQyZFJkcmSCZJJkomSyZNJk8mUCZRJlMmVCaTJpUmlyaZJpsmnCadJp4mnyahJqMmpCalJqcmqCbNJvEnGCc8Jz4nQCdCJ0QnRidIJ0knSydYJ2cnaSdrJ20nbydxJ3MndSeEJ4YniCeKJ4wnjieQJ5InlCfTJ9Un1yfZJ9sn3CfdJ94n3yfhJ+Mn5CflJ+cn6CgnKCkoKygtKC8oMCgxKDIoMyg1KDcoOCg5KDsoPCh7KH0ofyiBKIMohCiFKIYohyiJKIsojCiNKI8okCjPKNEo0yjVKNco2CjZKNoo2yjdKN8o4CjhKOMo5CjnKSYpKCkqKSwpLikvKTApMSkyKTQpNik3KTgpOik7KXopfCl+KYApgimDKYQphSmGKYgpiimLKYwpjimPKakp6CnqKewp7inwKfEp8inzKfQp9in4Kfkp+in8Kf0qSCprKosqqyqtKq8qsSqzKrUqtiq3Krkquiq8Kr0qvyrBKsIqwyrFKsYqyyrYKt0q3yrhKuYq6CrqKuwrESs1K1wrgCuCK4QrhiuIK4orjCuNK48rnCutK68rsSuzK7Urtyu5K7srvSvOK9Ar0ivUK9Yr2CvaK9wr3ivgLB8sISwjLCUsJywoLCksKiwrLC0sLywwLDEsMyw0LHMsdSx3LHkseyx8LH0sfix/LIEsgyyELIUshyyILMcsySzLLM0szyzQLNEs0izTLNUs1yzYLNks2yzcLOks6izrLO0tLC0uLTAtMi00LTUtNi03LTgtOi08LT0tPi1ALUEtgC2CLYQthi2ILYktii2LLYwtji2QLZEtki2ULZUt1C3WLdgt2i3cLd0t3i3fLeAt4i3kLeUt5i3oLekuKC4qLiwuLi4wLjEuMi4zLjQuNi44LjkuOi48Lj0ufC5+LoAugi6ELoUuhi6HLoguii6MLo0uji6QLpEuti7aLwEvJS8nLykvKy8tLy8vMS8yLzQvQS9QL1IvVC9WL1gvWi9cL14vbS9vL3Evcy91L3cveS97L30vvC++L8Avwi/EL8Uvxi/HL8gvyi/ML80vzi/QL9Ev1DATMBUwFzAZMBswHDAdMB4wHzAhMCMwJDAlMCcwKDBnMGkwazBtMG8wcDBxMHIwczB1MHcweDB5MHswfDC7ML0wvzDBMMMwxDDFMMYwxzDJMMswzDDNMM8w0DEPMRExEzEVMRcxGDEZMRoxGzEdMR8xIDEhMSMxJDFjMWUxZzFpMWsxbDFtMW4xbzFxMXMxdDF1MXcxeDG3MbkxuzG9Mb8xwDHBMcIxwzHFMccxyDHJMcsxzDIXMjoyWjJ6MnwyfjKAMoIyhDKFMoYyiDKJMosyjDKOMpAykTKSMpQylTKaMqcyrDKuMrAytTK3MrkyuzLgMwQzKzNPM1EzUzNVM1czWTNbM1wzXjNrM3wzfjOAM4IzhDOGM4gzijOMM50znzOhM6MzpTOnM6kzqzOtM68z7jPwM/Iz9DP2M/cz+DP5M/oz/DP+M/80ADQCNAM0QjRENEY0SDRKNEs0TDRNNE40UDRSNFM0VDRWNFc0ljSYNJo0nDSeNJ80oDShNKI0pDSmNKc0qDSqNKs0uDS5NLo0vDT7NP00/zUBNQM1BDUFNQY1BzUJNQs1DDUNNQ81EDVPNVE1UzVVNVc1WDVZNVo1WzVdNV81YDVhNWM1ZDWjNaU1pzWpNas1rDWtNa41rzWxNbM1tDW1Nbc1uDX3Nfk1+zX9Nf82ADYBNgI2AzYFNgc2CDYJNgs2DDZLNk02TzZRNlM2VDZVNlY2VzZZNls2XDZdNl82YDaFNqk20Db0NvY2+Db6Nvw2/jcANwE3AzcQNx83ITcjNyU3JzcpNys3LTc8Nz43QDdCN0Q3RjdIN0o3TDeLN403jzeRN5M3lDeVN5Y3lzeZN5s3nDedN583oDffN+E34zflN+c36DfpN+o36zftN+838DfxN/M39DgzODU4Nzg5ODs4PDg9OD44PzhBOEM4RDhFOEc4SDiHOIk4iziNOI84kDiROJI4kziVOJc4mDiZOJs4nDjbON043zjhOOM45DjlOOY45zjpOOs47DjtOO848DkvOTE5Mzk1OTc5ODk5OTo5Ozk9OT85QDlBOUM5RDldOZw5njmgOaI5pDmlOaY5pzmoOao5rDmtOa45sDmxOfw6Hzo/Ol86YTpjOmU6ZzppOmo6azptOm46cDpxOnM6dTp2Onc6eTp6On86jDqROpM6lTqaOpw6njqgOsU66TsQOzQ7Njs4Ozo7PDs+O0A7QTtDO1A7YTtjO2U7ZztpO2s7bTtvO3E7gjuEO4Y7iDuKO4w7jjuQO5I7lDvTO9U71zvZO9s73DvdO9473zvhO+M75DvlO+c76DwnPCk8KzwtPC88MDwxPDI8Mzw1PDc8ODw5PDs8PDx7PH08fzyBPIM8hDyFPIY8hzyJPIs8jDyNPI88kDydPJ48nzyhPOA84jzkPOY86DzpPOo86zzsPO488DzxPPI89Dz1PTQ9Nj04PTo9PD09PT49Pz1APUI9RD1FPUY9SD1JPYg9ij2MPY49kD2RPZI9kz2UPZY9mD2ZPZo9nD2dPdw93j3gPeI95D3lPeY95z3oPeo97D3tPe498D3xPjA+Mj40PjY+OD45Pjo+Oz48Pj4+QD5BPkI+RD5FPmo+jj61Ptk+2z7dPt8+4T7jPuU+5j7oPvU/BD8GPwg/Cj8MPw4/ED8SPyE/Iz8lPyc/KT8rPy0/Lz8xP3A/cj90P3Y/eD95P3o/ez98P34/gD+BP4I/hD+FP8Q/xj/IP8o/zD/NP84/zz/QP9I/1D/VP9Y/2D/ZQBhAGkAcQB5AIEAhQCJAI0AkQCZAKEApQCpALEAtQGxAbkBwQHJAdEB1QHZAd0B4QHpAfEB9QH5AgECBQIRAw0DFQMdAyUDLQMxAzUDOQM9A0UDTQNRA1UDXQNhBF0EZQRtBHUEfQSBBIUEiQSNBJUEnQShBKUErQSxBa0FtQW9BcUFzQXRBdUF2QXdBeUF7QXxBfUF/QYBBy0HuQg5CLkIwQjJCNEI2QjhCOUI6QjxCPUI/QkBCQkJEQkVCRkJIQklCUkJfQmRCZkJoQm1Cb0JxQnNCmEK8QuNDB0MJQwtDDUMPQxFDE0MUQxZDI0M0QzZDOEM6QzxDPkNAQ0JDRENVQ1dDWUNbQ11DX0NhQ2NDZUNnQ6ZDqEOqQ6xDrkOvQ7BDsUOyQ7RDtkO3Q7hDukO7Q/pD/EP+RABEAkQDRAREBUQGRAhECkQLRAxEDkQPRE5EUERSRFREVkRXRFhEWURaRFxEXkRfRGBEYkRjRHBEcURyRHREs0S1RLdEuUS7RLxEvUS+RL9EwUTDRMRExUTHRMhFB0UJRQtFDUUPRRBFEUUSRRNFFUUXRRhFGUUbRRxFW0VdRV9FYUVjRWRFZUVmRWdFaUVrRWxFbUVvRXBFr0WxRbNFtUW3RbhFuUW6RbtFvUW/RcBFwUXDRcRGA0YFRgdGCUYLRgxGDUYORg9GEUYTRhRGFUYXRhhGPUZhRohGrEauRrBGska0RrZGuEa5RrtGyEbXRtlG20bdRt9G4UbjRuVG9Eb2RvhG+kb8Rv5HAEcCRwRHQ0dFR0dHSUdLR0xHTUdOR09HUUdTR1RHVUdXR1hHl0eZR5tHnUefR6BHoUeiR6NHpUenR6hHqUerR6xH60ftR+9H8UfzR/RH9Uf2R/dH+Uf7R/xH/Uf/SABIP0hBSENIRUhHSEhISUhKSEtITUhPSFBIUUhTSFRIk0iVSJdImUibSJxInUieSJ9IoUijSKRIpUinSKhI50jpSOtI7UjvSPBI8UjySPNI9Uj3SPhI+Uj7SPxJFklVSVdJWUlbSV1JXklfSWBJYUljSWVJZklnSWlJakm1SdhJ+EoYShpKHEoeSiBKIkojSiRKJkonSilKKkosSi5KL0owSjJKM0o4SkVKSkpMSk5KU0pVSlhKWkp/SqNKykruSvBK8kr0SvZK+Er6SvtK/UsKSxtLHUsfSyFLI0slSydLKUsrSzxLPktAS0JLREtHS0pLTUtQS1JLkUuTS5VLl0uZS5pLm0ucS51Ln0uhS6JLo0ulS6ZL5UvnS+lL60vtS+5L70vwS/FL80v1S/ZL90v5S/pMOUw7TD1MP0xBTEJMQ0xETEVMR0xJTEpMS0xNTE5MW0xcTF1MX0yeTKBMokykTKZMp0yoTKlMqkysTK5Mr0ywTLJMs0zyTPRM9kz4TPpM+0z8TP1M/k0ATQJNA00ETQZNB01GTUhNSk1MTU5NT01QTVFNUk1UTVZNV01YTVpNW02aTZxNnk2gTaJNo02kTaVNpk2oTapNq02sTa5Nr03uTfBN8k30TfZN9034TflN+k38Tf5N/04ATgJOA04oTkxOc06XTplOm06dTp9OoU6jTqROp060TsNOxU7HTslOy07NTs9O0U7gTuNO5k7pTuxO707yTvVO9082TzhPOk88Tz9PQE9BT0JPQ09FT0dPSE9JT0tPTE+LT41Pj0+RT5RPlU+WT5dPmE+aT5xPnU+eT6BPoU/gT+JP5E/mT+lP6k/rT+xP7U/vT/FP8k/zT/VP9lA1UDdQOVA7UD5QP1BAUEFQQlBEUEZQR1BIUEpQS1CKUIxQjlCQUJNQlFCVUJZQl1CZUJtQnFCdUJ9QoFDfUOFQ5FDmUOlQ6lDrUOxQ7VDvUPFQ8lDzUPVQ9lEXUVZRWFFaUVxRX1FgUWFRYlFjUWVRZ1FoUWlRa1FsUbdR2lH6UhpSHFIeUiBSIlIkUiVSJlIpUipSLFItUi9SMVIyUjNSNlI3UjxSSVJOUlBSUlJXUlpSXVJfUoRSqFLPUvNS9lL4UvpS/FL+UwBTAVMEUxFTIlMkUyZTKFMqUyxTLlMwUzJTQ1NGU0lTTFNPU1JTVVNYU1tTXVOcU55ToFOiU6VTplOnU6hTqVOrU61TrlOvU7FTslPxU/NT9VP3U/pT+1P8U/1T/lQAVAJUA1QEVAZUB1RGVEhUS1RNVFBUUVRSVFNUVFRWVFhUWVRaVFxUXVRqVGtUbFRuVK1Ur1SxVLNUtlS3VLhUuVS6VLxUvlS/VMBUwlTDVQJVBFUGVQhVC1UMVQ1VDlUPVRFVE1UUVRVVF1UYVVdVWVVbVV1VYFVhVWJVY1VkVWZVaFVpVWpVbFVtVaxVrlWwVbJVtVW2VbdVuFW5VbtVvVW+Vb9VwVXCVgFWA1YFVgdWClYLVgxWDVYOVhBWElYTVhRWFlYXVjxWYFaHVqtWrlawVrJWtFa2VrhWuVa8VslW2FbaVtxW3lbgVuJW5FbmVvVW+Fb7Vv5XAVcEVwdXClcMV0tXTVdPV1FXVFdVV1ZXV1dYV1pXXFddV15XYFdhV6BXolekV6ZXqVeqV6tXrFetV69XsVeyV7NXtVe2V/VX91f5V/tX/lf/WABYAVgCWARYBlgHWAhYClgLWEpYTFhOWFBYU1hUWFVYVlhXWFlYW1hcWF1YX1hgWJ9YoVijWKVYqFipWKpYq1isWK5YsFixWLJYtFi1WPRY9lj4WPpY/Vj+WP9ZAFkBWQNZBVkGWQdZCVkKWUlZS1lNWU9ZUllTWVRZVVlWWVhZWllbWVxZXllfWapZzVntWg1aD1oRWhNaFVoXWhhaGVocWh1aH1ogWiJaJFolWiZaKVoqWjNaQFpFWkdaSVpOWlFaVFpWWntan1rGWupa7VrvWvFa81r1Wvda+Fr7WwhbGVsbWx1bH1shWyNbJVsnWylbOls9W0BbQ1tGW0lbTFtPW1JbVFuTW5Vbl1uZW5xbnVueW59boFuiW6RbpVumW6hbqVvoW+pb7FvuW/Fb8lvzW/Rb9Vv3W/lb+lv7W/1b/lw9XD9cQlxEXEdcSFxJXEpcS1xNXE9cUFxRXFNcVFxhXGJcY1xlXKRcplyoXKpcrVyuXK9csFyxXLNctVy2XLdcuVy6XPlc+1z9XP9dAl0DXQRdBV0GXQhdCl0LXQxdDl0PXU5dUF1SXVRdV11YXVldWl1bXV1dX11gXWFdY11kXaNdpV2nXaldrF2tXa5dr12wXbJdtF21XbZduF25Xfhd+l38Xf5eAV4CXgNeBF4FXgdeCV4KXgteDV4OXjNeV15+XqJepV6nXqleq16tXq9esF6zXsBez17RXtNe1V7XXtle217dXuxe717yXvVe+F77Xv5fAV8DX0JfRF9GX0hfS19MX01fTl9PX1FfU19UX1VfV19YX5dfmV+bX51foF+hX6Jfo1+kX6ZfqF+pX6pfrF+tX+xf7l/wX/Jf9V/2X/df+F/5X/tf/V/+X/9gAWACYEFgQ2BFYEdgSmBLYExgTWBOYFBgUmBTYFRgVmBXYJZgmGCaYJxgn2CgYKFgomCjYKVgp2CoYKlgq2CsYOtg7WDvYPFg9GD1YPZg92D4YPpg/GD9YP5hAGEBYUBhQmFEYUZhSWFKYUthTGFNYU9hUWFSYVNhVWFWYaFhxGHkYgRiBmIIYgpiDGIOYg9iEGITYhRiFmIXYhliG2IcYh1iIGIhYiZiM2I4YjpiPGJBYkRiR2JJYm5ikmK5Yt1i4GLiYuRi5mLoYupi62LuYvtjDGMOYxBjEmMUYxZjGGMaYxxjLWMwYzNjNmM5YzxjP2NCY0VjR2OGY4hjimOMY49jkGORY5Jjk2OVY5djmGOZY5tjnGPbY91j32PhY+Rj5WPmY+dj6GPqY+xj7WPuY/Bj8WQwZDJkNWQ3ZDpkO2Q8ZD1kPmRAZEJkQ2REZEZkR2RUZFVkVmRYZJdkmWSbZJ1koGShZKJko2SkZKZkqGSpZKpkrGStZOxk7mTwZPJk9WT2ZPdk+GT5ZPtk/WT+ZP9lAWUCZUFlQ2VFZUdlSmVLZUxlTWVOZVBlUmVTZVRlVmVXZZZlmGWaZZxln2WgZaFlomWjZaVlp2WoZallq2WsZetl7WXvZfFl9GX1ZfZl92X4Zfpl/GX9Zf5mAGYBZiZmSmZxZpVmmGaaZpxmnmagZqJmo2amZrNmwmbEZsZmyGbKZsxmzmbQZt9m4mblZuhm62buZvFm9Gb2ZzVnN2c5ZztnPmc/Z0BnQWdCZ0RnRmdHZ0hnSmdLZ4pnjGeOZ5Bnk2eUZ5VnlmeXZ5lnm2ecZ51nn2egZ99n4WfjZ+Vn6GfpZ+pn62fsZ+5n8GfxZ/Jn9Gf1aDRoNmg4aDpoPWg+aD9oQGhBaENoRWhGaEdoSWhKaIloi2iNaI9okmiTaJRolWiWaJhommibaJxonmifaN5o4GjiaORo52joaOlo6mjraO1o72jwaPFo82j0aTNpNWk3aTlpPGk9aT5pP2lAaUJpRGlFaUZpSGlJaVRpXWleaWBpaWl0aYNpjmmcabFpxWncae5p+2n8af1p/2oMag1qDmoQah1qHmofaiFqKmo5akZqVWpnantqkmqkaq1qrmqwar1qvmq/asFqwmrLatVq3AAAAAAAAAICAAAAAAAAD5kAAAAAAAAAAAAAAAAAAGrk\n</attribute>\n        <attribute name=\"destinationmodelpath\" type=\"string\">AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 4.xcdatamodel</attribute>\n        <attribute name=\"destinationmodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBkAALAAwAGQA1ADYANwA/AEAAWwBcAF0AYwBkAHAAhgCHAIgAiQCKAIsAjACNAI4AjwCoAKsAsgC4AMcA1gDZAOgA9wD6AFoBCgEZAR0BIQEwATYBNwE/AU4BTwFYAXYBdwF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGYAZkBoQGiAaMBrwHDAcQBxQHGAccByAHJAcoBywHaAekB+AH8AgsCGgIbAioCOQJIAlQCZgJnAmgCaQJqAmsCbAJtAnwCiwKaAqkCqgK5AsgCyQLYAuAC9QL2Av4DCgMeAy0DPANLA08DXgNtA3wDiwOaA6YDuAPHA9YD5QP0A/UEBAQTBBQEIwQ4BDkEQQRNBGEEcAR/BI4EkgShBLAEvwTOBN0E6QT7BQoFGQUoBTcFOAVHBVYFZQV6BXsFgwWPBaMFsgXBBdAF1AXjBfIGAQYQBh8GKwY9BkwGWwZqBnkGiAaXBpgGpwa8Br0GxQbRBuUG9AcDBxIHFgclBzQHQwdSB2EHbQd/B44HjweeB60HvAe9B8wH2wfqB/8IAAgICBQIKAg3CEYIVQhZCGgIdwiGCJUIpAiwCMII0QjgCO8I/gkNCRwJHQksCUEJQglKCVYJagl5CYgJlwmbCaoJuQnICdcJ5gnyCgQKEwoiCjEKQApBClAKXwpuCoMKhAqMCpgKrAq7CsoK2QrdCuwK+wsKCxkLKAs0C0YLVQtkC3MLgguRC6ALrwvEC8ULzQvZC+0L/AwLDBoMHgwtDDwMSwxaDGkMdQyHDJYMpQy0DMMM0gzhDPANBQ0GDQ4NGg0uDT0NTA1bDV8Nbg19DYwNmw2qDbYNyA3XDeYN9Q4EDhMOIg4xDkYORw5PDlsObw5+Do0OnA6gDq8Ovg7NDtwO6w73DwkPGA8ZDygPNw9GD1UPZA9zD4gPiQ+RD50PsQ/AD88P3g/iD/EQABAPEB4QLRA5EEsQWhBpEHgQhxCWEKUQtBDJEMoQ0hDeEPIRAREQER8RIxEyEUERUBFfEW4RehGMEZsRqhG5EcgR1xHmEecR9hH3EfoSAxIHEgsSDxIXEhoSHhIfVSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgQGPgACBAYyBAY2BAY7eABoAGwAcAB0AHgAfACAADgAhACIAIwAkACUAJgAnACgAKQAJACcAFQAtAC4ALwAwADEAJwAnABVfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASBAYqBAYiAAYAEgACBAYmBAYsQAIAFgAOABIAEgABQU1lFU9MAOAA5AA4AOgA8AD5XTlMua2V5c1pOUy5vYmplY3RzoQA7gAahAD2AB4AlXlVBSW5ib3hNZXNzYWdl3xAQAEEAQgBDAEQAHwBFAEYAIQBHAEgADgAjAEkASgAmAEsATABNACcAJwATAFEAUgAvACcATABVADsATABYAFkAWl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgC2ABIAEgAKACoEBhYAEgAmBAYeABoAJgQGGgAgIEwAAAAEuRERbV29yZGVyZWTTADgAOQAOAF4AYAA+oQBfgAuhAGGADIAlXlhEX1BTdGVyZW90eXBl2QAfACMAZQAOACYAZgAhAEsAZwA9AF8ATABrABUAJwAvAFoAb18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAsgACABAiADdMAOAA5AA4AcQB7AD6pAHIAcwB0AHUAdgB3AHgAeQB6gA6AD4AQgBGAEoATgBSAFYAWqQB8AH0AfgB/AIAAgQCCAIMAhIAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAJsAFQBhAFoAWgBaAC8AWgCiAHIAWgBaABUAWlVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADkADgCpAKqggBnSAKwArQCuAK9aJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMArgCwALFXTlNBcnJheVhOU09iamVjdNIArACtALMAtF8QEFhEVU1MUHJvcGVydHlJbXCkALUAtgC3ALFfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogBzAFoAWgAVAFqAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDJABUAYQBaAFoAWgAvAFoAogB0AFoAWgAVAFqAAIAdgACADAgICAiAGoAQCAiAAAjSADkADgDXAKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogB1AFoAWgAVAFqAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDqABUAYQBaAFoAWgAvAFoAogB2AFoAWgAVAFqAAIAggACADAgICAiAGoASCAiAAAjSADkADgD4AKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUAYQBaAFoAWgAvAFoAogB3AFoAWgAVAFqAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBDAAVAGEAWgBaAFoALwBaAKIAeABaAFoAFQBagACAJIAAgAwICAgIgBqAFAgIgAAI0wA4ADkADgEaARsAPqCggCXSAKwArQEeAR9fEBNOU011dGFibGVEaWN0aW9uYXJ5owEeASAAsVxOU0RpY3Rpb25hcnnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEjABUAYQBaAFoAWgAvAFoAogB5AFoAWgAVAFqAAIAngACADAgICAiAGoAVCAiAAAjWACMADgAmAEsAHwAhATEBMgAVAFoAFQAvgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKwArQE4ATldWERVTUxDbGFzc0ltcKYBOgE7ATwBPQE+ALFdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQFBABUAYQBaAFoAWgAvAFoAogB6AFoAWgAVAFqAAIArgACADAgICAiAGoAWCAiAAAhfEBJVQUluYm94TWVzc2FnZURhdGHSAKwArQFQAVFfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVIBUwFUAVUBVgFXALFfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOAVkBZwA+rQFaAVsBXAFdAV4BXwFgAWEBYgFjAWQBZQFmgC6AL4AwgDGAMoAzgDSANYA2gDeAOIA5gDqtAWgBaQFqAWsBbAFtAW4BbwFwAXEBcgFzAXSAO4BngICAmICwgMmA4YD5gQEQgQEngQE+gQFWgQFtgCVVZXh0cmFebWVzc2FnZUJvZHlVUkxfEBFtZXNzYWdlRXhwaXJhdGlvbl8QEG1lc3NhZ2VSZXBvcnRpbmddZGVsZXRlZENsaWVudF8QEHJhd01lc3NhZ2VPYmplY3RZbWVzc2FnZUlEW2NvbnRlbnRUeXBlW21lc3NhZ2VTZW50VXRpdGxlXHVucmVhZENsaWVudFZ1bnJlYWRabWVzc2FnZVVSTN8QEgCQAJEAkgGEAB8AlACVAYUAIQCTAYYAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAY4ALwBaAEwAWgGSAVoAWgBaAZYAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIA9CIAJCIBmgC4ICIA8CBJc/IHe0wA4ADkADgGaAZ0APqIBmwGcgD6AP6IBngGfgECAVIAlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAfACMBpAAOACYBpQAhAEsBpgFoAZsATABrABUAJwAvAFoBrl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA7gD6ACYAsgACABAiAQdMAOAA5AA4BsAG5AD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoAboBuwG8Ab0BvgG/AcABwYBKgEuATIBOgE+AUYBSgFOAJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFNLX2lzSW5kZXhlZF8QElhEX1BQU0tfaXNPcHRpb25hbF8QGlhEX1BQU0tfaXNTcG90bGlnaHRJbmRleGVkXxARWERfUFBTS19lbGVtZW50SURfEBNYRF9QUFNLX2lzVHJhbnNpZW503xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZ4AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgEAICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZ4AWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgEAICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB6wAVAZ4AWgBaAFoALwBaAKIBswBaAFoAFQBagACATYAAgEAICAgIgBqARAgIgAAI0wA4ADkADgH5AfoAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBngBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACAQAgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUBngBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACAQAgICAiAGoBGCAiAAAgJ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZ4AWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgEAICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZ4AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgEAICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZ4AWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgEAICAgIgBqASQgIgAAI2QAfACMCSQAOACYCSgAhAEsCSwFoAZwATABrABUAJwAvAFoCU18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA7gD+ACYAsgACABAiAVdMAOAA5AA4CVQJdAD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcCXgJfAmACYQJiAmMCZIBdgF6AX4BggGKAY4BlgCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACAVAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBnwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACAVAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACAVAgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKcABUBnwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIBhgACAVAgICAiAGoBZCAiAAAgRA+jfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACAVAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQK7ABUBnwBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIBkgACAVAgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACAVAgICAiAGoBcCAiAAAjSAKwArQLZAtpdWERQTUF0dHJpYnV0ZaYC2wLcAt0C3gLfALFdWERQTUF0dHJpYnV0ZVxYRFBNUHJvcGVydHlfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEBIAkACRAJIC4QAfAJQAlQLiACEAkwLjAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgLrAC8AWgBMAFoBkgFbAFoAWgLzAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAaQiACQiAZoAvCAiAaAgSZOK/XdMAOAA5AA4C9wL6AD6iAZsBnIA+gD+iAvsC/IBqgHWAJdkAHwAjAv8ADgAmAwAAIQBLAwEBaQGbAEwAawAVACcALwBaAwlfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAZ4A+gAmALIAAgAQIgGvTADgAOQAOAwsDFAA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAMVAxYDFwMYAxkDGgMbAxyAbIBtgG6AcIBxgHKAc4B0gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC+wBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAaggICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+wBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACAaggICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQM+ABUC+wBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIBvgACAaggICAiAGoBECAiAAAjTADgAOQAOA0wDTQA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL7AFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIBqCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQL7AFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIBqCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL7AFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAIBqCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL7AFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIBqCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL7AFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIBqCAgICIAagEkICIAACNkAHwAjA5sADgAmA5wAIQBLA50BaQGcAEwAawAVACcALwBaA6VfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAZ4A/gAmALIAAgAQIgHbTADgAOQAOA6cDrwA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynA7ADsQOyA7MDtAO1A7aAd4B4gHmAeoB8gH2Af4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvwAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgHUICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvwAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgHUICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvwAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgHUICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUD5wAVAvwAWgBaAFoALwBaAKICWQBaAFoAFQBagACAe4AAgHUICAgIgBqAWQgIgAAIEQcI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvwAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgHUICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUEBgAVAvwAWgBaAFoALwBaAKICWwBaAFoAFQBagACAfoAAgHUICAgIgBqAWwgIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvwAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgHUICAgIgBqAXAgIgAAI3xASAJAAkQCSBCQAHwCUAJUEJQAhAJMEJgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoELgAvAFoATABaAZIBXABaAFoENgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgIIIgAkIgGaAMAgIgIEIEoamt2jTADgAOQAOBDoEPQA+ogGbAZyAPoA/ogQ+BD+Ag4COgCXZAB8AIwRCAA4AJgRDACEASwREAWoBmwBMAGsAFQAnAC8AWgRMXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgICAPoAJgCyAAIAECICE0wA4ADkADgROBFcAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagEWARZBFoEWwRcBF0EXgRfgIWAhoCHgImAioCLgIyAjYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBD4AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgIMICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD4AWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgIMICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUEgQAVBD4AWgBaAFoALwBaAKIBswBaAFoAFQBagACAiIAAgIMICAgIgBqARAgIgAAI0wA4ADkADgSPBJAAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEPgBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACAgwgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUEPgBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACAgwgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEPgBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACAgwgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPgBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACAgwgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEPgBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACAgwgICAiAGoBJCAiAAAjZAB8AIwTeAA4AJgTfACEASwTgAWoBnABMAGsAFQAnAC8AWgToXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgICAP4AJgCyAAIAECICP0wA4ADkADgTqBPIAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4BcpwTzBPQE9QT2BPcE+AT5gJCAkYCSgJOAlYCWgJeAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ/AFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAICOCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ/AFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAICOCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ/AFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAICOCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBSoAFQQ/AFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgJSAAICOCAgICIAagFkICIAACBEDhN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ/AFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAICOCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ/AFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAICOCAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ/AFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAICOCAgICIAagFwICIAACN8QEgCQAJEAkgVmAB8AlACVBWcAIQCTBWgAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBXAALwBaAEwAWgGSAV0AWgBaBXgAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICICaCIAJCIBmgDEICICZCBL3jAP70wA4ADkADgV8BX8APqIBmwGcgD6AP6IFgAWBgJuApoAl2QAfACMFhAAOACYFhQAhAEsFhgFrAZsATABrABUAJwAvAFoFjl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCYgD6ACYAsgACABAiAnNMAOAA5AA4FkAWZAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoBZoFmwWcBZ0FngWfBaAFoYCdgJ6An4ChgKKAo4CkgKWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQWAAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAICbCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQWAAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAICbCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBcMAFQWAAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgKCAAICbCAgICIAagEQICIAACNMAOAA5AA4F0QXSAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBYAAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgJsICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCDQAVBYAAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAUIAAgJsICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBYAAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgJsICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBYAAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgJsICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBYAAWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgJsICAgIgBqASQgIgAAI2QAfACMGIAAOACYGIQAhAEsGIgFrAZwATABrABUAJwAvAFoGKl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCYgD+ACYAsgACABAiAp9MAOAA5AA4GLAY0AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcGNQY2BjcGOAY5BjoGO4CogKmAqoCrgKyArYCvgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFgQBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACApggICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFgQBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACApggICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFgQBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACApggICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKcABUFgQBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIBhgACApggICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFgQBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACApggICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQaKABUFgQBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAICugACApggICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFgQBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACApggICAiAGoBcCAiAAAjfEBIAkACRAJIGqAAfAJQAlQapACEAkwaqAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgayAC8AWgBMAFoBkgFeAFoAWga6AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAsgiACQiAZoAyCAiAsQgSRT2+uNMAOAA5AA4GvgbBAD6iAZsBnIA+gD+iBsIGw4CzgL6AJdkAHwAjBsYADgAmBscAIQBLBsgBbAGbAEwAawAVACcALwBaBtBfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAsIA+gAmALIAAgAQIgLTTADgAOQAOBtIG2wA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAbcBt0G3gbfBuAG4QbiBuOAtYC2gLeAuYC6gLuAvIC9gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGwgBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAswgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGwgBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACAswgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQcFABUGwgBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIC4gACAswgICAiAGoBECAiAAAjTADgAOQAOBxMHFAA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbCAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAICzCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbCAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAICzCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbCAFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAICzCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbCAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAICzCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbCAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAICzCAgICIAagEkICIAACNkAHwAjB2IADgAmB2MAIQBLB2QBbAGcAEwAawAVACcALwBaB2xfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAsIA/gAmALIAAgAQIgL/TADgAOQAOB24HdgA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynB3cHeAd5B3oHewd8B32AwIDCgMOAxIDGgMeAyIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHgQAVBsMAWgBaAFoALwBaAKICVgBaAFoAFQBagACAwYAAgL4ICAgIgBqAVggIgAAIUk5P3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBsMAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgL4ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBsMAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgL4ICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHrwAVBsMAWgBaAFoALwBaAKICWQBaAFoAFQBagACAxYAAgL4ICAgIgBqAWQgIgAAIEQMg3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBsMAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgL4ICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBsMAWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgL4ICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBsMAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgL4ICAgIgBqAXAgIgAAI3xASAJAAkQCSB+sAHwCUAJUH7AAhAJMH7QCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoH9QAvAFoATABaAZIBXwBaAFoH/QBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgMsIgAkIgGaAMwgIgMoIEwAAAAEFzTMw0wA4ADkADggBCAQAPqIBmwGcgD6AP6IIBQgGgMyA14Al2QAfACMICQAOACYICgAhAEsICwFtAZsATABrABUAJwAvAFoIE18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYDJgD6ACYAsgACABAiAzdMAOAA5AA4IFQgeAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoCB8IIAghCCIIIwgkCCUIJoDOgM+A0IDSgNOA1IDVgNaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgFAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIDMCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQgFAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIDMCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCEgAFQgFAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgNGAAIDMCAgICIAagEQICIAACNMAOAA5AA4IVghXAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAUAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgMwICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCDQAVCAUAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAUIAAgMwICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAUAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgMwICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAUAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgMwICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAUAWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgMwICAgIgBqASQgIgAAI2QAfACMIpQAOACYIpgAhAEsIpwFtAZwATABrABUAJwAvAFoIr18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYDJgD+ACYAsgACABAiA2NMAOAA5AA4IsQi5AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcIugi7CLwIvQi+CL8IwIDZgNqA24DcgN2A3oDggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACA1wgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUIBgBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACA1wgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACA1wgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKcABUIBgBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIBhgACA1wgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACA1wgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQkPABUIBgBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIDfgACA1wgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACA1wgICAiAGoBcCAiAAAjfEBIAkACRAJIJLQAfAJQAlQkuACEAkwkvAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgk3AC8AWgBMAFoBkgFgAFoAWgk/AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiA4wiACQiAZoA0CAiA4ggSp2WOnNMAOAA5AA4JQwlGAD6iAZsBnIA+gD+iCUcJSIDkgO+AJdkAHwAjCUsADgAmCUwAIQBLCU0BbgGbAEwAawAVACcALwBaCVVfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA4YA+gAmALIAAgAQIgOXTADgAOQAOCVcJYAA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAlhCWIJYwlkCWUJZglnCWiA5oDngOiA6oDrgOyA7YDugCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACA5AgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJRwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACA5AgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQmKABUJRwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIDpgACA5AgICAiAGoBECAiAAAjTADgAOQAOCZgJmQA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlHAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIDkCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQlHAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIDkCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlHAFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAIDkCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlHAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIDkCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlHAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIDkCAgICIAagEkICIAACNkAHwAjCecADgAmCegAIQBLCekBbgGcAEwAawAVACcALwBaCfFfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA4YA/gAmALIAAgAQIgPDTADgAOQAOCfMJ+wA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynCfwJ/Qn+Cf8KAAoBCgKA8YDygPOA9ID2gPeA+IAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUgAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgO8ICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCUgAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgO8ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUgAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgO8ICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKMwAVCUgAWgBaAFoALwBaAKICWQBaAFoAFQBagACA9YAAgO8ICAgIgBqAWQgIgAAIEQK83xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUgAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgO8ICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUgAWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgO8ICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUgAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgO8ICAgIgBqAXAgIgAAI3xASAJAAkQCSCm8AHwCUAJUKcAAhAJMKcQCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoKeQAvAFoATABaAZIBYQBaAFoKgQBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgPsIgAkIgGaANQgIgPoIEpfHgSrTADgAOQAOCoUKiAA+ogGbAZyAPoA/ogqJCoqA/IEBB4Al2QAfACMKjQAOACYKjgAhAEsKjwFvAZsATABrABUAJwAvAFoKl18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYD5gD6ACYAsgACABAiA/dMAOAA5AA4KmQqiAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoCqMKpAqlCqYKpwqoCqkKqoD+gP+BAQCBAQKBAQOBAQSBAQWBAQaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqJAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAID8CAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqJAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAID8CAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCswAFQqJAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgQEBgACA/AgICAiAGoBECAiAAAjTADgAOQAOCtoK2wA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqJAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAID8CAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQqJAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAID8CAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqJAFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAID8CAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqJAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAID8CAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqJAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAID8CAgICIAagEkICIAACNkAHwAjCykADgAmCyoAIQBLCysBbwGcAEwAawAVACcALwBaCzNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA+YA/gAmALIAAgAQIgQEI0wA4ADkADgs1Cz0APqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpws+Cz8LQAtBC0ILQwtEgQEJgQEKgQELgQEMgQENgQEOgQEPgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKigBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACBAQcICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCooAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgQEHCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqKAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBBwgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQozABUKigBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAID1gACBAQcICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCooAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgQEHCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqKAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIEBBwgICAiAGoBbCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKigBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBAQcICAgIgBqAXAgIgAAI3xASAJAAkQCSC7AAHwCUAJULsQAhAJMLsgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoLugAvAFoATABaAZIBYgBaAFoLwgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQESCIAJCIBmgDYICIEBEQgSapqjctMAOAA5AA4LxgvJAD6iAZsBnIA+gD+iC8oLy4EBE4EBHoAl2QAfACMLzgAOACYLzwAhAEsL0AFwAZsATABrABUAJwAvAFoL2F8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBEIA+gAmALIAAgAQIgQEU0wA4ADkADgvaC+MAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagL5AvlC+YL5wvoC+kL6gvrgQEVgQEWgQEXgQEZgQEagQEbgQEcgQEdgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULygBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACBARMICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8oAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgQETCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDA0AFQvKAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgQEYgACBARMICAgIgBqARAgIgAAI0wA4ADkADgwbDBwAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULygBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACBARMICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCDQAVC8oAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAUIAAgQETCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvKAFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAIEBEwgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULygBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACBARMICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8oAWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgQETCAgICIAagEkICIAACNkAHwAjDGoADgAmDGsAIQBLDGwBcAGcAEwAawAVACcALwBaDHRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBARCAP4AJgCyAAIAECIEBH9MAOAA5AA4Mdgx+AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcMfwyADIEMggyDDIQMhYEBIIEBIYEBIoEBI4EBJIEBJYEBJoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8sAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgQEeCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvLAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBHggICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULywBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAR4ICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFKgAVC8sAWgBaAFoALwBaAKICWQBaAFoAFQBagACAlIAAgQEeCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvLAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBHggICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULywBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAR4ICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8sAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQEeCAgICIAagFwICIAACN8QEgCQAJEAkgzxAB8AlACVDPIAIQCTDPMAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaDPsALwBaAEwAWgGSAWMAWgBaDQMAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBKQiACQiAZoA3CAiBASgIEmMT4kvTADgAOQAODQcNCgA+ogGbAZyAPoA/og0LDQyBASqBATWAJdkAHwAjDQ8ADgAmDRAAIQBLDREBcQGbAEwAawAVACcALwBaDRlfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBASeAPoAJgCyAAIAECIEBK9MAOAA5AA4NGw0kAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoDSUNJg0nDSgNKQ0qDSsNLIEBLIEBLYEBLoEBMIEBMYEBMoEBM4EBNIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQsAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQEqCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0LAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBKggICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ1OABUNCwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBL4AAgQEqCAgICIAagEQICIAACNMAOAA5AA4NXA1dAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQsAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQEqCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQ0LAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIEBKggICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNCwBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBASoICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQsAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQEqCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0LAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBKggICAiAGoBJCAiAAAjZAB8AIw2rAA4AJg2sACEASw2tAXEBnABMAGsAFQAnAC8AWg21XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQEngD+ACYAsgACABAiBATbTADgAOQAODbcNvwA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynDcANwQ3CDcMNxA3FDcaBATeBATiBATmBATqBATuBATyBAT2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0MAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIEBNQgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNDABaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBATUICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQwAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQE1CAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCjMAFQ0MAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgPWAAIEBNQgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNDABaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBATUICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQwAWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgQE1CAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0MAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIEBNQgICAiAGoBcCAiAAAjfEBIAkACRAJIOMgAfAJQAlQ4zACEAkw40AJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWg48AC8AWgBMAFoBkgFkAFoAWg5EAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBAUAIgAkIgGaAOAgIgQE/CBKQwO440wA4ADkADg5IDksAPqIBmwGcgD6AP6IOTA5NgQFBgQFMgCXZAB8AIw5QAA4AJg5RACEASw5SAXIBmwBMAGsAFQAnAC8AWg5aXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQE+gD6ACYAsgACABAiBAULTADgAOQAODlwOZQA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqA5mDmcOaA5pDmoOaw5sDm2BAUOBAUSBAUWBAUeBAUiBAUmBAUqBAUuAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5MAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOTABaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACBAUEICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUOjwAVDkwAWgBaAFoALwBaAKIBswBaAFoAFQBagACBAUaAAIEBQQgICAiAGoBECAiAAAjTADgAOQAODp0OngA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5MAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOTABaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACBAUEICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkwAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgQFBCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5MAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIEBQQgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOTABaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACBAUEICAgIgBqASQgIgAAI2QAfACMO7AAOACYO7QAhAEsO7gFyAZwATABrABUAJwAvAFoO9l8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBPoA/gAmALIAAgAQIgQFN0wA4ADkADg74DwAAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpw8BDwIPAw8EDwUPBg8HgQFOgQFQgQFRgQFSgQFTgQFUgQFVgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ8LABUOTQBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIEBT4AAgQFMCAgICIAagFYICIAACFNZRVPfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOTQBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBAUwICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDk0AWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQFMCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVB68AFQ5NAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgMWAAIEBTAgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOTQBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBAUwICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDk0AWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgQFMCAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5NAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIEBTAgICAiAGoBcCAiAAAjfEBIAkACRAJIPdAAfAJQAlQ91ACEAkw92AJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWg9+AC8AWgBMAFoBkgFlAFoAWg+GAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBAVgIgAkIgGaAOQgIgQFXCBLkDhOM0wA4ADkADg+KD40APqIBmwGcgD6AP6IPjg+PgQFZgQFkgCXZAB8AIw+SAA4AJg+TACEASw+UAXMBmwBMAGsAFQAnAC8AWg+cXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFWgD6ACYAsgACABAiBAVrTADgAOQAOD54PpwA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqA+oD6kPqg+rD6wPrQ+uD6+BAVuBAVyBAV2BAV+BAWCBAWGBAWKBAWOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+OAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBWQgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjgBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACBAVkICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUP0QAVD44AWgBaAFoALwBaAKIBswBaAFoAFQBagACBAV6AAIEBWQgICAiAGoBECAiAAAjTADgAOQAOD98P4AA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+OAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIEBWQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPjgBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACBAVkICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD44AWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgQFZCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+OAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIEBWQgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPjgBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACBAVkICAgIgBqASQgIgAAI2QAfACMQLgAOACYQLwAhAEsQMAFzAZwATABrABUAJwAvAFoQOF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBVoA/gAmALIAAgAQIgQFl0wA4ADkADhA6EEIAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4BcpxBDEEQQRRBGEEcQSBBJgQFmgQFngQFogQFpgQFqgQFrgQFsgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ8LABUPjwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIEBT4AAgQFkCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+PAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBZAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAWQICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHrwAVD48AWgBaAFoALwBaAKICWQBaAFoAFQBagACAxYAAgQFkCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+PAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBZAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjwBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAWQICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD48AWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQFkCAgICIAagFwICIAACN8QEgCQAJEAkhC1AB8AlACVELYAIQCTELcAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaEL8ALwBaAEwAWgGSAWYAWgBaEMcAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBbwiACQiAZoA6CAiBAW4IEnyO1U7TADgAOQAOEMsQzgA+ogGbAZyAPoA/ohDPENCBAXCBAXuAJdkAHwAjENMADgAmENQAIQBLENUBdAGbAEwAawAVACcALwBaEN1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAW2APoAJgCyAAIAECIEBcdMAOAA5AA4Q3xDoAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoEOkQ6hDrEOwQ7RDuEO8Q8IEBcoEBc4EBdIEBdoEBd4EBeIEBeYEBeoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVEM8AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFwCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFRDPAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBcAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFRESABUQzwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBdYAAgQFwCAgICIAagEQICIAACNMAOAA5AA4RIBEhAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVEM8AWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQFwCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFRDPAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIEBcAgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUQzwBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBAXAICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVEM8AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQFwCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFRDPAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBcAgICAiAGoBJCAiAAAjZAB8AIxFvAA4AJhFwACEASxFxAXQBnABMAGsAFQAnAC8AWhF5XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFtgD+ACYAsgACABAiBAXzTADgAOQAOEXsRgwA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynEYQRhRGGEYcRiBGJEYqBAX2BAX6BAX+BAYCBAYGBAYKBAYSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFRDQAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIEBewgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUQ0ABaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBAXsICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVENAAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQF7CAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA+cAFRDQAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgHuAAIEBewgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUQ0ABaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBAXsICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUR2QAVENAAWgBaAFoALwBaAKICWwBaAFoAFQBagACBAYOAAIEBewgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUQ0ABaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBAXsICAgIgBqAXAgIgAAIWmR1cGxpY2F0ZXPSADkADhH4AKqggBnSAKwArRH7EfxaWERQTUVudGl0eacR/RH+Ef8SABIBEgIAsVpYRFBNRW50aXR5XVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADhIEEgUAPqCggCXTADgAOQAOEggSCQA+oKCAJdMAOAA5AA4SDBINAD6goIAl0gCsAK0SEBIRXlhETW9kZWxQYWNrYWdlphISEhMSFBIVEhYAsV5YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADkADhIYAKqggBnTADgAOQAOEhsSHAA+oKCAJVDSAKwArRIgEiFZWERQTU1vZGVsoxIgEiIAsVdYRE1vZGVsAAgAGQAiACwAMQA6AD8AUQBWAFsAXQOBA4cDoAOyA7kDxwPUA+wEBgQIBAsEDQQQBBMEFgRPBG4EiwSqBLwE3ATjBQEFDQUpBS8FUQVyBYUFhwWKBY0FjwWRBZMFlgWZBZsFnQWfBaEFowWlBaYFqgW3Bb8FygXNBc8F0gXUBdYF5QYoBkwGcAaTBroG2gcBBygHSAdsB5AHnAeeB6AHogekB6YHqAerB60HrweyB7QHtge5B7sHvAfFB80H2gfdB98H4gfkB+YH9QgaCD4IZQiJCIsIjQiPCJEIkwiVCJYImAilCLgIugi8CL4IwAjCCMQIxgjICMoI3QjfCOEI4wjlCOcI6QjrCO0I7wjxCQcJGgk2CVMJbwmDCZUJqwnECgMKCQoSCh8KKwo1Cj8KSgpVCmIKagpsCm4KcApyCnMKdAp1CnYKeAp6CnsKfAp+Cn8KiAqJCosKlAqfCqgKtwq+CsYKzwrYCusK9AsHCx4LMAtvC3ELcwt1C3cLeAt5C3oLewt9C38LgAuBC4MLhAvDC8ULxwvJC8sLzAvNC84LzwvRC9ML1AvVC9cL2AvhC+IL5AwjDCUMJwwpDCsMLAwtDC4MLwwxDDMMNAw1DDcMOAx3DHkMewx9DH8MgAyBDIIMgwyFDIcMiAyJDIsMjAyVDJYMmAzXDNkM2wzdDN8M4AzhDOIM4wzlDOcM6AzpDOsM7AztDSwNLg0wDTINNA01DTYNNw04DToNPA09DT4NQA1BDU4NTw1QDVINWw1xDXgNhQ3EDcYNyA3KDcwNzQ3ODc8N0A3SDdQN1Q3WDdgN2Q3yDfQN9g34DfkN+w4SDhsOKQ42DkQOWQ5tDoQOlg7VDtcO2Q7bDt0O3g7fDuAO4Q7jDuUO5g7nDukO6g7/DwgPHQ8sD0EPTw9kD3gPjw+hD64PyQ/LD80Pzw/RD9MP1Q/XD9kP2w/dD98P4Q/jD/4QABACEAQQBhAIEAoQDBAOEBEQFBAXEBoQHRAfECUQNBBIEFsQaRB8EIYQkhCeEKQQsRC4EMMRDhExEVERcRFzEXURdxF5EXsRfBF9EX8RgBGCEYMRhRGHEYgRiRGLEYwRkRGeEaMRpRGnEawRrhGwEbIRxxHcEgESJRJMEnASchJ0EnYSeBJ6EnwSfRJ/EowSnRKfEqESoxKlEqcSqRKrEq0SvhLAEsISxBLGEsgSyhLMEs4S0BLuEwwTHxMzE0gTZRN5E48TzhPQE9IT1BPWE9cT2BPZE9oT3BPeE98T4BPiE+MUIhQkFCYUKBQqFCsULBQtFC4UMBQyFDMUNBQ2FDcUdhR4FHoUfBR+FH8UgBSBFIIUhBSGFIcUiBSKFIsUmBSZFJoUnBTbFN0U3xThFOMU5BTlFOYU5xTpFOsU7BTtFO8U8BUvFTEVMxU1FTcVOBU5FToVOxU9FT8VQBVBFUMVRBVFFYQVhhWIFYoVjBWNFY4VjxWQFZIVlBWVFZYVmBWZFdgV2hXcFd4V4BXhFeIV4xXkFeYV6BXpFeoV7BXtFiwWLhYwFjIWNBY1FjYWNxY4FjoWPBY9Fj4WQBZBFmYWihaxFtUW1xbZFtsW3RbfFuEW4hbkFvEXABcCFwQXBhcIFwoXDBcOFx0XHxchFyMXJRcnFykXKxctF00XeBeSF6sXxRflGAgYRxhJGEsYTRhPGFAYURhSGFMYVRhXGFgYWRhbGFwYmxidGJ8YoRijGKQYpRimGKcYqRirGKwYrRivGLAY7xjxGPMY9Rj3GPgY+Rj6GPsY/Rj/GQAZARkDGQQZQxlFGUcZSRlLGUwZTRlOGU8ZURlTGVQZVRlXGVgZWxmaGZwZnhmgGaIZoxmkGaUZphmoGaoZqxmsGa4ZrxnuGfAZ8hn0GfYZ9xn4GfkZ+hn8Gf4Z/xoAGgIaAxoqGmkaaxptGm8acRpyGnMadBp1GncaeRp6GnsafRp+GocalRqiGrAavRrQGuca+RtEG2cbhxunG6kbqxutG68bsRuyG7MbtRu2G7gbuRu7G70bvhu/G8EbwhvHG9Qb2RvbG90b4hvkG+Yb6BwNHDEcWBx8HH4cgByCHIQchhyIHIkcixyYHKkcqxytHK8csRyzHLUctxy5HMoczBzOHNAc0hzUHNYc2BzaHNwdGx0dHR8dIR0jHSQdJR0mHScdKR0rHSwdLR0vHTAdbx1xHXMddR13HXgdeR16HXsdfR1/HYAdgR2DHYQdwx3FHccdyR3LHcwdzR3OHc8d0R3THdQd1R3XHdgd5R3mHecd6R4oHioeLB4uHjAeMR4yHjMeNB42HjgeOR46HjwePR58Hn4egB6CHoQehR6GHoceiB6KHowejR6OHpAekR7QHtIe1B7WHtge2R7aHtse3B7eHuAe4R7iHuQe5R8kHyYfKB8qHywfLR8uHy8fMB8yHzQfNR82HzgfOR94H3offB9+H4AfgR+CH4MfhB+GH4gfiR+KH4wfjR+yH9Yf/SAhICMgJSAnICkgKyAtIC4gMCA9IEwgTiBQIFIgVCBWIFggWiBpIGsgbSBvIHEgcyB1IHcgeSC4ILogvCC+IMAgwSDCIMMgxCDGIMggySDKIMwgzSEMIQ4hECESIRQhFSEWIRchGCEaIRwhHSEeISAhISFgIWIhZCFmIWghaSFqIWshbCFuIXAhcSFyIXQhdSG0IbYhuCG6IbwhvSG+Ib8hwCHCIcQhxSHGIcghySHMIgsiDSIPIhEiEyIUIhUiFiIXIhkiGyIcIh0iHyIgIl8iYSJjImUiZyJoImkiaiJrIm0ibyJwInEicyJ0Ipsi2iLcIt4i4CLiIuMi5CLlIuYi6CLqIusi7CLuIu8jOiNdI30jnSOfI6EjoyOlI6cjqCOpI6sjrCOuI68jsSOzI7QjtSO3I7gjvSPKI88j0SPTI9gj2iPcI94kAyQnJE4kciR0JHYkeCR6JHwkfiR/JIEkjiSfJKEkoySlJKckqSSrJK0kryTAJMIkxCTGJMgkyiTMJM4k0CTSJRElEyUVJRclGSUaJRslHCUdJR8lISUiJSMlJSUmJWUlZyVpJWslbSVuJW8lcCVxJXMldSV2JXcleSV6JbkluyW9Jb8lwSXCJcMlxCXFJcclySXKJcslzSXOJdsl3CXdJd8mHiYgJiImJCYmJicmKCYpJiomLCYuJi8mMCYyJjMmciZ0JnYmeCZ6JnsmfCZ9Jn4mgCaCJoMmhCaGJocmxibIJsomzCbOJs8m0CbRJtIm1CbWJtcm2CbaJtsnGiccJx4nICciJyMnJCclJyYnKCcqJysnLCcuJy8nbidwJ3IndCd2J3cneCd5J3onfCd+J38ngCeCJ4MnqCfMJ/MoFygZKBsoHSgfKCEoIygkKCYoMyhCKEQoRihIKEooTChOKFAoXyhhKGMoZShnKGkoayhtKG8oriiwKLIotCi2KLcouCi5KLoovCi+KL8owCjCKMMpAikEKQYpCCkKKQspDCkNKQ4pECkSKRMpFCkWKRcpVilYKVopXCleKV8pYClhKWIpZClmKWcpaClqKWspqimsKa4psCmyKbMptCm1KbYpuCm6KbspvCm+Kb8pwioBKgMqBSoHKgkqCioLKgwqDSoPKhEqEioTKhUqFipVKlcqWSpbKl0qXipfKmAqYSpjKmUqZipnKmkqaiqpKqsqrSqvKrEqsiqzKrQqtSq3Krkquiq7Kr0qvisJKywrTCtsK24rcCtyK3Qrdit3K3greit7K30rfiuAK4IrgyuEK4YrhyuMK5krniugK6IrpyupK6srrSvSK/YsHSxBLEMsRSxHLEksSyxNLE4sUCxdLG4scCxyLHQsdix4LHosfCx+LI8skSyTLJUslyyZLJssnSyfLKEs4CziLOQs5izoLOks6izrLOws7izwLPEs8iz0LPUtNC02LTgtOi08LT0tPi0/LUAtQi1ELUUtRi1ILUktiC2KLYwtji2QLZEtki2TLZQtli2YLZktmi2cLZ0tqi2rLawtri3tLe8t8S3zLfUt9i33Lfgt+S37Lf0t/i3/LgEuAi5BLkMuRS5HLkkuSi5LLkwuTS5PLlEuUi5TLlUuVi6VLpcumS6bLp0uni6fLqAuoS6jLqUupi6nLqkuqi7pLusu7S7vLvEu8i7zLvQu9S73Lvku+i77Lv0u/i89Lz8vQS9DL0UvRi9HL0gvSS9LL00vTi9PL1EvUi93L5svwi/mL+gv6i/sL+4v8C/yL/Mv9TACMBEwEzAVMBcwGTAbMB0wHzAuMDAwMjA0MDYwODA6MDwwPjB9MH8wgTCDMIUwhjCHMIgwiTCLMI0wjjCPMJEwkjDRMNMw1TDXMNkw2jDbMNww3TDfMOEw4jDjMOUw5jElMScxKTErMS0xLjEvMTAxMTEzMTUxNjE3MTkxOjF5MXsxfTF/MYExgjGDMYQxhTGHMYkxijGLMY0xjjHNMc8x0THTMdUx1jHXMdgx2THbMd0x3jHfMeEx4jIhMiMyJTInMikyKjIrMiwyLTIvMjEyMjIzMjUyNjJdMpwynjKgMqIypDKlMqYypzKoMqoyrDKtMq4ysDKxMvwzHzM/M18zYTNjM2UzZzNpM2ozazNtM24zcDNxM3MzdTN2M3czeTN6M38zjDORM5MzlTOaM5wznjOgM8Uz6TQQNDQ0NjQ4NDo0PDQ+NEA0QTRDNFA0YTRjNGU0ZzRpNGs0bTRvNHE0gjSENIY0iDSKNIw0jjSQNJI0lDTTNNU01zTZNNs03DTdNN403zThNOM05DTlNOc06DUnNSk1KzUtNS81MDUxNTI1MzU1NTc1ODU5NTs1PDV7NX01fzWBNYM1hDWFNYY1hzWJNYs1jDWNNY81kDWdNZ41nzWhNeA14jXkNeY16DXpNeo16zXsNe418DXxNfI19DX1NjQ2NjY4Njo2PDY9Nj42PzZANkI2RDZFNkY2SDZJNog2ijaMNo42kDaRNpI2kzaUNpY2mDaZNpo2nDadNtw23jbgNuI25DblNuY25zboNuo27DbtNu428DbxNzA3Mjc0NzY3ODc5Nzo3Ozc8Nz43QDdBN0I3RDdFN2o3jje1N9k32zfdN9834TfjN+U35jfoN/U4BDgGOAg4CjgMOA44EDgSOCE4IzglOCc4KTgrOC04LzgxOHA4cjh0OHY4eDh5OHo4ezh8OH44gDiBOII4hDiFOIg4xzjJOMs4zTjPONA40TjSONM41TjXONg42TjbONw5GzkdOR85ITkjOSQ5JTkmOSc5KTkrOSw5LTkvOTA5bzlxOXM5dTl3OXg5eTl6OXs5fTl/OYA5gTmDOYQ5hznGOcg5yjnMOc45zznQOdE50jnUOdY51znYOdo52zoaOhw6HjogOiI6IzokOiU6JjooOio6KzosOi46LzpuOnA6cjp0OnY6dzp4Onk6ejp8On46fzqAOoI6gzrOOvE7ETsxOzM7NTs3Ozk7Ozs8Oz07PztAO0I7QztFO0c7SDtJO0s7TDtVO2I7ZztpO2s7cDtyO3Q7djubO7875jwKPAw8DjwQPBI8FDwWPBc8GTwmPDc8OTw7PD08PzxBPEM8RTxHPFg8WjxcPF48YDxiPGQ8ZjxoPGo8qTyrPK08rzyxPLI8szy0PLU8tzy5PLo8uzy9PL48/Tz/PQE9Az0FPQY9Bz0IPQk9Cz0NPQ49Dz0RPRI9UT1TPVU9Vz1ZPVo9Wz1cPV09Xz1hPWI9Yz1lPWY9cz10PXU9dz22Pbg9uj28Pb49vz3APcE9wj3EPcY9xz3IPco9yz4KPgw+Dj4QPhI+Ez4UPhU+Fj4YPho+Gz4cPh4+Hz5ePmA+Yj5kPmY+Zz5oPmk+aj5sPm4+bz5wPnI+cz6yPrQ+tj64Pro+uz68Pr0+vj7APsI+wz7EPsY+xz8GPwg/Cj8MPw4/Dz8QPxE/Ej8UPxY/Fz8YPxo/Gz9AP2Q/iz+vP7E/sz+1P7c/uT+7P7w/vj/LP9o/3D/eP+A/4j/kP+Y/6D/3P/k/+z/9P/9AAUADQAVAB0BGQEhASkBMQE5AT0BQQFFAUkBUQFZAV0BYQFpAW0CaQJxAnkCgQKJAo0CkQKVApkCoQKpAq0CsQK5Ar0DuQPBA8kD0QPZA90D4QPlA+kD8QP5A/0EAQQJBA0FCQURBRkFIQUpBS0FMQU1BTkFQQVJBU0FUQVZBV0GWQZhBmkGcQZ5Bn0GgQaFBokGkQaZBp0GoQapBq0HqQexB7kHwQfJB80H0QfVB9kH4QfpB+0H8Qf5B/0ImQmVCZ0JpQmtCbUJuQm9CcEJxQnNCdUJ2QndCeUJ6QsVC6EMIQyhDKkMsQy5DMEMyQzNDNEM2QzdDOUM6QzxDPkM/Q0BDQkNDQ0hDVUNaQ1xDXkNjQ2VDZ0NpQ45DskPZQ/1D/0QBRANEBUQHRAlECkQMRBlEKkQsRC5EMEQyRDRENkQ4RDpES0RNRE9EUURTRFVEV0RZRFtEXUScRJ5EoESiRKREpUSmRKdEqESqRKxErUSuRLBEsUTwRPJE9ET2RPhE+UT6RPtE/ET+RQBFAUUCRQRFBUVERUZFSEVKRUxFTUVORU9FUEVSRVRFVUVWRVhFWUVmRWdFaEVqRalFq0WtRa9FsUWyRbNFtEW1RbdFuUW6RbtFvUW+Rf1F/0YBRgNGBUYGRgdGCEYJRgtGDUYORg9GEUYSRlFGU0ZVRldGWUZaRltGXEZdRl9GYUZiRmNGZUZmRqVGp0apRqtGrUauRq9GsEaxRrNGtUa2RrdGuUa6RvlG+0b9Rv9HAUcCRwNHBEcFRwdHCUcKRwtHDUcORzNHV0d+R6JHpEemR6hHqkesR65Hr0exR75HzUfPR9FH00fVR9dH2UfbR+pH7EfuR/BH8kf0R/ZH+Ef6SDlIO0g9SD9IQUhCSENIREhFSEdISUhKSEtITUhOSI1Ij0iRSJNIlUiWSJdImEiZSJtInUieSJ9IoUiiSOFI40jlSOdI6UjqSOtI7EjtSO9I8UjySPNI9Uj2STVJN0k5STtJPUk+ST9JQElBSUNJRUlGSUdJSUlKSU1JjEmOSZBJkkmUSZVJlkmXSZhJmkmcSZ1JnkmgSaFJ4EniSeRJ5knoSelJ6knrSexJ7knwSfFJ8kn0SfVKNEo2SjhKOko8Sj1KPko/SkBKQkpESkVKRkpISklKlEq3StdK90r5SvtK/Ur/SwFLAksDSwVLBksISwlLC0sNSw5LD0sRSxJLF0skSylLK0stSzJLNEs3SzlLXkuCS6lLzUvPS9FL00vVS9dL2UvaS9xL6Uv6S/xL/kwATAJMBEwGTAhMCkwbTB1MH0wiTCVMKEwrTC5MMUwzTHJMdEx2THhMekx7THxMfUx+TIBMgkyDTIRMhkyHTMZMyEzKTMxMzkzPTNBM0UzSTNRM1kzXTNhM2kzbTRpNHE0fTSFNI00kTSVNJk0nTSlNK00sTS1NL00wTT1NPk0/TUFNgE2CTYRNhk2ITYlNik2LTYxNjk2QTZFNkk2UTZVN1E3WTdhN2k3cTd1N3k3fTeBN4k3kTeVN5k3oTelOKE4qTixOLk4wTjFOMk4zTjRONk44TjlOOk48Tj1OfE5+ToBOgk6EToVOhk6HTohOik6MTo1Ojk6QTpFO0E7STtRO1k7YTtlO2k7bTtxO3k7gTuFO4k7kTuVPCk8uT1VPeU97T31Pf0+BT4NPhU+GT4lPlk+lT6dPqU+rT61Pr0+xT7NPwk/FT8hPy0/OT9FP1E/XT9lQGFAaUBxQHlAhUCJQI1AkUCVQJ1ApUCpQK1AtUC5QbVBvUHFQc1B2UHdQeFB5UHpQfFB+UH9QgFCCUINQwlDEUMZQyFDLUMxQzVDOUM9Q0VDTUNRQ1VDXUNhRF1EZURtRHVEgUSFRIlEjUSRRJlEoUSlRKlEsUS1RbFFuUXBRclF1UXZRd1F4UXlRe1F9UX5Rf1GBUYJRwVHDUcVRx1HKUctRzFHNUc5R0FHSUdNR1FHWUddSFlIYUhpSHFIfUiBSIVIiUiNSJVInUihSKVIrUixSd1KaUrpS2lLcUt5S4FLiUuRS5VLmUulS6lLsUu1S71LxUvJS81L2UvdS/FMJUw5TEFMSUxdTGlMdUx9TRFNoU49Ts1O2U7hTulO8U75TwFPBU8RT0VPiU+RT5lPoU+pT7FPuU/BT8lQDVAZUCVQMVA9UElQVVBhUG1QdVFxUXlRgVGJUZVRmVGdUaFRpVGtUbVRuVG9UcVRyVLFUs1S1VLdUulS7VLxUvVS+VMBUwlTDVMRUxlTHVQZVCFULVQ1VEFURVRJVE1UUVRZVGFUZVRpVHFUdVSpVK1UsVS5VbVVvVXFVc1V2VXdVeFV5VXpVfFV+VX9VgFWCVYNVwlXEVcZVyFXLVcxVzVXOVc9V0VXTVdRV1VXXVdhWF1YZVhtWHVYgViFWIlYjViRWJlYoVilWKlYsVi1WbFZuVnBWclZ1VnZWd1Z4VnlWe1Z9Vn5Wf1aBVoJWwVbDVsVWx1bKVstWzFbNVs5W0FbSVtNW1FbWVtdW/FcgV0dXa1duV3BXcld0V3ZXeFd5V3xXiVeYV5pXnFeeV6BXolekV6ZXtVe4V7tXvlfBV8RXx1fKV8xYC1gNWA9YEVgUWBVYFlgXWBhYGlgcWB1YHlggWCFYYFhiWGRYZlhpWGpYa1hsWG1Yb1hxWHJYc1h1WHZYtVi3WLlYu1i+WL9YwFjBWMJYxFjGWMdYyFjKWMtZClkMWQ5ZEFkTWRRZFVkWWRdZGVkbWRxZHVkfWSBZX1lhWWNZZVloWWlZallrWWxZbllwWXFZcll0WXVZtFm2WbhZulm9Wb5Zv1nAWcFZw1nFWcZZx1nJWcpaCVoLWg1aD1oSWhNaFFoVWhZaGFoaWhtaHFoeWh9aalqNWq1azVrPWtFa01rVWtda2FrZWtxa3VrfWuBa4lrkWuVa5lrpWupa71r8WwFbA1sFWwpbDVsQWxJbN1tbW4JbplupW6tbrVuvW7Fbs1u0W7dbxFvVW9db2VvbW91b31vhW+Nb5Vv2W/lb/Fv/XAJcBVwIXAtcDlwQXE9cUVxTXFVcWFxZXFpcW1xcXF5cYFxhXGJcZFxlXKRcplyoXKpcrVyuXK9csFyxXLNctVy2XLdcuVy6XPlc+1z+XQBdA10EXQVdBl0HXQldC10MXQ1dD10QXR1dHl0fXSFdYF1iXWRdZl1pXWpda11sXW1db11xXXJdc111XXZdtV23Xbldu12+Xb9dwF3BXcJdxF3GXcddyF3KXcteCl4MXg5eEF4TXhReFV4WXhdeGV4bXhxeHV4fXiBeX15hXmNeZV5oXmleal5rXmxebl5wXnFecl50XnVetF62Xrheul69Xr5ev17AXsFew17FXsZex17JXspe718TXzpfXl9hX2NfZV9nX2lfa19sX29ffF+LX41fj1+RX5NflV+XX5lfqF+rX65fsV+0X7dful+9X79f/mAAYAJgBGAHYAhgCWAKYAtgDWAPYBBgEWATYBRgU2BVYFdgWWBcYF1gXmBfYGBgYmBkYGVgZmBoYGlgqGCqYKxgrmCxYLJgs2C0YLVgt2C5YLpgu2C9YL5g/WD/YQFhA2EGYQdhCGEJYQphDGEOYQ9hEGESYRNhUmFUYVZhWGFbYVxhXWFeYV9hYWFjYWRhZWFnYWhhp2GpYathrWGwYbFhsmGzYbRhtmG4YblhumG8Yb1h/GH+YgBiAmIFYgZiB2IIYgliC2INYg5iD2IRYhJiXWKAYqBiwGLCYsRixmLIYspiy2LMYs9i0GLSYtNi1WLXYthi2WLcYt1i4mLvYvRi9mL4Yv1jAGMDYwVjKmNOY3VjmWOcY55joGOiY6RjpmOnY6pjt2PIY8pjzGPOY9Bj0mPUY9Zj2GPpY+xj72PyY/Vj+GP7Y/5kAWQDZEJkRGRGZEhkS2RMZE1kTmRPZFFkU2RUZFVkV2RYZJdkmWSbZJ1koGShZKJko2SkZKZkqGSpZKpkrGStZOxk7mTxZPNk9mT3ZPhk+WT6ZPxk/mT/ZQBlAmUDZRBlEWUSZRRlU2VVZVdlWWVcZV1lXmVfZWBlYmVkZWVlZmVoZWllqGWqZaxlrmWxZbJls2W0ZbVlt2W5Zbplu2W9Zb5l/WX/ZgFmA2YGZgdmCGYJZgpmDGYOZg9mEGYSZhNmUmZUZlZmWGZbZlxmXWZeZl9mYWZjZmRmZWZnZmhmp2apZqtmrWawZrFmsmazZrRmtma4Zrlmuma8Zr1m4mcGZy1nUWdUZ1ZnWGdaZ1xnXmdfZ2Jnb2d+Z4BngmeEZ4ZniGeKZ4xnm2eeZ6FnpGenZ6pnrWewZ7Jn8WfzZ/Zn+Gf7Z/xn/Wf+Z/9oAWgDaARoBWgHaAhoDGhLaE1oT2hRaFRoVWhWaFdoWGhaaFxoXWheaGBoYWigaKJopGimaKloqmiraKxorWivaLFosmizaLVotmj1aPdo+Wj7aP5o/2kAaQFpAmkEaQZpB2kIaQppC2lKaUxpTmlQaVNpVGlVaVZpV2lZaVtpXGldaV9pYGmfaaFpo2mlaahpqWmqaatprGmuabBpsWmyabRptWn0afZp+Gn6af1p/mn/agBqAWoDagVqBmoHaglqCmpVanhqmGq4arpqvGq+asBqwmrDasRqx2rIaspqy2rNas9q0GrRatRq1Wraaudq7GruavBq9Wr4avtq/Wsia0ZrbWuRa5RrlmuYa5prnGuea59romuva8BrwmvEa8ZryGvKa8xrzmvQa+Fr5Gvna+pr7Wvwa/Nr9mv5a/tsOmw8bD5sQGxDbERsRWxGbEdsSWxLbExsTWxPbFBsj2yRbJNslWyYbJlsmmybbJxsnmygbKFsomykbKVs5GzmbOls62zubO9s8GzxbPJs9Gz2bPds+Gz6bPttCG0JbQptDG1LbU1tT21RbVRtVW1WbVdtWG1abVxtXW1ebWBtYW2gbaJtpG2mbaltqm2rbaxtrW2vbbFtsm2zbbVttm31bfdt+W37bf5t/24AbgFuAm4EbgZuB24IbgpuC25KbkxuTm5QblNuVG5VblZuV25ZbltuXG5dbl9uYG6fbqFuo26lbqhuqW6qbqturG6ubrBusW6ybrRutW7abv5vJW9Jb0xvTm9Qb1JvVG9Wb1dvWm9nb3ZveG96b3xvfm+Ab4JvhG+Tb5ZvmW+cb59vom+lb6hvqm/pb+tv7m/wb/Nv9G/1b/Zv92/5b/tv/G/9b/9wAHA/cEFwQ3BFcEhwSXBKcEtwTHBOcFBwUXBScFRwVXCUcJZwmHCacJ1wnnCfcKBwoXCjcKVwpnCncKlwqnDpcOtw7XDvcPJw83D0cPVw9nD4cPpw+3D8cP5w/3E+cUBxQnFEcUdxSHFJcUpxS3FNcU9xUHFRcVNxVHGTcZVxl3GZcZxxnXGecZ9xoHGicaRxpXGmcahxqXHocepx7HHucfFx8nHzcfRx9XH3cflx+nH7cf1x/nJJcmxyjHKscq5ysHKycrRytnK3crhyu3K8cr5yv3LBcsNyxHLFcshyyXLOctty4HLicuRy6XLscu9y8XMWczpzYXOFc4hzinOMc45zkHOSc5NzlnOjc7RztnO4c7pzvHO+c8BzwnPEc9Vz2HPbc95z4XPkc+dz6nPtc+90LnQwdDJ0NHQ3dDh0OXQ6dDt0PXQ/dEB0QXRDdER0g3SFdId0iXSMdI10jnSPdJB0knSUdJV0lnSYdJl02HTadN1033TidON05HTldOZ06HTqdOt07HTudO90/HT9dP51AHU/dUF1Q3VFdUh1SXVKdUt1THVOdVB1UXVSdVR1VXWUdZZ1mHWadZ11nnWfdaB1oXWjdaV1pnWndal1qnXpdet17XXvdfJ183X0dfV19nX4dfp1+3X8df51/3Y+dkB2QnZEdkd2SHZJdkp2S3ZNdk92UHZRdlN2VHaTdpV2l3aZdpx2nXaedp92oHaidqR2pXamdqh2qXbOdvJ3GXc9d0B3QndEd0Z3SHdKd0t3Tndbd2p3bHdud3B3cnd0d3Z3eHeHd4p3jXeQd5N3lneZd5x3nnfdd9934Xfjd+Z353fod+l36nfsd+5373fwd/J383gyeDR4Nng4eDt4PHg9eD54P3hBeEN4RHhFeEd4SHiHeIl4i3iNeJB4kXiSeJN4lHiWeJh4mXiaeJx4nXjceN544HjieOV45njneOh46XjreO147njvePF48nkxeTN5NXk3eTp5O3k8eT15PnlAeUJ5Q3lEeUZ5R3mGeYh5i3mNeZB5kXmSeZN5lHmWeZh5mXmaeZx5nXnEegN6BXoHegl6DHoNeg56D3oQehJ6FHoVehZ6GHoZeiR6LXouejB6OXpEelN6XnpseoF6lXqser56y3rMes16z3rcet163nrgeu167nrvevF6+nsJexZ7JXs3e0t7Ynt0e317fnuAe417jnuPe5F7knube6V7rAAAAAAAAAICAAAAAAAAEiMAAAAAAAAAAAAAAAAAAHu0\n</attribute>\n        <relationship name=\"entitymappings\" type=\"0/0\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z109\">\n        <attribute name=\"name\" type=\"string\">messageID</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z110\">\n        <attribute name=\"name\" type=\"string\">messageExpiration</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z111\">\n        <attribute name=\"name\" type=\"string\">deletedClient</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z112\">\n        <attribute name=\"name\" type=\"string\">messageSent</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z113\">\n        <attribute name=\"name\" type=\"string\">title</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z114\">\n        <attribute name=\"name\" type=\"string\">messageURL</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z115\">\n        <attribute name=\"name\" type=\"string\">unreadClient</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z116\">\n        <attribute name=\"name\" type=\"string\">messageBodyURL</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n</database>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInboxDataMappingV2toV4.xcmappingmodel/xcmapping.xml",
    "content": "<?xml version=\"1.0\" standalone=\"yes\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n    <databaseInfo>\n        <version>134481920</version>\n        <UUID>13F60737-FF29-41B8-8B9D-08195AEDDDA3</UUID>\n        <nextObjectID>116</nextObjectID>\n        <metadata>\n            <plist version=\"1.0\">\n                <dict>\n                    <key>NSPersistenceFrameworkVersion</key>\n                    <integer>1518</integer>\n                    <key>NSPersistenceMaximumFrameworkVersion</key>\n                    <integer>1518</integer>\n                    <key>NSStoreModelVersionChecksumKey</key>\n                    <string>bMpud663vz0bXQE24C6Rh4MvJ5jVnzsD2sI3njZkKbc=</string>\n                    <key>NSStoreModelVersionHashes</key>\n                    <dict>\n                        <key>XDDevAttributeMapping</key>\n                        <data>\n\t\t0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=\n\t\t</data>\n                        <key>XDDevEntityMapping</key>\n                        <data>\n\t\tqeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=\n\t\t</data>\n                        <key>XDDevMappingModel</key>\n                        <data>\n\t\tEqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=\n\t\t</data>\n                        <key>XDDevPropertyMapping</key>\n                        <data>\n\t\tXN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA=\n\t\t</data>\n                        <key>XDDevRelationshipMapping</key>\n                        <data>\n\t\takYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs=\n\t\t</data>\n                    </dict>\n                    <key>NSStoreModelVersionHashesDigest</key>\n                    <string>+Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A==</string>\n                    <key>NSStoreModelVersionHashesVersion</key>\n                    <integer>3</integer>\n                    <key>NSStoreModelVersionIdentifiers</key>\n                    <array>\n                        <string></string>\n                    </array>\n                </dict>\n            </plist>\n        </metadata>\n    </databaseInfo>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z102\">\n        <attribute name=\"name\" type=\"string\">messageURL</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z103\">\n        <attribute name=\"name\" type=\"string\">extra</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVENTITYMAPPING\" id=\"z104\">\n        <attribute name=\"migrationpolicyclassname\" type=\"string\">UAInboxDataMappingV2toV4</attribute>\n        <attribute name=\"sourcename\" type=\"string\">UAInboxMessage</attribute>\n        <attribute name=\"mappingtypename\" type=\"string\">Undefined</attribute>\n        <attribute name=\"mappingnumber\" type=\"int16\">1</attribute>\n        <attribute name=\"destinationname\" type=\"string\">UAInboxMessage</attribute>\n        <attribute name=\"autogenerateexpression\" type=\"bool\">1</attribute>\n        <relationship name=\"mappingmodel\" type=\"1/1\" destination=\"XDDEVMAPPINGMODEL\" idrefs=\"z107\"></relationship>\n        <relationship name=\"attributemappings\" type=\"0/0\" destination=\"XDDEVATTRIBUTEMAPPING\" idrefs=\"z103 z106 z110 z113 z105 z114 z108 z115 z109 z112 z102 z111 z116\"></relationship>\n        <relationship name=\"relationshipmappings\" type=\"0/0\" destination=\"XDDEVRELATIONSHIPMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z105\">\n        <attribute name=\"name\" type=\"string\">messageSent</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z106\">\n        <attribute name=\"name\" type=\"string\">messageExpiration</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVMAPPINGMODEL\" id=\"z107\">\n        <attribute name=\"sourcemodelpath\" type=\"string\">AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 2.xcdatamodel</attribute>\n        <attribute name=\"sourcemodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBdwALAAwAGQA1ADYANwA/AEAAWwBcAF0AYwBkAHAAhgCHAIgAiQCKAIsAjACNAI4AjwCoAKsAsgC4AMcA1gDZAOgA9wD6AFoBCgEZAR0BIQEwATYBNwE/AU4BTwFYAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABlQGWAZ4BnwGgAawBwAHBAcIBwwHEAcUBxgHHAcgB1wHmAfUB+QIIAhcCGAInAjYCRQJRAmMCZAJlAmYCZwJoAmkCagJ5AogClwKmAqcCtgLFAsYC1QLdAvIC8wL7AwcDGwMqAzkDSANMA1sDagN5A4gDlwOjA7UDxAPTA+ID8QQABA8EEAQfBDQENQQ9BEkEXQRsBHsEigSOBJ0ErAS7BMoE2QTlBPcFBgUVBSQFMwVCBVEFUgVhBXYFdwV/BYsFnwWuBb0FzAXQBd8F7gX9BgwGGwYnBjkGSAZXBmYGdQaEBpMGlAajBrgGuQbBBs0G4QbwBv8HDgcSByEHMAc/B04HXQdpB3sHigeLB5oHqQe4B7kHyAfXB+YH+wf8CAQIEAgkCDMIQghRCFUIZAhzCIIIkQigCKwIvgjNCNwI6wj6CPsJCgkZCSgJPQk+CUYJUglmCXUJhAmTCZcJpgm1CcQJ0wniCe4KAAoPCh4KLQo8Cj0KTApbCmoKfwqACogKlAqoCrcKxgrVCtkK6Ar3CwYLFQskCzALQgtRC2ALbwt+C40LnAurC8ALwQvJC9UL6Qv4DAcMFgwaDCkMOAxHDFYMZQxxDIMMkgyhDLAMvwzODN0M7A0BDQINCg0WDSoNOQ1IDVcNWw1qDXkNiA2XDaYNsg3EDdMN1A3jDfIOAQ4QDh8OLg5DDkQOTA5YDmwOew6KDpkOnQ6sDrsOyg7ZDugO9A8GDxUPJA8zD0IPUQ9gD28PhA+FD40PmQ+tD7wPyw/aD94P7Q/8EAsQGhApEDUQRxBWEGUQdBCDEJIQoRCiELEQshC1EL4QwhDGEMoQ0hDVENkQ2lUkbnVsbNYADQAOAA8AEAARABIAEwAUABUAFgAXABhfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXV94ZF9tb2RlbE5hbWVcX3hkX2NvbW1lbnRzXxAVX2NvbmZpZ3VyYXRpb25zQnlOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoEBdoAAgQFzgQF0gQF13gAaABsAHAAdAB4AHwAgAA4AIQAiACMAJAAlACYAJwAoACkACQAnABUALQAuAC8AMAAxACcAJwAVXxAcWERCdWNrZXRGb3JDbGFzc2Vzd2FzRW5jb2RlZF8QGlhEQnVja2V0Rm9yUGFja2FnZXNzdG9yYWdlXxAcWERCdWNrZXRGb3JJbnRlcmZhY2Vzc3RvcmFnZV8QD194ZF9vd25pbmdNb2RlbF8QHVhEQnVja2V0Rm9yUGFja2FnZXN3YXNFbmNvZGVkVl9vd25lcl8QG1hEQnVja2V0Rm9yRGF0YVR5cGVzc3RvcmFnZVtfdmlzaWJpbGl0eV8QGVhEQnVja2V0Rm9yQ2xhc3Nlc3N0b3JhZ2VVX25hbWVfEB9YREJ1Y2tldEZvckludGVyZmFjZXN3YXNFbmNvZGVkXxAeWERCdWNrZXRGb3JEYXRhVHlwZXN3YXNFbmNvZGVkXxAQX3VuaXF1ZUVsZW1lbnRJRIAEgQFxgQFvgAGABIAAgQFwgQFyEACABYADgASABIAAUFNZRVPTADgAOQAOADoAPAA+V05TLmtleXNaTlMub2JqZWN0c6EAO4AGoQA9gAeAJV5VQUluYm94TWVzc2FnZd8QEABBAEIAQwBEAB8ARQBGACEARwBIAA4AIwBJAEoAJgBLAEwATQAnACcAEwBRAFIALwAnAEwAVQA7AEwAWABZAFpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAtgASABIACgAqBAWyABIAJgQFugAaACYEBbYAICBKUfK23V29yZGVyZWTTADgAOQAOAF4AYAA+oQBfgAuhAGGADIAlXlhEX1BTdGVyZW90eXBl2QAfACMAZQAOACYAZgAhAEsAZwA9AF8ATABrABUAJwAvAFoAb18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYAsgACABAiADdMAOAA5AA4AcQB7AD6pAHIAcwB0AHUAdgB3AHgAeQB6gA6AD4AQgBGAEoATgBSAFYAWqQB8AH0AfgB/AIAAgQCCAIMAhIAXgBuAHIAegB+AIYAjgCaAKoAlXxATWERQTUNvbXBvdW5kSW5kZXhlc18QEFhEX1BTS19lbGVtZW50SURfEBlYRFBNVW5pcXVlbmVzc0NvbnN0cmFpbnRzXxAaWERfUFNLX3ZlcnNpb25IYXNoTW9kaWZpZXJfEBlYRF9QU0tfZmV0Y2hSZXF1ZXN0c0FycmF5XxARWERfUFNLX2lzQWJzdHJhY3RfEA9YRF9QU0tfdXNlckluZm9fEBNYRF9QU0tfY2xhc3NNYXBwaW5nXxAWWERfUFNLX2VudGl0eUNsYXNzTmFtZd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAJsAFQBhAFoAWgBaAC8AWgCiAHIAWgBaABUAWlVfdHlwZVhfZGVmYXVsdFxfYXNzb2NpYXRpb25bX2lzUmVhZE9ubHlZX2lzU3RhdGljWV9pc1VuaXF1ZVpfaXNEZXJpdmVkWl9pc09yZGVyZWRcX2lzQ29tcG9zaXRlV19pc0xlYWaAAIAYgACADAgICAiAGoAOCAiAAAjSADkADgCpAKqggBnSAKwArQCuAK9aJGNsYXNzbmFtZVgkY2xhc3Nlc15OU011dGFibGVBcnJheaMArgCwALFXTlNBcnJheVhOU09iamVjdNIArACtALMAtF8QEFhEVU1MUHJvcGVydHlJbXCkALUAtgC3ALFfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogBzAFoAWgAVAFqAAIAAgACADAgICAiAGoAPCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDJABUAYQBaAFoAWgAvAFoAogB0AFoAWgAVAFqAAIAdgACADAgICAiAGoAQCAiAAAjSADkADgDXAKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUAYQBaAFoAWgAvAFoAogB1AFoAWgAVAFqAAIAAgACADAgICAiAGoARCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQDqABUAYQBaAFoAWgAvAFoAogB2AFoAWgAVAFqAAIAggACADAgICAiAGoASCAiAAAjSADkADgD4AKqggBnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUAYQBaAFoAWgAvAFoAogB3AFoAWgAVAFqAAIAigACADAgICAiAGoATCAiAAAgI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUBDAAVAGEAWgBaAFoALwBaAKIAeABaAFoAFQBagACAJIAAgAwICAgIgBqAFAgIgAAI0wA4ADkADgEaARsAPqCggCXSAKwArQEeAR9fEBNOU011dGFibGVEaWN0aW9uYXJ5owEeASAAsVxOU0RpY3Rpb25hcnnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEjABUAYQBaAFoAWgAvAFoAogB5AFoAWgAVAFqAAIAngACADAgICAiAGoAVCAiAAAjWACMADgAmAEsAHwAhATEBMgAVAFoAFQAvgCiAKYAACIAAXxAUWERHZW5lcmljUmVjb3JkQ2xhc3PSAKwArQE4ATldWERVTUxDbGFzc0ltcKYBOgE7ATwBPQE+ALFdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQFBABUAYQBaAFoAWgAvAFoAogB6AFoAWgAVAFqAAIArgACADAgICAiAGoAWCAiAAAhfEBJVQUluYm94TWVzc2FnZURhdGHSAKwArQFQAVFfEBJYRFVNTFN0ZXJlb3R5cGVJbXCnAVIBUwFUAVUBVgFXALFfEBJYRFVNTFN0ZXJlb3R5cGVJbXBdWERVTUxDbGFzc0ltcF8QElhEVU1MQ2xhc3NpZmllckltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDTADgAOQAOAVkBZgA+rAFaAVsBXAFdAV4BXwFgAWEBYgFjAWQBZYAugC+AMIAxgDKAM4A0gDWANoA3gDiAOawBZwFoAWkBagFrAWwBbQFuAW8BcAFxAXKAOoBmgH6AloCugMeA34D3gQEOgQElgQE9gQFUgCVVZXh0cmFebWVzc2FnZUJvZHlVUkxfEBByYXdNZXNzYWdlT2JqZWN0XxAQbWVzc2FnZVJlcG9ydGluZ11kZWxldGVkQ2xpZW50XxARbWVzc2FnZUV4cGlyYXRpb25ZbWVzc2FnZUlEW21lc3NhZ2VTZW50VXRpdGxlXHVucmVhZENsaWVudFZ1bnJlYWRabWVzc2FnZVVSTN8QEgCQAJEAkgGBAB8AlACVAYIAIQCTAYMAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAYsALwBaAEwAWgGPAVoAWgBaAZMAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIA8CIAJCIBlgC4ICIA7CBK1hf9R0wA4ADkADgGXAZoAPqIBmAGZgD2APqIBmwGcgD+AU4AlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAfACMBoQAOACYBogAhAEsBowFnAZgATABrABUAJwAvAFoBq18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA6gD2ACYAsgACABAiAQNMAOAA5AA4BrQG2AD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioAbcBuAG5AboBuwG8Ab0BvoBJgEqAS4BNgE6AUIBRgFKAJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFNLX2lzSW5kZXhlZF8QElhEX1BQU0tfaXNPcHRpb25hbF8QGlhEX1BQU0tfaXNTcG90bGlnaHRJbmRleGVkXxARWERfUFBTS19lbGVtZW50SURfEBNYRF9QUFNLX2lzVHJhbnNpZW503xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZsAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgD8ICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZsAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgD8ICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB6AAVAZsAWgBaAFoALwBaAKIBsABaAFoAFQBagACATIAAgD8ICAgIgBqAQwgIgAAI0wA4ADkADgH2AfcAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBmwBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAPwgICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQIKABUBmwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIBPgACAPwgICAiAGoBFCAiAAAgJ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZsAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgD8ICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZsAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgD8ICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZsAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgD8ICAgIgBqASAgIgAAI2QAfACMCRgAOACYCRwAhAEsCSAFnAZkATABrABUAJwAvAFoCUF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA6gD6ACYAsgACABAiAVNMAOAA5AA4CUgJaAD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cCWwJcAl0CXgJfAmACYYBcgF2AXoBfgGGAYoBkgCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnABaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACAUwgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBnABaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACAUwgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnABaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACAUwgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKZABUBnABaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIBggACAUwgICAiAGoBYCAiAAAgRBwjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnABaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACAUwgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQK4ABUBnABaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIBjgACAUwgICAiAGoBaCAiAAAhfEBZVQUpTT05WYWx1ZVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZwAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgFMICAgIgBqAWwgIgAAI0gCsAK0C1gLXXVhEUE1BdHRyaWJ1dGWmAtgC2QLaAtsC3ACxXVhEUE1BdHRyaWJ1dGVcWERQTVByb3BlcnR5XxAQWERVTUxQcm9wZXJ0eUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w3xASAJAAkQCSAt4AHwCUAJUC3wAhAJMC4ACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoC6AAvAFoATABaAY8BWwBaAFoC8ABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgGgIgAkIgGWALwgIgGcIEwAAAAEDvVeE0wA4ADkADgL0AvcAPqIBmAGZgD2APqIC+AL5gGmAdIAl2QAfACMC/AAOACYC/QAhAEsC/gFoAZgATABrABUAJwAvAFoDBl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBmgD2ACYAsgACABAiAatMAOAA5AA4DCAMRAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioAxIDEwMUAxUDFgMXAxgDGYBrgGyAbYBvgHCAcYBygHOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL4AFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIBpCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL4AFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAIBpCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAzsAFQL4AFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgG6AAIBpCAgICIAagEMICIAACNMAOAA5AA4DSQNKAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvgAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgGkICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVAvgAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgGkICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvgAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgGkICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvgAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgGkICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvgAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgGkICAgIgBqASAgIgAAI2QAfACMDmAAOACYDmQAhAEsDmgFoAZkATABrABUAJwAvAFoDol8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBmgD6ACYAsgACABAiAddMAOAA5AA4DpAOsAD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cDrQOuA68DsAOxA7IDs4B2gHeAeIB5gHqAe4B9gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACAdAgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC+QBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACAdAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACAdAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKZABUC+QBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIBggACAdAgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACAdAgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQQCABUC+QBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIB8gACAdAgICAiAGoBaCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACAdAgICAiAGoBbCAiAAAjfEBIAkACRAJIEIAAfAJQAlQQhACEAkwQiAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgQqAC8AWgBMAFoBjwFcAFoAWgQyAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAgAiACQiAZYAwCAiAfwgS/7mhw9MAOAA5AA4ENgQ5AD6iAZgBmYA9gD6iBDoEO4CBgIyAJdkAHwAjBD4ADgAmBD8AIQBLBEABaQGYAEwAawAVACcALwBaBEhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAfoA9gAmALIAAgAQIgILTADgAOQAOBEoEUwA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqARUBFUEVgRXBFgEWQRaBFuAg4CEgIWAh4CIgImAioCLgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEOgBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAgQgICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEOgBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACAgQgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQR9ABUEOgBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAICGgACAgQgICAiAGoBDCAiAAAjTADgAOQAOBIsEjAA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ6AFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAICBCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAgoAFQQ6AFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgE+AAICBCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ6AFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAICBCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ6AFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgACAAICBCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ6AFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAICBCAgICIAagEgICIAACNkAHwAjBNoADgAmBNsAIQBLBNwBaQGZAEwAawAVACcALwBaBORfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAfoA+gAmALIAAgAQIgI3TADgAOQAOBOYE7gA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunBO8E8ATxBPIE8wT0BPWAjoCPgJCAkYCSgJOAlYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDsAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgIwICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBDsAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgIwICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDsAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgIwICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCmQAVBDsAWgBaAFoALwBaAKICVgBaAFoAFQBagACAYIAAgIwICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDsAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgIwICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFRAAVBDsAWgBaAFoALwBaAKICWABaAFoAFQBagACAlIAAgIwICAgIgBqAWggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDsAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgIwICAgIgBqAWwgIgAAI3xASAJAAkQCSBWIAHwCUAJUFYwAhAJMFZACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoFbAAvAFoATABaAY8BXQBaAFoFdABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgJgIgAkIgGWAMQgIgJcIEksSd8TTADgAOQAOBXgFewA+ogGYAZmAPYA+ogV8BX2AmYCkgCXZAB8AIwWAAA4AJgWBACEASwWCAWoBmABMAGsAFQAnAC8AWgWKXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJaAPYAJgCyAAIAECICa0wA4ADkADgWMBZUAPqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgFlgWXBZgFmQWaBZsFnAWdgJuAnICdgJ+AoIChgKKAo4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBXwAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgJkICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBXwAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgJkICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFvwAVBXwAWgBaAFoALwBaAKIBsABaAFoAFQBagACAnoAAgJkICAgIgBqAQwgIgAAI0wA4ADkADgXNBc4APqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFfABaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAmQgICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQIKABUFfABaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIBPgACAmQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFfABaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACAmQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFfABaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACAmQgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFfABaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACAmQgICAiAGoBICAiAAAjZAB8AIwYcAA4AJgYdACEASwYeAWoBmQBMAGsAFQAnAC8AWgYmXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJaAPoAJgCyAAIAECICl0wA4ADkADgYoBjAAPqcCUwJUAlUCVgJXAlgCWYBVgFaAV4BYgFmAWoBbpwYxBjIGMwY0BjUGNgY3gKaAp4CogKmAqoCrgK2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV9AFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAICkCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQV9AFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgCKAAICkCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV9AFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAICkCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVApkAFQV9AFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgGCAAICkCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV9AFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgACAAICkCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBoYAFQV9AFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgKyAAICkCAgICIAagFoICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV9AFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAICkCAgICIAagFsICIAACN8QEgCQAJEAkgakAB8AlACVBqUAIQCTBqYAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBq4ALwBaAEwAWgGPAV4AWgBaBrYAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICICwCIAJCIBlgDIICICvCBJ646yr0wA4ADkADga6Br0APqIBmAGZgD2APqIGvga/gLGAvIAl2QAfACMGwgAOACYGwwAhAEsGxAFrAZgATABrABUAJwAvAFoGzF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCugD2ACYAsgACABAiAstMAOAA5AA4GzgbXAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioBtgG2QbaBtsG3AbdBt4G34CzgLSAtYC3gLiAuYC6gLuAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQa+AFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAICxCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQa+AFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAICxCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBwEAFQa+AFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgLaAAICxCAgICIAagEMICIAACNMAOAA5AA4HDwcQAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBr4AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgLEICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBr4AWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgLEICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBr4AWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgLEICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBr4AWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgLEICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBr4AWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgLEICAgIgBqASAgIgAAI2QAfACMHXgAOACYHXwAhAEsHYAFrAZkATABrABUAJwAvAFoHaF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCugD6ACYAsgACABAiAvdMAOAA5AA4HagdyAD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cHcwd0B3UHdgd3B3gHeYC+gMCAwYDCgMSAxYDGgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQd9ABUGvwBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIC/gACAvAgICAiAGoBVCAiAAAhSTk/fEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGvwBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACAvAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvwBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACAvAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQerABUGvwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIDDgACAvAgICAiAGoBYCAiAAAgRAyDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACAvAgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACAvAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACAvAgICAiAGoBbCAiAAAjfEBIAkACRAJIH5wAfAJQAlQfoACEAkwfpAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgfxAC8AWgBMAFoBjwFfAFoAWgf5AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAyQiACQiAZYAzCAiAyAgSPd6Y4dMAOAA5AA4H/QgAAD6iAZgBmYA9gD6iCAEIAoDKgNWAJdkAHwAjCAUADgAmCAYAIQBLCAcBbAGYAEwAawAVACcALwBaCA9fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAx4A9gAmALIAAgAQIgMvTADgAOQAOCBEIGgA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqAgbCBwIHQgeCB8IIAghCCKAzIDNgM6A0IDRgNKA04DUgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUIAQBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAyggICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIAQBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACAyggICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQhEABUIAQBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIDPgACAyggICAiAGoBDCAiAAAjTADgAOQAOCFIIUwA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgBAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIDKCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAgoAFQgBAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgE+AAIDKCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgBAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAIDKCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQgBAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgACAAIDKCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgBAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIDKCAgICIAagEgICIAACNkAHwAjCKEADgAmCKIAIQBLCKMBbAGZAEwAawAVACcALwBaCKtfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAx4A+gAmALIAAgAQIgNbTADgAOQAOCK0ItQA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunCLYItwi4CLkIugi7CLyA14DYgNmA2oDcgN2A3oAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAIAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgNUICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAIAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgNUICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAIAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgNUICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUI7QAVCAIAWgBaAFoALwBaAKICVgBaAFoAFQBagACA24AAgNUICAgIgBqAWAgIgAAIEQOE3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAIAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgNUICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAIAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgNUICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAIAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgNUICAgIgBqAWwgIgAAI3xASAJAAkQCSCSkAHwCUAJUJKgAhAJMJKwCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoJMwAvAFoATABaAY8BYABaAFoJOwBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgOEIgAkIgGWANAgIgOAIEk7SE9zTADgAOQAOCT8JQgA+ogGYAZmAPYA+oglDCUSA4oDtgCXZAB8AIwlHAA4AJglIACEASwlJAW0BmABMAGsAFQAnAC8AWglRXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgN+APYAJgCyAAIAECIDj0wA4ADkADglTCVwAPqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgJXQleCV8JYAlhCWIJYwlkgOSA5YDmgOiA6YDqgOuA7IAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCUMAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgOIICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUMAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgOIICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUJhgAVCUMAWgBaAFoALwBaAKIBsABaAFoAFQBagACA54AAgOIICAgIgBqAQwgIgAAI0wA4ADkADgmUCZUAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJQwBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACA4ggICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQIKABUJQwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIBPgACA4ggICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJQwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACA4ggICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJQwBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACA4ggICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJQwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACA4ggICAiAGoBICAiAAAjZAB8AIwnjAA4AJgnkACEASwnlAW0BmQBMAGsAFQAnAC8AWgntXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgN+APoAJgCyAAIAECIDu0wA4ADkADgnvCfcAPqcCUwJUAlUCVgJXAlgCWYBVgFaAV4BYgFmAWoBbpwn4CfkJ+gn7CfwJ/Qn+gO+A8IDxgPKA9ID1gPaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlEAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIDtCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlEAFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgCKAAIDtCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlEAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIDtCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCi8AFQlEAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgPOAAIDtCAgICIAagFgICIAACBECvN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlEAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgACAAIDtCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlEAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIDtCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlEAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAIDtCAgICIAagFsICIAACN8QEgCQAJEAkgprAB8AlACVCmwAIQCTCm0AlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaCnUALwBaAEwAWgGPAWEAWgBaCn0AWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICID5CIAJCIBlgDUICID4CBKm6tL80wA4ADkADgqBCoQAPqIBmAGZgD2APqIKhQqGgPqBAQWAJdkAHwAjCokADgAmCooAIQBLCosBbgGYAEwAawAVACcALwBaCpNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA94A9gAmALIAAgAQIgPvTADgAOQAOCpUKngA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqAqfCqAKoQqiCqMKpAqlCqaA/ID9gP6BAQCBAQGBAQKBAQOBAQSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqFAFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAID6CAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqFAFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAID6CAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCsgAFQqFAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgP+AAID6CAgICIAagEMICIAACNMAOAA5AA4K1grXAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoUAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgPoICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVCoUAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgPoICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoUAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgPoICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoUAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgPoICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoUAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgPoICAgIgBqASAgIgAAI2QAfACMLJQAOACYLJgAhAEsLJwFuAZkATABrABUAJwAvAFoLL18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYD3gD6ACYAsgACABAiBAQbTADgAOQAOCzELOQA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunCzoLOws8Cz0LPgs/C0CBAQeBAQiBAQmBAQqBAQuBAQyBAQ2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqGAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIEBBQgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKhgBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACBAQUICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoYAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgQEFCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCO0AFQqGAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgNuAAIEBBQgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKhgBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACBAQUICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoYAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQEFCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqGAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAIEBBQgICAiAGoBbCAiAAAjfEBIAkACRAJILrAAfAJQAlQutACEAkwuuAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgu2AC8AWgBMAFoBjwFiAFoAWgu+AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBARAIgAkIgGWANggIgQEPCBMAAAABKsmTgdMAOAA5AA4LwgvFAD6iAZgBmYA9gD6iC8YLx4EBEYEBHIAl2QAfACMLygAOACYLywAhAEsLzAFvAZgATABrABUAJwAvAFoL1F8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBDoA9gAmALIAAgAQIgQES0wA4ADkADgvWC98APqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgL4AvhC+IL4wvkC+UL5gvngQETgQEUgQEVgQEXgQEYgQEZgQEagQEbgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULxgBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACBAREICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8YAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgQERCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDAkAFQvGAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgQEWgACBAREICAgIgBqAQwgIgAAI0wA4ADkADgwXDBgAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULxgBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACBAREICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVC8YAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgQERCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvGAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAIEBEQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxgBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACBAREICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8YAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgQERCAgICIAagEgICIAACNkAHwAjDGYADgAmDGcAIQBLDGgBbwGZAEwAawAVACcALwBaDHBfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAQ6APoAJgCyAAIAECIEBHdMAOAA5AA4Mcgx6AD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cMewx8DH0Mfgx/DIAMgYEBHoEBH4EBIIEBIYEBIoEBI4EBJIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8cAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgQEcCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvHAFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgCKAAIEBHAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxwBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACBARwICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKLwAVC8cAWgBaAFoALwBaAKICVgBaAFoAFQBagACA84AAgQEcCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvHAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgACAAIEBHAgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBARwICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8cAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgQEcCAgICIAagFsICIAACN8QEgCQAJEAkgztAB8AlACVDO4AIQCTDO8AlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaDPcALwBaAEwAWgGPAWMAWgBaDP8AWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBJwiACQiAZYA3CAiBASYIEodnMffTADgAOQAODQMNBgA+ogGYAZmAPYA+og0HDQiBASiBATOAJdkAHwAjDQsADgAmDQwAIQBLDQ0BcAGYAEwAawAVACcALwBaDRVfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBASWAPYAJgCyAAIAECIEBKdMAOAA5AA4NFw0gAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioDSENIg0jDSQNJQ0mDScNKIEBKoEBK4EBLIEBLoEBL4EBMIEBMYEBMoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQcAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgQEoCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0HAFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAIEBKAgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ1KABUNBwBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIEBLYAAgQEoCAgICIAagEMICIAACNMAOAA5AA4NWA1ZAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQcAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQEoCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0HAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgCKAAIEBKAgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNBwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACBASgICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQcAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgQEoCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0HAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIEBKAgICAiAGoBICAiAAAjZAB8AIw2nAA4AJg2oACEASw2pAXABmQBMAGsAFQAnAC8AWg2xXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQElgD6ACYAsgACABAiBATTTADgAOQAODbMNuwA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunDbwNvQ2+Db8NwA3BDcKBATWBATeBATiBATmBATqBATuBATyAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDcYAFQ0IAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgQE2gACBATMICAgIgBqAVQgIgAAIU1lFU98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0IAFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgCKAAIEBMwgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNCABaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACBATMICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHqwAVDQgAWgBaAFoALwBaAKICVgBaAFoAFQBagACAw4AAgQEzCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0IAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgACAAIEBMwgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNCABaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBATMICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQgAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgQEzCAgICIAagFsICIAACN8QEgCQAJEAkg4vAB8AlACVDjAAIQCTDjEAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaDjkALwBaAEwAWgGPAWQAWgBaDkEAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBPwiACQiAZYA4CAiBAT4IEvRkpnvTADgAOQAODkUOSAA+ogGYAZmAPYA+og5JDkqBAUCBAUuAJdkAHwAjDk0ADgAmDk4AIQBLDk8BcQGYAEwAawAVACcALwBaDldfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAT2APYAJgCyAAIAECIEBQdMAOAA5AA4OWQ5iAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioDmMOZA5lDmYOZw5oDmkOaoEBQoEBQ4EBRIEBRoEBR4EBSIEBSYEBSoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkkAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgQFACAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5JAFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAIEBQAgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ6MABUOSQBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIEBRYAAgQFACAgICIAagEMICIAACNMAOAA5AA4Omg6bAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkkAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFACAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5JAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgCKAAIEBQAgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOSQBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACBAUAICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkkAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgQFACAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5JAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIEBQAgICAiAGoBICAiAAAjZAB8AIw7pAA4AJg7qACEASw7rAXEBmQBMAGsAFQAnAC8AWg7zXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQE9gD6ACYAsgACABAiBAUzTADgAOQAODvUO/QA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunDv4O/w8ADwEPAg8DDwSBAU2BAU6BAU+BAVCBAVGBAVKBAVOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDcYAFQ5KAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgQE2gACBAUsICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkoAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgQFLCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5KAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIEBSwgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQerABUOSgBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIDDgACBAUsICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkoAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgQFLCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5KAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBSwgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOSgBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACBAUsICAgIgBqAWwgIgAAI3xASAJAAkQCSD3AAHwCUAJUPcQAhAJMPcgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoPegAvAFoATABaAY8BZQBaAFoPggBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQFWCIAJCIBlgDkICIEBVQgTAAAAAQEPir3TADgAOQAOD4YPiQA+ogGYAZmAPYA+og+KD4uBAVeBAWKAJdkAHwAjD44ADgAmD48AIQBLD5ABcgGYAEwAawAVACcALwBaD5hfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAVSAPYAJgCyAAIAECIEBWNMAOAA5AA4Pmg+jAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioD6QPpQ+mD6cPqA+pD6oPq4EBWYEBWoEBW4EBXYEBXoEBX4EBYIEBYYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD4oAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgQFXCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+KAFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAIEBVwgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ/NABUPigBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIEBXIAAgQFXCAgICIAagEMICIAACNMAOAA5AA4P2w/cAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD4oAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFXCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAgoAFQ+KAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgE+AAIEBVwgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPigBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACBAVcICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD4oAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgQFXCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+KAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIEBVwgICAiAGoBICAiAAAjZAB8AIxAqAA4AJhArACEASxAsAXIBmQBMAGsAFQAnAC8AWhA0XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFUgD6ACYAsgACABAiBAWPTADgAOQAOEDYQPgA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunED8QQBBBEEIQQxBEEEWBAWSBAWWBAWaBAWeBAWiBAWmBAWuAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+LAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIEBYggICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPiwBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACBAWIICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD4sAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgQFiCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVApkAFQ+LAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgGCAAIEBYggICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPiwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACBAWIICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUQlAAVD4sAWgBaAFoALwBaAKICWABaAFoAFQBagACBAWqAAIEBYggICAiAGoBaCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPiwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACBAWIICAgIgBqAWwgIgAAIWmR1cGxpY2F0ZXPSADkADhCzAKqggBnSAKwArRC2ELdaWERQTUVudGl0eacQuBC5ELoQuxC8EL0AsVpYRFBNRW50aXR5XVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADhC/EMAAPqCggCXTADgAOQAOEMMQxAA+oKCAJdMAOAA5AA4QxxDIAD6goIAl0gCsAK0QyxDMXlhETW9kZWxQYWNrYWdlphDNEM4QzxDQENEAsV5YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADkADhDTAKqggBnTADgAOQAOENYQ1wA+oKCAJVDSAKwArRDbENxZWERQTU1vZGVsoxDbEN0AsVdYRE1vZGVsAAgAGQAiACwAMQA6AD8AUQBWAFsAXQNPA1UDbgOAA4cDlQOiA7oD1APWA9kD2wPeA+ED5AQdBDwEWQR4BIoEqgSxBM8E2wT3BP0FHwVABVMFVQVYBVsFXQVfBWEFZAVnBWkFawVtBW8FcQVzBXQFeAWFBY0FmAWbBZ0FoAWiBaQFswX2BhoGPgZhBogGqAbPBvYHFgc6B14HagdsB24HcAdyB3QHdgd5B3sHfQeAB4IHhAeHB4kHigePB5cHpAenB6kHrAeuB7AHvwfkCAgILwhTCFUIVwhZCFsIXQhfCGAIYghvCIIIhAiGCIgIigiMCI4IkAiSCJQIpwipCKsIrQivCLEIswi1CLcIuQi7CNEI5AkACR0JOQlNCV8JdQmOCc0J0wncCekJ9Qn/CgkKFAofCiwKNAo2CjgKOgo8Cj0KPgo/CkAKQgpECkUKRgpICkkKUgpTClUKXgppCnIKgQqICpAKmQqiCrUKvgrRCugK+gs5CzsLPQs/C0ELQgtDC0QLRQtHC0kLSgtLC00LTguNC48LkQuTC5ULlguXC5gLmQubC50LngufC6ELogurC6wLrgvtC+8L8QvzC/UL9gv3C/gL+Qv7C/0L/gv/DAEMAgxBDEMMRQxHDEkMSgxLDEwMTQxPDFEMUgxTDFUMVgxfDGAMYgyhDKMMpQynDKkMqgyrDKwMrQyvDLEMsgyzDLUMtgy3DPYM+Az6DPwM/gz/DQANAQ0CDQQNBg0HDQgNCg0LDRgNGQ0aDRwNJQ07DUINTw2ODZANkg2UDZYNlw2YDZkNmg2cDZ4Nnw2gDaINow28Db4NwA3CDcMNxQ3cDeUN8w4ADg4OIw43Dk4OYA6fDqEOow6lDqcOqA6pDqoOqw6tDq8OsA6xDrMOtA7JDtIO5w72DwsPGQ8uD0IPWQ9rD3gPkQ+TD5UPlw+ZD5sPnQ+fD6EPow+lD6cPqQ/CD8QPxg/ID8oPzA/OD9AP0g/VD9gP2w/eD+AP5g/1EAgQGxApED0QRxBTEFkQZhBtEHgQwxDmEQYRJhEoESoRLBEuETARMREyETQRNRE3ETgROhE8ET0RPhFAEUERRhFTEVgRWhFcEWERYxFlEWcRfBGREbYR2hIBEiUSJxIpEisSLRIvEjESMhI0EkESUhJUElYSWBJaElwSXhJgEmIScxJ1EncSeRJ7En0SfxKBEoMShRKjEsES1BLoEv0TGhMuE0QTgxOFE4cTiROLE4wTjROOE48TkROTE5QTlROXE5gT1xPZE9sT3RPfE+AT4RPiE+MT5RPnE+gT6RPrE+wUKxQtFC8UMRQzFDQUNRQ2FDcUORQ7FDwUPRQ/FEAUTRROFE8UURSQFJIUlBSWFJgUmRSaFJsUnBSeFKAUoRSiFKQUpRTkFOYU6BTqFOwU7RTuFO8U8BTyFPQU9RT2FPgU+RT6FTkVOxU9FT8VQRVCFUMVRBVFFUcVSRVKFUsVTRVOFY0VjxWRFZMVlRWWFZcVmBWZFZsVnRWeFZ8VoRWiFeEV4xXlFecV6RXqFesV7BXtFe8V8RXyFfMV9RX2FhsWPxZmFooWjBaOFpAWkhaUFpYWlxaZFqYWtRa3FrkWuxa9Fr8WwRbDFtIW1BbWFtgW2hbcFt4W4BbiFwIXLRdHF2AXeheaF70X/Bf+GAAYAhgEGAUYBhgHGAgYChgMGA0YDhgQGBEYUBhSGFQYVhhYGFkYWhhbGFwYXhhgGGEYYhhkGGUYpBimGKgYqhisGK0YrhivGLAYshi0GLUYthi4GLkY+Bj6GPwY/hkAGQEZAhkDGQQZBhkIGQkZChkMGQ0ZEBlPGVEZUxlVGVcZWBlZGVoZWxldGV8ZYBlhGWMZZBmjGaUZpxmpGasZrBmtGa4ZrxmxGbMZtBm1GbcZuBnRGhAaEhoUGhYaGBoZGhoaGxocGh4aIBohGiIaJBolGi4aPBpJGlcaZBp3Go4aoBrrGw4bLhtOG1AbUhtUG1YbWBtZG1obXBtdG18bYBtiG2QbZRtmG2gbaRtyG38bhBuGG4gbjRuPG5Ebkxu4G9wcAxwnHCkcKxwtHC8cMRwzHDQcNhxDHFQcVhxYHFocXBxeHGAcYhxkHHUcdxx5HHscfRx/HIEcgxyFHIccxhzIHMoczBzOHM8c0BzRHNIc1BzWHNcc2BzaHNsdGh0cHR4dIB0iHSMdJB0lHSYdKB0qHSsdLB0uHS8dbh1wHXIddB12HXcdeB15HXodfB1+HX8dgB2CHYMdkB2RHZIdlB3THdUd1x3ZHdsd3B3dHd4d3x3hHeMd5B3lHecd6B4nHikeKx4tHi8eMB4xHjIeMx41HjceOB45HjsePB57Hn0efx6BHoMehB6FHoYehx6JHosejB6NHo8ekB7PHtEe0x7VHtce2B7ZHtoe2x7dHt8e4B7hHuMe5B8jHyUfJx8pHysfLB8tHy4fLx8xHzMfNB81HzcfOB9dH4EfqB/MH84f0B/SH9Qf1h/YH9kf2x/oH/cf+R/7H/0f/yABIAMgBSAUIBYgGCAaIBwgHiAgICIgJCBjIGUgZyBpIGsgbCBtIG4gbyBxIHMgdCB1IHcgeCC3ILkguyC9IL8gwCDBIMIgwyDFIMcgyCDJIMsgzCELIQ0hDyERIRMhFCEVIRYhFyEZIRshHCEdIR8hICFfIWEhYyFlIWchaCFpIWohayFtIW8hcCFxIXMhdCGzIbUhtyG5IbshvCG9Ib4hvyHBIcMhxCHFIcchyCIHIgkiCyINIg8iECIRIhIiEyIVIhciGCIZIhsiHCJDIoIihCKGIogiiiKLIowijSKOIpAikiKTIpQiliKXIuIjBSMlI0UjRyNJI0sjTSNPI1AjUSNTI1QjViNXI1kjWyNcI10jXyNgI2UjciN3I3kjeyOAI4IjhCOGI6sjzyP2JBokHCQeJCAkIiQkJCYkJyQpJDYkRyRJJEskTSRPJFEkUyRVJFckaCRqJGwkbiRwJHIkdCR2JHgkeiS5JLskvSS/JMEkwiTDJMQkxSTHJMkkyiTLJM0kziUNJQ8lESUTJRUlFiUXJRglGSUbJR0lHiUfJSElIiVhJWMlZSVnJWklaiVrJWwlbSVvJXElciVzJXUldiWDJYQlhSWHJcYlyCXKJcwlziXPJdAl0SXSJdQl1iXXJdgl2iXbJhomHCYeJiAmIiYjJiQmJSYmJigmKiYrJiwmLiYvJm4mcCZyJnQmdiZ3JngmeSZ6JnwmfiZ/JoAmgiaDJsImxCbGJsgmyibLJswmzSbOJtAm0ibTJtQm1ibXJxYnGCcaJxwnHicfJyAnISciJyQnJicnJygnKicrJ1AndCebJ78nwSfDJ8UnxyfJJ8snzCfOJ9sn6ifsJ+4n8CfyJ/Qn9if4KAcoCSgLKA0oDygRKBMoFSgXKFYoWChaKFwoXihfKGAoYShiKGQoZihnKGgoaihrKKoorCiuKLAosiizKLQotSi2KLgouii7KLwovii/KP4pACkCKQQpBikHKQgpCSkKKQwpDikPKRApEikTKVIpVClWKVgpWilbKVwpXSleKWApYiljKWQpZilnKaYpqCmqKawprimvKbApsSmyKbQptim3Kbgpuim7Kfop/Cn+KgAqAioDKgQqBSoGKggqCioLKgwqDioPKjYqdSp3Knkqeyp9Kn4qfyqAKoEqgyqFKoYqhyqJKooq1Sr4KxgrOCs6KzwrPitAK0IrQytEK0YrRytJK0orTCtOK08rUCtSK1MrWCtlK2orbCtuK3MrdSt3K3krnivCK+ksDSwPLBEsEywVLBcsGSwaLBwsKSw6LDwsPixALEIsRCxGLEgsSixbLF0sXyxhLGMsZSxnLGksayxtLKwsriywLLIstCy1LLYstyy4LLosvCy9LL4swCzBLQAtAi0ELQYtCC0JLQotCy0MLQ4tEC0RLRItFC0VLVQtVi1YLVotXC1dLV4tXy1gLWItZC1lLWYtaC1pLXYtdy14LXotuS27Lb0tvy3BLcItwy3ELcUtxy3JLcotyy3NLc4uDS4PLhEuEy4VLhYuFy4YLhkuGy4dLh4uHy4hLiIuYS5jLmUuZy5pLmouay5sLm0uby5xLnIucy51LnYutS63Lrkuuy69Lr4uvy7ALsEuwy7FLsYuxy7JLsovCS8LLw0vDy8RLxIvEy8ULxUvFy8ZLxovGy8dLx4vQy9nL44vsi+0L7YvuC+6L7wvvi+/L8Evzi/dL98v4S/jL+Uv5y/pL+sv+i/8L/4wADACMAQwBjAIMAowSTBLME0wTzBRMFIwUzBUMFUwVzBZMFowWzBdMF4wnTCfMKEwozClMKYwpzCoMKkwqzCtMK4wrzCxMLIw8TDzMPUw9zD5MPow+zD8MP0w/zEBMQIxAzEFMQYxRTFHMUkxSzFNMU4xTzFQMVExUzFVMVYxVzFZMVoxmTGbMZ0xnzGhMaIxozGkMaUxpzGpMaoxqzGtMa4x7THvMfEx8zH1MfYx9zH4Mfkx+zH9Mf4x/zIBMgIyKTJoMmoybDJuMnAycTJyMnMydDJ2MngyeTJ6MnwyfTLIMuszCzMrMy0zLzMxMzMzNTM2MzczOTM6MzwzPTM/M0EzQjNDM0UzRjNLM1gzXTNfM2EzZjNoM2ozbDORM7Uz3DQANAI0BDQGNAg0CjQMNA00DzQcNC00LzQxNDM0NTQ3NDk0OzQ9NE40UDRSNFQ0VjRYNFo0XDReNGA0nzShNKM0pTSnNKg0qTSqNKs0rTSvNLA0sTSzNLQ08zT1NPc0+TT7NPw0/TT+NP81ATUDNQQ1BTUHNQg1RzVJNUs1TTVPNVA1UTVSNVM1VTVXNVg1WTVbNVw1aTVqNWs1bTWsNa41sDWyNbQ1tTW2Nbc1uDW6Nbw1vTW+NcA1wTYANgI2BDYGNgg2CTYKNgs2DDYONhA2ETYSNhQ2FTZUNlY2WDZaNlw2XTZeNl82YDZiNmQ2ZTZmNmg2aTaoNqo2rDauNrA2sTayNrM2tDa2Nrg2uTa6Nrw2vTb8Nv43ADcCNwQ3BTcGNwc3CDcKNww3DTcONxA3ETc2N1o3gTelN6c3qTerN603rzexN7I3tDfBN9A30jfUN9Y32DfaN9w33jftN+838TfzN/U39zf5N/s3/Tg8OD44QDhCOEQ4RThGOEc4SDhKOEw4TThOOFA4UThUOJM4lTiXOJk4mzicOJ04njifOKE4ozikOKU4pzioOOc46TjrOO047zjwOPE48jjzOPU49zj4OPk4+zj8OTs5PTk/OUE5QzlEOUU5RjlHOUk5SzlMOU05TzlQOVM5kjmUOZY5mDmaOZs5nDmdOZ45oDmiOaM5pDmmOac55jnoOeo57DnuOe858DnxOfI59Dn2Ofc5+Dn6Ofs6Ojo8Oj46QDpCOkM6RDpFOkY6SDpKOks6TDpOOk86mjq9Ot06/Tr/OwE7AzsFOwc7CDsJOws7DDsOOw87ETsTOxQ7FTsXOxg7HTsqOy87MTszOzg7Ojs8Oz47YzuHO6470jvUO9Y72DvaO9w73jvfO+E77jv/PAE8AzwFPAc8CTwLPA08DzwgPCI8JDwmPCg8KjwsPC48MDwyPHE8czx1PHc8eTx6PHs8fDx9PH88gTyCPIM8hTyGPMU8xzzJPMs8zTzOPM880DzRPNM81TzWPNc82TzaPRk9Gz0dPR89IT0iPSM9JD0lPSc9KT0qPSs9LT0uPTs9PD09PT89fj2APYI9hD2GPYc9iD2JPYo9jD2OPY89kD2SPZM90j3UPdY92D3aPds93D3dPd494D3iPeM95D3mPec+Jj4oPio+LD4uPi8+MD4xPjI+ND42Pjc+OD46Pjs+ej58Pn4+gD6CPoM+hD6FPoY+iD6KPos+jD6OPo8+zj7QPtI+1D7WPtc+2D7ZPto+3D7ePt8+4D7iPuM/CD8sP1M/dz95P3s/fT9/P4E/gz+EP4Y/kz+iP6Q/pj+oP6o/rD+uP7A/vz/BP8M/xT/HP8k/yz/NP89ADkAQQBJAFEAWQBdAGEAZQBpAHEAeQB9AIEAiQCNAYkBkQGZAaEBqQGtAbEBtQG5AcEByQHNAdEB2QHdAtkC4QLpAvEC+QL9AwEDBQMJAxEDGQMdAyEDKQMtBCkEMQQ5BEEESQRNBFEEVQRZBGEEaQRtBHEEeQR9BIkFhQWNBZUFnQWlBakFrQWxBbUFvQXFBckFzQXVBdkG1QbdBuUG7Qb1BvkG/QcBBwUHDQcVBxkHHQclBykIJQgtCDUIPQhFCEkITQhRCFUIXQhlCGkIbQh1CHkJpQoxCrELMQs5C0ELSQtRC1kLXQthC2kLbQt1C3kLgQuJC40LkQuZC50LsQvlC/kMAQwJDB0MJQwtDDUMyQ1ZDfUOhQ6NDpUOnQ6lDq0OtQ65DsEO9Q85D0EPSQ9RD1kPYQ9pD3EPeQ+9D8UPzQ/VD90P5Q/tD/UP/RAFEQERCRERERkRIRElESkRLRExETkRQRFFEUkRURFVElESWRJhEmkScRJ1EnkSfRKBEokSkRKVEpkSoRKlE6ETqROxE7kTwRPFE8kTzRPRE9kT4RPlE+kT8RP1FCkULRQxFDkVNRU9FUUVTRVVFVkVXRVhFWUVbRV1FXkVfRWFFYkWhRaNFpUWnRalFqkWrRaxFrUWvRbFFskWzRbVFtkX1RfdF+UX7Rf1F/kX/RgBGAUYDRgVGBkYHRglGCkZJRktGTUZPRlFGUkZTRlRGVUZXRllGWkZbRl1GXkadRp9GoUajRqVGpkanRqhGqUarRq1GrkavRrFGskbXRvtHIkdGR0hHSkdMR05HUEdSR1NHVUdiR3FHc0d1R3dHeUd7R31Hf0eOR5BHkkeUR5ZHmEeaR5xHnkfdR99H4UfjR+VH5kfnR+hH6UfrR+1H7kfvR/FH8kgxSDNINUg3SDlIOkg7SDxIPUg/SEFIQkhDSEVIRkiFSIdIiUiLSI1IjkiPSJBIkUiTSJVIlkiXSJlImkjZSNtI3UjfSOFI4kjjSORI5UjnSOlI6kjrSO1I7kjxSTBJMkk0STZJOEk5STpJO0k8ST5JQElBSUJJRElFSYRJhkmISYpJjEmNSY5Jj0mQSZJJlEmVSZZJmEmZSdhJ2kncSd5J4EnhSeJJ40nkSeZJ6EnpSepJ7EntSjhKW0p7SptKnUqfSqFKo0qlSqZKp0qpSqpKrEqtSq9KsUqySrNKtUq2SrtKyErNSs9K0UrWSthK20rdSwJLJktNS3FLc0t1S3dLeUt7S31LfkuAS41LnkugS6JLpEumS6hLqkusS65Lv0vBS8NLxUvIS8tLzkvRS9RL1kwVTBdMGUwbTB1MHkwfTCBMIUwjTCVMJkwnTClMKkxpTGtMbUxvTHFMckxzTHRMdUx3THlMekx7TH1Mfky9TL9MwUzDTMVMxkzHTMhMyUzLTM1MzkzPTNFM0kzfTOBM4UzjTSJNJE0mTShNKk0rTSxNLU0uTTBNMk0zTTRNNk03TXZNeE16TXxNfk1/TYBNgU2CTYRNhk2HTYhNik2LTcpNzE3OTdBN0k3TTdRN1U3WTdhN2k3bTdxN3k3fTh5OIE4iTiROJk4nTihOKU4qTixOLk4vTjBOMk4zTnJOdE52TnhOek57TnxOfU5+ToBOgk6DToROhk6HTqxO0E73TxtPHU8fTyFPI08lTydPKE8rTzhPR09JT0tPTU9PT1FPU09VT2RPZ09qT21PcE9zT3ZPeU97T7pPvE++T8BPw0/ET8VPxk/HT8lPy0/MT81Pz0/QUA9QEVATUBVQGFAZUBpQG1AcUB5QIFAhUCJQJFAlUGRQZlBoUGpQbVBuUG9QcFBxUHNQdVB2UHdQeVB6ULlQu1C9UL9QwlDDUMRQxVDGUMhQylDLUMxQzlDPUQ5REFESURRRF1EYURlRGlEbUR1RH1EgUSFRI1EkUWNRZVFnUWlRbFFtUW5Rb1FwUXJRdFF1UXZReFF5UbhRulG8Ub5RwVHCUcNRxFHFUcdRyVHKUctRzVHOUhlSPFJcUnxSflKAUoJShFKGUodSiFKLUoxSjlKPUpFSk1KUUpVSmFKZUqJSr1K0UrZSuFK9UsBSw1LFUupTDlM1U1lTXFNeU2BTYlNkU2ZTZ1NqU3dTiFOKU4xTjlOQU5JTlFOWU5hTqVOsU69TslO1U7hTu1O+U8FTw1QCVARUBlQIVAtUDFQNVA5UD1QRVBNUFFQVVBdUGFRXVFlUW1RdVGBUYVRiVGNUZFRmVGhUaVRqVGxUbVSsVK5UsVSzVLZUt1S4VLlUulS8VL5Uv1TAVMJUw1TQVNFU0lTUVRNVFVUXVRlVHFUdVR5VH1UgVSJVJFUlVSZVKFUpVWhValVsVW5VcVVyVXNVdFV1VXdVeVV6VXtVfVV+Vb1Vv1XBVcNVxlXHVchVyVXKVcxVzlXPVdBV0lXTVhJWFFYWVhhWG1YcVh1WHlYfViFWI1YkViVWJ1YoVmdWaVZrVm1WcFZxVnJWc1Z0VnZWeFZ5VnpWfFZ9VqJWxlbtVxFXFFcWVxhXGlccVx5XH1ciVy9XPldAV0JXRFdGV0hXSldMV1tXXldhV2RXZ1dqV21XcFdyV7FXs1e1V7dXule7V7xXvVe+V8BXwlfDV8RXxlfHWAZYCFgKWAxYD1gQWBFYElgTWBVYF1gYWBlYG1gcWFtYXVhfWGFYZFhlWGZYZ1hoWGpYbFhtWG5YcFhxWLBYsli0WLZYuVi6WLtYvFi9WL9YwVjCWMNYxVjGWQVZB1kJWQtZDlkPWRBZEVkSWRRZFlkXWRhZGlkbWVpZXFleWWBZY1lkWWVZZllnWWlZa1lsWW1Zb1lwWa9ZsVmzWbVZuFm5WbpZu1m8Wb5ZwFnBWcJZxFnFWhBaM1pTWnNadVp3Wnlae1p9Wn5af1qCWoNahVqGWohailqLWoxaj1qQWpVaolqnWqlaq1qwWrNatlq4Wt1bAVsoW0xbT1tRW1NbVVtXW1lbWltdW2pbe1t9W39bgVuDW4Vbh1uJW4tbnFufW6JbpVuoW6tbrluxW7Rbtlv1W/db+Vv7W/5b/1wAXAFcAlwEXAZcB1wIXApcC1xKXExcTlxQXFNcVFxVXFZcV1xZXFtcXFxdXF9cYFyfXKFcpFymXKlcqlyrXKxcrVyvXLFcslyzXLVctlzDXMRcxVzHXQZdCF0KXQxdD10QXRFdEl0TXRVdF10YXRldG10cXVtdXV1fXWFdZF1lXWZdZ11oXWpdbF1tXW5dcF1xXbBdsl20XbZduV26XbtdvF29Xb9dwV3CXcNdxV3GXgVeB14JXgteDl4PXhBeEV4SXhReFl4XXhheGl4bXlpeXF5eXmBeY15kXmVeZl5nXmlea15sXm1eb15wXpVeuV7gXwRfB18JXwtfDV8PXxFfEl8VXyJfMV8zXzVfN185XztfPV8/X05fUV9UX1dfWl9dX2BfY19lX6Rfpl+pX6tfrl+vX7BfsV+yX7Rftl+3X7hful+7X79f/mAAYAJgBGAHYAhgCWAKYAtgDWAPYBBgEWATYBRgU2BVYFdgWWBcYF1gXmBfYGBgYmBkYGVgZmBoYGlgqGCqYKxgrmCxYLJgs2C0YLVgt2C5YLpgu2C9YL5g/WD/YQFhA2EGYQdhCGEJYQphDGEOYQ9hEGESYRNhUmFUYVZhWGFbYVxhXWFeYV9hYWFjYWRhZWFnYWhhp2GpYathrWGwYbFhsmGzYbRhtmG4YblhumG8Yb1iCGIrYktia2JtYm9icWJzYnVidmJ3Ynpie2J9Yn5igGKCYoNihGKHYohijWKaYp9ioWKjYqhiq2KuYrBi1WL5YyBjRGNHY0ljS2NNY09jUWNSY1VjYmNzY3Vjd2N5Y3tjfWN/Y4Fjg2OUY5djmmOdY6Bjo2OmY6ljrGOuY+1j72PxY/Nj9mP3Y/hj+WP6Y/xj/mP/ZABkAmQDZEJkRGRGZEhkS2RMZE1kTmRPZFFkU2RUZFVkV2RYZJdkmWScZJ5koWSiZKNkpGSlZKdkqWSqZKtkrWSuZLtkvGS9ZL9k/mUAZQJlBGUHZQhlCWUKZQtlDWUPZRBlEWUTZRRlU2VVZVdlWWVcZV1lXmVfZWBlYmVkZWVlZmVoZWllqGWqZaxlrmWxZbJls2W0ZbVlt2W5Zbplu2W9Zb5l/WX/ZgFmA2YGZgdmCGYJZgpmDGYOZg9mEGYSZhNmUmZUZlZmWGZbZlxmXWZeZl9mYWZjZmRmZWZnZmhmjWaxZthm/Gb/ZwFnA2cFZwdnCWcKZw1nGmcpZytnLWcvZzFnM2c1ZzdnRmdJZ0xnT2dSZ1VnWGdbZ11nnGeeZ6Fno2emZ6dnqGepZ6pnrGeuZ69nsGeyZ7Nn8mf0Z/Zn+Gf7Z/xn/Wf+Z/9oAWgDaARoBWgHaAhoR2hJaEtoTWhQaFFoUmhTaFRoVmhYaFloWmhcaF1onGieaKBoomilaKZop2ioaKloq2itaK5or2ixaLJo8WjzaPVo92j6aPto/Gj9aP5pAGkCaQNpBGkGaQdpRmlIaUppTGlPaVBpUWlSaVNpVWlXaVhpWWlbaVxpm2mdaZ9poWmkaaVppmmnaahpqmmsaa1prmmwabFp/Gofaj9qX2phamNqZWpnamlqampram5qb2pxanJqdGp2andqeGp7anxqhWqSapdqmWqbaqBqo2qmaqhqzWrxaxhrPGs/a0FrQ2tFa0drSWtKa01rWmtra21rb2txa3NrdWt3a3lre2uMa49rkmuVa5hrm2uea6FrpGuma+Vr52vpa+tr7mvva/Br8Wvya/Rr9mv3a/hr+mv7bDpsPGw+bEBsQ2xEbEVsRmxHbElsS2xMbE1sT2xQbI9skWyUbJZsmWyabJtsnGydbJ9soWyibKNspWymbLNstGy1bLds9mz4bPps/Gz/bQBtAW0CbQNtBW0HbQhtCW0LbQxtS21NbU9tUW1UbVVtVm1XbVhtWm1cbV1tXm1gbWFtoG2ibaRtpm2pbaptq22sba1tr22xbbJts221bbZt9W33bflt+23+bf9uAG4BbgJuBG4GbgduCG4KbgtuSm5Mbk5uUG5TblRuVW5WblduWW5bblxuXW5fbmBuhW6pbtBu9G73bvlu+279bv9vAW8CbwVvEm8hbyNvJW8nbylvK28tby9vPm9Bb0RvR29Kb01vUG9Tb1VvlG+Wb5hvmm+db55vn2+gb6Fvo2+lb6Zvp2+pb6pv6W/rb+1v72/yb/Nv9G/1b/Zv+G/6b/tv/G/+b/9wPnBAcEJwRHBHcEhwSXBKcEtwTXBPcFBwUXBTcFRwk3CVcJdwmXCccJ1wnnCfcKBwonCkcKVwpnCocKlw6HDqcOxw7nDxcPJw83D0cPVw93D5cPpw+3D9cP5xPXE/cUJxRHFHcUhxSXFKcUtxTXFPcVBxUXFTcVRxe3G6cbxxvnHAccNxxHHFccZxx3HJcctxzHHNcc9x0HHbceRx5XHncfBx+3IKchVyI3I4ckxyY3J1coJyg3KEcoZyk3KUcpVyl3KkcqVypnKocrFywHLNctxy7nMCcxlzK3M0czVzN3NEc0VzRnNIc0lzUnNcc2MAAAAAAAACAgAAAAAAABDeAAAAAAAAAAAAAAAAAABzaw==\n</attribute>\n        <attribute name=\"destinationmodelpath\" type=\"string\">AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 4.xcdatamodel</attribute>\n        <attribute name=\"destinationmodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBkAALAAwAGQA1ADYANwA/AEAAWwBcAF0AYwBkAHAAhgCHAIgAiQCKAIsAjACNAI4AjwCoAKsAsgC4AMcA1gDZAOgA9wD6AFoBCgEZAR0BIQEwATYBNwE/AU4BTwFYAXYBdwF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGYAZkBoQGiAaMBrwHDAcQBxQHGAccByAHJAcoBywHaAekB+AH8AgsCGgIbAioCOQJIAlQCZgJnAmgCaQJqAmsCbAJtAnwCiwKaAqkCqgK5AsgCyQLYAuAC9QL2Av4DCgMeAy0DPANLA08DXgNtA3wDiwOaA6YDuAPHA9YD5QP0A/UEBAQTBBQEIwQ4BDkEQQRNBGEEcAR/BI4EkgShBLAEvwTOBN0E6QT7BQoFGQUoBTcFOAVHBVYFZQV6BXsFgwWPBaMFsgXBBdAF1AXjBfIGAQYQBh8GKwY9BkwGWwZqBnkGiAaXBpgGpwa8Br0GxQbRBuUG9AcDBxIHFgclBzQHQwdSB2EHbQd/B44HjweeB60HvAe9B8wH2wfqB/8IAAgICBQIKAg3CEYIVQhZCGgIdwiGCJUIpAiwCMII0QjgCO8I/gkNCRwJHQksCUEJQglKCVYJagl5CYgJlwmbCaoJuQnICdcJ5gnyCgQKEwoiCjEKQApBClAKXwpuCoMKhAqMCpgKrAq7CsoK2QrdCuwK+wsKCxkLKAs0C0YLVQtkC3MLgguRC6ALrwvEC8ULzQvZC+0L/AwLDBoMHgwtDDwMSwxaDGkMdQyHDJYMpQy0DMMM0gzhDPANBQ0GDQ4NGg0uDT0NTA1bDV8Nbg19DYwNmw2qDbYNyA3XDeYN9Q4EDhMOIg4xDkYORw5PDlsObw5+Do0OnA6gDq8Ovg7NDtwO6w73DwkPGA8ZDygPNw9GD1UPZA9zD4gPiQ+RD50PsQ/AD88P3g/iD/EQABAPEB4QLRA5EEsQWhBpEHgQhxCWEKUQtBDJEMoQ0hDeEPIRAREQER8RIxEyEUERUBFfEW4RehGMEZsRqhG5EcgR1xHmEecR9hH3EfoSAxIHEgsSDxIXEhoSHhIfVSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgQGPgACBAYyBAY2BAY7eABoAGwAcAB0AHgAfACAADgAhACIAIwAkACUAJgAnACgAKQAJACcAFQAtAC4ALwAwADEAJwAnABVfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASBAYqBAYiAAYAEgACBAYmBAYsQAIAFgAOABIAEgABQU1lFU9MAOAA5AA4AOgA8AD5XTlMua2V5c1pOUy5vYmplY3RzoQA7gAahAD2AB4AlXlVBSW5ib3hNZXNzYWdl3xAQAEEAQgBDAEQAHwBFAEYAIQBHAEgADgAjAEkASgAmAEsATABNACcAJwATAFEAUgAvACcATABVADsATABYAFkAWl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgC2ABIAEgAKACoEBhYAEgAmBAYeABoAJgQGGgAgIEmgTRktXb3JkZXJlZNMAOAA5AA4AXgBgAD6hAF+AC6EAYYAMgCVeWERfUFN0ZXJlb3R5cGXZAB8AIwBlAA4AJgBmACEASwBnAD0AXwBMAGsAFQAnAC8AWgBvXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCyAAIAECIAN0wA4ADkADgBxAHsAPqkAcgBzAHQAdQB2AHcAeAB5AHqADoAPgBCAEYASgBOAFIAVgBapAHwAfQB+AH8AgACBAIIAgwCEgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHNfEBpYRF9QU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QGVhEX1BTS19mZXRjaFJlcXVlc3RzQXJyYXlfEBFYRF9QU0tfaXNBYnN0cmFjdF8QD1hEX1BTS191c2VySW5mb18QE1hEX1BTS19jbGFzc01hcHBpbmdfEBZYRF9QU0tfZW50aXR5Q2xhc3NOYW1l3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAmwAVAGEAWgBaAFoALwBaAKIAcgBaAFoAFQBaVV90eXBlWF9kZWZhdWx0XF9hc3NvY2lhdGlvbltfaXNSZWFkT25seVlfaXNTdGF0aWNZX2lzVW5pcXVlWl9pc0Rlcml2ZWRaX2lzT3JkZXJlZFxfaXNDb21wb3NpdGVXX2lzTGVhZoAAgBiAAIAMCAgICIAagA4ICIAACNIAOQAOAKkAqqCAGdIArACtAK4Ar1okY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owCuALAAsVdOU0FycmF5WE5TT2JqZWN00gCsAK0AswC0XxAQWERVTUxQcm9wZXJ0eUltcKQAtQC2ALcAsV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHMAWgBaABUAWoAAgACAAIAMCAgICIAagA8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAMkAFQBhAFoAWgBaAC8AWgCiAHQAWgBaABUAWoAAgB2AAIAMCAgICIAagBAICIAACNIAOQAOANcAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHUAWgBaABUAWoAAgACAAIAMCAgICIAagBEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAOoAFQBhAFoAWgBaAC8AWgCiAHYAWgBaABUAWoAAgCCAAIAMCAgICIAagBIICIAACNIAOQAOAPgAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQBhAFoAWgBaAC8AWgCiAHcAWgBaABUAWoAAgCKAAIAMCAgICIAagBMICIAACAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEMABUAYQBaAFoAWgAvAFoAogB4AFoAWgAVAFqAAIAkgACADAgICAiAGoAUCAiAAAjTADgAOQAOARoBGwA+oKCAJdIArACtAR4BH18QE05TTXV0YWJsZURpY3Rpb25hcnmjAR4BIACxXE5TRGljdGlvbmFyed8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVASMAFQBhAFoAWgBaAC8AWgCiAHkAWgBaABUAWoAAgCeAAIAMCAgICIAagBUICIAACNYAIwAOACYASwAfACEBMQEyABUAWgAVAC+AKIApgAAIgABfEBRYREdlbmVyaWNSZWNvcmRDbGFzc9IArACtATgBOV1YRFVNTENsYXNzSW1wpgE6ATsBPAE9AT4AsV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAUEAFQBhAFoAWgBaAC8AWgCiAHoAWgBaABUAWoAAgCuAAIAMCAgICIAagBYICIAACF8QElVBSW5ib3hNZXNzYWdlRGF0YdIArACtAVABUV8QElhEVU1MU3RlcmVvdHlwZUltcKcBUgFTAVQBVQFWAVcAsV8QElhEVU1MU3RlcmVvdHlwZUltcF1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4BWQFnAD6tAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlAWaALoAvgDCAMYAygDOANIA1gDaAN4A4gDmAOq0BaAFpAWoBawFsAW0BbgFvAXABcQFyAXMBdIA7gGeAgICYgLCAyYDhgPmBARCBASeBAT6BAVaBAW2AJVVleHRyYV5tZXNzYWdlQm9keVVSTF8QEW1lc3NhZ2VFeHBpcmF0aW9uXxAQbWVzc2FnZVJlcG9ydGluZ11kZWxldGVkQ2xpZW50XxAQcmF3TWVzc2FnZU9iamVjdFltZXNzYWdlSURbY29udGVudFR5cGVbbWVzc2FnZVNlbnRVdGl0bGVcdW5yZWFkQ2xpZW50VnVucmVhZFptZXNzYWdlVVJM3xASAJAAkQCSAYQAHwCUAJUBhQAhAJMBhgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoBjgAvAFoATABaAZIBWgBaAFoBlgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgD0IgAkIgGaALggIgDwIEv8F2wLTADgAOQAOAZoBnQA+ogGbAZyAPoA/ogGeAZ+AQIBUgCVfEBJYRF9QUHJvcFN0ZXJlb3R5cGVfEBJYRF9QQXR0X1N0ZXJlb3R5cGXZAB8AIwGkAA4AJgGlACEASwGmAWgBmwBMAGsAFQAnAC8AWgGuXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDuAPoAJgCyAAIAECIBB0wA4ADkADgGwAbkAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagBugG7AbwBvQG+Ab8BwAHBgEqAS4BMgE6AT4BRgFKAU4AlXxAbWERfUFBTS19pc1N0b3JlZEluVHJ1dGhGaWxlXxAbWERfUFBTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAQWERfUFBTS191c2VySW5mb18QEVhEX1BQU0tfaXNJbmRleGVkXxASWERfUFBTS19pc09wdGlvbmFsXxAaWERfUFBTS19pc1Nwb3RsaWdodEluZGV4ZWRfEBFYRF9QUFNLX2VsZW1lbnRJRF8QE1hEX1BQU0tfaXNUcmFuc2llbnTfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBngBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAQAgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBngBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACAQAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHrABUBngBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIBNgACAQAgICAiAGoBECAiAAAjTADgAOQAOAfkB+gA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGeAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIBACAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQGeAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIBACAgICIAagEYICIAACAnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBngBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACAQAgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBngBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACAQAgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBngBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACAQAgICAiAGoBJCAiAAAjZAB8AIwJJAA4AJgJKACEASwJLAWgBnABMAGsAFQAnAC8AWgJTXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDuAP4AJgCyAAIAECIBV0wA4ADkADgJVAl0APqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4BcpwJeAl8CYAJhAmICYwJkgF2AXoBfgGCAYoBjgGWAJV8QHVhEX1BBdHRLX2RlZmF1bHRWYWx1ZUFzU3RyaW5nXxAoWERfUEF0dEtfYWxsb3dzRXh0ZXJuYWxCaW5hcnlEYXRhU3RvcmFnZV8QF1hEX1BBdHRLX21pblZhbHVlU3RyaW5nXxAWWERfUEF0dEtfYXR0cmlidXRlVHlwZV8QF1hEX1BBdHRLX21heFZhbHVlU3RyaW5nXxAdWERfUEF0dEtfdmFsdWVUcmFuc2Zvcm1lck5hbWVfECBYRF9QQXR0S19yZWd1bGFyRXhwcmVzc2lvblN0cmluZ98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGfAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIBUCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGfAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIBUCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGfAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIBUCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVApwAFQGfAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgGGAAIBUCAgICIAagFkICIAACBED6N8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGfAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIBUCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVArsAFQGfAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgGSAAIBUCAgICIAagFsICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGfAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIBUCAgICIAagFwICIAACNIArACtAtkC2l1YRFBNQXR0cmlidXRlpgLbAtwC3QLeAt8AsV1YRFBNQXR0cmlidXRlXFhEUE1Qcm9wZXJ0eV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QEgCQAJEAkgLhAB8AlACVAuIAIQCTAuMAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAusALwBaAEwAWgGSAVsAWgBaAvMAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIBpCIAJCIBmgC8ICIBoCBJ8iIUn0wA4ADkADgL3AvoAPqIBmwGcgD6AP6IC+wL8gGqAdYAl2QAfACMC/wAOACYDAAAhAEsDAQFpAZsATABrABUAJwAvAFoDCV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBngD6ACYAsgACABAiAa9MAOAA5AA4DCwMUAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoAxUDFgMXAxgDGQMaAxsDHIBsgG2AboBwgHGAcoBzgHSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL7AFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIBqCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL7AFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIBqCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAz4AFQL7AFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgG+AAIBqCAgICIAagEQICIAACNMAOAA5AA4DTANNAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvsAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgGoICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCDQAVAvsAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAUIAAgGoICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvsAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgGoICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvsAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgGoICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvsAWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgGoICAgIgBqASQgIgAAI2QAfACMDmwAOACYDnAAhAEsDnQFpAZwATABrABUAJwAvAFoDpV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBngD+ACYAsgACABAiAdtMAOAA5AA4DpwOvAD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcDsAOxA7IDswO0A7UDtoB3gHiAeYB6gHyAfYB/gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC/ABaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACAdQgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC/ABaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACAdQgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC/ABaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACAdQgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQPnABUC/ABaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIB7gACAdQgICAiAGoBZCAiAAAgRBwjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC/ABaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACAdQgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQQGABUC/ABaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIB+gACAdQgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC/ABaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACAdQgICAiAGoBcCAiAAAjfEBIAkACRAJIEJAAfAJQAlQQlACEAkwQmAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgQuAC8AWgBMAFoBkgFcAFoAWgQ2AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAggiACQiAZoAwCAiAgQgS51jPW9MAOAA5AA4EOgQ9AD6iAZsBnIA+gD+iBD4EP4CDgI6AJdkAHwAjBEIADgAmBEMAIQBLBEQBagGbAEwAawAVACcALwBaBExfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAgIA+gAmALIAAgAQIgITTADgAOQAOBE4EVwA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqARYBFkEWgRbBFwEXQReBF+AhYCGgIeAiYCKgIuAjICNgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEPgBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAgwgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPgBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACAgwgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQSBABUEPgBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAICIgACAgwgICAiAGoBECAiAAAjTADgAOQAOBI8EkAA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ+AFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAICDCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQQ+AFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAICDCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ+AFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAICDCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ+AFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAICDCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ+AFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAICDCAgICIAagEkICIAACNkAHwAjBN4ADgAmBN8AIQBLBOABagGcAEwAawAVACcALwBaBOhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAgIA/gAmALIAAgAQIgI/TADgAOQAOBOoE8gA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynBPME9AT1BPYE9wT4BPmAkICRgJKAk4CVgJaAl4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD8AWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgI4ICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBD8AWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgI4ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD8AWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgI4ICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFKgAVBD8AWgBaAFoALwBaAKICWQBaAFoAFQBagACAlIAAgI4ICAgIgBqAWQgIgAAIEQOE3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD8AWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgI4ICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD8AWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgI4ICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD8AWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgI4ICAgIgBqAXAgIgAAI3xASAJAAkQCSBWYAHwCUAJUFZwAhAJMFaACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoFcAAvAFoATABaAZIBXQBaAFoFeABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgJoIgAkIgGaAMQgIgJkIEjGGxpzTADgAOQAOBXwFfwA+ogGbAZyAPoA/ogWABYGAm4CmgCXZAB8AIwWEAA4AJgWFACEASwWGAWsBmwBMAGsAFQAnAC8AWgWOXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJiAPoAJgCyAAIAECICc0wA4ADkADgWQBZkAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagFmgWbBZwFnQWeBZ8FoAWhgJ2AnoCfgKGAooCjgKSApYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBYAAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgJsICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBYAAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgJsICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFwwAVBYAAWgBaAFoALwBaAKIBswBaAFoAFQBagACAoIAAgJsICAgIgBqARAgIgAAI0wA4ADkADgXRBdIAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFgABaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACAmwgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUFgABaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACAmwgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFgABaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACAmwgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFgABaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACAmwgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFgABaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACAmwgICAiAGoBJCAiAAAjZAB8AIwYgAA4AJgYhACEASwYiAWsBnABMAGsAFQAnAC8AWgYqXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgJiAP4AJgCyAAIAECICn0wA4ADkADgYsBjQAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4BcpwY1BjYGNwY4BjkGOgY7gKiAqYCqgKuArICtgK+AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQWBAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAICmCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQWBAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAICmCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQWBAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAICmCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVApwAFQWBAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgGGAAICmCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQWBAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAICmCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBooAFQWBAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgK6AAICmCAgICIAagFsICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQWBAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAICmCAgICIAagFwICIAACN8QEgCQAJEAkgaoAB8AlACVBqkAIQCTBqoAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBrIALwBaAEwAWgGSAV4AWgBaBroAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICICyCIAJCIBmgDIICICxCBKRyklF0wA4ADkADga+BsEAPqIBmwGcgD6AP6IGwgbDgLOAvoAl2QAfACMGxgAOACYGxwAhAEsGyAFsAZsATABrABUAJwAvAFoG0F8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCwgD6ACYAsgACABAiAtNMAOAA5AA4G0gbbAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoBtwG3QbeBt8G4AbhBuIG44C1gLaAt4C5gLqAu4C8gL2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbCAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAICzCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbCAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAICzCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBwUAFQbCAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgLiAAICzCAgICIAagEQICIAACNMAOAA5AA4HEwcUAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBsIAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgLMICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBsIAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgLMICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBsIAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgLMICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBsIAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgLMICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBsIAWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgLMICAgIgBqASQgIgAAI2QAfACMHYgAOACYHYwAhAEsHZAFsAZwATABrABUAJwAvAFoHbF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCwgD+ACYAsgACABAiAv9MAOAA5AA4Hbgd2AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcHdwd4B3kHegd7B3wHfYDAgMKAw4DEgMaAx4DIgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQeBABUGwwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIDBgACAvggICAiAGoBWCAiAAAhSTk/fEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGwwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACAvggICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGwwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACAvggICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQevABUGwwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIDFgACAvggICAiAGoBZCAiAAAgRAyDfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGwwBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACAvggICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGwwBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACAvggICAiAGoBbCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGwwBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACAvggICAiAGoBcCAiAAAjfEBIAkACRAJIH6wAfAJQAlQfsACEAkwftAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgf1AC8AWgBMAFoBkgFfAFoAWgf9AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAywiACQiAZoAzCAiAyggSW3zYEtMAOAA5AA4IAQgEAD6iAZsBnIA+gD+iCAUIBoDMgNeAJdkAHwAjCAkADgAmCAoAIQBLCAsBbQGbAEwAawAVACcALwBaCBNfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAyYA+gAmALIAAgAQIgM3TADgAOQAOCBUIHgA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAgfCCAIIQgiCCMIJAglCCaAzoDPgNCA0oDTgNSA1YDWgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUIBQBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAzAgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBQBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACAzAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQhIABUIBQBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIDRgACAzAgICAiAGoBECAiAAAjTADgAOQAOCFYIVwA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgFAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIDMCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQgFAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIDMCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgFAFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAIDMCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQgFAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIDMCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgFAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIDMCAgICIAagEkICIAACNkAHwAjCKUADgAmCKYAIQBLCKcBbQGcAEwAawAVACcALwBaCK9fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAyYA/gAmALIAAgAQIgNjTADgAOQAOCLEIuQA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynCLoIuwi8CL0Ivgi/CMCA2YDagNuA3IDdgN6A4IAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAYAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgNcICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAYAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgNcICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAYAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgNcICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCnAAVCAYAWgBaAFoALwBaAKICWQBaAFoAFQBagACAYYAAgNcICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAYAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgNcICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUJDwAVCAYAWgBaAFoALwBaAKICWwBaAFoAFQBagACA34AAgNcICAgIgBqAWwgIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAYAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgNcICAgIgBqAXAgIgAAI3xASAJAAkQCSCS0AHwCUAJUJLgAhAJMJLwCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoJNwAvAFoATABaAZIBYABaAFoJPwBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgOMIgAkIgGaANAgIgOIIEtgvqhvTADgAOQAOCUMJRgA+ogGbAZyAPoA/oglHCUiA5IDvgCXZAB8AIwlLAA4AJglMACEASwlNAW4BmwBMAGsAFQAnAC8AWglVXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgOGAPoAJgCyAAIAECIDl0wA4ADkADglXCWAAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagJYQliCWMJZAllCWYJZwlogOaA54DogOqA64DsgO2A7oAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCUcAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgOQICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUcAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgOQICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUJigAVCUcAWgBaAFoALwBaAKIBswBaAFoAFQBagACA6YAAgOQICAgIgBqARAgIgAAI0wA4ADkADgmYCZkAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACA5AgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUJRwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACA5AgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACA5AgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJRwBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACA5AgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACA5AgICAiAGoBJCAiAAAjZAB8AIwnnAA4AJgnoACEASwnpAW4BnABMAGsAFQAnAC8AWgnxXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgOGAP4AJgCyAAIAECIDw0wA4ADkADgnzCfsAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpwn8Cf0J/gn/CgAKAQoCgPGA8oDzgPSA9oD3gPiAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIDvCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlIAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIDvCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIDvCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCjMAFQlIAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgPWAAIDvCAgICIAagFkICIAACBECvN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIDvCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIDvCAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIDvCAgICIAagFwICIAACN8QEgCQAJEAkgpvAB8AlACVCnAAIQCTCnEAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaCnkALwBaAEwAWgGSAWEAWgBaCoEAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICID7CIAJCIBmgDUICID6CBI0BoyX0wA4ADkADgqFCogAPqIBmwGcgD6AP6IKiQqKgPyBAQeAJdkAHwAjCo0ADgAmCo4AIQBLCo8BbwGbAEwAawAVACcALwBaCpdfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA+YA+gAmALIAAgAQIgP3TADgAOQAOCpkKogA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAqjCqQKpQqmCqcKqAqpCqqA/oD/gQEAgQECgQEDgQEEgQEFgQEGgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACA/AgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKiQBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACA/AgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQrMABUKiQBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBAYAAgPwICAgIgBqARAgIgAAI0wA4ADkADgraCtsAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACA/AgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUKiQBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACA/AgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACA/AgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKiQBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACA/AgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACA/AgICAiAGoBJCAiAAAjZAB8AIwspAA4AJgsqACEASwsrAW8BnABMAGsAFQAnAC8AWgszXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgPmAP4AJgCyAAIAECIEBCNMAOAA5AA4LNQs9AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcLPgs/C0ALQQtCC0MLRIEBCYEBCoEBC4EBDIEBDYEBDoEBD4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCooAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgQEHCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqKAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBBwgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKigBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAQcICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKMwAVCooAWgBaAFoALwBaAKICWQBaAFoAFQBagACA9YAAgQEHCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqKAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBBwgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKigBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAQcICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCooAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQEHCAgICIAagFwICIAACN8QEgCQAJEAkguwAB8AlACVC7EAIQCTC7IAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaC7oALwBaAEwAWgGSAWIAWgBaC8IAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBEgiACQiAZoA2CAiBAREIEnGeQjTTADgAOQAOC8YLyQA+ogGbAZyAPoA/ogvKC8uBAROBAR6AJdkAHwAjC84ADgAmC88AIQBLC9ABcAGbAEwAawAVACcALwBaC9hfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBARCAPoAJgCyAAIAECIEBFNMAOAA5AA4L2gvjAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoC+QL5QvmC+cL6AvpC+oL64EBFYEBFoEBF4EBGYEBGoEBG4EBHIEBHYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8oAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQETCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvKAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBEwgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQwNABULygBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBGIAAgQETCAgICIAagEQICIAACNMAOAA5AA4MGwwcAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8oAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQETCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQvKAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIEBEwgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULygBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBARMICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8oAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQETCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvKAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBEwgICAiAGoBJCAiAAAjZAB8AIwxqAA4AJgxrACEASwxsAXABnABMAGsAFQAnAC8AWgx0XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQEQgD+ACYAsgACABAiBAR/TADgAOQAODHYMfgA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynDH8MgAyBDIIMgwyEDIWBASCBASGBASKBASOBASSBASWBASaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvLAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIEBHggICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULywBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBAR4ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8sAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQEeCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBSoAFQvLAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgJSAAIEBHggICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULywBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBAR4ICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8sAWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgQEeCAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvLAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIEBHggICAiAGoBcCAiAAAjfEBIAkACRAJIM8QAfAJQAlQzyACEAkwzzAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgz7AC8AWgBMAFoBkgFjAFoAWg0DAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBASkIgAkIgGaANwgIgQEoCBJdgOpe0wA4ADkADg0HDQoAPqIBmwGcgD6AP6INCw0MgQEqgQE1gCXZAB8AIw0PAA4AJg0QACEASw0RAXEBmwBMAGsAFQAnAC8AWg0ZXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQEngD6ACYAsgACABAiBASvTADgAOQAODRsNJAA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqA0lDSYNJw0oDSkNKg0rDSyBASyBAS2BAS6BATCBATGBATKBATOBATSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0LAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBKggICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNCwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACBASoICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUNTgAVDQsAWgBaAFoALwBaAKIBswBaAFoAFQBagACBAS+AAIEBKggICAiAGoBECAiAAAjTADgAOQAODVwNXQA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0LAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIEBKggICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUNCwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACBASoICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQsAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgQEqCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0LAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIEBKggICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNCwBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACBASoICAgIgBqASQgIgAAI2QAfACMNqwAOACYNrAAhAEsNrQFxAZwATABrABUAJwAvAFoNtV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBJ4A/gAmALIAAgAQIgQE20wA4ADkADg23Db8APqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpw3ADcENwg3DDcQNxQ3GgQE3gQE4gQE5gQE6gQE7gQE8gQE9gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNDABaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACBATUICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQwAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgQE1CAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0MAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBNQgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQozABUNDABaAFoAWgAvAFoAogJZAFoAWgAVAFqAAID1gACBATUICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQwAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgQE1CAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0MAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIEBNQgICAiAGoBbCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNDABaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBATUICAgIgBqAXAgIgAAI3xASAJAAkQCSDjIAHwCUAJUOMwAhAJMONACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoOPAAvAFoATABaAZIBZABaAFoORABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQFACIAJCIBmgDgICIEBPwgTAAAAAQWaPffTADgAOQAODkgOSwA+ogGbAZyAPoA/og5MDk2BAUGBAUyAJdkAHwAjDlAADgAmDlEAIQBLDlIBcgGbAEwAawAVACcALwBaDlpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAT6APoAJgCyAAIAECIEBQtMAOAA5AA4OXA5lAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoDmYOZw5oDmkOag5rDmwObYEBQ4EBRIEBRYEBR4EBSIEBSYEBSoEBS4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkwAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFBCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5MAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBQQgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ6PABUOTABaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBRoAAgQFBCAgICIAagEQICIAACNMAOAA5AA4OnQ6eAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkwAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQFBCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5MAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOTABaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBAUEICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkwAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQFBCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5MAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBJCAiAAAjZAB8AIw7sAA4AJg7tACEASw7uAXIBnABMAGsAFQAnAC8AWg72XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQE+gD+ACYAsgACABAiBAU3TADgAOQAODvgPAAA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynDwEPAg8DDwQPBQ8GDweBAU6BAVCBAVGBAVKBAVOBAVSBAVWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDwsAFQ5NAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgQFPgACBAUwICAgIgBqAVggIgAAIU1lFU98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5NAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBTAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOTQBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAUwICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHrwAVDk0AWgBaAFoALwBaAKICWQBaAFoAFQBagACAxYAAgQFMCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5NAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBTAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOTQBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAUwICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDk0AWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQFMCAgICIAagFwICIAACN8QEgCQAJEAkg90AB8AlACVD3UAIQCTD3YAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaD34ALwBaAEwAWgGSAWUAWgBaD4YAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBWAiACQiAZoA5CAiBAVcIEnQ0EmrTADgAOQAOD4oPjQA+ogGbAZyAPoA/og+OD4+BAVmBAWSAJdkAHwAjD5IADgAmD5MAIQBLD5QBcwGbAEwAawAVACcALwBaD5xfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAVaAPoAJgCyAAIAECIEBWtMAOAA5AA4Png+nAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoD6gPqQ+qD6sPrA+tD64Pr4EBW4EBXIEBXYEBX4EBYIEBYYEBYoEBY4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD44AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFZCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+OAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBWQgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ/RABUPjgBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBXoAAgQFZCAgICIAagEQICIAACNMAOAA5AA4P3w/gAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD44AWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQFZCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+OAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIEBWQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPjgBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBAVkICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD44AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQFZCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+OAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBWQgICAiAGoBJCAiAAAjZAB8AIxAuAA4AJhAvACEASxAwAXMBnABMAGsAFQAnAC8AWhA4XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFWgD+ACYAsgACABAiBAWXTADgAOQAOEDoQQgA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynEEMQRBBFEEYQRxBIEEmBAWaBAWeBAWiBAWmBAWqBAWuBAWyAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDwsAFQ+PAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgQFPgACBAWQICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD48AWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgQFkCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+PAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBZAgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQevABUPjwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIDFgACBAWQICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD48AWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgQFkCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+PAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIEBZAgICAiAGoBbCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjwBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBAWQICAgIgBqAXAgIgAAI3xASAJAAkQCSELUAHwCUAJUQtgAhAJMQtwCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoQvwAvAFoATABaAZIBZgBaAFoQxwBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQFvCIAJCIBmgDoICIEBbggTAAAAASy5H3zTADgAOQAOEMsQzgA+ogGbAZyAPoA/ohDPENCBAXCBAXuAJdkAHwAjENMADgAmENQAIQBLENUBdAGbAEwAawAVACcALwBaEN1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAW2APoAJgCyAAIAECIEBcdMAOAA5AA4Q3xDoAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoEOkQ6hDrEOwQ7RDuEO8Q8IEBcoEBc4EBdIEBdoEBd4EBeIEBeYEBeoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVEM8AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFwCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFRDPAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBcAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFRESABUQzwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBdYAAgQFwCAgICIAagEQICIAACNMAOAA5AA4RIBEhAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVEM8AWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQFwCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFRDPAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIEBcAgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUQzwBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBAXAICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVEM8AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQFwCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFRDPAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBcAgICAiAGoBJCAiAAAjZAB8AIxFvAA4AJhFwACEASxFxAXQBnABMAGsAFQAnAC8AWhF5XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFtgD+ACYAsgACABAiBAXzTADgAOQAOEXsRgwA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynEYQRhRGGEYcRiBGJEYqBAX2BAX6BAX+BAYCBAYGBAYKBAYSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFRDQAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIEBewgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUQ0ABaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBAXsICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVENAAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQF7CAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA+cAFRDQAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgHuAAIEBewgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUQ0ABaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBAXsICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUR2QAVENAAWgBaAFoALwBaAKICWwBaAFoAFQBagACBAYOAAIEBewgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUQ0ABaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBAXsICAgIgBqAXAgIgAAIWmR1cGxpY2F0ZXPSADkADhH4AKqggBnSAKwArRH7EfxaWERQTUVudGl0eacR/RH+Ef8SABIBEgIAsVpYRFBNRW50aXR5XVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADhIEEgUAPqCggCXTADgAOQAOEggSCQA+oKCAJdMAOAA5AA4SDBINAD6goIAl0gCsAK0SEBIRXlhETW9kZWxQYWNrYWdlphISEhMSFBIVEhYAsV5YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADkADhIYAKqggBnTADgAOQAOEhsSHAA+oKCAJVDSAKwArRIgEiFZWERQTU1vZGVsoxIgEiIAsVdYRE1vZGVsAAgAGQAiACwAMQA6AD8AUQBWAFsAXQOBA4cDoAOyA7kDxwPUA+wEBgQIBAsEDQQQBBMEFgRPBG4EiwSqBLwE3ATjBQEFDQUpBS8FUQVyBYUFhwWKBY0FjwWRBZMFlgWZBZsFnQWfBaEFowWlBaYFqgW3Bb8FygXNBc8F0gXUBdYF5QYoBkwGcAaTBroG2gcBBygHSAdsB5AHnAeeB6AHogekB6YHqAerB60HrweyB7QHtge5B7sHvAfBB8kH1gfZB9sH3gfgB+IH8QgWCDoIYQiFCIcIiQiLCI0IjwiRCJIIlAihCLQItgi4CLoIvAi+CMAIwgjECMYI2QjbCN0I3wjhCOMI5QjnCOkI6wjtCQMJFgkyCU8Jawl/CZEJpwnACf8KBQoOChsKJwoxCjsKRgpRCl4KZgpoCmoKbApuCm8KcApxCnIKdAp2CncKeAp6CnsKhAqFCocKkAqbCqQKswq6CsIKywrUCucK8AsDCxoLLAtrC20LbwtxC3MLdAt1C3YLdwt5C3sLfAt9C38LgAu/C8ELwwvFC8cLyAvJC8oLywvNC88L0AvRC9ML1AvdC94L4AwfDCEMIwwlDCcMKAwpDCoMKwwtDC8MMAwxDDMMNAxzDHUMdwx5DHsMfAx9DH4MfwyBDIMMhAyFDIcMiAyRDJIMlAzTDNUM1wzZDNsM3AzdDN4M3wzhDOMM5AzlDOcM6AzpDSgNKg0sDS4NMA0xDTINMw00DTYNOA05DToNPA09DUoNSw1MDU4NVw1tDXQNgQ3ADcINxA3GDcgNyQ3KDcsNzA3ODdAN0Q3SDdQN1Q3uDfAN8g30DfUN9w4ODhcOJQ4yDkAOVQ5pDoAOkg7RDtMO1Q7XDtkO2g7bDtwO3Q7fDuEO4g7jDuUO5g77DwQPGQ8oDz0PSw9gD3QPiw+dD6oPxQ/HD8kPyw/ND88P0Q/TD9UP1w/ZD9sP3Q/fD/oP/A/+EAAQAhAEEAYQCBAKEA0QEBATEBYQGRAbECEQMBBEEFcQZRB4EIIQjhCaEKAQrRC0EL8RChEtEU0RbRFvEXERcxF1EXcReBF5EXsRfBF+EX8RgRGDEYQRhRGHEYgRjRGaEZ8RoRGjEagRqhGsEa4RwxHYEf0SIRJIEmwSbhJwEnISdBJ2EngSeRJ7EogSmRKbEp0SnxKhEqMSpRKnEqkSuhK8Er4SwBLCEsQSxhLIEsoSzBLqEwgTGxMvE0QTYRN1E4sTyhPME84T0BPSE9MT1BPVE9YT2BPaE9sT3BPeE98UHhQgFCIUJBQmFCcUKBQpFCoULBQuFC8UMBQyFDMUchR0FHYUeBR6FHsUfBR9FH4UgBSCFIMUhBSGFIcUlBSVFJYUmBTXFNkU2xTdFN8U4BThFOIU4xTlFOcU6BTpFOsU7BUrFS0VLxUxFTMVNBU1FTYVNxU5FTsVPBU9FT8VQBVBFYAVghWEFYYViBWJFYoVixWMFY4VkBWRFZIVlBWVFdQV1hXYFdoV3BXdFd4V3xXgFeIV5BXlFeYV6BXpFigWKhYsFi4WMBYxFjIWMxY0FjYWOBY5FjoWPBY9FmIWhhatFtEW0xbVFtcW2RbbFt0W3hbgFu0W/Bb+FwAXAhcEFwYXCBcKFxkXGxcdFx8XIRcjFyUXJxcpF0kXdBeOF6cXwRfhGAQYQxhFGEcYSRhLGEwYTRhOGE8YURhTGFQYVRhXGFgYlxiZGJsYnRifGKAYoRiiGKMYpRinGKgYqRirGKwY6xjtGO8Y8RjzGPQY9Rj2GPcY+Rj7GPwY/Rj/GQAZPxlBGUMZRRlHGUgZSRlKGUsZTRlPGVAZURlTGVQZVxmWGZgZmhmcGZ4ZnxmgGaEZohmkGaYZpxmoGaoZqxnqGewZ7hnwGfIZ8xn0GfUZ9hn4GfoZ+xn8Gf4Z/xomGmUaZxppGmsabRpuGm8acBpxGnMadRp2GncaeRp6GoMakRqeGqwauRrMGuMa9RtAG2MbgxujG6UbpxupG6sbrRuuG68bsRuyG7QbtRu3G7kbuhu7G70bvhvDG9Ab1RvXG9kb3hvgG+Ib5BwJHC0cVBx4HHocfBx+HIAcghyEHIUchxyUHKUcpxypHKscrRyvHLEcsxy1HMYcyBzKHMwczhzQHNIc1BzWHNgdFx0ZHRsdHR0fHSAdIR0iHSMdJR0nHSgdKR0rHSwdax1tHW8dcR1zHXQddR12HXcdeR17HXwdfR1/HYAdvx3BHcMdxR3HHcgdyR3KHcsdzR3PHdAd0R3THdQd4R3iHeMd5R4kHiYeKB4qHiweLR4uHi8eMB4yHjQeNR42HjgeOR54HnoefB5+HoAegR6CHoMehB6GHogeiR6KHowejR7MHs4e0B7SHtQe1R7WHtce2B7aHtwe3R7eHuAe4R8gHyIfJB8mHygfKR8qHysfLB8uHzAfMR8yHzQfNR90H3YfeB96H3wffR9+H38fgB+CH4QfhR+GH4gfiR+uH9If+SAdIB8gISAjICUgJyApICogLCA5IEggSiBMIE4gUCBSIFQgViBlIGcgaSBrIG0gbyBxIHMgdSC0ILYguCC6ILwgvSC+IL8gwCDCIMQgxSDGIMggySEIIQohDCEOIRAhESESIRMhFCEWIRghGSEaIRwhHSFcIV4hYCFiIWQhZSFmIWchaCFqIWwhbSFuIXAhcSGwIbIhtCG2IbghuSG6IbshvCG+IcAhwSHCIcQhxSHIIgciCSILIg0iDyIQIhEiEiITIhUiFyIYIhkiGyIcIlsiXSJfImEiYyJkImUiZiJnImkiayJsIm0ibyJwIpci1iLYItoi3CLeIt8i4CLhIuIi5CLmIuci6CLqIusjNiNZI3kjmSObI50jnyOhI6MjpCOlI6cjqCOqI6sjrSOvI7AjsSOzI7QjuSPGI8sjzSPPI9Qj1iPYI9oj/yQjJEokbiRwJHIkdCR2JHgkeiR7JH0kiiSbJJ0knyShJKMkpSSnJKkkqyS8JL4kwCTCJMQkxiTIJMokzCTOJQ0lDyURJRMlFSUWJRclGCUZJRslHSUeJR8lISUiJWElYyVlJWclaSVqJWslbCVtJW8lcSVyJXMldSV2JbUltyW5JbslvSW+Jb8lwCXBJcMlxSXGJcclySXKJdcl2CXZJdsmGiYcJh4mICYiJiMmJCYlJiYmKCYqJismLCYuJi8mbiZwJnImdCZ2JncmeCZ5JnomfCZ+Jn8mgCaCJoMmwibEJsYmyCbKJssmzCbNJs4m0CbSJtMm1CbWJtcnFicYJxonHCceJx8nICchJyInJCcmJycnKCcqJysnaidsJ24ncCdyJ3MndCd1J3YneCd6J3snfCd+J38npCfIJ+8oEygVKBcoGSgbKB0oHyggKCIoLyg+KEAoQihEKEYoSChKKEwoWyhdKF8oYShjKGUoZyhpKGsoqiisKK4osCiyKLMotCi1KLYouCi6KLsovCi+KL8o/ikAKQIpBCkGKQcpCCkJKQopDCkOKQ8pECkSKRMpUilUKVYpWClaKVspXCldKV4pYCliKWMpZClmKWcppimoKaoprCmuKa8psCmxKbIptCm2KbcpuCm6Kbspvin9Kf8qASoDKgUqBioHKggqCSoLKg0qDioPKhEqEipRKlMqVSpXKlkqWipbKlwqXSpfKmEqYipjKmUqZiqlKqcqqSqrKq0qriqvKrAqsSqzKrUqtiq3KrkquisFKygrSCtoK2orbCtuK3ArcitzK3Qrdit3K3kreit8K34rfyuAK4IrgyuIK5UrmiucK54royulK6crqSvOK/IsGSw9LD8sQSxDLEUsRyxJLEosTCxZLGosbCxuLHAscix0LHYseCx6LIssjSyPLJEskyyVLJcsmSybLJ0s3CzeLOAs4izkLOUs5iznLOgs6izsLO0s7izwLPEtMC0yLTQtNi04LTktOi07LTwtPi1ALUEtQi1ELUUthC2GLYgtii2MLY0tji2PLZAtki2ULZUtli2YLZktpi2nLagtqi3pLest7S3vLfEt8i3zLfQt9S33Lfkt+i37Lf0t/i49Lj8uQS5DLkUuRi5HLkguSS5LLk0uTi5PLlEuUi6RLpMulS6XLpkumi6bLpwunS6fLqEuoi6jLqUupi7lLucu6S7rLu0u7i7vLvAu8S7zLvUu9i73Lvku+i85LzsvPS8/L0EvQi9DL0QvRS9HL0kvSi9LL00vTi9zL5cvvi/iL+Qv5i/oL+ov7C/uL+8v8S/+MA0wDzARMBMwFTAXMBkwGzAqMCwwLjAwMDIwNDA2MDgwOjB5MHswfTB/MIEwgjCDMIQwhTCHMIkwijCLMI0wjjDNMM8w0TDTMNUw1jDXMNgw2TDbMN0w3jDfMOEw4jEhMSMxJTEnMSkxKjErMSwxLTEvMTExMjEzMTUxNjF1MXcxeTF7MX0xfjF/MYAxgTGDMYUxhjGHMYkxijHJMcsxzTHPMdEx0jHTMdQx1THXMdkx2jHbMd0x3jIdMh8yITIjMiUyJjInMigyKTIrMi0yLjIvMjEyMjJZMpgymjKcMp4yoDKhMqIyozKkMqYyqDKpMqoyrDKtMvgzGzM7M1szXTNfM2EzYzNlM2YzZzNpM2ozbDNtM28zcTNyM3MzdTN2M3sziDONM48zkTOWM5gzmjOcM8Ez5TQMNDA0MjQ0NDY0ODQ6NDw0PTQ/NEw0XTRfNGE0YzRlNGc0aTRrNG00fjSANII0hDSGNIg0ijSMNI40kDTPNNE00zTVNNc02DTZNNo02zTdNN804DThNOM05DUjNSU1JzUpNSs1LDUtNS41LzUxNTM1NDU1NTc1ODV3NXk1ezV9NX81gDWBNYI1gzWFNYc1iDWJNYs1jDWZNZo1mzWdNdw13jXgNeI15DXlNeY15zXoNeo17DXtNe418DXxNjA2MjY0NjY2ODY5Njo2OzY8Nj42QDZBNkI2RDZFNoQ2hjaINoo2jDaNNo42jzaQNpI2lDaVNpY2mDaZNtg22jbcNt424DbhNuI24zbkNuY26DbpNuo27DbtNyw3LjcwNzI3NDc1NzY3Nzc4Nzo3PDc9Nz43QDdBN2Y3ijexN9U31zfZN9s33TffN+E34jfkN/E4ADgCOAQ4BjgIOAo4DDgOOB04HzghOCM4JTgnOCk4KzgtOGw4bjhwOHI4dDh1OHY4dzh4OHo4fDh9OH44gDiBOIQ4wzjFOMc4yTjLOMw4zTjOOM840TjTONQ41TjXONg5FzkZORs5HTkfOSA5ITkiOSM5JTknOSg5KTkrOSw5azltOW85cTlzOXQ5dTl2OXc5eTl7OXw5fTl/OYA5gznCOcQ5xjnIOco5yznMOc05zjnQOdI50znUOdY51zoWOhg6GjocOh46HzogOiE6IjokOiY6JzooOio6KzpqOmw6bjpwOnI6czp0OnU6djp4Ono6ezp8On46fzrKOu07DTstOy87MTszOzU7Nzs4Ozk7Ozs8Oz47PztBO0M7RDtFO0c7SDtNO1o7XzthO2M7aDtqO2w7bjuTO7c73jwCPAQ8BjwIPAo8DDwOPA88ETwePC88MTwzPDU8Nzw5PDs8PTw/PFA8UjxUPFY8WDxaPFw8XjxgPGI8oTyjPKU8pzypPKo8qzysPK08rzyxPLI8szy1PLY89Tz3PPk8+zz9PP48/z0APQE9Az0FPQY9Bz0JPQo9ST1LPU09Tz1RPVI9Uz1UPVU9Vz1ZPVo9Wz1dPV49az1sPW09bz2uPbA9sj20PbY9tz24Pbk9uj28Pb49vz3APcI9wz4CPgQ+Bj4IPgo+Cz4MPg0+Dj4QPhI+Ez4UPhY+Fz5WPlg+Wj5cPl4+Xz5gPmE+Yj5kPmY+Zz5oPmo+az6qPqw+rj6wPrI+sz60PrU+tj64Pro+uz68Pr4+vz7+PwA/Aj8EPwY/Bz8IPwk/Cj8MPw4/Dz8QPxI/Ez84P1w/gz+nP6k/qz+tP68/sT+zP7Q/tj/DP9I/1D/WP9g/2j/cP94/4D/vP/E/8z/1P/c/+T/7P/0//0A+QEBAQkBEQEZAR0BIQElASkBMQE5AT0BQQFJAU0CSQJRAlkCYQJpAm0CcQJ1AnkCgQKJAo0CkQKZAp0DmQOhA6kDsQO5A70DwQPFA8kD0QPZA90D4QPpA+0E6QTxBPkFAQUJBQ0FEQUVBRkFIQUpBS0FMQU5BT0GOQZBBkkGUQZZBl0GYQZlBmkGcQZ5Bn0GgQaJBo0HiQeRB5kHoQepB60HsQe1B7kHwQfJB80H0QfZB90IeQl1CX0JhQmNCZUJmQmdCaEJpQmtCbUJuQm9CcUJyQr1C4EMAQyBDIkMkQyZDKEMqQytDLEMuQy9DMUMyQzRDNkM3QzhDOkM7Q0BDTUNSQ1RDVkNbQ11DX0NhQ4ZDqkPRQ/VD90P5Q/tD/UP/RAFEAkQERBFEIkQkRCZEKEQqRCxELkQwRDJEQ0RFREdESURLRE1ET0RRRFNEVUSURJZEmESaRJxEnUSeRJ9EoESiRKREpUSmRKhEqUToROpE7ETuRPBE8UTyRPNE9ET2RPhE+UT6RPxE/UU8RT5FQEVCRURFRUVGRUdFSEVKRUxFTUVORVBFUUVeRV9FYEViRaFFo0WlRadFqUWqRatFrEWtRa9FsUWyRbNFtUW2RfVF90X5RftF/UX+Rf9GAEYBRgNGBUYGRgdGCUYKRklGS0ZNRk9GUUZSRlNGVEZVRldGWUZaRltGXUZeRp1Gn0ahRqNGpUamRqdGqEapRqtGrUauRq9GsUayRvFG80b1RvdG+Ub6RvtG/Eb9Rv9HAUcCRwNHBUcGRytHT0d2R5pHnEeeR6BHokekR6ZHp0epR7ZHxUfHR8lHy0fNR89H0UfTR+JH5EfmR+hH6kfsR+5H8EfySDFIM0g1SDdIOUg6SDtIPEg9SD9IQUhCSENIRUhGSIVIh0iJSItIjUiOSI9IkEiRSJNIlUiWSJdImUiaSNlI20jdSN9I4UjiSONI5EjlSOdI6UjqSOtI7UjuSS1JL0kxSTNJNUk2STdJOEk5STtJPUk+ST9JQUlCSUVJhEmGSYhJikmMSY1JjkmPSZBJkkmUSZVJlkmYSZlJ2EnaSdxJ3kngSeFJ4knjSeRJ5knoSelJ6knsSe1KLEouSjBKMko0SjVKNko3SjhKOko8Sj1KPkpASkFKjEqvSs9K70rxSvNK9Ur3SvlK+kr7Sv1K/ksASwFLA0sFSwZLB0sJSwpLD0scSyFLI0slSypLLEsvSzFLVkt6S6FLxUvHS8lLy0vNS89L0UvSS9RL4UvyS/RL9kv4S/pL/Ev+TABMAkwTTBVMF0waTB1MIEwjTCZMKUwrTGpMbExuTHBMckxzTHRMdUx2THhMekx7THxMfkx/TL5MwEzCTMRMxkzHTMhMyUzKTMxMzkzPTNBM0kzTTRJNFE0XTRlNG00cTR1NHk0fTSFNI00kTSVNJ00oTTVNNk03TTlNeE16TXxNfk2ATYFNgk2DTYRNhk2ITYlNik2MTY1NzE3OTdBN0k3UTdVN1k3XTdhN2k3cTd1N3k3gTeFOIE4iTiROJk4oTilOKk4rTixOLk4wTjFOMk40TjVOdE52TnhOek58Tn1Ofk5/ToBOgk6EToVOhk6ITolOyE7KTsxOzk7QTtFO0k7TTtRO1k7YTtlO2k7cTt1PAk8mT01PcU9zT3VPd095T3tPfU9+T4FPjk+dT59PoU+jT6VPp0+pT6tPuk+9T8BPw0/GT8lPzE/PT9FQEFASUBRQFlAZUBpQG1AcUB1QH1AhUCJQI1AlUCZQZVBnUGlQa1BuUG9QcFBxUHJQdFB2UHdQeFB6UHtQulC8UL5QwFDDUMRQxVDGUMdQyVDLUMxQzVDPUNBRD1ERURNRFVEYURlRGlEbURxRHlEgUSFRIlEkUSVRZFFmUWhRalFtUW5Rb1FwUXFRc1F1UXZRd1F5UXpRuVG7Ub1Rv1HCUcNRxFHFUcZRyFHKUctRzFHOUc9SDlIQUhJSFFIXUhhSGVIaUhtSHVIfUiBSIVIjUiRSb1KSUrJS0lLUUtZS2FLaUtxS3VLeUuFS4lLkUuVS51LpUupS61LuUu9S9FMBUwZTCFMKUw9TElMVUxdTPFNgU4dTq1OuU7BTslO0U7ZTuFO5U7xTyVPaU9xT3lPgU+JT5FPmU+hT6lP7U/5UAVQEVAdUClQNVBBUE1QVVFRUVlRYVFpUXVReVF9UYFRhVGNUZVRmVGdUaVRqVKlUq1StVK9UslSzVLRUtVS2VLhUulS7VLxUvlS/VP5VAFUDVQVVCFUJVQpVC1UMVQ5VEFURVRJVFFUVVSJVI1UkVSZVZVVnVWlVa1VuVW9VcFVxVXJVdFV2VXdVeFV6VXtVulW8Vb5VwFXDVcRVxVXGVcdVyVXLVcxVzVXPVdBWD1YRVhNWFVYYVhlWGlYbVhxWHlYgViFWIlYkViVWZFZmVmhWalZtVm5Wb1ZwVnFWc1Z1VnZWd1Z5VnpWuVa7Vr1Wv1bCVsNWxFbFVsZWyFbKVstWzFbOVs9W9FcYVz9XY1dmV2hXaldsV25XcFdxV3RXgVeQV5JXlFeWV5hXmlecV55XrVewV7NXtle5V7xXv1fCV8RYA1gFWAdYCVgMWA1YDlgPWBBYElgUWBVYFlgYWBlYWFhaWFxYXlhhWGJYY1hkWGVYZ1hpWGpYa1htWG5YrVivWLFYs1i2WLdYuFi5WLpYvFi+WL9YwFjCWMNZAlkEWQZZCFkLWQxZDVkOWQ9ZEVkTWRRZFVkXWRhZV1lZWVtZXVlgWWFZYlljWWRZZlloWWlZallsWW1ZrFmuWbBZslm1WbZZt1m4WblZu1m9Wb5Zv1nBWcJaAVoDWgVaB1oKWgtaDFoNWg5aEFoSWhNaFFoWWhdaYlqFWqVaxVrHWslay1rNWs9a0FrRWtRa1VrXWtha2lrcWt1a3lrhWuJa51r0Wvla+1r9WwJbBVsIWwpbL1tTW3pbnluhW6NbpVunW6lbq1usW69bvFvNW89b0VvTW9Vb11vZW9tb3VvuW/Fb9Fv3W/pb/VwAXANcBlwIXEdcSVxLXE1cUFxRXFJcU1xUXFZcWFxZXFpcXFxdXJxcnlygXKJcpVymXKdcqFypXKtcrVyuXK9csVyyXPFc81z2XPhc+1z8XP1c/lz/XQFdA10EXQVdB10IXRVdFl0XXRldWF1aXVxdXl1hXWJdY11kXWVdZ11pXWpda11tXW5drV2vXbFds122XbdduF25XbpdvF2+Xb9dwF3CXcNeAl4EXgZeCF4LXgxeDV4OXg9eEV4TXhReFV4XXhheV15ZXlteXV5gXmFeYl5jXmReZl5oXmleal5sXm1erF6uXrBesl61XrZet164Xrleu169Xr5ev17BXsJe518LXzJfVl9ZX1tfXV9fX2FfY19kX2dfdF+DX4Vfh1+JX4tfjV+PX5FfoF+jX6ZfqV+sX69fsl+1X7df9l/4X/pf/F//YABgAWACYANgBWAHYAhgCWALYAxgS2BNYE9gUWBUYFVgVmBXYFhgWmBcYF1gXmBgYGFgoGCiYKRgpmCpYKpgq2CsYK1gr2CxYLJgs2C1YLZg9WD3YPlg+2D+YP9hAGEBYQJhBGEGYQdhCGEKYQthSmFMYU5hUGFTYVRhVWFWYVdhWWFbYVxhXWFfYWBhn2GhYaNhpWGoYalhqmGrYaxhrmGwYbFhsmG0YbVh9GH2Yfhh+mH9Yf5h/2IAYgFiA2IFYgZiB2IJYgpiVWJ4YphiuGK6YrxivmLAYsJiw2LEYsdiyGLKYstizWLPYtBi0WLUYtVi3mLrYvBi8mL0Yvli/GL/YwFjJmNKY3FjlWOYY5pjnGOeY6BjomOjY6Zjs2PEY8ZjyGPKY8xjzmPQY9Jj1GPlY+hj62PuY/Fj9GP3Y/pj/WP/ZD5kQGRCZERkR2RIZElkSmRLZE1kT2RQZFFkU2RUZJNklWSXZJlknGSdZJ5kn2SgZKJkpGSlZKZkqGSpZOhk6mTtZO9k8mTzZPRk9WT2ZPhk+mT7ZPxk/mT/ZQxlDWUOZRBlT2VRZVNlVWVYZVllWmVbZVxlXmVgZWFlYmVkZWVlpGWmZahlqmWtZa5lr2WwZbFls2W1ZbZlt2W5Zbpl+WX7Zf1l/2YCZgNmBGYFZgZmCGYKZgtmDGYOZg9mTmZQZlJmVGZXZlhmWWZaZltmXWZfZmBmYWZjZmRmo2alZqdmqWasZq1mrmavZrBmsma0ZrVmtma4Zrlm3mcCZylnTWdQZ1JnVGdWZ1hnWmdbZ15na2d6Z3xnfmeAZ4JnhGeGZ4hnl2eaZ51noGejZ6ZnqWesZ65n7WfvZ/Jn9Gf3Z/hn+Wf6Z/tn/Wf/aABoAWgDaARoCGhHaEloS2hNaFBoUWhSaFNoVGhWaFhoWWhaaFxoXWicaJ5ooGiiaKVopminaKhoqWiraK1ormivaLFosmjxaPNo9Wj3aPpo+2j8aP1o/mkAaQJpA2kEaQZpB2lGaUhpSmlMaU9pUGlRaVJpU2lVaVdpWGlZaVtpXGmbaZ1pn2mhaaRppWmmaadpqGmqaaxprWmuabBpsWnwafJp9Gn2aflp+mn7afxp/Wn/agFqAmoDagVqBmpRanRqlGq0arZquGq6arxqvmq/asBqw2rEasZqx2rJastqzGrNatBq0WrWauNq6Grqauxq8Wr0avdq+Wsea0JraWuNa5BrkmuUa5ZrmGuaa5trnmura7xrvmvAa8JrxGvGa8hrymvMa91r4Gvja+Zr6Wvsa+9r8mv1a/dsNmw4bDpsPGw/bEBsQWxCbENsRWxHbEhsSWxLbExsi2yNbI9skWyUbJVslmyXbJhsmmycbJ1snmygbKFs4GzibOVs52zqbOts7GztbO5s8GzybPNs9Gz2bPdtBG0FbQZtCG1HbUltS21NbVBtUW1SbVNtVG1WbVhtWW1abVxtXW2cbZ5toG2ibaVtpm2nbahtqW2rba1trm2vbbFtsm3xbfNt9W33bfpt+238bf1t/m4AbgJuA24EbgZuB25GbkhuSm5Mbk9uUG5RblJuU25VblduWG5ZbltuXG6bbp1un26hbqRupW6mbqduqG6qbqxurW6ubrBusW7WbvpvIW9Fb0hvSm9Mb05vUG9Sb1NvVm9jb3JvdG92b3hvem98b35vgG+Pb5JvlW+Yb5tvnm+hb6Rvpm/lb+dv6m/sb+9v8G/xb/Jv82/1b/dv+G/5b/tv/HA7cD1wP3BBcERwRXBGcEdwSHBKcExwTXBOcFBwUXCQcJJwlHCWcJlwmnCbcJxwnXCfcKFwonCjcKVwpnDlcOdw6XDrcO5w73DwcPFw8nD0cPZw93D4cPpw+3E6cTxxPnFAcUNxRHFFcUZxR3FJcUtxTHFNcU9xUHGPcZFxk3GVcZhxmXGacZtxnHGecaBxoXGicaRxpXHkceZx6HHqce1x7nHvcfBx8XHzcfVx9nH3cflx+nJFcmhyiHKocqpyrHKucrBysnKzcrRyt3K4crpyu3K9cr9ywHLBcsRyxXLOctty4HLicuRy6XLscu9y8XMWczpzYXOFc4hzinOMc45zkHOSc5NzlnOjc7RztnO4c7pzvHO+c8BzwnPEc9Vz2HPbc95z4XPkc+dz6nPtc+90LnQwdDJ0NHQ3dDh0OXQ6dDt0PXQ/dEB0QXRDdER0g3SFdId0iXSMdI10jnSPdJB0knSUdJV0lnSYdJl02HTadN1033TidON05HTldOZ06HTqdOt07HTudO90/HT9dP51AHU/dUF1Q3VFdUh1SXVKdUt1THVOdVB1UXVSdVR1VXWUdZZ1mHWadZ11nnWfdaB1oXWjdaV1pnWndal1qnXpdet17XXvdfJ183X0dfV19nX4dfp1+3X8df51/3Y+dkB2QnZEdkd2SHZJdkp2S3ZNdk92UHZRdlN2VHaTdpV2l3aZdpx2nXaedp92oHaidqR2pXamdqh2qXbOdvJ3GXc9d0B3QndEd0Z3SHdKd0t3Tndbd2p3bHdud3B3cnd0d3Z3eHeHd4p3jXeQd5N3lneZd5x3nnfdd9934Xfjd+Z353fod+l36nfsd+5373fwd/J383gyeDR4Nng4eDt4PHg9eD54P3hBeEN4RHhFeEd4SHiHeIl4i3iNeJB4kXiSeJN4lHiWeJh4mXiaeJx4nXjceN544HjieOV45njneOh46XjreO147njvePF48nkxeTN5NXk3eTp5O3k8eT15PnlAeUJ5Q3lEeUZ5R3mGeYh5i3mNeZB5kXmSeZN5lHmWeZh5mXmaeZx5nXnEegN6BXoHegl6DHoNeg56D3oQehJ6FHoVehZ6GHoZeiR6LXouejB6OXpEelN6XnpseoF6lXqser56y3rMes16z3rcet163nrgeu167nrvevF6+nsJexZ7JXs3e0t7Ynt0e317fnuAe417jnuPe5F7knube6V7rAAAAAAAAAICAAAAAAAAEiMAAAAAAAAAAAAAAAAAAHu0\n</attribute>\n        <relationship name=\"entitymappings\" type=\"0/0\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z108\">\n        <attribute name=\"name\" type=\"string\">unreadClient</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z109\">\n        <attribute name=\"name\" type=\"string\">contentType</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z110\">\n        <attribute name=\"name\" type=\"string\">title</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z111\">\n        <attribute name=\"name\" type=\"string\">deletedClient</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z112\">\n        <attribute name=\"name\" type=\"string\">messageID</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z113\">\n        <attribute name=\"name\" type=\"string\">messageBodyURL</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z114\">\n        <attribute name=\"name\" type=\"string\">unread</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z115\">\n        <attribute name=\"name\" type=\"string\">rawMessageObject</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z116\">\n        <attribute name=\"name\" type=\"string\">messageReporting</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z104\"></relationship>\n    </object>\n</database>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Resources/UAInboxDataMappingV3toV4.xcmappingmodel/xcmapping.xml",
    "content": "<?xml version=\"1.0\"?>\n<!DOCTYPE database SYSTEM \"file:///System/Library/DTDs/CoreData.dtd\">\n\n<database>\n    <databaseInfo>\n        <version>134481920</version>\n        <UUID>9E7BB05D-8284-4C86-94B5-62A27077AAF2</UUID>\n        <nextObjectID>116</nextObjectID>\n        <metadata>\n            <plist version=\"1.0\">\n                <dict>\n                    <key>NSPersistenceFrameworkVersion</key>\n                    <integer>1518</integer>\n                    <key>NSPersistenceMaximumFrameworkVersion</key>\n                    <integer>1518</integer>\n                    <key>NSStoreModelVersionChecksumKey</key>\n                    <string>bMpud663vz0bXQE24C6Rh4MvJ5jVnzsD2sI3njZkKbc=</string>\n                    <key>NSStoreModelVersionHashes</key>\n                    <dict>\n                        <key>XDDevAttributeMapping</key>\n                        <data>\n\t\t0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc=\n\t\t</data>\n                        <key>XDDevEntityMapping</key>\n                        <data>\n\t\tqeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI=\n\t\t</data>\n                        <key>XDDevMappingModel</key>\n                        <data>\n\t\tEqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ=\n\t\t</data>\n                        <key>XDDevPropertyMapping</key>\n                        <data>\n\t\tXN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA=\n\t\t</data>\n                        <key>XDDevRelationshipMapping</key>\n                        <data>\n\t\takYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs=\n\t\t</data>\n                    </dict>\n                    <key>NSStoreModelVersionHashesDigest</key>\n                    <string>+Hmc2uYZK6og+Pvx5GUJ7oW75UG4V/ksQanTjfTKUnxyGWJRMtB5tIRgVwGsrd7lz/QR57++wbvWsr6nxwyS0A==</string>\n                    <key>NSStoreModelVersionHashesVersion</key>\n                    <integer>3</integer>\n                    <key>NSStoreModelVersionIdentifiers</key>\n                    <array>\n                        <string></string>\n                    </array>\n                </dict>\n            </plist>\n        </metadata>\n    </databaseInfo>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z102\">\n        <attribute name=\"name\" type=\"string\">messageSent</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z103\">\n        <attribute name=\"name\" type=\"string\">messageID</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z104\">\n        <attribute name=\"name\" type=\"string\">extra</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z105\">\n        <attribute name=\"name\" type=\"string\">unread</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z106\">\n        <attribute name=\"name\" type=\"string\">unreadClient</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z107\">\n        <attribute name=\"name\" type=\"string\">contentType</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVMAPPINGMODEL\" id=\"z108\">\n        <attribute name=\"sourcemodelpath\" type=\"string\">AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 3.xcdatamodel</attribute>\n        <attribute name=\"sourcemodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBeAALAAwAGQA1ADYANwA/AEAAWwBcAF0AYwBkAHAAhgCHAIgAiQCKAIsAjACNAI4AjwCoAKsAsgC4AMcA1gDZAOgA9wD6AFoBCgEZAR0BIQEwATYBNwE/AU4BTwFYAXQBdQF2AXcBeAF5AXoBewF8AX0BfgF/AYABlQGWAZ4BnwGgAawBwAHBAcIBwwHEAcUBxgHHAcgB1wHmAfUB+QIIAhcCGAInAjYCRQJRAmMCZAJlAmYCZwJoAmkCagJ5AogClwKmAqcCtgLFAsYC1QLdAvIC8wL7AwcDGwMqAzkDSANMA1sDagN5A4gDlwOjA7UDxAPTA+ID8QPyBAEEEAQRBCAENQQ2BD4ESgReBG0EfASLBI8EngStBLwEywTaBOYE+AUHBRYFJQU0BUMFUgVTBWIFdwV4BYAFjAWgBa8FvgXNBdEF4AXvBf4GDQYcBigGOgZJBlgGZwZ2BoUGlAaVBqQGuQa6BsIGzgbiBvEHAAcPBxMHIgcxB0AHTwdeB2oHfAeLB4wHmweqB7kHugfJB9gH5wf8B/0IBQgRCCUINAhDCFIIVghlCHQIgwiSCKEIrQi/CM4I3QjsCPsI/AkLCRoJKQk+CT8JRwlTCWcJdgmFCZQJmAmnCbYJxQnUCeMJ7woBChAKHwouCj0KPgpNClwKawqACoEKiQqVCqkKuArHCtYK2grpCvgLBwsWCyULMQtDC1ILYQtwC38LjgudC6wLwQvCC8oL1gvqC/kMCAwXDBsMKgw5DEgMVwxmDHIMhAyTDKIMsQzADM8M3gztDQINAw0LDRcNKw06DUkNWA1cDWsNeg2JDZgNpw2zDcUN1A3VDeQN8w4CDhEOIA4vDkQORQ5NDlkObQ58DosOmg6eDq0OvA7LDtoO6Q71DwcPFg8lDzQPQw9SD2EPcA+FD4YPjg+aD64PvQ/MD9sP3w/uD/0QDBAbECoQNhBIEFcQZhB1EIQQkxCiEKMQshCzELYQvxDDEMcQyxDTENYQ2hDbVSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgQF3gACBAXSBAXWBAXbeABoAGwAcAB0AHgAfACAADgAhACIAIwAkACUAJgAnACgAKQAJACcAFQAtAC4ALwAwADEAJwAnABVfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASBAXKBAXCAAYAEgACBAXGBAXMQAIAFgAOABIAEgABQU1lFU9MAOAA5AA4AOgA8AD5XTlMua2V5c1pOUy5vYmplY3RzoQA7gAahAD2AB4AlXlVBSW5ib3hNZXNzYWdl3xAQAEEAQgBDAEQAHwBFAEYAIQBHAEgADgAjAEkASgAmAEsATABNACcAJwATAFEAUgAvACcATABVADsATABYAFkAWl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgC2ABIAEgAKACoEBbYAEgAmBAW+ABoAJgQFugAgIEqsitt9Xb3JkZXJlZNMAOAA5AA4AXgBgAD6hAF+AC6EAYYAMgCVeWERfUFN0ZXJlb3R5cGXZAB8AIwBlAA4AJgBmACEASwBnAD0AXwBMAGsAFQAnAC8AWgBvXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCyAAIAECIAN0wA4ADkADgBxAHsAPqkAcgBzAHQAdQB2AHcAeAB5AHqADoAPgBCAEYASgBOAFIAVgBapAHwAfQB+AH8AgACBAIIAgwCEgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHNfEBpYRF9QU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QGVhEX1BTS19mZXRjaFJlcXVlc3RzQXJyYXlfEBFYRF9QU0tfaXNBYnN0cmFjdF8QD1hEX1BTS191c2VySW5mb18QE1hEX1BTS19jbGFzc01hcHBpbmdfEBZYRF9QU0tfZW50aXR5Q2xhc3NOYW1l3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAmwAVAGEAWgBaAFoALwBaAKIAcgBaAFoAFQBaVV90eXBlWF9kZWZhdWx0XF9hc3NvY2lhdGlvbltfaXNSZWFkT25seVlfaXNTdGF0aWNZX2lzVW5pcXVlWl9pc0Rlcml2ZWRaX2lzT3JkZXJlZFxfaXNDb21wb3NpdGVXX2lzTGVhZoAAgBiAAIAMCAgICIAagA4ICIAACNIAOQAOAKkAqqCAGdIArACtAK4Ar1okY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owCuALAAsVdOU0FycmF5WE5TT2JqZWN00gCsAK0AswC0XxAQWERVTUxQcm9wZXJ0eUltcKQAtQC2ALcAsV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHMAWgBaABUAWoAAgACAAIAMCAgICIAagA8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAMkAFQBhAFoAWgBaAC8AWgCiAHQAWgBaABUAWoAAgB2AAIAMCAgICIAagBAICIAACNIAOQAOANcAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHUAWgBaABUAWoAAgACAAIAMCAgICIAagBEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAOoAFQBhAFoAWgBaAC8AWgCiAHYAWgBaABUAWoAAgCCAAIAMCAgICIAagBIICIAACNIAOQAOAPgAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQBhAFoAWgBaAC8AWgCiAHcAWgBaABUAWoAAgCKAAIAMCAgICIAagBMICIAACAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEMABUAYQBaAFoAWgAvAFoAogB4AFoAWgAVAFqAAIAkgACADAgICAiAGoAUCAiAAAjTADgAOQAOARoBGwA+oKCAJdIArACtAR4BH18QE05TTXV0YWJsZURpY3Rpb25hcnmjAR4BIACxXE5TRGljdGlvbmFyed8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVASMAFQBhAFoAWgBaAC8AWgCiAHkAWgBaABUAWoAAgCeAAIAMCAgICIAagBUICIAACNYAIwAOACYASwAfACEBMQEyABUAWgAVAC+AKIApgAAIgABfEBRYREdlbmVyaWNSZWNvcmRDbGFzc9IArACtATgBOV1YRFVNTENsYXNzSW1wpgE6ATsBPAE9AT4AsV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAUEAFQBhAFoAWgBaAC8AWgCiAHoAWgBaABUAWoAAgCuAAIAMCAgICIAagBYICIAACF8QElVBSW5ib3hNZXNzYWdlRGF0YdIArACtAVABUV8QElhEVU1MU3RlcmVvdHlwZUltcKcBUgFTAVQBVQFWAVcAsV8QElhEVU1MU3RlcmVvdHlwZUltcF1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4BWQFmAD6sAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlgC6AL4AwgDGAMoAzgDSANYA2gDeAOIA5rAFnAWgBaQFqAWsBbAFtAW4BbwFwAXEBcoA6gGaAf4CXgK+AyIDggPiBAQ+BASaBAT6BAVWAJVVleHRyYV5tZXNzYWdlQm9keVVSTF8QEHJhd01lc3NhZ2VPYmplY3RfEBBtZXNzYWdlUmVwb3J0aW5nXWRlbGV0ZWRDbGllbnRfEBFtZXNzYWdlRXhwaXJhdGlvblltZXNzYWdlSURbbWVzc2FnZVNlbnRVdGl0bGVcdW5yZWFkQ2xpZW50VnVucmVhZFptZXNzYWdlVVJM3xASAJAAkQCSAYEAHwCUAJUBggAhAJMBgwCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoBiwAvAFoATABaAY8BWgBaAFoBkwBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgDwIgAkIgGWALggIgDsIEust2O/TADgAOQAOAZcBmgA+ogGYAZmAPYA+ogGbAZyAP4BTgCVfEBJYRF9QUHJvcFN0ZXJlb3R5cGVfEBJYRF9QQXR0X1N0ZXJlb3R5cGXZAB8AIwGhAA4AJgGiACEASwGjAWcBmABMAGsAFQAnAC8AWgGrXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDqAPYAJgCyAAIAECIBA0wA4ADkADgGtAbYAPqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgBtwG4AbkBugG7AbwBvQG+gEmASoBLgE2AToBQgFGAUoAlXxAbWERfUFBTS19pc1N0b3JlZEluVHJ1dGhGaWxlXxAbWERfUFBTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAQWERfUFBTS191c2VySW5mb18QEVhEX1BQU0tfaXNJbmRleGVkXxASWERfUFBTS19pc09wdGlvbmFsXxAaWERfUFBTS19pc1Nwb3RsaWdodEluZGV4ZWRfEBFYRF9QUFNLX2VsZW1lbnRJRF8QE1hEX1BQU0tfaXNUcmFuc2llbnTfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBmwBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAPwgICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBmwBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACAPwgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQHoABUBmwBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIBMgACAPwgICAiAGoBDCAiAAAjTADgAOQAOAfYB9wA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGbAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIA/CAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAgoAFQGbAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgE+AAIA/CAgICIAagEUICIAACAnfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBmwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACAPwgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBmwBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACAPwgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBmwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACAPwgICAiAGoBICAiAAAjZAB8AIwJGAA4AJgJHACEASwJIAWcBmQBMAGsAFQAnAC8AWgJQXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgDqAPoAJgCyAAIAECIBU0wA4ADkADgJSAloAPqcCUwJUAlUCVgJXAlgCWYBVgFaAV4BYgFmAWoBbpwJbAlwCXQJeAl8CYAJhgFyAXYBegF+AYYBigGSAJV8QHVhEX1BBdHRLX2RlZmF1bHRWYWx1ZUFzU3RyaW5nXxAoWERfUEF0dEtfYWxsb3dzRXh0ZXJuYWxCaW5hcnlEYXRhU3RvcmFnZV8QF1hEX1BBdHRLX21pblZhbHVlU3RyaW5nXxAWWERfUEF0dEtfYXR0cmlidXRlVHlwZV8QF1hEX1BBdHRLX21heFZhbHVlU3RyaW5nXxAdWERfUEF0dEtfdmFsdWVUcmFuc2Zvcm1lck5hbWVfECBYRF9QQXR0S19yZWd1bGFyRXhwcmVzc2lvblN0cmluZ98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGcAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIBTCAgICIAagFUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQGcAFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgCKAAIBTCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGcAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIBTCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVApkAFQGcAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgGCAAIBTCAgICIAagFgICIAACBED6N8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGcAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgACAAIBTCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVArgAFQGcAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgGOAAIBTCAgICIAagFoICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQGcAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAIBTCAgICIAagFsICIAACNIArACtAtYC111YRFBNQXR0cmlidXRlpgLYAtkC2gLbAtwAsV1YRFBNQXR0cmlidXRlXFhEUE1Qcm9wZXJ0eV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QEgCQAJEAkgLeAB8AlACVAt8AIQCTAuAAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaAugALwBaAEwAWgGPAVsAWgBaAvAAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIBoCIAJCIBlgC8ICIBnCBLkcfj30wA4ADkADgL0AvcAPqIBmAGZgD2APqIC+AL5gGmAdIAl2QAfACMC/AAOACYC/QAhAEsC/gFoAZgATABrABUAJwAvAFoDBl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBmgD2ACYAsgACABAiAatMAOAA5AA4DCAMRAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioAxIDEwMUAxUDFgMXAxgDGYBrgGyAbYBvgHCAcYBygHOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL4AFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIBpCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL4AFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAIBpCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAzsAFQL4AFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgG6AAIBpCAgICIAagEMICIAACNMAOAA5AA4DSQNKAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvgAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgGkICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVAvgAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgGkICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvgAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgGkICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvgAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgGkICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvgAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgGkICAgIgBqASAgIgAAI2QAfACMDmAAOACYDmQAhAEsDmgFoAZkATABrABUAJwAvAFoDol8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYBmgD6ACYAsgACABAiAddMAOAA5AA4DpAOsAD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cDrQOuA68DsAOxA7IDs4B2gHeAeIB5gHuAfIB+gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACAdAgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC+QBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACAdAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACAdAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQPkABUC+QBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIB6gACAdAgICAiAGoBYCAiAAAgRBwjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACAdAgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQQDABUC+QBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIB9gACAdAgICAiAGoBaCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+QBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACAdAgICAiAGoBbCAiAAAjfEBIAkACRAJIEIQAfAJQAlQQiACEAkwQjAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgQrAC8AWgBMAFoBjwFcAFoAWgQzAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAgQiACQiAZYAwCAiAgAgSsXUxs9MAOAA5AA4ENwQ6AD6iAZgBmYA9gD6iBDsEPICCgI2AJdkAHwAjBD8ADgAmBEAAIQBLBEEBaQGYAEwAawAVACcALwBaBElfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAf4A9gAmALIAAgAQIgIPTADgAOQAOBEsEVAA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqARVBFYEVwRYBFkEWgRbBFyAhICFgIaAiICJgIqAi4CMgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEOwBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACAgggICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEOwBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACAgggICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQR+ABUEOwBaAFoAWgAvAFoAogGwAFoAWgAVAFqAAICHgACAgggICAiAGoBDCAiAAAjTADgAOQAOBIwEjQA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ7AFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAICCCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAgoAFQQ7AFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgE+AAICCCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ7AFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAICCCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ7AFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgACAAICCCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ7AFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAICCCAgICIAagEgICIAACNkAHwAjBNsADgAmBNwAIQBLBN0BaQGZAEwAawAVACcALwBaBOVfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAf4A+gAmALIAAgAQIgI7TADgAOQAOBOcE7wA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunBPAE8QTyBPME9AT1BPaAj4CQgJGAkoCTgJSAloAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDwAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgI0ICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBDwAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgI0ICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDwAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgI0ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCmQAVBDwAWgBaAFoALwBaAKICVgBaAFoAFQBagACAYIAAgI0ICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDwAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgI0ICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUFRQAVBDwAWgBaAFoALwBaAKICWABaAFoAFQBagACAlYAAgI0ICAgIgBqAWggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBDwAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgI0ICAgIgBqAWwgIgAAI3xASAJAAkQCSBWMAHwCUAJUFZAAhAJMFZQCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoFbQAvAFoATABaAY8BXQBaAFoFdQBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgJkIgAkIgGWAMQgIgJgIEwAAAAErq04N0wA4ADkADgV5BXwAPqIBmAGZgD2APqIFfQV+gJqApYAl2QAfACMFgQAOACYFggAhAEsFgwFqAZgATABrABUAJwAvAFoFi18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCXgD2ACYAsgACABAiAm9MAOAA5AA4FjQWWAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioBZcFmAWZBZoFmwWcBZ0FnoCcgJ2AnoCggKGAooCjgKSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQV9AFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAICaCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQV9AFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAICaCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBcAAFQV9AFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgJ+AAICaCAgICIAagEMICIAACNMAOAA5AA4FzgXPAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBX0AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgJoICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVBX0AWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgJoICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBX0AWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgJoICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBX0AWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgJoICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBX0AWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgJoICAgIgBqASAgIgAAI2QAfACMGHQAOACYGHgAhAEsGHwFqAZkATABrABUAJwAvAFoGJ18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCXgD6ACYAsgACABAiAptMAOAA5AA4GKQYxAD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cGMgYzBjQGNQY2BjcGOICngKiAqYCqgKuArICugCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFfgBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACApQgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFfgBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACApQgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFfgBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACApQgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKZABUFfgBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIBggACApQgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFfgBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACApQgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQaHABUFfgBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAICtgACApQgICAiAGoBaCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFfgBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACApQgICAiAGoBbCAiAAAjfEBIAkACRAJIGpQAfAJQAlQamACEAkwanAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgavAC8AWgBMAFoBjwFeAFoAWga3AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAsQiACQiAZYAyCAiAsAgTAAAAARY4m7zTADgAOQAOBrsGvgA+ogGYAZmAPYA+oga/BsCAsoC9gCXZAB8AIwbDAA4AJgbEACEASwbFAWsBmABMAGsAFQAnAC8AWgbNXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgK+APYAJgCyAAIAECICz0wA4ADkADgbPBtgAPqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgG2QbaBtsG3AbdBt4G3wbggLSAtYC2gLiAuYC6gLuAvIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBr8AWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgLIICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBr8AWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgLIICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHAgAVBr8AWgBaAFoALwBaAKIBsABaAFoAFQBagACAt4AAgLIICAgIgBqAQwgIgAAI0wA4ADkADgcQBxEAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGvwBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAsggICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGvwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAigACAsggICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGvwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIAigACAsggICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGvwBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACAsggICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGvwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACAsggICAiAGoBICAiAAAjZAB8AIwdfAA4AJgdgACEASwdhAWsBmQBMAGsAFQAnAC8AWgdpXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgK+APoAJgCyAAIAECIC+0wA4ADkADgdrB3MAPqcCUwJUAlUCVgJXAlgCWYBVgFaAV4BYgFmAWoBbpwd0B3UHdgd3B3gHeQd6gL+AwYDCgMOAxYDGgMeAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVB34AFQbAAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgMCAAIC9CAgICIAagFUICIAACFJOT98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbAAFoAWgBaAC8AWgCiAlQAWgBaABUAWoAAgCKAAIC9CAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbAAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIC9CAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVB6wAFQbAAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgMSAAIC9CAgICIAagFgICIAACBEDIN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbAAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgACAAIC9CAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbAAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIC9CAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbAAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAIC9CAgICIAagFsICIAACN8QEgCQAJEAkgfoAB8AlACVB+kAIQCTB+oAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaB/IALwBaAEwAWgGPAV8AWgBaB/oAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIDKCIAJCIBlgDMICIDJCBLbjZZt0wA4ADkADgf+CAEAPqIBmAGZgD2APqIIAggDgMuA1oAl2QAfACMIBgAOACYIBwAhAEsICAFsAZgATABrABUAJwAvAFoIEF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYDIgD2ACYAsgACABAiAzNMAOAA5AA4IEggbAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioCBwIHQgeCB8IIAghCCIII4DNgM6Az4DRgNKA04DUgNWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgCAFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIDLCAgICIAagEEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQgCAFoAWgBaAC8AWgCiAa8AWgBaABUAWoAAgACAAIDLCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCEUAFQgCAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgNCAAIDLCAgICIAagEMICIAACNMAOAA5AA4IUwhUAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAIAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgMsICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVCAIAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgMsICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAIAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgMsICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAIAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgMsICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAIAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgMsICAgIgBqASAgIgAAI2QAfACMIogAOACYIowAhAEsIpAFsAZkATABrABUAJwAvAFoIrF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYDIgD6ACYAsgACABAiA19MAOAA5AA4Irgi2AD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cItwi4CLkIugi7CLwIvYDYgNmA2oDbgN2A3oDfgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIAwBaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACA1ggICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUIAwBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACA1ggICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIAwBaAFoAWgAvAFoAogJVAFoAWgAVAFqAAIAAgACA1ggICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQjuABUIAwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIDcgACA1ggICAiAGoBYCAiAAAgRA4TfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIAwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACA1ggICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIAwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACA1ggICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIAwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACA1ggICAiAGoBbCAiAAAjfEBIAkACRAJIJKgAfAJQAlQkrACEAkwksAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgk0AC8AWgBMAFoBjwFgAFoAWgk8AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiA4giACQiAZYA0CAiA4QgSPI0s5dMAOAA5AA4JQAlDAD6iAZgBmYA9gD6iCUQJRYDjgO6AJdkAHwAjCUgADgAmCUkAIQBLCUoBbQGYAEwAawAVACcALwBaCVJfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA4IA9gAmALIAAgAQIgOTTADgAOQAOCVQJXQA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqAleCV8JYAlhCWIJYwlkCWWA5YDmgOeA6YDqgOuA7IDtgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRABaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACA4wgICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJRABaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACA4wgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQmHABUJRABaAFoAWgAvAFoAogGwAFoAWgAVAFqAAIDogACA4wgICAiAGoBDCAiAAAjTADgAOQAOCZUJlgA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlEAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIDjCAgICIAagEQICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAgoAFQlEAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgE+AAIDjCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlEAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAIDjCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlEAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgACAAIDjCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlEAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIDjCAgICIAagEgICIAACNkAHwAjCeQADgAmCeUAIQBLCeYBbQGZAEwAawAVACcALwBaCe5fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA4IA+gAmALIAAgAQIgO/TADgAOQAOCfAJ+AA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunCfkJ+gn7CfwJ/Qn+Cf+A8IDxgPKA84D1gPaA94Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUUAWgBaAFoALwBaAKICUwBaAFoAFQBagACAAIAAgO4ICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCUUAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgO4ICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUUAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgO4ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKMAAVCUUAWgBaAFoALwBaAKICVgBaAFoAFQBagACA9IAAgO4ICAgIgBqAWAgIgAAIEQK83xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUUAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgO4ICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUUAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgO4ICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUUAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgO4ICAgIgBqAWwgIgAAI3xASAJAAkQCSCmwAHwCUAJUKbQAhAJMKbgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoKdgAvAFoATABaAY8BYQBaAFoKfgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgPoIgAkIgGWANQgIgPkIEmP06fbTADgAOQAOCoIKhQA+ogGYAZmAPYA+ogqGCoeA+4EBBoAl2QAfACMKigAOACYKiwAhAEsKjAFuAZgATABrABUAJwAvAFoKlF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYD4gD2ACYAsgACABAiA/NMAOAA5AA4KlgqfAD6oAa4BrwGwAbEBsgGzAbQBtYBBgEKAQ4BEgEWARoBHgEioCqAKoQqiCqMKpAqlCqYKp4D9gP6A/4EBAYEBAoEBA4EBBIEBBYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoYAWgBaAFoALwBaAKIBrgBaAFoAFQBagACAIoAAgPsICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoYAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgPsICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKyQAVCoYAWgBaAFoALwBaAKIBsABaAFoAFQBagACBAQCAAID7CAgICIAagEMICIAACNMAOAA5AA4K1wrYAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoYAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgPsICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCCgAVCoYAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAT4AAgPsICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoYAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgPsICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCoYAWgBaAFoALwBaAKIBtABaAFoAFQBagACAAIAAgPsICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCoYAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgPsICAgIgBqASAgIgAAI2QAfACMLJgAOACYLJwAhAEsLKAFuAZkATABrABUAJwAvAFoLMF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYD4gD6ACYAsgACABAiBAQfTADgAOQAOCzILOgA+pwJTAlQCVQJWAlcCWAJZgFWAVoBXgFiAWYBagFunCzsLPAs9Cz4LPwtAC0GBAQiBAQmBAQqBAQuBAQyBAQ2BAQ6AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqHAFoAWgBaAC8AWgCiAlMAWgBaABUAWoAAgACAAIEBBggICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKhwBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACBAQYICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCocAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgQEGCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCO4AFQqHAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgNyAAIEBBggICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKhwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACBAQYICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCocAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQEGCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqHAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAIEBBggICAiAGoBbCAiAAAjfEBIAkACRAJILrQAfAJQAlQuuACEAkwuvAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgu3AC8AWgBMAFoBjwFiAFoAWgu/AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBAREIgAkIgGWANggIgQEQCBLbOkJX0wA4ADkADgvDC8YAPqIBmAGZgD2APqILxwvIgQESgQEdgCXZAB8AIwvLAA4AJgvMACEASwvNAW8BmABMAGsAFQAnAC8AWgvVXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQEPgD2ACYAsgACABAiBARPTADgAOQAOC9cL4AA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqAvhC+IL4wvkC+UL5gvnC+iBARSBARWBARaBARiBARmBARqBARuBARyAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvHAFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIEBEggICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULxwBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACBARIICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUMCgAVC8cAWgBaAFoALwBaAKIBsABaAFoAFQBagACBAReAAIEBEggICAiAGoBDCAiAAAjTADgAOQAODBgMGQA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvHAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBEggICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQIKABULxwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIBPgACBARIICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8cAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgQESCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvHAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgACAAIEBEggICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULxwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACBARIICAgIgBqASAgIgAAI2QAfACMMZwAOACYMaAAhAEsMaQFvAZkATABrABUAJwAvAFoMcV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBD4A+gAmALIAAgAQIgQEe0wA4ADkADgxzDHsAPqcCUwJUAlUCVgJXAlgCWYBVgFaAV4BYgFmAWoBbpwx8DH0Mfgx/DIAMgQyCgQEfgQEggQEhgQEigQEjgQEkgQElgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULyABaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACBAR0ICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8gAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgQEdCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvIAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIEBHQgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQowABULyABaAFoAWgAvAFoAogJWAFoAWgAVAFqAAID0gACBAR0ICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8gAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgQEdCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvIAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBHQgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULyABaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACBAR0ICAgIgBqAWwgIgAAI3xASAJAAkQCSDO4AHwCUAJUM7wAhAJMM8ACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoM+AAvAFoATABaAY8BYwBaAFoNAABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQEoCIAJCIBlgDcICIEBJwgSvCuZwtMAOAA5AA4NBA0HAD6iAZgBmYA9gD6iDQgNCYEBKYEBNIAl2QAfACMNDAAOACYNDQAhAEsNDgFwAZgATABrABUAJwAvAFoNFl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBJoA9gAmALIAAgAQIgQEq0wA4ADkADg0YDSEAPqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgNIg0jDSQNJQ0mDScNKA0pgQErgQEsgQEtgQEvgQEwgQExgQEygQEzgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNCABaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACBASkICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQgAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgQEpCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDUsAFQ0IAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgQEugACBASkICAgIgBqAQwgIgAAI0wA4ADkADg1ZDVoAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNCABaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACBASkICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQgAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgQEpCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0IAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAIEBKQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNCABaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACBASkICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQgAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgQEpCAgICIAagEgICIAACNkAHwAjDagADgAmDakAIQBLDaoBcAGZAEwAawAVACcALwBaDbJfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBASaAPoAJgCyAAIAECIEBNdMAOAA5AA4NtA28AD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cNvQ2+Db8NwA3BDcINw4EBNoEBOIEBOYEBOoEBO4EBPIEBPYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUNxwAVDQkAWgBaAFoALwBaAKICUwBaAFoAFQBagACBATeAAIEBNAgICAiAGoBVCAiAAAhTWUVT3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQkAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgQE0CAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0JAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIEBNAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQesABUNCQBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIDEgACBATQICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQkAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgQE0CAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0JAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBNAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNCQBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIAAgACBATQICAgIgBqAWwgIgAAI3xASAJAAkQCSDjAAHwCUAJUOMQAhAJMOMgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoOOgAvAFoATABaAY8BZABaAFoOQgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQFACIAJCIBlgDgICIEBPwgSYr3L3tMAOAA5AA4ORg5JAD6iAZgBmYA9gD6iDkoOS4EBQYEBTIAl2QAfACMOTgAOACYOTwAhAEsOUAFxAZgATABrABUAJwAvAFoOWF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBPoA9gAmALIAAgAQIgQFC0wA4ADkADg5aDmMAPqgBrgGvAbABsQGyAbMBtAG1gEGAQoBDgESARYBGgEeASKgOZA5lDmYOZw5oDmkOag5rgQFDgQFEgQFFgQFHgQFIgQFJgQFKgQFLgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOSgBaAFoAWgAvAFoAogGuAFoAWgAVAFqAAIAigACBAUEICAgIgBqAQQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkoAWgBaAFoALwBaAKIBrwBaAFoAFQBagACAAIAAgQFBCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDo0AFQ5KAFoAWgBaAC8AWgCiAbAAWgBaABUAWoAAgQFGgACBAUEICAgIgBqAQwgIgAAI0wA4ADkADg6bDpwAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOSgBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACBAUEICAgIgBqARAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkoAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAIoAAgQFBCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5KAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOSgBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAAgACBAUEICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkoAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAIoAAgQFBCAgICIAagEgICIAACNkAHwAjDuoADgAmDusAIQBLDuwBcQGZAEwAawAVACcALwBaDvRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAT6APoAJgCyAAIAECIEBTdMAOAA5AA4O9g7+AD6nAlMCVAJVAlYCVwJYAlmAVYBWgFeAWIBZgFqAW6cO/w8ADwEPAg8DDwQPBYEBToEBT4EBUIEBUYEBUoEBU4EBVIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUNxwAVDksAWgBaAFoALwBaAKICUwBaAFoAFQBagACBATeAAIEBTAgICAiAGoBVCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOSwBaAFoAWgAvAFoAogJUAFoAWgAVAFqAAIAigACBAUwICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDksAWgBaAFoALwBaAKICVQBaAFoAFQBagACAAIAAgQFMCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVB6wAFQ5LAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgMSAAIEBTAgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOSwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAAgACBAUwICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDksAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQFMCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5LAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgACAAIEBTAgICAiAGoBbCAiAAAjfEBIAkACRAJIPcQAfAJQAlQ9yACEAkw9zAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWg97AC8AWgBMAFoBjwFlAFoAWg+DAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBAVcIgAkIgGWAOQgIgQFWCBKAlVD/0wA4ADkADg+HD4oAPqIBmAGZgD2APqIPiw+MgQFYgQFjgCXZAB8AIw+PAA4AJg+QACEASw+RAXIBmABMAGsAFQAnAC8AWg+ZXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFVgD2ACYAsgACABAiBAVnTADgAOQAOD5sPpAA+qAGuAa8BsAGxAbIBswG0AbWAQYBCgEOARIBFgEaAR4BIqA+lD6YPpw+oD6kPqg+rD6yBAVqBAVuBAVyBAV6BAV+BAWCBAWGBAWKAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+LAFoAWgBaAC8AWgCiAa4AWgBaABUAWoAAgCKAAIEBWAgICAiAGoBBCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPiwBaAFoAWgAvAFoAogGvAFoAWgAVAFqAAIAAgACBAVgICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUPzgAVD4sAWgBaAFoALwBaAKIBsABaAFoAFQBagACBAV2AAIEBWAgICAiAGoBDCAiAAAjTADgAOQAOD9wP3QA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+LAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBWAgICAiAGoBECAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQIKABUPiwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIBPgACBAVgICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD4sAWgBaAFoALwBaAKIBswBaAFoAFQBagACAIoAAgQFYCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+LAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgACAAIEBWAgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPiwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACBAVgICAgIgBqASAgIgAAI2QAfACMQKwAOACYQLAAhAEsQLQFyAZkATABrABUAJwAvAFoQNV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBVYA+gAmALIAAgAQIgQFk0wA4ADkADhA3ED8APqcCUwJUAlUCVgJXAlgCWYBVgFaAV4BYgFmAWoBbpxBAEEEQQhBDEEQQRRBGgQFlgQFmgQFngQFogQFpgQFqgQFsgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjABaAFoAWgAvAFoAogJTAFoAWgAVAFqAAIAAgACBAWMICAgIgBqAVQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD4wAWgBaAFoALwBaAKICVABaAFoAFQBagACAIoAAgQFjCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+MAFoAWgBaAC8AWgCiAlUAWgBaABUAWoAAgACAAIEBYwgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQPkABUPjABaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIB6gACBAWMICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD4wAWgBaAFoALwBaAKICVwBaAFoAFQBagACAAIAAgQFjCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVEJUAFQ+MAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgQFrgACBAWMICAgIgBqAWggIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD4wAWgBaAFoALwBaAKICWQBaAFoAFQBagACAAIAAgQFjCAgICIAagFsICIAACFpkdXBsaWNhdGVz0gA5AA4QtACqoIAZ0gCsAK0QtxC4WlhEUE1FbnRpdHmnELkQuhC7ELwQvRC+ALFaWERQTUVudGl0eV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4QwBDBAD6goIAl0wA4ADkADhDEEMUAPqCggCXTADgAOQAOEMgQyQA+oKCAJdIArACtEMwQzV5YRE1vZGVsUGFja2FnZaYQzhDPENAQ0RDSALFeWERNb2RlbFBhY2thZ2VfEA9YRFVNTFBhY2thZ2VJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0gA5AA4Q1ACqoIAZ0wA4ADkADhDXENgAPqCggCVQ0gCsAK0Q3BDdWVhEUE1Nb2RlbKMQ3BDeALFXWERNb2RlbAAIABkAIgAsADEAOgA/AFEAVgBbAF0DUQNXA3ADggOJA5cDpAO8A9YD2APbA90D4APjA+YEHwQ+BFsEegSMBKwEswTRBN0E+QT/BSEFQgVVBVcFWgVdBV8FYQVjBWYFaQVrBW0FbwVxBXMFdQV2BXoFhwWPBZoFnQWfBaIFpAWmBbUF+AYcBkAGYwaKBqoG0Qb4BxgHPAdgB2wHbgdwB3IHdAd2B3gHewd9B38HggeEB4YHiQeLB4wHkQeZB6YHqQerB64HsAeyB8EH5ggKCDEIVQhXCFkIWwhdCF8IYQhiCGQIcQiECIYIiAiKCIwIjgiQCJIIlAiWCKkIqwitCK8IsQizCLUItwi5CLsIvQjTCOYJAgkfCTsJTwlhCXcJkAnPCdUJ3gnrCfcKAQoLChYKIQouCjYKOAo6CjwKPgo/CkAKQQpCCkQKRgpHCkgKSgpLClQKVQpXCmAKawp0CoMKigqSCpsKpAq3CsAK0wrqCvwLOws9Cz8LQQtDC0QLRQtGC0cLSQtLC0wLTQtPC1ALjwuRC5MLlQuXC5gLmQuaC5sLnQufC6ALoQujC6QLrQuuC7AL7wvxC/ML9Qv3C/gL+Qv6C/sL/Qv/DAAMAQwDDAQMQwxFDEcMSQxLDEwMTQxODE8MUQxTDFQMVQxXDFgMYQxiDGQMowylDKcMqQyrDKwMrQyuDK8MsQyzDLQMtQy3DLgMuQz4DPoM/Az+DQANAQ0CDQMNBA0GDQgNCQ0KDQwNDQ0aDRsNHA0eDScNPQ1EDVENkA2SDZQNlg2YDZkNmg2bDZwNng2gDaENog2kDaUNvg3ADcINxA3FDccN3g3nDfUOAg4QDiUOOQ5QDmIOoQ6jDqUOpw6pDqoOqw6sDq0Orw6xDrIOsw61DrYOyw7UDukO+A8NDxsPMA9ED1sPbQ96D5MPlQ+XD5kPmw+dD58PoQ+jD6UPpw+pD6sPxA/GD8gPyg/MD84P0A/SD9QP1w/aD90P4A/iD+gP9xAKEB0QKxA/EEkQVRBbEGgQbxB6EMUQ6BEIESgRKhEsES4RMBEyETMRNBE2ETcRORE6ETwRPhE/EUARQhFDEUgRVRFaEVwRXhFjEWURZxFpEX4RkxG4EdwSAxInEikSKxItEi8SMRIzEjQSNhJDElQSVhJYEloSXBJeEmASYhJkEnUSdxJ5EnsSfRJ/EoESgxKFEocSpRLDEtYS6hL/ExwTMBNGE4UThxOJE4sTjROOE48TkBORE5MTlROWE5cTmROaE9kT2xPdE98T4RPiE+MT5BPlE+cT6RPqE+sT7RPuFC0ULxQxFDMUNRQ2FDcUOBQ5FDsUPRQ+FD8UQRRCFE8UUBRRFFMUkhSUFJYUmBSaFJsUnBSdFJ4UoBSiFKMUpBSmFKcU5hToFOoU7BTuFO8U8BTxFPIU9BT2FPcU+BT6FPsU/BU7FT0VPxVBFUMVRBVFFUYVRxVJFUsVTBVNFU8VUBWPFZEVkxWVFZcVmBWZFZoVmxWdFZ8VoBWhFaMVpBXjFeUV5xXpFesV7BXtFe4V7xXxFfMV9BX1FfcV+BYdFkEWaBaMFo4WkBaSFpQWlhaYFpkWmxaoFrcWuRa7Fr0WvxbBFsMWxRbUFtYW2BbaFtwW3hbgFuIW5BcEFy8XSRdiF3wXnBe/F/4YABgCGAQYBhgHGAgYCRgKGAwYDhgPGBAYEhgTGFIYVBhWGFgYWhhbGFwYXRheGGAYYhhjGGQYZhhnGKYYqBiqGKwYrhivGLAYsRiyGLQYthi3GLgYuhi7GPoY/Bj+GQAZAhkDGQQZBRkGGQgZChkLGQwZDhkPGRIZURlTGVUZVxlZGVoZWxlcGV0ZXxlhGWIZYxllGWYZpRmnGakZqxmtGa4ZrxmwGbEZsxm1GbYZtxm5GboZ4RogGiIaJBomGigaKRoqGisaLBouGjAaMRoyGjQaNRo+GkwaWRpnGnQahxqeGrAa+xseGz4bXhtgG2IbZBtmG2gbaRtqG2wbbRtvG3Abcht0G3Ubdht4G3kbfhuLG5AbkhuUG5kbmxudG58bxBvoHA8cMxw1HDccORw7HD0cPxxAHEIcTxxgHGIcZBxmHGgcahxsHG4ccByBHIMchRyHHIkcixyNHI8ckRyTHNIc1BzWHNgc2hzbHNwc3RzeHOAc4hzjHOQc5hznHSYdKB0qHSwdLh0vHTAdMR0yHTQdNh03HTgdOh07HXodfB1+HYAdgh2DHYQdhR2GHYgdih2LHYwdjh2PHZwdnR2eHaAd3x3hHeMd5R3nHegd6R3qHesd7R3vHfAd8R3zHfQeMx41HjceOR47HjwePR4+Hj8eQR5DHkQeRR5HHkgehx6JHosejR6PHpAekR6SHpMelR6XHpgemR6bHpwe2x7dHt8e4R7jHuQe5R7mHuce6R7rHuwe7R7vHvAfLx8xHzMfNR83HzgfOR86HzsfPR8/H0AfQR9DH0QfaR+NH7Qf2B/aH9wf3h/gH+If5B/lH+cf9CADIAUgByAJIAsgDSAPIBEgICAiICQgJiAoICogLCAuIDAgbyBxIHMgdSB3IHggeSB6IHsgfSB/IIAggSCDIIQgwyDFIMcgySDLIMwgzSDOIM8g0SDTINQg1SDXINghFyEZIRshHSEfISAhISEiISMhJSEnISghKSErISwhayFtIW8hcSFzIXQhdSF2IXcheSF7IXwhfSF/IYAhgyHCIcQhxiHIIcohyyHMIc0hziHQIdIh0yHUIdYh1yIWIhgiGiIcIh4iHyIgIiEiIiIkIiYiJyIoIioiKyJSIpEikyKVIpcimSKaIpsinCKdIp8ioSKiIqMipSKmIvEjFCM0I1QjViNYI1ojXCNeI18jYCNiI2MjZSNmI2gjaiNrI2wjbiNvI3QjgSOGI4gjiiOPI5EjkyOVI7oj3iQFJCkkKyQtJC8kMSQzJDUkNiQ4JEUkViRYJFokXCReJGAkYiRkJGYkdyR5JHskfSR/JIEkgySFJIckiSTIJMokzCTOJNAk0STSJNMk1CTWJNgk2STaJNwk3SUcJR4lICUiJSQlJSUmJSclKCUqJSwlLSUuJTAlMSVwJXIldCV2JXgleSV6JXslfCV+JYAlgSWCJYQlhSWSJZMllCWWJdUl1yXZJdsl3SXeJd8l4CXhJeMl5SXmJecl6SXqJikmKyYtJi8mMSYyJjMmNCY1JjcmOSY6JjsmPSY+Jn0mfyaBJoMmhSaGJocmiCaJJosmjSaOJo8mkSaSJtEm0ybVJtcm2SbaJtsm3CbdJt8m4SbiJuMm5SbmJyUnJycpJysnLScuJy8nMCcxJzMnNSc2JzcnOSc6J18ngyeqJ84n0CfSJ9Qn1ifYJ9on2yfdJ+on+Sf7J/0n/ygBKAMoBSgHKBYoGCgaKBwoHiggKCIoJCgmKGUoZyhpKGsobShuKG8ocChxKHModSh2KHcoeSh6KLkouyi9KL8owSjCKMMoxCjFKMcoySjKKMsozSjOKQ0pDykRKRMpFSkWKRcpGCkZKRspHSkeKR8pISkiKWEpYyllKWcpaSlqKWspbCltKW8pcSlyKXMpdSl2KbUptym5KbspvSm+Kb8pwCnBKcMpxSnGKccpySnKKgkqCyoNKg8qESoSKhMqFCoVKhcqGSoaKhsqHSoeKkUqhCqGKogqiiqMKo0qjiqPKpAqkiqUKpUqliqYKpkq5CsHKycrRytJK0srTStPK1ErUitTK1UrVitYK1krWytdK14rXythK2Irayt4K30rfyuBK4YriCuKK4wrsSvVK/wsICwiLCQsJiwoLCosLCwtLC8sPCxNLE8sUSxTLFUsVyxZLFssXSxuLHAscix0LHYseCx6LHwsfiyALL8swSzDLMUsxyzILMksyizLLM0szyzQLNEs0yzULRMtFS0XLRktGy0cLR0tHi0fLSEtIy0kLSUtJy0oLWctaS1rLW0tby1wLXEtci1zLXUtdy14LXktey18LYktii2LLY0tzC3OLdAt0i3ULdUt1i3XLdgt2i3cLd0t3i3gLeEuIC4iLiQuJi4oLikuKi4rLiwuLi4wLjEuMi40LjUudC52Lnguei58Ln0ufi5/LoAugi6ELoUuhi6ILokuyC7KLswuzi7QLtEu0i7TLtQu1i7YLtku2i7cLt0vHC8eLyAvIi8kLyUvJi8nLygvKi8sLy0vLi8wLzEvVi96L6EvxS/HL8kvyy/NL88v0S/SL9Qv4S/wL/Iv9C/2L/gv+i/8L/4wDTAPMBEwEzAVMBcwGTAbMB0wXDBeMGAwYjBkMGUwZjBnMGgwajBsMG0wbjBwMHEwsDCyMLQwtjC4MLkwujC7MLwwvjDAMMEwwjDEMMUxBDEGMQgxCjEMMQ0xDjEPMRAxEjEUMRUxFjEYMRkxWDFaMVwxXjFgMWExYjFjMWQxZjFoMWkxajFsMW0xrDGuMbAxsjG0MbUxtjG3MbgxujG8Mb0xvjHAMcEyADICMgQyBjIIMgkyCjILMgwyDjIQMhEyEjIUMhUyPDJ7Mn0yfzKBMoMyhDKFMoYyhzKJMosyjDKNMo8ykDLbMv4zHjM+M0AzQjNEM0YzSDNJM0ozTDNNM08zUDNSM1QzVTNWM1gzWTNiM28zdDN2M3gzfTN/M4EzgzOoM8wz8zQXNBk0GzQdNB80ITQjNCQ0JjQzNEQ0RjRINEo0TDRONFA0UjRUNGU0ZzRpNGs0bTRvNHE0czR1NHc0tjS4NLo0vDS+NL80wDTBNMI0xDTGNMc0yDTKNMs1CjUMNQ41EDUSNRM1FDUVNRY1GDUaNRs1HDUeNR81XjVgNWI1ZDVmNWc1aDVpNWo1bDVuNW81cDVyNXM1gDWBNYI1hDXDNcU1xzXJNcs1zDXNNc41zzXRNdM11DXVNdc12DYXNhk2GzYdNh82IDYhNiI2IzYlNic2KDYpNis2LDZrNm02bzZxNnM2dDZ1NnY2dzZ5Nns2fDZ9Nn82gDa/NsE2wzbFNsc2yDbJNso2yzbNNs820DbRNtM21DcTNxU3FzcZNxs3HDcdNx43HzchNyM3JDclNyc3KDdNN3E3mDe8N743wDfCN8Q3xjfIN8k3yzfYN+c36TfrN+037zfxN/M39TgEOAY4CDgKOAw4DjgQOBI4FDhTOFU4VzhZOFs4XDhdOF44XzhhOGM4ZDhlOGc4aDhrOKo4rDiuOLA4sjizOLQ4tTi2OLg4uji7OLw4vji/OP45ADkCOQQ5BjkHOQg5CTkKOQw5DjkPORA5EjkTOVI5VDlWOVg5WjlbOVw5XTleOWA5YjljOWQ5ZjlnOWo5qTmrOa05rzmxObI5szm0ObU5tzm5Obo5uzm9Ob45/Tn/OgE6AzoFOgY6BzoIOgk6CzoNOg46DzoROhI6UTpTOlU6VzpZOlo6WzpcOl06XzphOmI6YzplOmY6sTrUOvQ7FDsWOxg7GjscOx47HzsgOyI7IzslOyY7KDsqOys7LDsuOy87NDtBO0Y7SDtKO087UTtTO1U7ejueO8U76TvrO+077zvxO/M79Tv2O/g8BTwWPBg8GjwcPB48IDwiPCQ8Jjw3PDk8Ozw9PD88QTxDPEU8RzxJPIg8ijyMPI48kDyRPJI8kzyUPJY8mDyZPJo8nDydPNw83jzgPOI85DzlPOY85zzoPOo87DztPO488DzxPTA9Mj00PTY9OD05PTo9Oz08PT49QD1BPUI9RD1FPVI9Uz1UPVY9lT2XPZk9mz2dPZ49nz2gPaE9oz2lPaY9pz2pPao96T3rPe097z3xPfI98z30PfU99z35Pfo9+z39Pf4+PT4/PkE+Qz5FPkY+Rz5IPkk+Sz5NPk4+Tz5RPlI+kT6TPpU+lz6ZPpo+mz6cPp0+nz6hPqI+oz6lPqY+5T7nPuk+6z7tPu4+7z7wPvE+8z71PvY+9z75Pvo/Hz9DP2o/jj+QP5I/lD+WP5g/mj+bP50/qj+5P7s/vT+/P8E/wz/FP8c/1j/YP9o/3D/eP+A/4j/kP+ZAJUAnQClAK0AtQC5AL0AwQDFAM0A1QDZAN0A5QDpAeUB7QH1Af0CBQIJAg0CEQIVAh0CJQIpAi0CNQI5AzUDPQNFA00DVQNZA10DYQNlA20DdQN5A30DhQOJBIUEjQSVBJ0EpQSpBK0EsQS1BL0ExQTJBM0E1QTZBOUF4QXpBfEF+QYBBgUGCQYNBhEGGQYhBiUGKQYxBjUHMQc5B0EHSQdRB1UHWQddB2EHaQdxB3UHeQeBB4UIgQiJCJEImQihCKUIqQitCLEIuQjBCMUIyQjRCNUKAQqNCw0LjQuVC50LpQutC7ULuQu9C8ULyQvRC9UL3QvlC+kL7Qv1C/kMDQxBDFUMXQxlDHkMgQyJDJENJQ21DlEO4Q7pDvEO+Q8BDwkPEQ8VDx0PUQ+VD50PpQ+tD7UPvQ/FD80P1RAZECEQKRAxEDkQQRBJEFEQWRBhEV0RZRFtEXURfRGBEYURiRGNEZURnRGhEaURrRGxEq0StRK9EsUSzRLREtUS2RLdEuUS7RLxEvUS/RMBE/0UBRQNFBUUHRQhFCUUKRQtFDUUPRRBFEUUTRRRFIUUiRSNFJUVkRWZFaEVqRWxFbUVuRW9FcEVyRXRFdUV2RXhFeUW4RbpFvEW+RcBFwUXCRcNFxEXGRchFyUXKRcxFzUYMRg5GEEYSRhRGFUYWRhdGGEYaRhxGHUYeRiBGIUZgRmJGZEZmRmhGaUZqRmtGbEZuRnBGcUZyRnRGdUa0RrZGuEa6RrxGvUa+Rr9GwEbCRsRGxUbGRshGyUbuRxJHOUddR19HYUdjR2VHZ0dpR2pHbEd5R4hHikeMR45HkEeSR5RHlkelR6dHqUerR61Hr0exR7NHtUf0R/ZH+Ef6R/xH/Uf+R/9IAEgCSARIBUgGSAhICUhISEpITEhOSFBIUUhSSFNIVEhWSFhIWUhaSFxIXUicSJ5IoEiiSKRIpUimSKdIqEiqSKxIrUiuSLBIsUjwSPJI9Ej2SPhI+Uj6SPtI/Ej+SQBJAUkCSQRJBUkISUdJSUlLSU1JT0lQSVFJUklTSVVJV0lYSVlJW0lcSZtJnUmfSaFJo0mkSaVJpkmnSalJq0msSa1Jr0mwSe9J8UnzSfVJ90n4SflJ+kn7Sf1J/0oASgFKA0oESk9KckqSSrJKtEq2SrhKukq8Sr1KvkrASsFKw0rESsZKyErJSspKzErNStJK30rkSuZK6ErtSu9K8kr0SxlLPUtkS4hLikuMS45LkEuSS5RLlUuXS6RLtUu3S7lLu0u9S79LwUvDS8VL1kvYS9pL3EvfS+JL5UvoS+tL7UwsTC5MMEwyTDRMNUw2TDdMOEw6TDxMPUw+TEBMQUyATIJMhEyGTIhMiUyKTItMjEyOTJBMkUySTJRMlUzUTNZM2UzbTN1M3kzfTOBM4UzjTOVM5kznTOlM6kz3TPhM+Uz7TTpNPE0+TUBNQk1DTURNRU1GTUhNSk1LTUxNTk1PTY5NkE2STZRNlk2XTZhNmU2aTZxNnk2fTaBNok2jTeJN5E3mTehN6k3rTexN7U3uTfBN8k3zTfRN9k33TjZOOE46TjxOPk4/TkBOQU5CTkRORk5HTkhOSk5LTopOjE6OTpBOkk6TTpROlU6WTphOmk6bTpxOnk6fTsRO6E8PTzNPNU83TzlPO089Tz9PQE9DT1BPX09hT2NPZU9nT2lPa09tT3xPf0+CT4VPiE+LT45PkU+TT9JP1E/WT9hP20/cT91P3k/fT+FP40/kT+VP50/oUCdQKVArUC1QMFAxUDJQM1A0UDZQOFA5UDpQPFA9UHxQflCAUIJQhVCGUIdQiFCJUItQjVCOUI9QkVCSUNFQ01DVUNdQ2lDbUNxQ3VDeUOBQ4lDjUORQ5lDnUSZRKFEqUSxRL1EwUTFRMlEzUTVRN1E4UTlRO1E8UXtRfVF/UYFRhFGFUYZRh1GIUYpRjFGNUY5RkFGRUdBR0lHUUdZR2VHaUdtR3FHdUd9R4VHiUeNR5VHmUjFSVFJ0UpRSllKYUppSnFKeUp9SoFKjUqRSplKnUqlSq1KsUq1SsFKxUrZSw1LIUspSzFLRUtRS11LZUv5TIlNJU21TcFNyU3RTdlN4U3pTe1N+U4tTnFOeU6BTolOkU6ZTqFOqU6xTvVPAU8NTxlPJU8xTz1PSU9VT11QWVBhUGlQcVB9UIFQhVCJUI1QlVCdUKFQpVCtULFRrVG1Ub1RxVHRUdVR2VHdUeFR6VHxUfVR+VIBUgVTAVMJUxVTHVMpUy1TMVM1UzlTQVNJU01TUVNZU11TkVOVU5lToVSdVKVUrVS1VMFUxVTJVM1U0VTZVOFU5VTpVPFU9VXxVflWAVYJVhVWGVYdViFWJVYtVjVWOVY9VkVWSVdFV01XVVddV2lXbVdxV3VXeVeBV4lXjVeRV5lXnViZWKFYqVixWL1YwVjFWMlYzVjVWN1Y4VjlWO1Y8VntWfVZ/VoFWhFaFVoZWh1aIVopWjFaNVo5WkFaRVrZW2lcBVyVXKFcqVyxXLlcwVzJXM1c2V0NXUldUV1ZXWFdaV1xXXldgV29Xcld1V3hXe1d+V4FXhFeGV8VXx1fJV8tXzlfPV9BX0VfSV9RX1lfXV9hX2lfbWBpYHFgeWCBYI1gkWCVYJlgnWClYK1gsWC1YL1gwWG9YcVhzWHVYeFh5WHpYe1h8WH5YgFiBWIJYhFiFWMRYxljIWMpYzVjOWM9Y0FjRWNNY1VjWWNdY2VjaWRlZG1kdWR9ZIlkjWSRZJVkmWShZKlkrWSxZLlkvWW5ZcFlyWXRZd1l4WXlZell7WX1Zf1mAWYFZg1mEWcNZxVnHWclZzFnNWc5Zz1nQWdJZ1FnVWdZZ2FnZWiRaR1pnWodaiVqLWo1aj1qRWpJak1qWWpdamVqaWpxanlqfWqBao1qkWqlatlq7Wr1av1rEWsdaylrMWvFbFVs8W2BbY1tlW2dbaVtrW21bbltxW35bj1uRW5NblVuXW5lbm1udW59bsFuzW7ZbuVu8W79bwlvFW8hbylwJXAtcDVwPXBJcE1wUXBVcFlwYXBpcG1wcXB5cH1xeXGBcYlxkXGdcaFxpXGpca1xtXG9ccFxxXHNcdFyzXLVcuFy6XL1cvly/XMBcwVzDXMVcxlzHXMlcylzXXNhc2VzbXRpdHF0eXSBdI10kXSVdJl0nXSldK10sXS1dL10wXW9dcV1zXXVdeF15XXpde118XX5dgF2BXYJdhF2FXcRdxl3IXcpdzV3OXc9d0F3RXdNd1V3WXddd2V3aXhleG14dXh9eIl4jXiReJV4mXiheKl4rXixeLl4vXm5ecF5yXnRed154Xnleel57Xn1ef16AXoFeg16EXqlezV70XxhfG18dXx9fIV8jXyVfJl8pXzZfRV9HX0lfS19NX09fUV9TX2JfZV9oX2tfbl9xX3Rfd195X7hful+9X79fwl/DX8RfxV/GX8hfyl/LX8xfzl/PX9NgEmAUYBZgGGAbYBxgHWAeYB9gIWAjYCRgJWAnYChgZ2BpYGtgbWBwYHFgcmBzYHRgdmB4YHlgemB8YH1gvGC+YMBgwmDFYMZgx2DIYMlgy2DNYM5gz2DRYNJhEWETYRVhF2EaYRthHGEdYR5hIGEiYSNhJGEmYSdhZmFoYWphbGFvYXBhcWFyYXNhdWF3YXhheWF7YXxhu2G9Yb9hwWHEYcVhxmHHYchhymHMYc1hzmHQYdFiHGI/Yl9if2KBYoNihWKHYoliimKLYo5ij2KRYpJilGKWYpdimGKbYpxioWKuYrNitWK3Yrxiv2LCYsRi6WMNYzRjWGNbY11jX2NhY2NjZWNmY2ljdmOHY4lji2ONY49jkWOTY5Vjl2OoY6tjrmOxY7Rjt2O6Y71jwGPCZAFkA2QFZAdkCmQLZAxkDWQOZBBkEmQTZBRkFmQXZFZkWGRaZFxkX2RgZGFkYmRjZGVkZ2RoZGlka2RsZKtkrWSwZLJktWS2ZLdkuGS5ZLtkvWS+ZL9kwWTCZM9k0GTRZNNlEmUUZRZlGGUbZRxlHWUeZR9lIWUjZSRlJWUnZShlZ2VpZWtlbWVwZXFlcmVzZXRldmV4ZXllemV8ZX1lvGW+ZcBlwmXFZcZlx2XIZclly2XNZc5lz2XRZdJmEWYTZhVmF2YaZhtmHGYdZh5mIGYiZiNmJGYmZidmZmZoZmpmbGZvZnBmcWZyZnNmdWZ3ZnhmeWZ7ZnxmoWbFZuxnEGcTZxVnF2cZZxtnHWceZyFnLmc9Zz9nQWdDZ0VnR2dJZ0tnWmddZ2BnY2dmZ2lnbGdvZ3FnsGeyZ7Vnt2e6Z7tnvGe9Z75nwGfCZ8NnxGfGZ8doBmgIaApoDGgPaBBoEWgSaBNoFWgXaBhoGWgbaBxoW2hdaF9oYWhkaGVoZmhnaGhoamhsaG1obmhwaHFosGiyaLRotmi5aLpou2i8aL1ov2jBaMJow2jFaMZpBWkHaQlpC2kOaQ9pEGkRaRJpFGkWaRdpGGkaaRtpWmlcaV5pYGljaWRpZWlmaWdpaWlraWxpbWlvaXBpr2mxabNptWm4ablpumm7abxpvmnAacFpwmnEacVqEGozalNqc2p1andqeWp7an1qfmp/aoJqg2qFaoZqiGqKaotqjGqPapBqlWqiaqdqqWqrarBqs2q2arhq3WsBayhrTGtPa1FrU2tVa1drWWtaa11ramt7a31rf2uBa4NrhWuHa4lri2uca59romula6hrq2uua7FrtGu2a/Vr92v5a/tr/mv/bABsAWwCbARsBmwHbAhsCmwLbEpsTGxObFBsU2xUbFVsVmxXbFlsW2xcbF1sX2xgbJ9soWykbKZsqWyqbKtsrGytbK9ssWyybLNstWy2bMNsxGzFbMdtBm0IbQptDG0PbRBtEW0SbRNtFW0XbRhtGW0bbRxtW21dbV9tYW1kbWVtZm1nbWhtam1sbW1tbm1wbXFtsG2ybbRttm25bbptu228bb1tv23BbcJtw23FbcZuBW4HbgluC24Obg9uEG4RbhJuFG4WbhduGG4abhtuWm5cbl5uYG5jbmRuZW5mbmduaW5rbmxubW5vbnBulW65buBvBG8HbwlvC28Nbw9vEW8SbxVvIm8xbzNvNW83bzlvO289bz9vTm9Rb1RvV29ab11vYG9jb2VvpG+mb6hvqm+tb65vr2+wb7Fvs2+1b7Zvt2+5b7pv+W/7b/1v/3ACcANwBHAFcAZwCHAKcAtwDHAOcA9wTnBQcFJwVHBXcFhwWXBacFtwXXBfcGBwYXBjcGRwo3ClcKdwqXCscK1wrnCvcLBwsnC0cLVwtnC4cLlw+HD6cPxw/nEBcQJxA3EEcQVxB3EJcQpxC3ENcQ5xTXFPcVJxVHFXcVhxWXFacVtxXXFfcWBxYXFjcWRxi3HKccxxznHQcdNx1HHVcdZx13HZcdtx3HHdcd9x4HHrcfRx9XH3cgByC3IaciVyM3JIclxyc3KFcpJyk3KUcpZyo3KkcqVyp3K0crVytnK4csFy0HLdcuxy/nMScylzO3NEc0VzR3NUc1VzVnNYc1lzYnNsc3MAAAAAAAACAgAAAAAAABDfAAAAAAAAAAAAAAAAAABzew==\n</attribute>\n        <attribute name=\"destinationmodelpath\" type=\"string\">AirshipMessageCenter/Resources/UAInbox.xcdatamodeld/UAInbox 4.xcdatamodel</attribute>\n        <attribute name=\"destinationmodeldata\" type=\"binary\">YnBsaXN0MDDUAAEAAgADAAQABQAGAAcAClgkdmVyc2lvblkkYXJjaGl2ZXJUJHRvcFgkb2JqZWN0\ncxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxEBkAALAAwAGQA1ADYANwA/AEAAWwBcAF0AYwBkAHAAhgCHAIgAiQCKAIsAjACNAI4AjwCoAKsAsgC4AMcA1gDZAOgA9wD6AFoBCgEZAR0BIQEwATYBNwE/AU4BTwFYAXYBdwF4AXkBegF7AXwBfQF+AX8BgAGBAYIBgwGYAZkBoQGiAaMBrwHDAcQBxQHGAccByAHJAcoBywHaAekB+AH8AgsCGgIbAioCOQJIAlQCZgJnAmgCaQJqAmsCbAJtAnwCiwKaAqkCqgK5AsgCyQLYAuAC9QL2Av4DCgMeAy0DPANLA08DXgNtA3wDiwOaA6YDuAPHA9YD5QP0A/UEBAQTBBQEIwQ4BDkEQQRNBGEEcAR/BI4EkgShBLAEvwTOBN0E6QT7BQoFGQUoBTcFOAVHBVYFZQV6BXsFgwWPBaMFsgXBBdAF1AXjBfIGAQYQBh8GKwY9BkwGWwZqBnkGiAaXBpgGpwa8Br0GxQbRBuUG9AcDBxIHFgclBzQHQwdSB2EHbQd/B44HjweeB60HvAe9B8wH2wfqB/8IAAgICBQIKAg3CEYIVQhZCGgIdwiGCJUIpAiwCMII0QjgCO8I/gkNCRwJHQksCUEJQglKCVYJagl5CYgJlwmbCaoJuQnICdcJ5gnyCgQKEwoiCjEKQApBClAKXwpuCoMKhAqMCpgKrAq7CsoK2QrdCuwK+wsKCxkLKAs0C0YLVQtkC3MLgguRC6ALrwvEC8ULzQvZC+0L/AwLDBoMHgwtDDwMSwxaDGkMdQyHDJYMpQy0DMMM0gzhDPANBQ0GDQ4NGg0uDT0NTA1bDV8Nbg19DYwNmw2qDbYNyA3XDeYN9Q4EDhMOIg4xDkYORw5PDlsObw5+Do0OnA6gDq8Ovg7NDtwO6w73DwkPGA8ZDygPNw9GD1UPZA9zD4gPiQ+RD50PsQ/AD88P3g/iD/EQABAPEB4QLRA5EEsQWhBpEHgQhxCWEKUQtBDJEMoQ0hDeEPIRAREQER8RIxEyEUERUBFfEW4RehGMEZsRqhG5EcgR1xHmEecR9hH3EfoSAxIHEgsSDxIXEhoSHhIfVSRudWxs1gANAA4ADwAQABEAEgATABQAFQAWABcAGF8QD194ZF9yb290UGFja2FnZVYkY2xhc3NdX3hkX21vZGVsTmFtZVxfeGRfY29tbWVudHNfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVfEBdfbW9kZWxWZXJzaW9uSWRlbnRpZmllcoACgQGPgACBAYyBAY2BAY7eABoAGwAcAB0AHgAfACAADgAhACIAIwAkACUAJgAnACgAKQAJACcAFQAtAC4ALwAwADEAJwAnABVfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASBAYqBAYiAAYAEgACBAYmBAYsQAIAFgAOABIAEgABQU1lFU9MAOAA5AA4AOgA8AD5XTlMua2V5c1pOUy5vYmplY3RzoQA7gAahAD2AB4AlXlVBSW5ib3hNZXNzYWdl3xAQAEEAQgBDAEQAHwBFAEYAIQBHAEgADgAjAEkASgAmAEsATABNACcAJwATAFEAUgAvACcATABVADsATABYAFkAWl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZV8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc2R1cGxpY2F0ZXNfECRYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zb3JkZXJlZF8QIVhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zc3RvcmFnZVtfaXNBYnN0cmFjdIAJgC2ABIAEgAKACoEBhYAEgAmBAYeABoAJgQGGgAgIElci9zNXb3JkZXJlZNMAOAA5AA4AXgBgAD6hAF+AC6EAYYAMgCVeWERfUFN0ZXJlb3R5cGXZAB8AIwBlAA4AJgBmACEASwBnAD0AXwBMAGsAFQAnAC8AWgBvXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgAeAC4AJgCyAAIAECIAN0wA4ADkADgBxAHsAPqkAcgBzAHQAdQB2AHcAeAB5AHqADoAPgBCAEYASgBOAFIAVgBapAHwAfQB+AH8AgACBAIIAgwCEgBeAG4AcgB6AH4AhgCOAJoAqgCVfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGVhEUE1VbmlxdWVuZXNzQ29uc3RyYWludHNfEBpYRF9QU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QGVhEX1BTS19mZXRjaFJlcXVlc3RzQXJyYXlfEBFYRF9QU0tfaXNBYnN0cmFjdF8QD1hEX1BTS191c2VySW5mb18QE1hEX1BTS19jbGFzc01hcHBpbmdfEBZYRF9QU0tfZW50aXR5Q2xhc3NOYW1l3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAmwAVAGEAWgBaAFoALwBaAKIAcgBaAFoAFQBaVV90eXBlWF9kZWZhdWx0XF9hc3NvY2lhdGlvbltfaXNSZWFkT25seVlfaXNTdGF0aWNZX2lzVW5pcXVlWl9pc0Rlcml2ZWRaX2lzT3JkZXJlZFxfaXNDb21wb3NpdGVXX2lzTGVhZoAAgBiAAIAMCAgICIAagA4ICIAACNIAOQAOAKkAqqCAGdIArACtAK4Ar1okY2xhc3NuYW1lWCRjbGFzc2VzXk5TTXV0YWJsZUFycmF5owCuALAAsVdOU0FycmF5WE5TT2JqZWN00gCsAK0AswC0XxAQWERVTUxQcm9wZXJ0eUltcKQAtQC2ALcAsV8QEFhEVU1MUHJvcGVydHlJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHMAWgBaABUAWoAAgACAAIAMCAgICIAagA8ICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAMkAFQBhAFoAWgBaAC8AWgCiAHQAWgBaABUAWoAAgB2AAIAMCAgICIAagBAICIAACNIAOQAOANcAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQBhAFoAWgBaAC8AWgCiAHUAWgBaABUAWoAAgACAAIAMCAgICIAagBEICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAOoAFQBhAFoAWgBaAC8AWgCiAHYAWgBaABUAWoAAgCCAAIAMCAgICIAagBIICIAACNIAOQAOAPgAqqCAGd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQBhAFoAWgBaAC8AWgCiAHcAWgBaABUAWoAAgCKAAIAMCAgICIAagBMICIAACAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQEMABUAYQBaAFoAWgAvAFoAogB4AFoAWgAVAFqAAIAkgACADAgICAiAGoAUCAiAAAjTADgAOQAOARoBGwA+oKCAJdIArACtAR4BH18QE05TTXV0YWJsZURpY3Rpb25hcnmjAR4BIACxXE5TRGljdGlvbmFyed8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVASMAFQBhAFoAWgBaAC8AWgCiAHkAWgBaABUAWoAAgCeAAIAMCAgICIAagBUICIAACNYAIwAOACYASwAfACEBMQEyABUAWgAVAC+AKIApgAAIgABfEBRYREdlbmVyaWNSZWNvcmRDbGFzc9IArACtATgBOV1YRFVNTENsYXNzSW1wpgE6ATsBPAE9AT4AsV1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAUEAFQBhAFoAWgBaAC8AWgCiAHoAWgBaABUAWoAAgCuAAIAMCAgICIAagBYICIAACF8QElVBSW5ib3hNZXNzYWdlRGF0YdIArACtAVABUV8QElhEVU1MU3RlcmVvdHlwZUltcKcBUgFTAVQBVQFWAVcAsV8QElhEVU1MU3RlcmVvdHlwZUltcF1YRFVNTENsYXNzSW1wXxASWERVTUxDbGFzc2lmaWVySW1wXxARWERVTUxOYW1lc3BhY2VJbXBfEBRYRFVNTE5hbWVkRWxlbWVudEltcF8QD1hEVU1MRWxlbWVudEltcNMAOAA5AA4BWQFnAD6tAVoBWwFcAV0BXgFfAWABYQFiAWMBZAFlAWaALoAvgDCAMYAygDOANIA1gDaAN4A4gDmAOq0BaAFpAWoBawFsAW0BbgFvAXABcQFyAXMBdIA7gGeAgICYgLCAyYDhgPmBARCBASeBAT6BAVaBAW2AJVVleHRyYV5tZXNzYWdlQm9keVVSTF8QEW1lc3NhZ2VFeHBpcmF0aW9uXxAQbWVzc2FnZVJlcG9ydGluZ11kZWxldGVkQ2xpZW50XxAQcmF3TWVzc2FnZU9iamVjdFltZXNzYWdlSURbY29udGVudFR5cGVbbWVzc2FnZVNlbnRVdGl0bGVcdW5yZWFkQ2xpZW50VnVucmVhZFptZXNzYWdlVVJM3xASAJAAkQCSAYQAHwCUAJUBhQAhAJMBhgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoBjgAvAFoATABaAZIBWgBaAFoBlgBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgD0IgAkIgGaALggIgDwIEwAAAAEW7FMV0wA4ADkADgGaAZ0APqIBmwGcgD6AP6IBngGfgECAVIAlXxASWERfUFByb3BTdGVyZW90eXBlXxASWERfUEF0dF9TdGVyZW90eXBl2QAfACMBpAAOACYBpQAhAEsBpgFoAZsATABrABUAJwAvAFoBrl8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA7gD6ACYAsgACABAiAQdMAOAA5AA4BsAG5AD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoAboBuwG8Ab0BvgG/AcABwYBKgEuATIBOgE+AUYBSgFOAJV8QG1hEX1BQU0tfaXNTdG9yZWRJblRydXRoRmlsZV8QG1hEX1BQU0tfdmVyc2lvbkhhc2hNb2RpZmllcl8QEFhEX1BQU0tfdXNlckluZm9fEBFYRF9QUFNLX2lzSW5kZXhlZF8QElhEX1BQU0tfaXNPcHRpb25hbF8QGlhEX1BQU0tfaXNTcG90bGlnaHRJbmRleGVkXxARWERfUFBTS19lbGVtZW50SURfEBNYRF9QUFNLX2lzVHJhbnNpZW503xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZ4AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgEAICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZ4AWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgEAICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUB6wAVAZ4AWgBaAFoALwBaAKIBswBaAFoAFQBagACATYAAgEAICAgIgBqARAgIgAAI0wA4ADkADgH5AfoAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBngBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACAQAgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUBngBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACAQAgICAiAGoBGCAiAAAgJ3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZ4AWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgEAICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAZ4AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgEAICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAZ4AWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgEAICAgIgBqASQgIgAAI2QAfACMCSQAOACYCSgAhAEsCSwFoAZwATABrABUAJwAvAFoCU18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYA7gD+ACYAsgACABAiAVdMAOAA5AA4CVQJdAD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcCXgJfAmACYQJiAmMCZIBdgF6AX4BggGKAY4BlgCVfEB1YRF9QQXR0S19kZWZhdWx0VmFsdWVBc1N0cmluZ18QKFhEX1BBdHRLX2FsbG93c0V4dGVybmFsQmluYXJ5RGF0YVN0b3JhZ2VfEBdYRF9QQXR0S19taW5WYWx1ZVN0cmluZ18QFlhEX1BBdHRLX2F0dHJpYnV0ZVR5cGVfEBdYRF9QQXR0S19tYXhWYWx1ZVN0cmluZ18QHVhEX1BBdHRLX3ZhbHVlVHJhbnNmb3JtZXJOYW1lXxAgWERfUEF0dEtfcmVndWxhckV4cHJlc3Npb25TdHJpbmffEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACAVAgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUBnwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACAVAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACAVAgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKcABUBnwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIBhgACAVAgICAiAGoBZCAiAAAgRA+jfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACAVAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQK7ABUBnwBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIBkgACAVAgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUBnwBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACAVAgICAiAGoBcCAiAAAjSAKwArQLZAtpdWERQTUF0dHJpYnV0ZaYC2wLcAt0C3gLfALFdWERQTUF0dHJpYnV0ZVxYRFBNUHJvcGVydHlfEBBYRFVNTFByb3BlcnR5SW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDfEBIAkACRAJIC4QAfAJQAlQLiACEAkwLjAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgLrAC8AWgBMAFoBkgFbAFoAWgLzAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAaQiACQiAZoAvCAiAaAgTAAAAASSZKw/TADgAOQAOAvcC+gA+ogGbAZyAPoA/ogL7AvyAaoB1gCXZAB8AIwL/AA4AJgMAACEASwMBAWkBmwBMAGsAFQAnAC8AWgMJXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgGeAPoAJgCyAAIAECIBr0wA4ADkADgMLAxQAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagDFQMWAxcDGAMZAxoDGwMcgGyAbYBugHCAcYBygHOAdIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVAvsAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgGoICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVAvsAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgGoICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUDPgAVAvsAWgBaAFoALwBaAKIBswBaAFoAFQBagACAb4AAgGoICAgIgBqARAgIgAAI0wA4ADkADgNMA00APqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC+wBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACAaggICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUC+wBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACAaggICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC+wBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACAaggICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUC+wBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACAaggICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUC+wBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACAaggICAiAGoBJCAiAAAjZAB8AIwObAA4AJgOcACEASwOdAWkBnABMAGsAFQAnAC8AWgOlXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgGeAP4AJgCyAAIAECIB20wA4ADkADgOnA68APqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4BcpwOwA7EDsgOzA7QDtQO2gHeAeIB5gHqAfIB9gH+AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL8AFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIB1CAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQL8AFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIB1CAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL8AFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIB1CAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA+cAFQL8AFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgHuAAIB1CAgICIAagFkICIAACBEHCN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL8AFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIB1CAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBAYAFQL8AFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgH6AAIB1CAgICIAagFsICIAACF8QJE5TU2VjdXJlVW5hcmNoaXZlRnJvbURhdGFUcmFuc2Zvcm1lct8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQL8AFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIB1CAgICIAagFwICIAACN8QEgCQAJEAkgQkAB8AlACVBCUAIQCTBCYAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaBC4ALwBaAEwAWgGSAVwAWgBaBDYAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICICCCIAJCIBmgDAICICBCBKiWCPU0wA4ADkADgQ6BD0APqIBmwGcgD6AP6IEPgQ/gIOAjoAl2QAfACMEQgAOACYEQwAhAEsERAFqAZsATABrABUAJwAvAFoETF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCAgD6ACYAsgACABAiAhNMAOAA5AA4ETgRXAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoBFgEWQRaBFsEXARdBF4EX4CFgIaAh4CJgIqAi4CMgI2AJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQQ+AFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAICDCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQQ+AFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAICDCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBIEAFQQ+AFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgIiAAICDCAgICIAagEQICIAACNMAOAA5AA4EjwSQAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBD4AWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgIMICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCDQAVBD4AWgBaAFoALwBaAKIBtQBaAFoAFQBagACAUIAAgIMICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBD4AWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgIMICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBD4AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgIMICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBD4AWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgIMICAgIgBqASQgIgAAI2QAfACME3gAOACYE3wAhAEsE4AFqAZwATABrABUAJwAvAFoE6F8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYCAgD+ACYAsgACABAiAj9MAOAA5AA4E6gTyAD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcE8wT0BPUE9gT3BPgE+YCQgJGAkoCTgJWAloCXgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACAjggICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUEPwBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACAjggICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACAjggICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQUqABUEPwBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAICUgACAjggICAiAGoBZCAiAAAgRA4TfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPwBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACAjggICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPwBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACAjggICAiAGoBbCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUEPwBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACAjggICAiAGoBcCAiAAAjfEBIAkACRAJIFZgAfAJQAlQVnACEAkwVoAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgVwAC8AWgBMAFoBkgFdAFoAWgV4AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiAmgiACQiAZoAxCAiAmQgSZG5dftMAOAA5AA4FfAV/AD6iAZsBnIA+gD+iBYAFgYCbgKaAJdkAHwAjBYQADgAmBYUAIQBLBYYBawGbAEwAawAVACcALwBaBY5fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAmIA+gAmALIAAgAQIgJzTADgAOQAOBZAFmQA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAWaBZsFnAWdBZ4FnwWgBaGAnYCegJ+AoYCigKOApIClgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUFgABaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACAmwgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUFgABaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACAmwgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQXDABUFgABaAFoAWgAvAFoAogGzAFoAWgAVAFqAAICggACAmwgICAiAGoBECAiAAAjTADgAOQAOBdEF0gA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQWAAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAICbCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQWAAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAICbCAgICIAagEYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQWAAFoAWgBaAC8AWgCiAbYAWgBaABUAWoAAgCKAAICbCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQWAAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAICbCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQWAAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAICbCAgICIAagEkICIAACNkAHwAjBiAADgAmBiEAIQBLBiIBawGcAEwAawAVACcALwBaBipfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAmIA/gAmALIAAgAQIgKfTADgAOQAOBiwGNAA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynBjUGNgY3BjgGOQY6BjuAqICpgKqAq4CsgK2Ar4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBYEAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgKYICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBYEAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgKYICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBYEAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgKYICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCnAAVBYEAWgBaAFoALwBaAKICWQBaAFoAFQBagACAYYAAgKYICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBYEAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgKYICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUGigAVBYEAWgBaAFoALwBaAKICWwBaAFoAFQBagACAroAAgKYICAgIgBqAWwgIgAAIXxAkTlNTZWN1cmVVbmFyY2hpdmVGcm9tRGF0YVRyYW5zZm9ybWVy3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBYEAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgKYICAgIgBqAXAgIgAAI3xASAJAAkQCSBqgAHwCUAJUGqQAhAJMGqgCWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoGsgAvAFoATABaAZIBXgBaAFoGugBaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgLIIgAkIgGaAMggIgLEIEqfUj+vTADgAOQAOBr4GwQA+ogGbAZyAPoA/ogbCBsOAs4C+gCXZAB8AIwbGAA4AJgbHACEASwbIAWwBmwBMAGsAFQAnAC8AWgbQXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgLCAPoAJgCyAAIAECIC00wA4ADkADgbSBtsAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagG3AbdBt4G3wbgBuEG4gbjgLWAtoC3gLmAuoC7gLyAvYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVBsIAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgLMICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVBsIAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgLMICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHBQAVBsIAWgBaAFoALwBaAKIBswBaAFoAFQBagACAuIAAgLMICAgIgBqARAgIgAAI0wA4ADkADgcTBxQAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGwgBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACAswgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGwgBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACAswgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGwgBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACAswgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUGwgBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACAswgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUGwgBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACAswgICAiAGoBJCAiAAAjZAB8AIwdiAA4AJgdjACEASwdkAWwBnABMAGsAFQAnAC8AWgdsXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgLCAP4AJgCyAAIAECIC/0wA4ADkADgduB3YAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpwd3B3gHeQd6B3sHfAd9gMCAwoDDgMSAxoDHgMiAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVB4EAFQbDAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgMGAAIC+CAgICIAagFYICIAACFJOT98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQbDAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIC+CAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbDAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIC+CAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVB68AFQbDAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgMWAAIC+CAgICIAagFkICIAACBEDIN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbDAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIC+CAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbDAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIC+CAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQbDAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIC+CAgICIAagFwICIAACN8QEgCQAJEAkgfrAB8AlACVB+wAIQCTB+0AlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaB/UALwBaAEwAWgGSAV8AWgBaB/0AWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIDLCIAJCIBmgDMICIDKCBKAVxlU0wA4ADkADggBCAQAPqIBmwGcgD6AP6IIBQgGgMyA14Al2QAfACMICQAOACYICgAhAEsICwFtAZsATABrABUAJwAvAFoIE18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYDJgD6ACYAsgACABAiAzdMAOAA5AA4IFQgeAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoCB8IIAghCCIIIwgkCCUIJoDOgM+A0IDSgNOA1IDVgNaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQgFAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIDMCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQgFAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIDMCAgICIAagEMICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCEgAFQgFAFoAWgBaAC8AWgCiAbMAWgBaABUAWoAAgNGAAIDMCAgICIAagEQICIAACNMAOAA5AA4IVghXAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAUAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgMwICAgIgBqARQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUCDQAVCAUAWgBaAFoALwBaAKIBtQBaAFoAFQBagACAUIAAgMwICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAUAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgMwICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCAUAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgMwICAgIgBqASAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCAUAWgBaAFoALwBaAKIBuABaAFoAFQBagACAIoAAgMwICAgIgBqASQgIgAAI2QAfACMIpQAOACYIpgAhAEsIpwFtAZwATABrABUAJwAvAFoIr18QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYDJgD+ACYAsgACABAiA2NMAOAA5AA4IsQi5AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcIugi7CLwIvQi+CL8IwIDZgNqA24DcgN2A3oDggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACA1wgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUIBgBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACA1wgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACA1wgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQKcABUIBgBaAFoAWgAvAFoAogJZAFoAWgAVAFqAAIBhgACA1wgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACA1wgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQkPABUIBgBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIDfgACA1wgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUIBgBaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACA1wgICAiAGoBcCAiAAAjfEBIAkACRAJIJLQAfAJQAlQkuACEAkwkvAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgk3AC8AWgBMAFoBkgFgAFoAWgk/AFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiA4wiACQiAZoA0CAiA4ggTAAAAAST3A8jTADgAOQAOCUMJRgA+ogGbAZyAPoA/oglHCUiA5IDvgCXZAB8AIwlLAA4AJglMACEASwlNAW4BmwBMAGsAFQAnAC8AWglVXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgOGAPoAJgCyAAIAECIDl0wA4ADkADglXCWAAPqgBsQGyAbMBtAG1AbYBtwG4gEKAQ4BEgEWARoBHgEiASagJYQliCWMJZAllCWYJZwlogOaA54DogOqA64DsgO2A7oAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVCUcAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgOQICAgIgBqAQggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCUcAWgBaAFoALwBaAKIBsgBaAFoAFQBagACAAIAAgOQICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUJigAVCUcAWgBaAFoALwBaAKIBswBaAFoAFQBagACA6YAAgOQICAgIgBqARAgIgAAI0wA4ADkADgmYCZkAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACA5AgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUJRwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACA5AgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACA5AgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUJRwBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACA5AgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUJRwBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACA5AgICAiAGoBJCAiAAAjZAB8AIwnnAA4AJgnoACEASwnpAW4BnABMAGsAFQAnAC8AWgnxXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgOGAP4AJgCyAAIAECIDw0wA4ADkADgnzCfsAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpwn8Cf0J/gn/CgAKAQoCgPGA8oDzgPSA9oD3gPiAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIDvCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQlIAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIDvCAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIDvCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVCjMAFQlIAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgPWAAIDvCAgICIAagFkICIAACBECvN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIDvCAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIDvCAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQlIAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIDvCAgICIAagFwICIAACN8QEgCQAJEAkgpvAB8AlACVCnAAIQCTCnEAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaCnkALwBaAEwAWgGSAWEAWgBaCoEAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICID7CIAJCIBmgDUICID6CBKCWBop0wA4ADkADgqFCogAPqIBmwGcgD6AP6IKiQqKgPyBAQeAJdkAHwAjCo0ADgAmCo4AIQBLCo8BbwGbAEwAawAVACcALwBaCpdfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WA+YA+gAmALIAAgAQIgP3TADgAOQAOCpkKogA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqAqjCqQKpQqmCqcKqAqpCqqA/oD/gQEAgQECgQEDgQEEgQEFgQEGgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogGxAFoAWgAVAFqAAIAigACA/AgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKiQBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACA/AgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQrMABUKiQBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBAYAAgPwICAgIgBqARAgIgAAI0wA4ADkADgraCtsAPqCggCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogG0AFoAWgAVAFqAAIAigACA/AgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUKiQBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACA/AgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACA/AgICAiAGoBHCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKiQBaAFoAWgAvAFoAogG3AFoAWgAVAFqAAIAAgACA/AgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUKiQBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACA/AgICAiAGoBJCAiAAAjZAB8AIwspAA4AJgsqACEASwsrAW8BnABMAGsAFQAnAC8AWgszXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgPmAP4AJgCyAAIAECIEBCNMAOAA5AA4LNQs9AD6nAlYCVwJYAlkCWgJbAlyAVoBXgFiAWYBagFuAXKcLPgs/C0ALQQtCC0MLRIEBCYEBCoEBC4EBDIEBDYEBDoEBD4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCooAWgBaAFoALwBaAKICVgBaAFoAFQBagACAAIAAgQEHCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQqKAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBBwgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKigBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAQcICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUKMwAVCooAWgBaAFoALwBaAKICWQBaAFoAFQBagACA9YAAgQEHCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQqKAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBBwgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUKigBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAQcICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVCooAWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQEHCAgICIAagFwICIAACN8QEgCQAJEAkguwAB8AlACVC7EAIQCTC7IAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaC7oALwBaAEwAWgGSAWIAWgBaC8IAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBEgiACQiAZoA2CAiBAREIEuj/P8fTADgAOQAOC8YLyQA+ogGbAZyAPoA/ogvKC8uBAROBAR6AJdkAHwAjC84ADgAmC88AIQBLC9ABcAGbAEwAawAVACcALwBaC9hfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBARCAPoAJgCyAAIAECIEBFNMAOAA5AA4L2gvjAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoC+QL5QvmC+cL6AvpC+oL64EBFYEBFoEBF4EBGYEBGoEBG4EBHIEBHYAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8oAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQETCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvKAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBEwgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQwNABULygBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBGIAAgQETCAgICIAagEQICIAACNMAOAA5AA4MGwwcAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVC8oAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQETCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFQvKAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIEBEwgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULygBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBARMICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8oAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQETCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQvKAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBEwgICAiAGoBJCAiAAAjZAB8AIwxqAA4AJgxrACEASwxsAXABnABMAGsAFQAnAC8AWgx0XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQEQgD+ACYAsgACABAiBAR/TADgAOQAODHYMfgA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynDH8MgAyBDIIMgwyEDIWBASCBASGBASKBASOBASSBASWBASaAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvLAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIEBHggICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABULywBaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBAR4ICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8sAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQEeCAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVBSoAFQvLAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgJSAAIEBHggICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABULywBaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBAR4ICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVC8sAWgBaAFoALwBaAKICWwBaAFoAFQBagACAAIAAgQEeCAgICIAagFsICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQvLAFoAWgBaAC8AWgCiAlwAWgBaABUAWoAAgACAAIEBHggICAiAGoBcCAiAAAjfEBIAkACRAJIM8QAfAJQAlQzyACEAkwzzAJYADgAjAJcAmAAmAJkAFQAVABUAJwA9AFoAWgz7AC8AWgBMAFoBkgFjAFoAWg0DAFpfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWSAAIAAgACABIAHCAiBASkIgAkIgGaANwgIgQEoCBIyM6nA0wA4ADkADg0HDQoAPqIBmwGcgD6AP6INCw0MgQEqgQE1gCXZAB8AIw0PAA4AJg0QACEASw0RAXEBmwBMAGsAFQAnAC8AWg0ZXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQEngD6ACYAsgACABAiBASvTADgAOQAODRsNJAA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqA0lDSYNJw0oDSkNKg0rDSyBASyBAS2BAS6BATCBATGBATKBATOBATSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0LAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBKggICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNCwBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACBASoICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUNTgAVDQsAWgBaAFoALwBaAKIBswBaAFoAFQBagACBAS+AAIEBKggICAiAGoBECAiAAAjTADgAOQAODVwNXQA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ0LAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIEBKggICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQINABUNCwBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIBQgACBASoICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQsAWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgQEqCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0LAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIEBKggICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUNCwBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACBASoICAgIgBqASQgIgAAI2QAfACMNqwAOACYNrAAhAEsNrQFxAZwATABrABUAJwAvAFoNtV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBJ4A/gAmALIAAgAQIgQE20wA4ADkADg23Db8APqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4Bcpw3ADcENwg3DDcQNxQ3GgQE3gQE4gQE5gQE6gQE7gQE8gQE9gCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNDABaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIAAgACBATUICAgIgBqAVggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDQwAWgBaAFoALwBaAKICVwBaAFoAFQBagACAIoAAgQE1CAgICIAagFcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0MAFoAWgBaAC8AWgCiAlgAWgBaABUAWoAAgACAAIEBNQgICAiAGoBYCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQozABUNDABaAFoAWgAvAFoAogJZAFoAWgAVAFqAAID1gACBATUICAgIgBqAWQgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDQwAWgBaAFoALwBaAKICWgBaAFoAFQBagACAAIAAgQE1CAgICIAagFoICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ0MAFoAWgBaAC8AWgCiAlsAWgBaABUAWoAAgACAAIEBNQgICAiAGoBbCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUNDABaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBATUICAgIgBqAXAgIgAAI3xASAJAAkQCSDjIAHwCUAJUOMwAhAJMONACWAA4AIwCXAJgAJgCZABUAFQAVACcAPQBaAFoOPAAvAFoATABaAZIBZABaAFoORABaXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkgACAAIAAgASABwgIgQFACIAJCIBmgDgICIEBPwgTAAAAAR1pSq7TADgAOQAODkgOSwA+ogGbAZyAPoA/og5MDk2BAUGBAUyAJdkAHwAjDlAADgAmDlEAIQBLDlIBcgGbAEwAawAVACcALwBaDlpfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAT6APoAJgCyAAIAECIEBQtMAOAA5AA4OXA5lAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoDmYOZw5oDmkOag5rDmwObYEBQ4EBRIEBRYEBR4EBSIEBSYEBSoEBS4Al3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkwAWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFBCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5MAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBQQgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ6PABUOTABaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBRoAAgQFBCAgICIAagEQICIAACNMAOAA5AA4OnQ6eAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVDkwAWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQFBCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5MAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUOTABaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBAUEICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDkwAWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQFBCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5MAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBQQgICAiAGoBJCAiAAAjZAB8AIw7sAA4AJg7tACEASw7uAXIBnABMAGsAFQAnAC8AWg72XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQE+gD+ACYAsgACABAiBAU3TADgAOQAODvgPAAA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynDwEPAg8DDwQPBQ8GDweBAU6BAVCBAVGBAVKBAVOBAVSBAVWAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVDwsAFQ5NAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgQFPgACBAUwICAgIgBqAVggIgAAIU1lFU98QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ5NAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBTAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOTQBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAUwICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHrwAVDk0AWgBaAFoALwBaAKICWQBaAFoAFQBagACAxYAAgQFMCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ5NAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBTAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUOTQBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAUwICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVDk0AWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQFMCAgICIAagFwICIAACN8QEgCQAJEAkg90AB8AlACVD3UAIQCTD3YAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaD34ALwBaAEwAWgGSAWUAWgBaD4YAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBWAiACQiAZoA5CAiBAVcIEwAAAAEpSLpj0wA4ADkADg+KD40APqIBmwGcgD6AP6IPjg+PgQFZgQFkgCXZAB8AIw+SAA4AJg+TACEASw+UAXMBmwBMAGsAFQAnAC8AWg+cXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFWgD6ACYAsgACABAiBAVrTADgAOQAOD54PpwA+qAGxAbIBswG0AbUBtgG3AbiAQoBDgESARYBGgEeASIBJqA+oD6kPqg+rD6wPrQ+uD6+BAVuBAVyBAV2BAV+BAWCBAWGBAWKBAWOAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+OAFoAWgBaAC8AWgCiAbEAWgBaABUAWoAAgCKAAIEBWQgICAiAGoBCCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjgBaAFoAWgAvAFoAogGyAFoAWgAVAFqAAIAAgACBAVkICAgIgBqAQwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUP0QAVD44AWgBaAFoALwBaAKIBswBaAFoAFQBagACBAV6AAIEBWQgICAiAGoBECAiAAAjTADgAOQAOD98P4AA+oKCAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+OAFoAWgBaAC8AWgCiAbQAWgBaABUAWoAAgCKAAIEBWQgICAiAGoBFCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPjgBaAFoAWgAvAFoAogG1AFoAWgAVAFqAAIAigACBAVkICAgIgBqARggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVD44AWgBaAFoALwBaAKIBtgBaAFoAFQBagACAIoAAgQFZCAgICIAagEcICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+OAFoAWgBaAC8AWgCiAbcAWgBaABUAWoAAgACAAIEBWQgICAiAGoBICAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUPjgBaAFoAWgAvAFoAogG4AFoAWgAVAFqAAIAigACBAVkICAgIgBqASQgIgAAI2QAfACMQLgAOACYQLwAhAEsQMAFzAZwATABrABUAJwAvAFoQOF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYEBVoA/gAmALIAAgAQIgQFl0wA4ADkADhA6EEIAPqcCVgJXAlgCWQJaAlsCXIBWgFeAWIBZgFqAW4BcpxBDEEQQRRBGEEcQSBBJgQFmgQFngQFogQFpgQFqgQFrgQFsgCXfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQ8LABUPjwBaAFoAWgAvAFoAogJWAFoAWgAVAFqAAIEBT4AAgQFkCAgICIAagFYICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFQ+PAFoAWgBaAC8AWgCiAlcAWgBaABUAWoAAgCKAAIEBZAgICAiAGoBXCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjwBaAFoAWgAvAFoAogJYAFoAWgAVAFqAAIAAgACBAWQICAgIgBqAWAgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUHrwAVD48AWgBaAFoALwBaAKICWQBaAFoAFQBagACAxYAAgQFkCAgICIAagFkICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFQ+PAFoAWgBaAC8AWgCiAloAWgBaABUAWoAAgACAAIEBZAgICAiAGoBaCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUPjwBaAFoAWgAvAFoAogJbAFoAWgAVAFqAAIAAgACBAWQICAgIgBqAWwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVD48AWgBaAFoALwBaAKICXABaAFoAFQBagACAAIAAgQFkCAgICIAagFwICIAACN8QEgCQAJEAkhC1AB8AlACVELYAIQCTELcAlgAOACMAlwCYACYAmQAVABUAFQAnAD0AWgBaEL8ALwBaAEwAWgGSAWYAWgBaEMcAWl8QIFhEQnVja2V0Rm9yU3RlcmVvdHlwZXN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc3N0b3JhZ2VfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzb3JkZXJlZIAAgACAAIAEgAcICIEBbwiACQiAZoA6CAiBAW4IEn+VhTvTADgAOQAOEMsQzgA+ogGbAZyAPoA/ohDPENCBAXCBAXuAJdkAHwAjENMADgAmENQAIQBLENUBdAGbAEwAawAVACcALwBaEN1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WBAW2APoAJgCyAAIAECIEBcdMAOAA5AA4Q3xDoAD6oAbEBsgGzAbQBtQG2AbcBuIBCgEOARIBFgEaAR4BIgEmoEOkQ6hDrEOwQ7RDuEO8Q8IEBcoEBc4EBdIEBdoEBd4EBeIEBeYEBeoAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVEM8AWgBaAFoALwBaAKIBsQBaAFoAFQBagACAIoAAgQFwCAgICIAagEIICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFRDPAFoAWgBaAC8AWgCiAbIAWgBaABUAWoAAgACAAIEBcAgICAiAGoBDCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFRESABUQzwBaAFoAWgAvAFoAogGzAFoAWgAVAFqAAIEBdYAAgQFwCAgICIAagEQICIAACNMAOAA5AA4RIBEhAD6goIAl3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUA/AAVEM8AWgBaAFoALwBaAKIBtABaAFoAFQBagACAIoAAgQFwCAgICIAagEUICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAg0AFRDPAFoAWgBaAC8AWgCiAbUAWgBaABUAWoAAgFCAAIEBcAgICAiAGoBGCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUQzwBaAFoAWgAvAFoAogG2AFoAWgAVAFqAAIAigACBAXAICAgIgBqARwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVEM8AWgBaAFoALwBaAKIBtwBaAFoAFQBagACAAIAAgQFwCAgICIAagEgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVAPwAFRDPAFoAWgBaAC8AWgCiAbgAWgBaABUAWoAAgCKAAIEBcAgICAiAGoBJCAiAAAjZAB8AIxFvAA4AJhFwACEASxFxAXQBnABMAGsAFQAnAC8AWhF5XxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAkWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXN3YXNFbmNvZGVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlgQFtgD+ACYAsgACABAiBAXzTADgAOQAOEXsRgwA+pwJWAlcCWAJZAloCWwJcgFaAV4BYgFmAWoBbgFynEYQRhRGGEYcRiBGJEYqBAX2BAX6BAX+BAYCBAYGBAYKBAYSAJd8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVABUAFRDQAFoAWgBaAC8AWgCiAlYAWgBaABUAWoAAgACAAIEBewgICAiAGoBWCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQD8ABUQ0ABaAFoAWgAvAFoAogJXAFoAWgAVAFqAAIAigACBAXsICAgIgBqAVwgIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUAFQAVENAAWgBaAFoALwBaAKICWABaAFoAFQBagACAAIAAgQF7CAgICIAagFgICIAACN8QDwCQAJEAkgAfAJMAlACVACEAlgAOACMAlwCYACYAmQAVA+cAFRDQAFoAWgBaAC8AWgCiAlkAWgBaABUAWoAAgHuAAIEBewgICAiAGoBZCAiAAAjfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUQ0ABaAFoAWgAvAFoAogJaAFoAWgAVAFqAAIAAgACBAXsICAgIgBqAWggIgAAI3xAPAJAAkQCSAB8AkwCUAJUAIQCWAA4AIwCXAJgAJgCZABUR2QAVENAAWgBaAFoALwBaAKICWwBaAFoAFQBagACBAYOAAIEBewgICAiAGoBbCAiAAAhfECROU1NlY3VyZVVuYXJjaGl2ZUZyb21EYXRhVHJhbnNmb3JtZXLfEA8AkACRAJIAHwCTAJQAlQAhAJYADgAjAJcAmAAmAJkAFQAVABUQ0ABaAFoAWgAvAFoAogJcAFoAWgAVAFqAAIAAgACBAXsICAgIgBqAXAgIgAAIWmR1cGxpY2F0ZXPSADkADhH4AKqggBnSAKwArRH7EfxaWERQTUVudGl0eacR/RH+Ef8SABIBEgIAsVpYRFBNRW50aXR5XVhEVU1MQ2xhc3NJbXBfEBJYRFVNTENsYXNzaWZpZXJJbXBfEBFYRFVNTE5hbWVzcGFjZUltcF8QFFhEVU1MTmFtZWRFbGVtZW50SW1wXxAPWERVTUxFbGVtZW50SW1w0wA4ADkADhIEEgUAPqCggCXTADgAOQAOEggSCQA+oKCAJdMAOAA5AA4SDBINAD6goIAl0gCsAK0SEBIRXlhETW9kZWxQYWNrYWdlphISEhMSFBIVEhYAsV5YRE1vZGVsUGFja2FnZV8QD1hEVU1MUGFja2FnZUltcF8QEVhEVU1MTmFtZXNwYWNlSW1wXxAUWERVTUxOYW1lZEVsZW1lbnRJbXBfEA9YRFVNTEVsZW1lbnRJbXDSADkADhIYAKqggBnTADgAOQAOEhsSHAA+oKCAJVDSAKwArRIgEiFZWERQTU1vZGVsoxIgEiIAsVdYRE1vZGVsAAgAGQAiACwAMQA6AD8AUQBWAFsAXQOBA4cDoAOyA7kDxwPUA+wEBgQIBAsEDQQQBBMEFgRPBG4EiwSqBLwE3ATjBQEFDQUpBS8FUQVyBYUFhwWKBY0FjwWRBZMFlgWZBZsFnQWfBaEFowWlBaYFqgW3Bb8FygXNBc8F0gXUBdYF5QYoBkwGcAaTBroG2gcBBygHSAdsB5AHnAeeB6AHogekB6YHqAerB60HrweyB7QHtge5B7sHvAfBB8kH1gfZB9sH3gfgB+IH8QgWCDoIYQiFCIcIiQiLCI0IjwiRCJIIlAihCLQItgi4CLoIvAi+CMAIwgjECMYI2QjbCN0I3wjhCOMI5QjnCOkI6wjtCQMJFgkyCU8Jawl/CZEJpwnACf8KBQoOChsKJwoxCjsKRgpRCl4KZgpoCmoKbApuCm8KcApxCnIKdAp2CncKeAp6CnsKhAqFCocKkAqbCqQKswq6CsIKywrUCucK8AsDCxoLLAtrC20LbwtxC3MLdAt1C3YLdwt5C3sLfAt9C38LgAu/C8ELwwvFC8cLyAvJC8oLywvNC88L0AvRC9ML1AvdC94L4AwfDCEMIwwlDCcMKAwpDCoMKwwtDC8MMAwxDDMMNAxzDHUMdwx5DHsMfAx9DH4MfwyBDIMMhAyFDIcMiAyRDJIMlAzTDNUM1wzZDNsM3AzdDN4M3wzhDOMM5AzlDOcM6AzpDSgNKg0sDS4NMA0xDTINMw00DTYNOA05DToNPA09DUoNSw1MDU4NVw1tDXQNgQ3ADcINxA3GDcgNyQ3KDcsNzA3ODdAN0Q3SDdQN1Q3uDfAN8g30DfUN9w4ODhcOJQ4yDkAOVQ5pDoAOkg7RDtMO1Q7XDtkO2g7bDtwO3Q7fDuEO4g7jDuUO5g77DwQPGQ8oDz0PSw9gD3QPiw+dD6oPxQ/HD8kPyw/ND88P0Q/TD9UP1w/ZD9sP3Q/fD/oP/A/+EAAQAhAEEAYQCBAKEA0QEBATEBYQGRAbECEQMBBEEFcQZRB4EIIQjhCaEKAQrRC0EL8RChEtEU0RbRFvEXERcxF1EXcReBF5EXsRfBF+EX8RgRGDEYQRhRGHEYgRkRGeEaMRpRGnEawRrhGwEbIRxxHcEgESJRJMEnASchJ0EnYSeBJ6EnwSfRJ/EowSnRKfEqESoxKlEqcSqRKrEq0SvhLAEsISxBLGEsgSyhLMEs4S0BLuEwwTHxMzE0gTZRN5E48TzhPQE9IT1BPWE9cT2BPZE9oT3BPeE98T4BPiE+MUIhQkFCYUKBQqFCsULBQtFC4UMBQyFDMUNBQ2FDcUdhR4FHoUfBR+FH8UgBSBFIIUhBSGFIcUiBSKFIsUmBSZFJoUnBTbFN0U3xThFOMU5BTlFOYU5xTpFOsU7BTtFO8U8BUvFTEVMxU1FTcVOBU5FToVOxU9FT8VQBVBFUMVRBVFFYQVhhWIFYoVjBWNFY4VjxWQFZIVlBWVFZYVmBWZFdgV2hXcFd4V4BXhFeIV4xXkFeYV6BXpFeoV7BXtFiwWLhYwFjIWNBY1FjYWNxY4FjoWPBY9Fj4WQBZBFmYWihaxFtUW1xbZFtsW3RbfFuEW4hbkFvEXABcCFwQXBhcIFwoXDBcOFx0XHxchFyMXJRcnFykXKxctF00XeBeSF6sXxRflGAgYRxhJGEsYTRhPGFAYURhSGFMYVRhXGFgYWRhbGFwYmxidGJ8YoRijGKQYpRimGKcYqRirGKwYrRivGLAY7xjxGPMY9Rj3GPgY+Rj6GPsY/Rj/GQAZARkDGQQZQxlFGUcZSRlLGUwZTRlOGU8ZURlTGVQZVRlXGVgZWxmaGZwZnhmgGaIZoxmkGaUZphmoGaoZqxmsGa4ZrxnuGfAZ8hn0GfYZ9xn4GfkZ+hn8Gf4Z/xoAGgIaAxoqGmkaaxptGm8acRpyGnMadBp1GncaeRp6GnsafRp+GocalRqiGrAavRrQGuca+RtEG2cbhxunG6kbqxutG68bsRuyG7MbtRu2G7gbuRu7G70bvhu/G8EbwhvLG9gb3RvfG+Eb5hvoG+ob7BwRHDUcXByAHIIchByGHIgcihyMHI0cjxycHK0crxyxHLMctRy3HLkcuxy9HM4c0BzSHNQc1hzYHNoc3BzeHOAdHx0hHSMdJR0nHSgdKR0qHSsdLR0vHTAdMR0zHTQdcx11HXcdeR17HXwdfR1+HX8dgR2DHYQdhR2HHYgdxx3JHcsdzR3PHdAd0R3SHdMd1R3XHdgd2R3bHdwd6R3qHesd7R4sHi4eMB4yHjQeNR42HjceOB46HjwePR4+HkAeQR6AHoIehB6GHogeiR6KHosejB6OHpAekR6SHpQelR7UHtYe2B7aHtwe3R7eHt8e4B7iHuQe5R7mHuge6R8oHyofLB8uHzAfMR8yHzMfNB82HzgfOR86HzwfPR98H34fgB+CH4QfhR+GH4cfiB+KH4wfjR+OH5AfkR+2H9ogASAlICcgKSArIC0gLyAxIDIgNCBBIFAgUiBUIFYgWCBaIFwgXiBtIG8gcSBzIHUgdyB5IHsgfSC8IL4gwCDCIMQgxSDGIMcgyCDKIMwgzSDOINAg0SEQIRIhFCEWIRghGSEaIRshHCEeISAhISEiISQhJSFkIWYhaCFqIWwhbSFuIW8hcCFyIXQhdSF2IXgheSG4IbohvCG+IcAhwSHCIcMhxCHGIcghySHKIcwhzSHQIg8iESITIhUiFyIYIhkiGiIbIh0iHyIgIiEiIyIkImMiZSJnImkiayJsIm0ibiJvInEicyJ0InUidyJ4Ip8i3iLgIuIi5CLmIuci6CLpIuoi7CLuIu8i8CLyIvMjPiNhI4EjoSOjI6UjpyOpI6sjrCOtI68jsCOyI7MjtSO3I7gjuSO7I7wjwSPOI9Mj1SPXI9wj3iPgI+IkByQrJFIkdiR4JHokfCR+JIAkgiSDJIUkkiSjJKUkpySpJKskrSSvJLEksyTEJMYkyCTKJMwkziTQJNIk1CTWJRUlFyUZJRslHSUeJR8lICUhJSMlJSUmJSclKSUqJWklayVtJW8lcSVyJXMldCV1JXcleSV6JXslfSV+Jb0lvyXBJcMlxSXGJcclyCXJJcslzSXOJc8l0SXSJd8l4CXhJeMmIiYkJiYmKCYqJismLCYtJi4mMCYyJjMmNCY2JjcmdiZ4JnomfCZ+Jn8mgCaBJoImhCaGJocmiCaKJosmyibMJs4m0CbSJtMm1CbVJtYm2CbaJtsm3CbeJt8nHicgJyInJCcmJycnKCcpJyonLCcuJy8nMCcyJzMncid0J3YneCd6J3snfCd9J34ngCeCJ4MnhCeGJ4cnrCfQJ/coGygdKB8oISgjKCUoJygoKCooNyhGKEgoSihMKE4oUChSKFQoYyhlKGcoaShrKG0obyhxKHMosii0KLYouCi6KLsovCi9KL4owCjCKMMoxCjGKMcpBikIKQopDCkOKQ8pECkRKRIpFCkWKRcpGCkaKRspWilcKV4pYCliKWMpZCllKWYpaClqKWspbCluKW8primwKbIptCm2KbcpuCm5KbopvCm+Kb8pwCnCKcMpxioFKgcqCSoLKg0qDioPKhAqESoTKhUqFioXKhkqGipZKlsqXSpfKmEqYipjKmQqZSpnKmkqaiprKm0qbiqtKq8qsSqzKrUqtiq3KrgquSq7Kr0qviq/KsEqwisNKzArUCtwK3IrdCt2K3greit7K3wrfit/K4ErgiuEK4YrhyuIK4oriyuQK50roiukK6YrqyutK68rsSvWK/osISxFLEcsSSxLLE0sTyxRLFIsVCxhLHIsdCx2LHgseix8LH4sgCyCLJMslSyXLJksmyydLJ8soSyjLKUs5CzmLOgs6izsLO0s7izvLPAs8iz0LPUs9iz4LPktOC06LTwtPi1ALUEtQi1DLUQtRi1ILUktSi1MLU0tjC2OLZAtki2ULZUtli2XLZgtmi2cLZ0tni2gLaEtri2vLbAtsi3xLfMt9S33Lfkt+i37Lfwt/S3/LgEuAi4DLgUuBi5FLkcuSS5LLk0uTi5PLlAuUS5TLlUuVi5XLlkuWi6ZLpsunS6fLqEuoi6jLqQupS6nLqkuqi6rLq0uri7tLu8u8S7zLvUu9i73Lvgu+S77Lv0u/i7/LwEvAi9BL0MvRS9HL0kvSi9LL0wvTS9PL1EvUi9TL1UvVi97L58vxi/qL+wv7i/wL/Iv9C/2L/cv+TAGMBUwFzAZMBswHTAfMCEwIzAyMDQwNjA4MDowPDA+MEAwQjCBMIMwhTCHMIkwijCLMIwwjTCPMJEwkjCTMJUwljDVMNcw2TDbMN0w3jDfMOAw4TDjMOUw5jDnMOkw6jEpMSsxLTEvMTExMjEzMTQxNTE3MTkxOjE7MT0xPjF9MX8xgTGDMYUxhjGHMYgxiTGLMY0xjjGPMZExkjHRMdMx1THXMdkx2jHbMdwx3THfMeEx4jHjMeUx5jIlMicyKTIrMi0yLjIvMjAyMTIzMjUyNjI3MjkyOjJhMqAyojKkMqYyqDKpMqoyqzKsMq4ysDKxMrIytDK1MwAzIzNDM2MzZTNnM2kzazNtM24zbzNxM3IzdDN1M3czeTN6M3szfTN+M4MzkDOVM5czmTOeM6AzojOkM8kz7TQUNDg0OjQ8ND40QDRCNEQ0RTRHNFQ0ZTRnNGk0azRtNG80cTRzNHU0hjSINIo0jDSONJA0kjSUNJY0mDTXNNk02zTdNN804DThNOI04zTlNOc06DTpNOs07DUrNS01LzUxNTM1NDU1NTY1NzU5NTs1PDU9NT81QDV/NYE1gzWFNYc1iDWJNYo1izWNNY81kDWRNZM1lDWhNaI1ozWlNeQ15jXoNeo17DXtNe417zXwNfI19DX1NfY1+DX5Njg2OjY8Nj42QDZBNkI2QzZENkY2SDZJNko2TDZNNow2jjaQNpI2lDaVNpY2lzaYNpo2nDadNp42oDahNuA24jbkNuY26DbpNuo26zbsNu428DbxNvI29Db1NzQ3Njc4Nzo3PDc9Nz43PzdAN0I3RDdFN0Y3SDdJN243kje5N9033zfhN+M35TfnN+k36jfsN/k4CDgKOAw4DjgQOBI4FDgWOCU4JzgpOCs4LTgvODE4Mzg1OHQ4djh4OHo4fDh9OH44fziAOII4hDiFOIY4iDiJOIw4yzjNOM840TjTONQ41TjWONc42TjbONw43TjfOOA5HzkhOSM5JTknOSg5KTkqOSs5LTkvOTA5MTkzOTQ5czl1OXc5eTl7OXw5fTl+OX85gTmDOYQ5hTmHOYg5iznKOcw5zjnQOdI50znUOdU51jnYOdo52zncOd453zoeOiA6IjokOiY6JzooOik6KjosOi46LzowOjI6MzpyOnQ6djp4Ono6ezp8On06fjqAOoI6gzqEOoY6hzrSOvU7FTs1Ozc7OTs7Oz07PztAO0E7QztEO0Y7RztJO0s7TDtNO087UDtVO2I7ZztpO2s7cDtyO3Q7djubO7875jwKPAw8DjwQPBI8FDwWPBc8GTwmPDc8OTw7PD08PzxBPEM8RTxHPFg8WjxcPF48YDxiPGQ8ZjxoPGo8qTyrPK08rzyxPLI8szy0PLU8tzy5PLo8uzy9PL48/Tz/PQE9Az0FPQY9Bz0IPQk9Cz0NPQ49Dz0RPRI9UT1TPVU9Vz1ZPVo9Wz1cPV09Xz1hPWI9Yz1lPWY9cz10PXU9dz22Pbg9uj28Pb49vz3APcE9wj3EPcY9xz3IPco9yz4KPgw+Dj4QPhI+Ez4UPhU+Fj4YPho+Gz4cPh4+Hz5ePmA+Yj5kPmY+Zz5oPmk+aj5sPm4+bz5wPnI+cz6yPrQ+tj64Pro+uz68Pr0+vj7APsI+wz7EPsY+xz8GPwg/Cj8MPw4/Dz8QPxE/Ej8UPxY/Fz8YPxo/Gz9AP2Q/iz+vP7E/sz+1P7c/uT+7P7w/vj/LP9o/3D/eP+A/4j/kP+Y/6D/3P/k/+z/9P/9AAUADQAVAB0BGQEhASkBMQE5AT0BQQFFAUkBUQFZAV0BYQFpAW0CaQJxAnkCgQKJAo0CkQKVApkCoQKpAq0CsQK5Ar0DuQPBA8kD0QPZA90D4QPlA+kD8QP5A/0EAQQJBA0FCQURBRkFIQUpBS0FMQU1BTkFQQVJBU0FUQVZBV0GWQZhBmkGcQZ5Bn0GgQaFBokGkQaZBp0GoQapBq0HqQexB7kHwQfJB80H0QfVB9kH4QfpB+0H8Qf5B/0ImQmVCZ0JpQmtCbUJuQm9CcEJxQnNCdUJ2QndCeUJ6QsVC6EMIQyhDKkMsQy5DMEMyQzNDNEM2QzdDOUM6QzxDPkM/Q0BDQkNDQ0xDWUNeQ2BDYkNnQ2lDa0NtQ5JDtkPdRAFEA0QFRAdECUQLRA1EDkQQRB1ELkQwRDJENEQ2RDhEOkQ8RD5ET0RRRFNEVURXRFlEW0RdRF9EYUSgRKJEpESmRKhEqUSqRKtErESuRLBEsUSyRLREtUT0RPZE+ET6RPxE/UT+RP9FAEUCRQRFBUUGRQhFCUVIRUpFTEVORVBFUUVSRVNFVEVWRVhFWUVaRVxFXUVqRWtFbEVuRa1Fr0WxRbNFtUW2RbdFuEW5RbtFvUW+Rb9FwUXCRgFGA0YFRgdGCUYKRgtGDEYNRg9GEUYSRhNGFUYWRlVGV0ZZRltGXUZeRl9GYEZhRmNGZUZmRmdGaUZqRqlGq0atRq9GsUayRrNGtEa1RrdGuUa6RrtGvUa+Rv1G/0cBRwNHBUcGRwdHCEcJRwtHDUcORw9HEUcSRzdHW0eCR6ZHqEeqR6xHrkewR7JHs0e1R8JH0UfTR9VH10fZR9tH3UffR+5H8EfyR/RH9kf4R/pH/Ef+SD1IP0hBSENIRUhGSEdISEhJSEtITUhOSE9IUUhSSJFIk0iVSJdImUiaSJtInEidSJ9IoUiiSKNIpUimSOVI50jpSOtI7UjuSO9I8EjxSPNI9Uj2SPdI+Uj6STlJO0k9ST9JQUlCSUNJRElFSUdJSUlKSUtJTUlOSVFJkEmSSZRJlkmYSZlJmkmbSZxJnkmgSaFJokmkSaVJ5EnmSehJ6knsSe1J7knvSfBJ8kn0SfVJ9kn4SflKOEo6SjxKPkpASkFKQkpDSkRKRkpISklKSkpMSk1KmEq7SttK+0r9Sv9LAUsDSwVLBksHSwlLCksMSw1LD0sRSxJLE0sVSxZLG0soSy1LL0sxSzZLOEs7Sz1LYkuGS61L0UvTS9VL10vZS9tL3UveS+BL7Uv+TABMAkwETAZMCEwKTAxMDkwfTCFMI0wmTClMLEwvTDJMNUw3THZMeEx6THxMfkx/TIBMgUyCTIRMhkyHTIhMikyLTMpMzEzOTNBM0kzTTNRM1UzWTNhM2kzbTNxM3kzfTR5NIE0jTSVNJ00oTSlNKk0rTS1NL00wTTFNM000TUFNQk1DTUVNhE2GTYhNik2MTY1Njk2PTZBNkk2UTZVNlk2YTZlN2E3aTdxN3k3gTeFN4k3jTeRN5k3oTelN6k3sTe1OLE4uTjBOMk40TjVONk43TjhOOk48Tj1OPk5ATkFOgE6CToROhk6ITolOik6LToxOjk6QTpFOkk6UTpVO1E7WTthO2k7cTt1O3k7fTuBO4k7kTuVO5k7oTulPDk8yT1lPfU9/T4FPg0+FT4dPiU+KT41Pmk+pT6tPrU+vT7FPs0+1T7dPxk/JT8xPz0/ST9VP2E/bT91QHFAeUCBQIlAlUCZQJ1AoUClQK1AtUC5QL1AxUDJQcVBzUHVQd1B6UHtQfFB9UH5QgFCCUINQhFCGUIdQxlDIUMpQzFDPUNBQ0VDSUNNQ1VDXUNhQ2VDbUNxRG1EdUR9RIVEkUSVRJlEnUShRKlEsUS1RLlEwUTFRcFFyUXRRdlF5UXpRe1F8UX1Rf1GBUYJRg1GFUYZRxVHHUclRy1HOUc9R0FHRUdJR1FHWUddR2FHaUdtSGlIcUh5SIFIjUiRSJVImUidSKVIrUixSLVIvUjBSe1KeUr5S3lLgUuJS5FLmUuhS6VLqUu1S7lLwUvFS81L1UvZS91L6UvtTAFMNUxJTFFMWUxtTHlMhUyNTSFNsU5NTt1O6U7xTvlPAU8JTxFPFU8hT1VPmU+hT6lPsU+5T8FPyU/RT9lQHVApUDVQQVBNUFlQZVBxUH1QhVGBUYlRkVGZUaVRqVGtUbFRtVG9UcVRyVHNUdVR2VLVUt1S5VLtUvlS/VMBUwVTCVMRUxlTHVMhUylTLVQpVDFUPVRFVFFUVVRZVF1UYVRpVHFUdVR5VIFUhVS5VL1UwVTJVcVVzVXVVd1V6VXtVfFV9VX5VgFWCVYNVhFWGVYdVxlXIVcpVzFXPVdBV0VXSVdNV1VXXVdhV2VXbVdxWG1YdVh9WIVYkViVWJlYnVihWKlYsVi1WLlYwVjFWcFZyVnRWdlZ5VnpWe1Z8Vn1Wf1aBVoJWg1aFVoZWxVbHVslWy1bOVs9W0FbRVtJW1FbWVtdW2FbaVttXAFckV0tXb1dyV3RXdld4V3pXfFd9V4BXjVecV55XoFeiV6RXpleoV6pXuVe8V79XwlfFV8hXy1fOV9BYD1gRWBNYFVgYWBlYGlgbWBxYHlggWCFYIlgkWCVYZFhmWGhYalhtWG5Yb1hwWHFYc1h1WHZYd1h5WHpYuVi7WL1Yv1jCWMNYxFjFWMZYyFjKWMtYzFjOWM9ZDlkQWRJZFFkXWRhZGVkaWRtZHVkfWSBZIVkjWSRZY1llWWdZaVlsWW1ZbllvWXBZcll0WXVZdll4WXlZuFm6WbxZvlnBWcJZw1nEWcVZx1nJWcpZy1nNWc5aDVoPWhFaE1oWWhdaGFoZWhpaHFoeWh9aIFoiWiNablqRWrFa0VrTWtVa11rZWtta3FrdWuBa4VrjWuRa5lroWula6lrtWu5a81sAWwVbB1sJWw5bEVsUWxZbO1tfW4ZbqlutW69bsVuzW7Vbt1u4W7tbyFvZW9tb3VvfW+Fb41vlW+db6Vv6W/1cAFwDXAZcCVwMXA9cElwUXFNcVVxXXFlcXFxdXF5cX1xgXGJcZFxlXGZcaFxpXKhcqlysXK5csVyyXLNctFy1XLdcuVy6XLtcvVy+XP1c/10CXQRdB10IXQldCl0LXQ1dD10QXRFdE10UXSFdIl0jXSVdZF1mXWhdal1tXW5db11wXXFdc111XXZdd115XXpduV27Xb1dv13CXcNdxF3FXcZdyF3KXctdzF3OXc9eDl4QXhJeFF4XXhheGV4aXhteHV4fXiBeIV4jXiReY15lXmdeaV5sXm1ebl5vXnBecl50XnVedl54XnleuF66Xrxevl7BXsJew17EXsVex17JXspey17NXs5e818XXz5fYl9lX2dfaV9rX21fb19wX3NfgF+PX5Ffk1+VX5dfmV+bX51frF+vX7JftV+4X7tfvl/BX8NgAmAEYAZgCGALYAxgDWAOYA9gEWATYBRgFWAXYBhgV2BZYFtgXWBgYGFgYmBjYGRgZmBoYGlgamBsYG1grGCuYLBgsmC1YLZgt2C4YLlgu2C9YL5gv2DBYMJhAWEDYQVhB2EKYQthDGENYQ5hEGESYRNhFGEWYRdhVmFYYVphXGFfYWBhYWFiYWNhZWFnYWhhaWFrYWxhq2GtYa9hsWG0YbVhtmG3YbhhumG8Yb1hvmHAYcFiAGICYgRiBmIJYgpiC2IMYg1iD2IRYhJiE2IVYhZiYWKEYqRixGLGYshiymLMYs5iz2LQYtNi1GLWYtdi2WLbYtxi3WLgYuFi6mL3Yvxi/mMAYwVjCGMLYw1jMmNWY31joWOkY6ZjqGOqY6xjrmOvY7Jjv2PQY9Jj1GPWY9hj2mPcY95j4GPxY/Rj92P6Y/1kAGQDZAZkCWQLZEpkTGROZFBkU2RUZFVkVmRXZFlkW2RcZF1kX2RgZJ9koWSjZKVkqGSpZKpkq2SsZK5ksGSxZLJktGS1ZPRk9mT5ZPtk/mT/ZQBlAWUCZQRlBmUHZQhlCmULZRhlGWUaZRxlW2VdZV9lYWVkZWVlZmVnZWhlamVsZW1lbmVwZXFlsGWyZbRltmW5Zbplu2W8Zb1lv2XBZcJlw2XFZcZmBWYHZglmC2YOZg9mEGYRZhJmFGYWZhdmGGYaZhtmWmZcZl5mYGZjZmRmZWZmZmdmaWZrZmxmbWZvZnBmr2axZrNmtWa4Zrlmuma7ZrxmvmbAZsFmwmbEZsVm6mcOZzVnWWdcZ15nYGdiZ2RnZmdnZ2pnd2eGZ4hnimeMZ45nkGeSZ5Rno2emZ6lnrGevZ7JntWe4Z7pn+Wf7Z/5oAGgDaARoBWgGaAdoCWgLaAxoDWgPaBBoFGhTaFVoV2hZaFxoXWheaF9oYGhiaGRoZWhmaGhoaWioaKporGiuaLFosmizaLRotWi3aLloumi7aL1ovmj9aP9pAWkDaQZpB2kIaQlpCmkMaQ5pD2kQaRJpE2lSaVRpVmlYaVtpXGldaV5pX2lhaWNpZGllaWdpaGmnaalpq2mtabBpsWmyabNptGm2abhpuWm6abxpvWn8af5qAGoCagVqBmoHaghqCWoLag1qDmoPahFqEmpdaoBqoGrAasJqxGrGashqymrLasxqz2rQatJq02rVatdq2GrZatxq3WrmavNq+Gr6avxrAWsEawdrCWsua1JreWuda6Bromuka6ZrqGuqa6trrmu7a8xrzmvQa9Jr1GvWa9hr2mvca+1r8Gvza/Zr+Wv8a/9sAmwFbAdsRmxIbEpsTGxPbFBsUWxSbFNsVWxXbFhsWWxbbFxsm2ydbJ9soWykbKVspmynbKhsqmysbK1srmywbLFs8GzybPVs92z6bPts/Gz9bP5tAG0CbQNtBG0GbQdtFG0VbRZtGG1XbVltW21dbWBtYW1ibWNtZG1mbWhtaW1qbWxtbW2sba5tsG2ybbVttm23bbhtuW27bb1tvm2/bcFtwm4BbgNuBW4HbgpuC24Mbg1uDm4QbhJuE24UbhZuF25WblhuWm5cbl9uYG5hbmJuY25lbmduaG5pbmtubG6rbq1ur26xbrRutW62brduuG66brxuvW6+bsBuwW7mbwpvMW9Vb1hvWm9cb15vYG9ib2NvZm9zb4JvhG+Gb4hvim+Mb45vkG+fb6JvpW+ob6tvrm+xb7Rvtm/1b/dv+m/8b/9wAHABcAJwA3AFcAdwCHAJcAtwDHBLcE1wT3BRcFRwVXBWcFdwWHBacFxwXXBecGBwYXCgcKJwpHCmcKlwqnCrcKxwrXCvcLFwsnCzcLVwtnD1cPdw+XD7cP5w/3EAcQFxAnEEcQZxB3EIcQpxC3FKcUxxTnFQcVNxVHFVcVZxV3FZcVtxXHFdcV9xYHGfcaFxo3GlcahxqXGqcatxrHGucbBxsXGycbRxtXH0cfZx+HH6cf1x/nH/cgByAXIDcgVyBnIHcglyCnJVcnhymHK4crpyvHK+csBywnLDcsRyx3LIcspyy3LNcs9y0HLRctRy1XLacudy7HLucvBy9XL4cvty/XMic0ZzbXORc5RzlnOYc5pznHOec59zonOvc8BzwnPEc8ZzyHPKc8xzznPQc+Fz5HPnc+pz7XPwc/Nz9nP5c/t0OnQ8dD50QHRDdER0RXRGdEd0SXRLdEx0TXRPdFB0j3SRdJN0lXSYdJl0mnSbdJx0nnSgdKF0onSkdKV05HTmdOl063TudO908HTxdPJ09HT2dPd0+HT6dPt1CHUJdQp1DHVLdU11T3VRdVR1VXVWdVd1WHVadVx1XXVedWB1YXWgdaJ1pHWmdal1qnWrdax1rXWvdbF1snWzdbV1tnX1dfd1+XX7df51/3YAdgF2AnYEdgZ2B3YIdgp2C3ZKdkx2TnZQdlN2VHZVdlZ2V3ZZdlt2XHZddl92YHafdqF2o3aldqh2qXaqdqt2rHaudrB2sXaydrR2tXbadv53JXdJd0x3TndQd1J3VHdWd1d3Wndnd3Z3eHd6d3x3fneAd4J3hHeTd5Z3mXecd593oneld6h3qnfpd+t37Xfvd/J383f0d/V39nf4d/p3+3f8d/53/3g+eEB4QnhEeEd4SHhJeEp4S3hNeE94UHhReFN4VHiTeJV4l3iZeJx4nXieeJ94oHiieKR4pXimeKh4qXjoeOp47HjuePF48njzePR49Xj3ePl4+nj7eP14/nk9eT95QXlDeUZ5R3lIeUl5SnlMeU55T3lQeVJ5U3mSeZR5l3mZeZx5nXmeeZ95oHmieaR5pXmmeah5qXnQeg96EXoTehV6GHoZehp6G3oceh56IHoheiJ6JHolejB6OXo6ejx6RXpQel96anp4eo16oXq4esp613rYetl623roeul66nrsevl6+nr7ev17BnsVeyJ7MXtDe1d7bnuAe4l7inuMe5l7mnube517nnune7F7uAAAAAAAAAICAAAAAAAAEiMAAAAAAAAAAAAAAAAAAHvA\n</attribute>\n        <relationship name=\"entitymappings\" type=\"0/0\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z109\">\n        <attribute name=\"name\" type=\"string\">title</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z110\">\n        <attribute name=\"name\" type=\"string\">messageReporting</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z111\">\n        <attribute name=\"name\" type=\"string\">messageExpiration</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVENTITYMAPPING\" id=\"z112\">\n        <attribute name=\"sourcename\" type=\"string\">UAInboxMessage</attribute>\n        <attribute name=\"mappingtypename\" type=\"string\">Undefined</attribute>\n        <attribute name=\"mappingnumber\" type=\"int16\">1</attribute>\n        <attribute name=\"destinationname\" type=\"string\">UAInboxMessage</attribute>\n        <attribute name=\"autogenerateexpression\" type=\"bool\">1</attribute>\n        <relationship name=\"mappingmodel\" type=\"1/1\" destination=\"XDDEVMAPPINGMODEL\" idrefs=\"z108\"></relationship>\n        <relationship name=\"attributemappings\" type=\"0/0\" destination=\"XDDEVATTRIBUTEMAPPING\" idrefs=\"z102 z115 z116 z105 z106 z107 z109 z110 z111 z113 z114 z103 z104\"></relationship>\n        <relationship name=\"relationshipmappings\" type=\"0/0\" destination=\"XDDEVRELATIONSHIPMAPPING\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z113\">\n        <attribute name=\"name\" type=\"string\">messageURL</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z114\">\n        <attribute name=\"name\" type=\"string\">deletedClient</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z115\">\n        <attribute name=\"name\" type=\"string\">messageBodyURL</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n    <object type=\"XDDEVATTRIBUTEMAPPING\" id=\"z116\">\n        <attribute name=\"name\" type=\"string\">rawMessageObject</attribute>\n        <relationship name=\"entitymapping\" type=\"1/1\" destination=\"XDDEVENTITYMAPPING\" idrefs=\"z112\"></relationship>\n    </object>\n</database>"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/AirshipMessageCenterResources.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Resources for AirshipMessageCenter\npublic final class AirshipMessageCenterResources {\n    \n    /// Module bundle\n    public static let bundle = resolveBundle()\n\n    private static func resolveBundle() -> Bundle {\n#if SWIFT_PACKAGE\n        AirshipLogger.trace(\"Using Bundle.module for AirshipMessageCenter\")\n        let bundle = Bundle.module\n#if DEBUG\n        if bundle.resourceURL == nil {\n            assertionFailure(\"\"\"\n            AirshipMessageCenter module was built with SWIFT_PACKAGE\n            but no resources were found. Check your build configuration.\n            \"\"\")\n        }\n#endif\n        return bundle\n#endif\n\n        return Bundle.airshipFindModule(\n            moduleName: \"AirshipMessageCenter\",\n            sourceBundle: Bundle(for: Self.self)\n        )\n    }\n\n    public static func localizedString(key: String) -> String? {\n        return AirshipLocalizationUtils.localizedString(\n            key,\n            withTable: \"UrbanAirship\",\n            moduleBundle: bundle\n        )\n    }\n}\n\nextension String {\n    var messageCenterLocalizedString: String {\n        return AirshipMessageCenterResources.localizedString(key: self) ?? self\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n\n/// Delegate protocol for receiving callbacks related to message center.\npublic protocol MessageCenterDisplayDelegate {\n\n    /// Called when a message is requested to be displayed.\n    ///\n    /// - Parameters:\n    ///   - messageID: The message ID.\n    @MainActor\n    func displayMessageCenter(messageID: String)\n\n    /// Called when the message center is requested to be displayed.\n    @MainActor\n    func displayMessageCenter()\n\n    /// Called when the message center is requested to be dismissed.\n    @MainActor\n    func dismissMessageCenter()\n}\n\n/// Airship Message Center Protocol.\n@MainActor\npublic protocol MessageCenter: AnyObject, Sendable {\n\n    /// Called when the Message Center is requested to be displayed.\n    /// Return `true` if the display was handled, `false` to fall back to default SDK behavior.\n    var onDisplay: (@MainActor @Sendable (_ messageID: String?) -> Bool)? { get set }\n\n    /// Called when the Message Center is requested to be dismissed.\n    var onDismissDisplay: (@MainActor @Sendable () -> Void)? { get set }\n\n    /// Message center display delegate.\n    var displayDelegate: (any MessageCenterDisplayDelegate)? { get set }\n\n    /// Message center inbox.\n    var inbox: any MessageCenterInbox { get }\n\n    /// The message center controller.\n    var controller: MessageCenterController { get set }\n\n    /// Default message center theme.\n    var theme: MessageCenterTheme? { get set }\n\n    /// Default message center predicate. Only applies to the OOTB Message Center. If you are embedding the MessageCenterView directly\n    ///  you should pass the predicate in through the view extension `.messageCenterPredicate(_:)`.\n    var predicate: (any MessageCenterPredicate)? { get set }\n\n    /// Loads a Message center theme from a plist file. If you are embedding the MessageCenterView directly\n    ///  you should pass the theme in through the view extension `.messageCenterTheme(_:)`.\n    /// - Parameters:\n    ///     - plist: The name of the plist in the bundle.\n    func setThemeFromPlist(_ plist: String) throws\n\n    /// Display the message center.\n    func display()\n\n    /// Display the given message with animation.\n    /// - Parameters:\n    ///     - messageID:  The messageID of the message.\n    func display(messageID: String)\n\n    /// Dismiss the message center.\n    func dismiss()\n}\n\n/// Airship Message Center module.\nfinal class DefaultMessageCenter: MessageCenter {\n\n    @MainActor\n    public var onDisplay: (@MainActor @Sendable (_ messageID: String?) -> Bool)?\n\n    @MainActor\n    public var onDismissDisplay: (@MainActor @Sendable () -> Void)?\n\n    @MainActor\n    public var displayDelegate: (any MessageCenterDisplayDelegate)? {\n        get {\n            mutable.displayDelegate\n        }\n        set {\n            mutable.displayDelegate = newValue\n        }\n    }\n\n    private let mutable: MutableValues\n    private let privacyManager: any AirshipPrivacyManager\n    \n    let internalInbox: any InternalMessageCenterInbox\n    let meteredUsage: any AirshipMeteredUsage\n    let analytics: any InternalAirshipAnalytics\n    \n    public var inbox: any MessageCenterInbox {\n        return self.internalInbox\n    }\n\n    @MainActor\n    public var controller: MessageCenterController {\n        get {\n            self.mutable.controller\n        }\n        set {\n            self.mutable.controller = newValue\n        }\n    }\n\n    @MainActor\n    public var theme: MessageCenterTheme? {\n        get {\n            self.mutable.theme\n        }\n        set {\n            self.mutable.theme = newValue\n        }\n    }\n\n    @MainActor\n    public func setThemeFromPlist(_ plist: String) throws {\n        self.theme = try MessageCenterTheme.fromPlist(plist)\n    }\n\n    @MainActor\n    public var predicate: (any MessageCenterPredicate)? {\n        get {\n            self.mutable.predicate\n        }\n        set {\n            self.mutable.predicate = newValue\n        }\n    }\n\n    private var enabled: Bool {\n        return self.privacyManager.isEnabled(.messageCenter)\n    }\n\n    @MainActor\n    init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        privacyManager: any AirshipPrivacyManager,\n        notificationCenter: NotificationCenter = NotificationCenter.default,\n        inbox: DefaultMessageCenterInbox,\n        controller: MessageCenterController,\n        meteredUsage: any AirshipMeteredUsage,\n        analytics: any InternalAirshipAnalytics\n    ) {\n        self.internalInbox = inbox\n        self.privacyManager = privacyManager\n        self.mutable = MutableValues(controller: controller, theme: MessageCenterThemeLoader.defaultPlist())\n        self.meteredUsage = meteredUsage\n        self.analytics = analytics\n\n        if let plist = config.airshipConfig.messageCenterStyleConfig {\n            do {\n                try setThemeFromPlist(plist)\n            } catch {\n                AirshipLogger.error(\"Failed to load Message Center \\(plist) theme \\(error) \")\n            }\n        }\n\n        notificationCenter.addObserver(\n            forName: AirshipNotifications.PrivacyManagerUpdated.name,\n            object: nil,\n            queue: nil\n        ) { [weak self, inbox] _ in\n            Task { @MainActor in\n                inbox.enabled = self?.enabled ?? false\n            }\n        }\n\n        inbox.enabled = self.enabled\n    }\n\n    @MainActor\n    convenience init(\n        dataStore: PreferenceDataStore,\n        config: RuntimeConfig,\n        channel: any InternalAirshipChannel,\n        privacyManager: any AirshipPrivacyManager,\n        workManager: any AirshipWorkManagerProtocol,\n        meteredUsage: any AirshipMeteredUsage,\n        analytics: any InternalAirshipAnalytics\n    ) {\n\n        let controller = MessageCenterController()\n        let inbox = DefaultMessageCenterInbox(\n            with: config,\n            dataStore: dataStore,\n            channel: channel,\n            workManager: workManager\n        )\n\n        self.init(\n            dataStore: dataStore,\n            config: config,\n            privacyManager: privacyManager,\n            inbox: inbox,\n            controller: controller,\n            meteredUsage: meteredUsage,\n            analytics: analytics\n        )\n    }\n\n    @MainActor\n    public func display() {\n        guard self.enabled else {\n            AirshipLogger.warn(\"Message center disabled. Unable to display.\")\n            return\n        }\n\n        let handled: Bool\n        if let onDisplay {\n            handled = onDisplay(nil)\n        } else if let displayDelegate {\n            displayDelegate.displayMessageCenter()\n            handled = true\n        } else {\n            handled = false\n        }\n\n        guard !handled else {\n            AirshipLogger.trace(\n                \"Message center display request handled by the app.\"\n            )\n            return\n        }\n\n        AirshipLogger.trace(\"Launching OOTB message center\")\n        showDefaultMessageCenter()\n        self.controller.navigate(messageID: nil)\n    }\n\n    @MainActor\n    public func display(messageID: String) {\n        guard self.enabled else {\n            AirshipLogger.warn(\"Message center disabled. Unable to display.\")\n            return\n        }\n\n        let handled: Bool\n        if let onDisplay {\n            handled = onDisplay(messageID)\n        } else if let displayDelegate {\n            displayDelegate.displayMessageCenter(messageID: messageID)\n            handled = true\n        } else {\n            handled = false\n        }\n\n        guard !handled else {\n            AirshipLogger.trace(\n                \"Message center display request for message \\(messageID) handled by the app.\"\n            )\n            return\n        }\n\n        AirshipLogger.trace(\"Launching OOTB message center\")\n        showDefaultMessageCenter()\n        self.controller.navigate(messageID: messageID)\n    }\n\n    @MainActor\n    public func dismiss() {\n        if let onDismissDisplay {\n            onDismissDisplay()\n        } else if let displayDelegate {\n            displayDelegate.dismissMessageCenter()\n        } else {\n            Task { @MainActor in\n                self.dismissDefaultMessageCenter()\n            }\n        }\n    }\n\n    @MainActor\n    final class MutableValues: Sendable {\n        var displayDelegate: (any MessageCenterDisplayDelegate)?\n        var controller: MessageCenterController\n        var predicate: (any MessageCenterPredicate)?\n        var theme: MessageCenterTheme?\n        var currentDisplay: (any AirshipMainActorCancellable)?\n\n        init(\n            displayDelegate: (any MessageCenterDisplayDelegate)? = nil,\n            controller: MessageCenterController,\n            predicate: (any MessageCenterPredicate)? = nil,\n            theme: MessageCenterTheme? = nil,\n            currentDisplay: (any AirshipMainActorCancellable)? = nil\n        ) {\n            self.displayDelegate = displayDelegate\n            self.controller = controller\n            self.predicate = predicate\n            self.theme = theme\n            self.currentDisplay = currentDisplay\n        }\n    }\n}\n\nextension DefaultMessageCenter {\n    private static let kUARichPushMessageIDKey = \"_uamid\"\n\n    @MainActor\n    func deepLink(_ deepLink: URL) -> Bool {\n\n        // Ensure the scheme matches Airship deeplLink scheme\n        guard deepLink.scheme == Airship.deepLinkScheme else {\n            return false\n        }\n\n        // Ensure the host matches\n        guard deepLink.host == \"message_center\" else {\n            return false\n        }\n\n        let components = deepLink.pathComponents\n        let path = deepLink.path\n\n        // Case 1: No path -> open message center\n        if path.isEmpty || path == \"/\" {\n            display()\n            return true\n        }\n\n        // Case 2: /message/<id>\n        if components.count == 3, components[1] == \"message\" {\n            display(messageID: components[2])\n            return true\n        }\n\n        // Case 3: /<id>\n        if components.count == 2 {\n            display(messageID: components[1])\n            return true\n        }\n\n        // Anything else is unsupported\n        return false\n    }\n\n    @MainActor\n    func receivedRemoteNotification(\n        _ notification: AirshipJSON\n    ) async -> UABackgroundFetchResult {\n        guard\n            let userInfo = notification.unWrap() as? [AnyHashable: Any],\n            let messageID = MessageCenterMessage.parseMessageID(\n                userInfo: userInfo\n            )\n        else {\n            return .noData\n        }\n\n        let result = await self.inbox.refreshMessages()\n\n        if !result {\n            return .failed\n        }\n\n        let message = await self.inbox.message(forID: messageID)\n\n        guard message != nil else {\n            return .noData\n        }\n\n        return .newData\n    }\n\n    @MainActor\n    fileprivate func showDefaultMessageCenter() {\n        guard self.mutable.currentDisplay == nil else {\n            return\n        }\n\n        let displayable = AirshipDisplayTarget().prepareDisplay(for: .modal)\n\n        let controller = MessageCenterViewControllerFactory.make(\n            theme: theme,\n            predicate: predicate,\n            controller: self.controller\n        ) {\n            self.mutable.currentDisplay?.cancel()\n            self.mutable.currentDisplay = nil\n        }\n\n        do {\n            try displayable.display { _ in\n                return controller\n            }\n            self.mutable.currentDisplay = AirshipMainActorCancellableBlock {\n                displayable.dismiss()\n            }\n        } catch {\n            AirshipLogger.error(\"Unable to display message center \\(error)\")\n        }\n    }\n\n    @MainActor\n    fileprivate func dismissDefaultMessageCenter() {\n        self.mutable.currentDisplay?.cancel()\n        self.mutable.currentDisplay = nil\n    }\n}\n\npublic extension Airship {\n    /// The shared `MessageCenter` instance. `Airship.takeOff` must be called before accessing this instance.\n    static var messageCenter: any MessageCenter {\n        Airship.requireComponent(\n            ofType: MessageCenterComponent.self\n        ).messageCenter\n    }\n}\n\nextension Airship {\n    static var internalMessageCenter: DefaultMessageCenter {\n        Airship.requireComponent(\n            ofType: MessageCenterComponent.self\n        ).messageCenter\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterAPIClient.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Message Center API client protocol\nprotocol MessageCenterAPIClientProtocol: Sendable {\n\n    /// Retrieves the full message list from the server.\n    /// - Parameters:\n    ///   - user: The user credentials\n    ///   - channel: The channel ID\n    ///   - lastModified: The last modified time\n    /// - Returns: The messages, or throws an error if there was an error or the message list has not changed since the last update.\n    func retrieveMessageList(\n        user: MessageCenterUser,\n        channelID: String,\n        lastModified: String?\n    ) async throws -> AirshipHTTPResponse<[MessageCenterMessage]>\n\n    /// Performs a batch delete request on the server.\n    ///  - Parameters:\n    ///    - messages: An array of messages.\n    ///    - user: The user credentials\n    ///    - channel: The channel ID\n    /// - Returns: Returns an AirshipHTTPResponse\n    func performBatchDelete(\n        forMessages messages: [MessageCenterMessage],\n        user: MessageCenterUser,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void>\n\n    /// Performs a batch mark-as-read request on the server.\n    /// - Parameters:\n    ///   - messages: An Array of messages be marked as read.\n    ///   - user: The user credentials\n    ///   - channelID: The channel ID.\n    /// - Returns: Returns an AirshipHTTPResponse\n    func performBatchMarkAsRead(\n        forMessages messages: [MessageCenterMessage],\n        user: MessageCenterUser,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void>\n\n    /// Create a user.\n    /// - Parameters:\n    ///   -  channelID: The channel ID\n    /// - Returns: The user credentials, or throws an error if there was an error.\n    func createUser(\n        withChannelID channelID: String\n    ) async throws -> AirshipHTTPResponse<MessageCenterUser>\n\n    /// Update a user.\n    /// - Parameters:\n    ///   -  user: The user credentials\n    ///   -  channelID: The channel ID\n    /// - Returns: An airship http response.\n    func updateUser(\n        _ user: MessageCenterUser,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void>\n}\n\nstruct MessageCenterAPIClient: MessageCenterAPIClientProtocol, Sendable {\n\n    private static let channelIDHeader = \"X-UA-Channel-ID\"\n    private static let lastModifiedIDHeader = \"If-Modified-Since\"\n\n    private let config: RuntimeConfig\n    private let session: any AirshipRequestSession\n\n    init(config: RuntimeConfig, session: any AirshipRequestSession) {\n        self.config = config\n        self.session = session\n    }\n\n    func retrieveMessageList(\n        user: MessageCenterUser,\n        channelID: String,\n        lastModified: String?\n    ) async throws -> AirshipHTTPResponse<[MessageCenterMessage]> {\n        guard let deviceAPIURL = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"The deviceAPIURL is nil\")\n        }\n\n        let urlString =\n            \"\\(deviceAPIURL)\\(\"/api/user/\")\\(user.username)\\(\"/messages/\")\"\n        var headers: [String: String] = [\n            MessageCenterAPIClient.channelIDHeader: channelID,\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ]\n\n        if let lastModified = lastModified {\n            headers[MessageCenterAPIClient.lastModifiedIDHeader] = lastModified\n        }\n\n        let request = AirshipRequest(\n            url: URL(string: urlString),\n            headers: headers,\n            method: \"GET\",\n            auth: .basic(username: user.username, password: user.password)\n        )\n\n        AirshipLogger.trace(\"Request to retrieve message list: \\(urlString)\")\n\n        return try await self.session.performHTTPRequest(request) { data, response in\n            guard response.isSuccess else { return nil }\n\n            do {\n                guard let data else { throw AirshipErrors.parseError(\"Missing response body\") }\n                let parsed = try JSONDecoder().decode(MessageListResponse.self, from: data)\n                return try parsed.convertMessages()\n            } catch {\n                let responseBody = data.flatMap { String(data: $0, encoding: .utf8) } ?? \"nil\"\n                AirshipLogger.error(\"Failed to parse message list response: \\(error) responseBody: \\(responseBody)\")\n                throw error\n            }\n        }\n    }\n\n    func performBatchDelete(\n        forMessages messages: [MessageCenterMessage],\n        user: MessageCenterUser,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void> {\n        guard let deviceAPIURL = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"The deviceAPIURL is nil\")\n        }\n\n        let messageReportings = messages.compactMap { message in\n            message.messageReporting\n        }\n\n        guard !messageReportings.isEmpty else {\n            throw AirshipErrors.error(\"No reporting\")\n        }\n\n        let urlString =\n            \"\\(deviceAPIURL)\\(\"/api/user/\")\\(user.username)\\(\"/messages/delete/\")\"\n\n        let body = UpdateMessagesRequestBody(messages: messageReportings)\n\n        let request = try AirshipRequest(\n            url: URL(string: urlString),\n            headers: [\n                \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n                \"Content-Type\": \"application/json\",\n                MessageCenterAPIClient.channelIDHeader: channelID,\n            ],\n            method: \"POST\",\n            auth: .basic(username: user.username, password: user.password),\n            encodableBody: body\n        )\n\n        AirshipLogger.trace(\n            \"Request to perform batch delete: \\(urlString)  body: \\(body)\"\n        )\n        return try await self.session.performHTTPRequest(request)\n    }\n\n    func performBatchMarkAsRead(\n        forMessages messages: [MessageCenterMessage],\n        user: MessageCenterUser,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void> {\n\n        guard let deviceAPIURL = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"The deviceAPIURL is nil\")\n        }\n\n        let messageReportings = messages.compactMap { message in\n            message.messageReporting\n        }\n\n        guard !messageReportings.isEmpty else {\n            throw AirshipErrors.error(\"No reporting\")\n        }\n\n        let urlString =\n            \"\\(deviceAPIURL)\\(\"/api/user/\")\\(user.username)\\(\"/messages/unread/\")\"\n\n        let headers: [String: String] = [\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            \"Content-Type\": \"application/json\",\n            MessageCenterAPIClient.channelIDHeader: channelID,\n        ]\n\n        let body = UpdateMessagesRequestBody(messages: messageReportings)\n        let request = try AirshipRequest(\n            url: URL(string: urlString),\n            headers: headers,\n            method: \"POST\",\n            auth: .basic(username: user.username, password: user.password),\n            encodableBody: body\n        )\n\n        AirshipLogger.trace(\n            \"Request to perform batch mark messages as read: \\(urlString) body: \\(body)\"\n        )\n\n        return try await self.session.performHTTPRequest(request)\n    }\n\n    func createUser(\n        withChannelID channelID: String\n    ) async throws -> AirshipHTTPResponse<MessageCenterUser> {\n\n        guard let deviceAPIURL = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"The deviceAPIURL is nil\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(\"/api/user/\")\"\n        let headers: [String: String] = [\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            \"Content-Type\": \"application/json\",\n            MessageCenterAPIClient.channelIDHeader: channelID,\n        ]\n\n        let body = CreateUserRequestBody(iOSChannels: [channelID])\n        let request = try AirshipRequest(\n            url: URL(string: urlString),\n            headers: headers,\n            method: \"POST\",\n            auth: .channelAuthToken(identifier: channelID),\n            encodableBody: body\n        )\n\n        AirshipLogger.trace(\n            \"Request to perform batch create user: \\(urlString) body: \\(body)\"\n        )\n\n        return try await self.session.performHTTPRequest(request) {\n            data,\n            response in\n            guard response.isSuccess else { return nil }\n\n            guard let data else { throw AirshipErrors.parseError(\"Missing response body\") }\n            return try JSONDecoder().decode(MessageCenterUser.self, from: data)\n        }\n    }\n\n    func updateUser(\n        _ user: MessageCenterUser,\n        channelID: String\n    ) async throws -> AirshipHTTPResponse<Void> {\n\n        guard let deviceAPIURL = config.deviceAPIURL else {\n            throw AirshipErrors.error(\"The deviceAPIURL is nil\")\n        }\n\n        let urlString = \"\\(deviceAPIURL)\\(\"/api/user/\")\\(user.username)\"\n        let headers: [String: String] = [\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\",\n            \"Content-Type\": \"application/json\",\n        ]\n\n        let body = UpdateUserRequestBody(\n            iOSChannels: UpdateUserRequestBody.UserOperation(\n                add: [channelID]\n            )\n        )\n\n        let request = try AirshipRequest(\n            url: URL(string: urlString),\n            headers: headers,\n            method: \"POST\",\n            auth: .basic(username: user.username, password: user.password),\n            encodableBody: body\n        )\n        AirshipLogger.trace(\n            \"Request to perform batch update user: \\(urlString) body: \\(body)\"\n        )\n\n        return try await self.session.performHTTPRequest(request)\n    }\n}\n\nprivate struct UpdateMessagesRequestBody: Encodable {\n    let messages: [AirshipJSON]\n}\n\nprivate struct UpdateUserRequestBody: Encodable {\n    var iOSChannels: UserOperation\n\n    private enum CodingKeys: String, CodingKey {\n        case iOSChannels = \"ios_channels\"\n    }\n\n    fileprivate struct UserOperation: Encodable {\n        var add: [String]\n    }\n}\n\nprivate struct CreateUserRequestBody: Encodable {\n    var iOSChannels: [String]\n\n    private enum CodingKeys: String, CodingKey {\n        case iOSChannels = \"ios_channels\"\n    }\n}\n\nprivate struct MessageListResponse: Decodable {\n    let messages: [Message]\n\n    struct Message: Decodable {\n        let messageID: String\n        let messageBodyURL: URL\n        let messageReporting: AirshipJSON\n        let messageURL: URL\n        let contentType: MessageCenterMessage.ContentType?\n        /// String instead of Date because they might be nonstandard ISO dates\n        let messageSent: String\n        let messageExpiration: String?\n        let title: String\n        let extra: AirshipJSON?\n        let icons: AirshipJSON?\n        let unread: Bool\n        let rawJSON: AirshipJSON\n\n        private enum CodingKeys: String, CodingKey {\n            case messageID = \"message_id\"\n            case title = \"title\"\n            case contentType = \"content_type\"\n            case messageBodyURL = \"message_body_url\"\n            case messageURL = \"message_url\"\n            case unread = \"unread\"\n            case messageSent = \"message_sent\"\n            case messageExpiration = \"message_expiry\"\n            case extra = \"extra\"\n            case icons = \"icons\"\n            case messageReporting = \"message_reporting\"\n        }\n\n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.messageID = try container.decode(String.self, forKey: .messageID)\n            self.messageBodyURL = try container.decode(URL.self, forKey: .messageBodyURL)\n            self.messageReporting = try container.decode(AirshipJSON.self, forKey: .messageReporting)\n            self.messageURL = try container.decode(URL.self, forKey: .messageURL)\n            self.contentType = try? container.decode(MessageCenterMessage.ContentType.self, forKey: .contentType)\n            self.messageSent = try container.decode(String.self, forKey: .messageSent)\n            self.messageExpiration = try container.decodeIfPresent(String.self, forKey: .messageExpiration)\n            self.title = try container.decode(String.self, forKey: .title)\n            self.extra = try container.decodeIfPresent(AirshipJSON.self, forKey: .extra)\n            self.icons = try container.decodeIfPresent(AirshipJSON.self, forKey: .icons)\n            self.unread = try container.decode(Bool.self, forKey: .unread)\n            self.rawJSON = try AirshipJSON(from: decoder)\n        }\n    }\n}\n\nextension MessageListResponse {\n    fileprivate func convertMessages() throws -> [MessageCenterMessage] {\n        return try self.messages.map { responseMessage in\n            return MessageCenterMessage(\n                title: responseMessage.title,\n                id: responseMessage.messageID,\n                contentType: responseMessage.contentType ?? .unknown(nil),\n                extra: responseMessage.extra?.object?.compactMapValues { $0.string } ?? [:],\n                bodyURL: responseMessage.messageBodyURL,\n                expirationDate: try responseMessage.messageExpiration?.toDate(),\n                messageReporting: responseMessage.messageReporting,\n                unread: responseMessage.unread,\n                sentDate: try responseMessage.messageSent.toDate(),\n                messageURL: responseMessage.messageURL,\n                rawMessageObject: responseMessage.rawJSON\n            )\n        }\n    }\n}\n\nextension HTTPURLResponse {\n    fileprivate var isSuccess: Bool {\n        return self.statusCode >= 200 && self.statusCode <= 299\n    }\n}\n\nextension String {\n    fileprivate func toDate() throws -> Date {\n        guard let date = AirshipDateFormatter.date(fromISOString: self) else {\n            throw AirshipErrors.error(\"Invalid date \\(self)\")\n        }\n        return date\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterAction.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Message center action that launches the message center.\n///\n///\n/// Valid situations: `ActionSituation.manualInvocation`, `ActionSituation.launchedFromPush`\n/// `ActionSituation.webViewInvocation`, `ActionSituation.foregroundInteractiveButton`,\n/// `ActionSituation.manualInvocation`, and `ActionSituation.automation`\n///\npublic final class MessageCenterAction: AirshipAction {\n\n    /// Default names - \"_uamid\", \"overlay_inbox_action\", \"display_inbox_action\", \"^mc\", \"^mco\"\n    public static let defaultNames = [\"_uamid\", \"overlay_inbox_action\", \"display_inbox_action\", \"^mc\", \"^mco\"]\n\n    /// Action value for the message ID place holder.\n    public static let messageIDPlaceHolder = \"auto\"\n\n    public func accepts(arguments: ActionArguments) async -> Bool {\n        switch arguments.situation {\n        case .manualInvocation, .launchedFromPush, .webViewInvocation,\n            .automation,\n            .foregroundInteractiveButton:\n            return true\n        case .backgroundInteractiveButton: fallthrough\n        case .foregroundPush: fallthrough\n        case .backgroundPush: fallthrough\n        @unknown default: return false\n        }\n    }\n\n    private func parseMessageID(arguments: ActionArguments) -> String? {\n        if let value = arguments.value.unWrap() as? String {\n            guard value == MessageCenterAction.messageIDPlaceHolder else {\n                return value\n            }\n            if let messageID =\n                arguments.metadata[ActionArguments.inboxMessageIDMetadataKey]\n                as? String\n            {\n                return messageID\n            } else if let payload =\n                        (arguments.metadata[ActionArguments.pushPayloadJSONMetadataKey] as? AirshipJSON)?.unWrap()\n                as? [AnyHashable: Any]\n            {\n                return MessageCenterMessage.parseMessageID(userInfo: payload)\n            } else {\n                return nil\n            }\n\n        } else if let value = arguments.value.unWrap() as? [String] {\n            return value.first\n        } else {\n            return nil\n        }\n    }\n\n    @MainActor\n    public func perform(arguments: ActionArguments) async throws -> AirshipJSON? {\n        if let messageID = parseMessageID(arguments: arguments),\n            !messageID.isEmpty\n        {\n            Airship.messageCenter.display(messageID: messageID)\n        } else {\n            Airship.messageCenter.display()\n        }\n\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\nimport UserNotifications\n\n/// Actual airship component for MessageCenter. Used to hide AirshipComponent methods.\nfinal class MessageCenterComponent : AirshipComponent, AirshipPushableComponent, Sendable {\n    final let messageCenter: DefaultMessageCenter\n\n    init(messageCenter: DefaultMessageCenter) {\n        self.messageCenter = messageCenter\n    }\n    \n    @MainActor\n    public func deepLink(_ deepLink: URL) -> Bool {\n        return self.messageCenter.deepLink(deepLink)\n    }\n\n    func receivedRemoteNotification(_ notification: AirshipJSON) async -> UABackgroundFetchResult {\n        return await self.messageCenter.receivedRemoteNotification(notification)\n    }\n\n#if !os(tvOS)\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        // no-op\n    }\n#endif\n}\n\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterList.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\n\npublic import Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Errors that can occur when loading or refreshing Message Center Inbox.\npublic enum MessageCenterInboxError: Error, Equatable {\n    /// Message Center is unavailable because the `AirshipFeature.messageCenter` feature\n    /// is disabled in the privacy manager.\n    case disabled\n\n    /// The message could not be fetched from the server.\n    case failedToFetchMessage\n    \n    /// The refresh operation has been cancelled\n    case cancelled\n}\n\n/// Airship Message Center inbox protocol.\npublic protocol MessageCenterInbox: AnyObject, Sendable {\n\n    /// Refreshes the list of messages in the inbox.\n    /// - Returns: `true` if the messages was refreshed, otherwise `false`.\n    @discardableResult\n    func refreshMessages() async -> Bool\n    \n    /// Refreshes the list of messages in the inbox.\n    /// - Throws : An error of type `MessageCenterInboxError`\n    func refreshMessagesThrowing() async throws\n\n    /// Marks messages read.\n    /// - Parameters:\n    ///     - messages: The list of messages to be marked read.\n    func markRead(messages: [MessageCenterMessage]) async\n\n    /// Marks messages read by message IDs.\n    /// - Parameters:\n    ///     - messageIDs: The list of message IDs for the messages to be marked read.\n    func markRead(messageIDs: [String]) async\n\n    /// Marks messages deleted.\n    /// - Parameters:\n    ///     - messages: The list of messages to be marked deleted.\n    func delete(messages: [MessageCenterMessage]) async\n\n    /// Marks messages deleted by message IDs.\n    /// - Parameters:\n    ///     - messageIDs: The list of message IDs for the messages to be marked deleted.\n    func delete(messageIDs: [String]) async\n\n    /// Returns the message associated with a particular URL.\n    /// - Parameters:\n    ///     - bodyURL: The URL of the message.\n    /// - Returns: The associated `MessageCenterMessage` object or nil if a message was unable to be found.\n    func message(forBodyURL bodyURL: URL) async -> MessageCenterMessage?\n\n    /// Returns the message associated with a particular ID.\n    /// - Parameters:\n    ///     - messageID: The message ID.\n    /// - Returns: The associated `MessageCenterMessage` object or nil if a message was unable to be found.\n    func message(forID messageID: String) async -> MessageCenterMessage?\n\n    /// Publisher that emits messages.\n    @MainActor\n    var messagePublisher: AnyPublisher<[MessageCenterMessage], Never> { get }\n    \n    /// Async Stream on messages' updates\n    var messageUpdates: AsyncStream<[MessageCenterMessage]> { get }\n    \n    /// Publisher that emits unread counts.\n    @MainActor\n    var unreadCountPublisher: AnyPublisher<Int, Never> { get }\n    \n    /// Async Stream of unread count updates\n    var unreadCountUpdates: AsyncStream<Int> { get }\n    \n    /// The list of messages in the inbox.\n    var messages: [MessageCenterMessage] { get async }\n\n    /// The user associated to the Message Center\n    var user: MessageCenterUser? { get async }\n\n    /// The number of messages that are currently unread.\n    var unreadCount: Int { get async }\n\n    /// Refreshes the list of messages in the inbox.\n    /// - Returns: `true` if the messages was refreshed, otherwise `false`.\n    @discardableResult\n    func refreshMessages(timeout: TimeInterval) async throws -> Bool\n}\n\nprotocol InternalMessageCenterInbox: MessageCenterInbox {\n    func saveDisplayHistory(for messageID: String, history: MessageDisplayHistory) async\n    func saveLayoutState(for messageID: String, state: MessageCenterMessage.AssociatedData.ViewState?) async\n    \n    @MainActor\n    func getNativeStateStorage(for messageID: String) -> any LayoutDataStorage\n}\n\n/// Airship Message Center inbox.\nfinal class DefaultMessageCenterInbox: InternalMessageCenterInbox, Sendable {\n\n    private enum UpdateType: Sendable {\n        case local\n        case refreshSucess\n        case refreshFailed\n    }\n\n    private let updateWorkID = \"Airship.MessageCenterInbox#update\"\n\n    private let store: MessageCenterStore\n    private let channel: any InternalAirshipChannel\n    private let client: any MessageCenterAPIClientProtocol\n    private let config: RuntimeConfig\n    private let notificationCenter: NotificationCenter\n    private let date: any AirshipDateProtocol\n    private let workManager: any AirshipWorkManagerProtocol\n    private let startUpTask: Task<Void, Never>?\n    private let _enabled: AirshipAtomicValue<Bool> = AirshipAtomicValue(false)\n    private let refreshOnExpireTask: AirshipAtomicValue<Task<Void, any Error>?> = AirshipAtomicValue(nil)\n    private let taskSleeper: any AirshipTaskSleeper\n    \n    private let nativeStateStorageFactory: @Sendable @MainActor (String) -> any LayoutDataStorage\n    private let nativeMessageStorageValue: AirshipMainActorValue<(any LayoutDataStorage)?> = .init(nil)\n    \n    var enabled: Bool {\n        get {\n            _enabled.value\n        }\n        set {\n            if (_enabled.setValue(newValue)) {\n                self.dispatchUpdateWorkRequest()\n            }\n        }\n    }\n\n    @MainActor\n    public var messagePublisher: AnyPublisher<[MessageCenterMessage], Never> {\n        return self.messageUpdates\n            .airshipPublisher\n            .compactMap { $0 }\n            .eraseToAnyPublisher()\n    }\n    \n    var messageUpdates: AsyncStream<[MessageCenterMessage]> {\n        return self.updateChannel.makeNonIsolatedDedupingStream { [weak self] in\n            await self?.messages\n        } transform: { [weak self] _ in\n            await self?.messages\n        }\n    }\n\n    @MainActor\n    public var unreadCountPublisher: AnyPublisher<Int, Never> {\n        return self.unreadCountUpdates\n            .airshipPublisher\n            .compactMap { $0 }\n            .eraseToAnyPublisher()\n    }\n    \n    var unreadCountUpdates: AsyncStream<Int> {\n        return self.updateChannel.makeNonIsolatedDedupingStream { [weak self] in\n            await self?.unreadCount\n        } transform: { [weak self] _ in\n            await self?.unreadCount\n        }\n    }\n\n    public var messages: [MessageCenterMessage] {\n        get async {\n            guard self.enabled else {\n                AirshipLogger.error(\"Message center is disabled\")\n                return []\n            }\n            return await self.store.messages\n        }\n    }\n\n    public var user: MessageCenterUser? {\n        get async {\n            guard self.enabled else {\n                AirshipLogger.error(\"Message center is disabled\")\n                return nil\n            }\n\n            await self.startUpTask?.value\n            return await self.store.user\n        }\n    }\n\n    public var unreadCount: Int {\n        get async {\n            guard self.enabled else {\n                AirshipLogger.error(\"Message center is disabled\")\n                return 0\n            }\n\n            return await self.store.unreadCount\n        }\n    }\n\n    @MainActor\n    init(\n        channel: any InternalAirshipChannel,\n        client: any MessageCenterAPIClientProtocol,\n        config: RuntimeConfig,\n        store: MessageCenterStore,\n        notificationCenter: NotificationCenter = NotificationCenter.default,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        workManager: any AirshipWorkManagerProtocol,\n        taskSleeper: (any AirshipTaskSleeper)? = nil,\n        stateStorageFactory: (@Sendable @MainActor (String) -> any LayoutDataStorage)? = nil\n    ) {\n        self.channel = channel\n        self.client = client\n        self.config = config\n        self.store = store\n        self.notificationCenter = notificationCenter\n        self.date = date\n        self.workManager = workManager\n        self.taskSleeper = taskSleeper ?? DefaultAirshipTaskSleeper.shared\n        self.nativeStateStorageFactory = stateStorageFactory ?? { messageID in\n            //can't use self here because it's init\n            NativeLayoutPersistentDataStore(\n                messageID: messageID) { state in\n                    Task {\n                        await Airship.internalMessageCenter\n                            .internalInbox\n                            .saveLayoutState(\n                                for: messageID,\n                                state: state\n                            )\n                    }\n                } onFetch: {\n                    await Airship.internalMessageCenter\n                        .internalInbox\n                        .message(forID: messageID)?\n                        .associatedData.viewState\n                }\n        }\n\n        self.startUpTask = if channel.identifier == nil, !config.airshipConfig.restoreMessageCenterOnReinstall {\n            Task { [weak store] in\n                await store?.resetUser()\n            }\n        } else {\n            nil\n        }\n        \n        workManager.registerWorker(\n            updateWorkID\n        ) { [weak self] request in\n            self?.refreshOnExpireTask.value?.cancel()\n            return try await self?.updateInbox() ?? .success\n        }\n\n        notificationCenter.addObserver(\n            forName: RuntimeConfig.configUpdatedEvent,\n            object: nil,\n            queue: nil\n        ) { [weak self] _ in\n            self?.remoteURLConfigUpdated()\n        }\n\n        notificationCenter.addObserver(\n            forName: AppStateTracker.didBecomeActiveNotification,\n            object: nil,\n            queue: nil\n        ) { [weak self] _ in\n            self?.dispatchUpdateWorkRequest()\n        }\n        \n        notificationCenter.addObserver(\n            forName: AppStateTracker.didEnterBackgroundNotification,\n            object: nil,\n            queue: nil\n        ) { [weak self] _ in\n            self?.refreshOnExpireTask.value?.cancel()\n        }\n\n        notificationCenter.addObserver(\n            forName: AirshipNotifications.ChannelCreated.name,\n            object: nil,\n            queue: nil\n        ) { [weak self] _ in\n            self?.dispatchUpdateWorkRequest(\n                conflictPolicy: .replace\n            )\n        }\n\n        Task { @MainActor [weak self] in\n            guard let stream = await self?.updateChannel.makeStream() else { return }\n            for await update in stream {\n                guard update != .refreshFailed else { continue }\n                notificationCenter.post(\n                    name: AirshipNotifications.MessageCenterListUpdated.name,\n                    object: nil\n                )\n                \n                await self?.setupRefreshOnMessageExpires()\n            }\n        }\n\n        self.channel.addRegistrationExtender { [weak self] payload in\n            await self?.startUpTask?.value\n            guard self?.enabled == true,\n                  let user = await self?.store.user\n            else {\n                return\n            }\n\n            if payload.identityHints == nil {\n                payload.identityHints = ChannelRegistrationPayload.IdentityHints(\n                    userID: user.username\n                )\n            } else {\n                payload.identityHints?.userID = user.username\n            }\n        }\n    }\n\n    @MainActor\n    convenience init(\n        with config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        channel: any InternalAirshipChannel,\n        workManager: any AirshipWorkManagerProtocol\n    ) {\n        self.init(\n            channel: channel,\n            client: MessageCenterAPIClient(\n                config: config,\n                session: config.requestSession\n            ),\n            config: config,\n            store: MessageCenterStore(\n                config: config,\n                dataStore: dataStore\n            ),\n            workManager: workManager\n        )\n    }\n\n    private func sendUpdate(_ update: UpdateType) async {\n        await self.updateChannel.send(update)\n    }\n    \n    private func setupRefreshOnMessageExpires() async {\n        self.refreshOnExpireTask.value?.cancel()\n        \n        guard\n            let refresh = await self.messages\n                .compactMap({ $0.expirationDate })\n                .sorted()\n                .first\n        else {\n            return\n        }\n        \n        let delay = refresh.timeIntervalSince(self.date.now)\n        \n        self.refreshOnExpireTask.value = Task { [weak self] in\n            try await self?.taskSleeper.sleep(timeInterval: delay)\n            self?.dispatchUpdateWorkRequest()\n        }\n    }\n\n    private let updateChannel: AirshipAsyncChannel<UpdateType> = AirshipAsyncChannel()\n\n    @discardableResult\n    public func refreshMessages() async -> Bool {\n        do {\n            try await refreshMessagesThrowing()\n            return true\n        } catch {\n            return false\n        }\n    }\n    \n    public func refreshMessagesThrowing() async throws {\n        if !self.enabled {\n            AirshipLogger.error(\"Message center is disabled\")\n            throw MessageCenterInboxError.disabled\n        }\n\n        let stream = await updateChannel.makeStream()\n\n        dispatchUpdateWorkRequest(\n            conflictPolicy: .replace,\n            requireNetwork: false\n        )\n\n        for await update in stream {\n            guard !Task.isCancelled else { break }\n            guard update == .refreshSucess || update == .refreshFailed else {\n                continue\n            }\n            \n            if update != .refreshSucess {\n                throw MessageCenterInboxError.failedToFetchMessage\n            }\n            \n            return\n        }\n        \n        throw MessageCenterInboxError.cancelled\n    }\n\n    func refreshMessages(timeout: TimeInterval) async throws -> Bool {\n        return try await withThrowingTaskGroup(of: Bool.self) { [weak self] group in\n\n            group.addTask { [weak self] in\n                return await self?.refreshMessages() ?? false\n            }\n\n            group.addTask {\n                try await Task.sleep(nanoseconds: UInt64(timeout * 1_000_000_000))\n                throw AirshipErrors.error(\"Timed out\")\n            }\n\n            guard let success = try await group.next() else {\n                group.cancelAll()\n                throw CancellationError()\n            }\n            group.cancelAll()\n            return success\n        }\n    }\n\n    public func markRead(messages: [MessageCenterMessage]) async {\n        await self.markRead(\n            messageIDs: messages.map { message in message.id }\n        )\n    }\n\n    public func markRead(messageIDs: [String]) async {\n        do {\n            try await self.store.markRead(messageIDs: messageIDs, level: .local)\n            self.dispatchUpdateWorkRequest()\n            await self.sendUpdate(.local)\n        } catch {\n            AirshipLogger.error(\"Failed to mark messages read: \\(error)\")\n        }\n    }\n\n    public func delete(messages: [MessageCenterMessage]) async {\n        await self.delete(\n            messageIDs: messages.map { message in message.id }\n        )\n    }\n\n    public func delete(messageIDs: [String]) async {\n        do {\n            try await self.store.markDeleted(messageIDs: messageIDs)\n            self.dispatchUpdateWorkRequest()\n            await self.sendUpdate(.local)\n        } catch {\n            AirshipLogger.error(\"Failed to delete messages: \\(error)\")\n        }\n    }\n\n    public func message(forBodyURL bodyURL: URL) async -> MessageCenterMessage?\n    {\n        do {\n            return try await self.store.message(forBodyURL: bodyURL)\n        } catch {\n            AirshipLogger.error(\"Failed to fetch message: \\(error)\")\n            return nil\n        }\n\n    }\n\n    public func message(forID messageID: String) async -> MessageCenterMessage?\n    {\n        do {\n            return try await self.store.message(forID: messageID)\n        } catch {\n            AirshipLogger.error(\"Failed to fetch message: \\(error)\")\n            return nil\n        }\n    }\n    \n    func saveDisplayHistory(for messageID: String, history: MessageDisplayHistory) async {\n        await updateAssociatedData(for: messageID) { associatedData in\n            associatedData.displayHistory = try JSONEncoder().encode(history)\n        }\n    }\n    \n    func saveLayoutState(for messageID: String, state: MessageCenterMessage.AssociatedData.ViewState?) async {\n        await updateAssociatedData(for: messageID) { $0.viewState = state }\n    }\n    \n    private func updateAssociatedData(for messageID: String, block: (inout MessageCenterMessage.AssociatedData) throws -> Void) async {\n        do {\n            guard var message = try await self.store.message(forID: messageID) else {\n                AirshipLogger.error(\"Failed to find message to update\")\n                return\n            }\n            \n            try block(&message.associatedData)\n            \n            try await self.store.updateMessages(\n                messages: [message],\n                lastModifiedTime: nil,\n                updateLastModifiedTime: false,\n                overwriteAssociatedData: true\n            )\n        } catch {\n            AirshipLogger.error(\"Failed to save history data message: \\(error)\")\n        }\n    }\n    \n    @MainActor\n    func getNativeStateStorage(for messageID: String) -> any LayoutDataStorage {\n        if\n            let stored = self.nativeMessageStorageValue.value,\n            stored.messageID == messageID {\n            return stored\n        }\n        \n        let storage = self.nativeStateStorageFactory(messageID)\n        self.nativeMessageStorageValue.set(storage)\n        return storage\n    }\n\n    private func getOrCreateUser(\n        forChannelID channelID: String\n    ) async -> MessageCenterUser? {\n        guard let user = await self.store.user else {\n            do {\n                AirshipLogger.debug(\"Creating Message Center user\")\n\n                let response = try await self.client.createUser(\n                    withChannelID: channelID\n                )\n                AirshipLogger.debug(\n                    \"Message Center user create request finished with response: \\(response)\"\n                )\n\n                guard let user = response.result else {\n                    return nil\n                }\n                await self.store.setUserRequireUpdate(false)\n                await self.store.saveUser(user, channelID: channelID)\n                return user\n            } catch {\n                AirshipLogger.info(\n                    \"Failed to create Message Center user: \\(error)\"\n                )\n                return nil\n            }\n        }\n\n        let requireUpdate = await self.store.userRequiredUpdate\n        let channelMismatch = await self.store.registeredChannelID != channelID\n\n        guard requireUpdate || channelMismatch else {\n            return user\n        }\n\n        do {\n            AirshipLogger.debug(\"Updating Message Center user\")\n            let response = try await self.client.updateUser(\n                user,\n                channelID: channelID\n            )\n\n            AirshipLogger.debug(\n                \"Message Center update request finished with response: \\(response)\"\n            )\n\n            guard response.isSuccess else {\n                return nil\n            }\n\n            await self.store.setUserRegisteredChannelID(channelID)\n            await self.store.setUserRequireUpdate(false)\n            return user\n        } catch {\n            AirshipLogger.info(\"Failed to update Message Center user: \\(error)\")\n            return nil\n        }\n    }\n\n    private func updateInbox() async throws -> AirshipWorkResult {\n        await self.startUpTask?.value\n\n        guard self.enabled else {\n            await self.sendUpdate(.refreshFailed)\n            return .success\n        }\n\n        guard let channelID = channel.identifier else {\n            await self.sendUpdate(.refreshFailed)\n            return .success\n        }\n\n        guard\n            let user = await getOrCreateUser(\n                forChannelID: channelID\n            )\n        else {\n            await self.sendUpdate(.refreshFailed)\n            return .failure\n        }\n\n        let syncedRead = await syncReadMessageState(\n            user: user,\n            channelID: channelID\n        )\n\n        let synedDeleted = await syncDeletedMessageState(\n            user: user,\n            channelID: channelID\n        )\n\n        let syncedList = await syncMessageList(\n            user: user,\n            channelID: channelID\n        )\n\n        if syncedList {\n            await self.sendUpdate(.refreshSucess)\n        } else {\n            await self.sendUpdate(.refreshFailed)\n        }\n\n        guard syncedRead && synedDeleted && syncedList else {\n            return .failure\n        }\n        return .success\n    }\n\n    // MARK: Enqueue tasks\n\n    private func dispatchUpdateWorkRequest(\n        conflictPolicy: AirshipWorkRequestConflictPolicy = .keepIfNotStarted,\n        requireNetwork: Bool = true\n    ) {\n        guard self.enabled else { return }\n        self.workManager.dispatchWorkRequest(\n            AirshipWorkRequest(\n                workID: self.updateWorkID,\n                requiresNetwork: requireNetwork,\n                conflictPolicy: conflictPolicy\n            )\n        )\n    }\n\n    private func syncMessageList(\n        user: MessageCenterUser,\n        channelID: String\n    ) async -> Bool {\n        do {\n            let lastModified = await self.store.lastMessageListModifiedTime\n            let response = try await self.client.retrieveMessageList(\n                user: user,\n                channelID: channelID,\n                lastModified: lastModified\n            )\n\n            guard\n                response.isSuccess || response.statusCode == 304\n            else {\n                AirshipLogger.error(\"Retrieve list message failed\")\n                return false\n            }\n\n            if response.isSuccess, let messages = response.result {\n                try await self.store.updateMessages(\n                    messages: messages,\n                    lastModifiedTime: response.headers[\"Last-Modified\"]\n                )\n            }\n            \n            return true\n        } catch {\n            AirshipLogger.error(\"Retrieve message list failed with error \\(error.localizedDescription)\")\n        }\n\n        return false\n    }\n\n    private func syncReadMessageState(\n        user: MessageCenterUser,\n        channelID: String\n    ) async -> Bool {\n        do {\n            let messages = try await self.store.fetchLocallyReadOnlyMessages()\n            guard !messages.isEmpty else {\n                return true\n            }\n\n            AirshipLogger.trace(\n                \"Synchronizing locally read messages on server. \\(messages)\"\n            )\n            let response = try await self.client.performBatchMarkAsRead(\n                forMessages: messages,\n                user: user,\n                channelID: channelID\n            )\n\n            if response.isSuccess {\n                AirshipLogger.trace(\n                    \"Successfully synchronized locally read messages on server.\"\n                )\n\n                try await self.store.markRead(\n                    messageIDs: messages.compactMap { $0.id },\n                    level: .local\n                )\n                return true\n            }\n        } catch {\n            AirshipLogger.trace(\n                \"Failed to synchronize locally read messages on server.\"\n            )\n        }\n        return false\n    }\n\n    private func syncDeletedMessageState(\n        user: MessageCenterUser,\n        channelID: String\n    ) async -> Bool {\n        do {\n\n            let messages = try await self.store.fetchLocallyDeletedMessages()\n            guard !messages.isEmpty else {\n                return true\n            }\n\n            AirshipLogger.trace(\n                \"Synchronizing locally deleted messages on server.\"\n            )\n            let response = try await self.client.performBatchDelete(\n                forMessages: messages,\n                user: user,\n                channelID: channelID\n            )\n\n            if response.isSuccess {\n                AirshipLogger.trace(\n                    \"Successfully synchronized locally deleted messages on server.\"\n                )\n\n                try await self.store.delete(\n                    messageIDs: messages.compactMap { $0.id }\n                )\n\n                return true\n            }\n\n        } catch {\n            AirshipLogger.trace(\n                \"Failed to synchronize locally deleted messages on server.\"\n            )\n        }\n        return false\n    }\n\n    private func remoteURLConfigUpdated() {\n        Task {\n            await self.store.setUserRequireUpdate(true)\n            dispatchUpdateWorkRequest(\n                conflictPolicy: .replace\n            )\n        }\n    }\n}\n\n\npublic extension AirshipNotifications {\n    \n    /// NSNotification info when the inbox is updated is updated.\n    final class MessageCenterListUpdated: NSObject {\n\n        /// NSNotification name.\n        public static let name = NSNotification.Name(\n            \"com.urbanairship.notification.message_list_updated\"\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterNativeBridgeExtension.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS)\nimport Foundation\npublic import WebKit\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Airship native bridge extension for the Message Center.\npublic final class MessageCenterNativeBridgeExtension: NSObject, NativeBridgeExtensionDelegate, Sendable {\n\n    let message: MessageCenterMessage\n    let user: MessageCenterUser\n\n    public init(\n        message: MessageCenterMessage,\n        user: MessageCenterUser\n    ) {\n        self.message = message\n        self.user = user\n    }\n\n    public func actionsMetadata(\n        for command: JavaScriptCommand,\n        webView: WKWebView\n    ) -> [String: String] {\n        return [\n            ActionArguments.inboxMessageIDMetadataKey: message.id\n        ]\n    }\n\n    public func extendJavaScriptEnvironment(\n        _ js: any JavaScriptEnvironmentProtocol,\n        webView: WKWebView\n    ) async {\n        js.add(\"getMessageId\", string: self.message.id)\n        js.add(\"getMessageTitle\", string: self.message.title)\n        js.add(\n            \"getMessageSentDateMS\",\n            number: (self.message.sentDate.timeIntervalSince1970 * 1000.0).rounded()\n        )\n        js.add(\n            \"getMessageSentDate\",\n            string: AirshipDateFormatter.string(fromDate: message.sentDate, format: .iso)\n        )\n        js.add(\"getMessageExtras\", dictionary: message.extra)\n        js.add(\"getUserId\", string: self.user.username)\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterPredicate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Predicate for filtering messages in the Message Center.\npublic protocol MessageCenterPredicate: Sendable {\n    /// Evaluate the message center message. Used to filter the message center list\n    /// - Parameters:\n    ///     - message: The message center message\n    /// - Returns: True if the message passed the evaluation, otherwise false.\n    func evaluate(message: MessageCenterMessage) -> Bool\n}\n\n// MARK: Message center predicate\nextension View {\n    /// Overrides the message center predicate\n    /// - Parameters:\n    ///     - predicate: The message center predicate\n    public func messageCenterPredicate(_ predicate: (any MessageCenterPredicate)?) -> some View {\n        environment(\\.airshipMessageCenterPredicate, predicate)\n    }\n}\n\nstruct MessageCenterPredicateKey: EnvironmentKey {\n    static let defaultValue: (any MessageCenterPredicate)? = nil\n}\n\nextension EnvironmentValues {\n    /// Airship message center predicate environment value\n    public var airshipMessageCenterPredicate: (any MessageCenterPredicate)? {\n        get { self[MessageCenterPredicateKey.self] }\n        set { self[MessageCenterPredicateKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterSDKModule.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\npublic import Foundation\n\n/// AirshipMessageCenter module loader.\n/// @note For internal use only. :nodoc:\n@objc(UAMessageCenterSDKModule)\npublic class MessageCenterSDKModule: NSObject, AirshipSDKModule {\n\n    public let actionsManifest: (any ActionsManifest)? = MessageCenterActionsManifest()\n    public let components: [any AirshipComponent]\n\n    init(messageCenter: DefaultMessageCenter) {\n        self.components = [MessageCenterComponent(messageCenter: messageCenter)]\n    }\n\n    public static func load(_ args: AirshiopModuleLoaderArgs) -> (any AirshipSDKModule)? {\n        let messageCenter = DefaultMessageCenter(\n            dataStore: args.dataStore,\n            config: args.config,\n            channel: args.channel,\n            privacyManager: args.privacyManager,\n            workManager: args.workManager,\n            meteredUsage: args.meteredUsage,\n            analytics: args.analytics\n        )\n\n        return MessageCenterSDKModule(messageCenter: messageCenter)\n    }\n}\n\n\nfileprivate struct MessageCenterActionsManifest : ActionsManifest {\n    var manifest: [[String] : () -> ActionEntry] = [\n        MessageCenterAction.defaultNames: {\n            return ActionEntry(\n                action: MessageCenterAction()\n            )\n        }\n    ]\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageCenterStore.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nenum MessageCenterStoreError: Error {\n    case coreDataUnavailble\n    case coreDataError\n}\n\nenum MessageCenterStoreLevel: Int {\n    case local\n    case global\n}\n\n\nactor MessageCenterStore {\n\n    /// User defualts key to clear the keychain of Airship values for one app run. Used for testing. :nodoc:\n    private static let resetKeychainKey = \"com.urbanairship.reset_keychain\"\n\n    private static let coreDataStoreName = \"Inbox-%@.sqlite\"\n    private static let lastMessageListModifiedTime =\n        \"UALastMessageListModifiedTime\"\n    private static let userRegisteredChannelID = \"UAUserRegisteredChannelID\"\n    private static let userRequireUpdate = \"UAUserRequireUpdate\"\n\n    private let coreData: UACoreData?\n    private let config: RuntimeConfig\n    private let dataStore: PreferenceDataStore\n    private let keychainAccess: any AirshipKeychainAccessProtocol\n    private let date: any AirshipDateProtocol\n\n    private nonisolated let inMemory: Bool\n    \n    var registeredChannelID: String? {\n        return self.dataStore.string(\n            forKey: MessageCenterStore.userRegisteredChannelID\n        )\n    }\n\n    private var _user: MessageCenterUser? = nil\n    var user: MessageCenterUser? {\n        get async {\n            // Clearing the keychain\n            if UserDefaults.standard.bool(forKey: MessageCenterStore.resetKeychainKey) == true {\n                AirshipLogger.debug(\"Deleting the keychain credentials\")\n                await resetUser()\n                UserDefaults.standard.removeObject(\n                    forKey: MessageCenterStore.resetKeychainKey\n                )\n            }\n\n            if let user = _user {\n                return user\n            }\n\n            let credentials = await self.keychainAccess.readCredentails(\n                identifier: self.config.appCredentials.appKey,\n                appKey: self.config.appCredentials.appKey\n            )\n\n            if let credentials = credentials {\n                _user = MessageCenterUser(\n                    username: credentials.username,\n                    password: credentials.password\n                )\n            }\n            return _user\n        }\n    }\n\n    var userRequiredUpdate: Bool {\n        return self.dataStore.bool(forKey: MessageCenterStore.userRequireUpdate)\n    }\n\n    var lastMessageListModifiedTime: String? {\n        return self.dataStore.string(\n            forKey: MessageCenterStore.lastMessageListModifiedTime\n        )\n    }\n\n    \n    var messages: [MessageCenterMessage] {\n        get async {\n            let predicate = AirshipCoreDataPredicate(\n                format:\n                    \"(messageExpiration == nil || messageExpiration >= %@) && (deletedClient == NO || deletedClient == nil)\",\n                args: [self.date.now]\n            )\n            \n            let messages = try? await fetchMessages(withPredicate: predicate)\n            return messages ?? []\n        }\n    }\n\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.config = config\n        self.dataStore = dataStore\n        self.keychainAccess = AirshipKeychainAccess.shared\n        self.date = date\n        \n        let modelURL = AirshipMessageCenterResources.bundle\n            .url(\n                forResource: \"UAInbox\",\n                withExtension: \"momd\"\n            )\n        if let modelURL = modelURL {\n            let storeName = String(\n                format: MessageCenterStore.coreDataStoreName,\n                config.appCredentials.appKey\n            )\n            self.coreData = UACoreData(\n                name: \"UAInbox\",\n                modelURL: modelURL,\n                inMemory: false,\n                stores: [storeName]\n            )\n        } else {\n            self.coreData = nil\n        }\n        self.inMemory = false\n    }\n\n    init(\n        config: RuntimeConfig,\n        dataStore: PreferenceDataStore,\n        coreData: UACoreData,\n        date: any AirshipDateProtocol = AirshipDate.shared\n    ) {\n        self.inMemory = coreData.inMemory\n        self.config = config\n        self.dataStore = dataStore\n        self.coreData = coreData\n        self.keychainAccess = AirshipKeychainAccess.shared\n        self.date = date\n    }\n\n    var unreadCount: Int {\n        get async {\n            guard let coreData = self.coreData else {\n                return 0\n            }\n\n            let result: Int? = try? await coreData.performWithResult { context in\n                let request: NSFetchRequest<InboxMessageData> =\n                    InboxMessageData.fetchRequest()\n                request.predicate = NSPredicate(format: \"unread == YES\")\n                request.includesPropertyValues = false\n                let fetchedMessages = try context.fetch(request)\n                return fetchedMessages.count\n            }\n\n            return result ?? 0\n        }\n    }\n\n    func message(forID messageID: String) async throws -> MessageCenterMessage?\n    {\n        let predicate = AirshipCoreDataPredicate(\n            format:\n                \"messageID == %@ && (messageExpiration == nil || messageExpiration >= %@) && (deletedClient == NO || deletedClient == nil)\",\n            args: [\n                messageID,\n                self.date.now\n            ]\n        )\n\n        let messages = try await fetchMessages(withPredicate: predicate)\n        return messages.first\n    }\n\n    func message(forBodyURL bodyURL: URL) async throws -> MessageCenterMessage?\n    {\n        let predicate = AirshipCoreDataPredicate(\n            format:\n                \"messageBodyURL == %@ && (messageExpiration == nil || messageExpiration >= %@) && (deletedClient == NO || deletedClient == nil)\",\n            args: [\n                bodyURL,\n                self.date.now\n            ]\n        )\n\n        let messages = try await fetchMessages(withPredicate: predicate)\n        return messages.first\n    }\n\n    func markRead(\n        messageIDs: [String],\n        level: MessageCenterStoreLevel\n    ) async throws {\n        guard let coreData = self.coreData else {\n            throw MessageCenterStoreError.coreDataUnavailble\n        }\n\n        AirshipLogger.trace(\"Mark messages with IDs: \\(messageIDs) read\")\n\n        try await coreData.perform { context in\n            let request = InboxMessageData.batchUpdateRequest()\n            request.predicate = NSPredicate(\n                format: \"messageID IN %@\",\n                messageIDs\n            )\n            if level == .local {\n                request.propertiesToUpdate = [\"unreadClient\": false]\n            } else if level == .global {\n                request.propertiesToUpdate = [\"unread\": false]\n            }\n\n            request.resultType = .updatedObjectsCountResultType\n            try context.execute(request)\n        }\n    }\n\n    func delete(messageIDs: [String]) async throws {\n        guard let coreData = self.coreData else {\n            throw MessageCenterStoreError.coreDataUnavailble\n        }\n\n        AirshipLogger.trace(\"Deleting messages with IDs: \\(messageIDs)\")\n\n        try await coreData.perform { context in\n            try self.delete(\n                predicate: NSPredicate(\n                    format: \"messageID IN %@\",\n                    messageIDs\n                ),\n                useBatch: !self.inMemory,\n                context: context\n            )\n        }\n    }\n\n    func markDeleted(messageIDs: [String]) async throws {\n        guard let coreData = self.coreData else {\n            throw MessageCenterStoreError.coreDataUnavailble\n        }\n\n        AirshipLogger.trace(\"Mark messages with IDs: \\(messageIDs) deleted\")\n\n        try await coreData.perform { context in\n            let request = InboxMessageData.batchUpdateRequest()\n            request.predicate = NSPredicate(\n                format: \"messageID IN %@\",\n                messageIDs\n            )\n            request.propertiesToUpdate = [\"deletedClient\": true]\n            request.resultType = .updatedObjectsCountResultType\n            try context.execute(request)\n        }\n    }\n\n    func fetchLocallyDeletedMessages() async throws -> [MessageCenterMessage] {\n        let predicate = AirshipCoreDataPredicate(\n            format: \"deletedClient == YES\"\n        )\n\n        return try await fetchMessages(withPredicate: predicate)\n    }\n\n    func fetchLocallyReadOnlyMessages() async throws -> [MessageCenterMessage] {\n        let predicate = AirshipCoreDataPredicate(\n            format: \"unreadClient == NO && unread == YES\"\n        )\n\n        return try await fetchMessages(withPredicate: predicate)\n    }\n\n    func saveUser(_ user: MessageCenterUser, channelID: String) async {\n        let result = await self.keychainAccess.writeCredentials(\n            AirshipKeychainCredentials(\n                username: user.username,\n                password: user.password\n            ),\n            identifier: self.config.appCredentials.appKey,\n            appKey: self.config.appCredentials.appKey\n        )\n\n        if !result {\n            AirshipLogger.error(\"Failed to write user credentials\")\n        }\n\n        setUserRegisteredChannelID(channelID)\n        _user = user\n    }\n\n    func resetUser() async {\n        _user = nil\n        await self.keychainAccess.deleteCredentials(\n            identifier: self.config.appCredentials.appKey,\n            appKey: self.config.appCredentials.appKey\n        )\n    }\n\n    func setUserRequireUpdate(_ value: Bool) {\n        self.dataStore.setBool(\n            value,\n            forKey: MessageCenterStore.userRequireUpdate\n        )\n    }\n\n    func setUserRegisteredChannelID(_ value: String) {\n        self.dataStore.setValue(\n            value,\n            forKey: MessageCenterStore.userRegisteredChannelID\n        )\n    }\n\n    func setLastMessageListModifiedTime(_ value: String?) {\n        self.dataStore.setValue(\n            value,\n            forKey: MessageCenterStore.lastMessageListModifiedTime\n        )\n    }\n\n    func clearLastModified(username: String) {\n        self.dataStore.removeObject(\n            forKey: MessageCenterStore.lastMessageListModifiedTime\n        )\n    }\n\n    private func fetchMessages(\n        withPredicate predicate: AirshipCoreDataPredicate? = nil\n    ) async throws -> [MessageCenterMessage] {\n        guard let coreData = self.coreData else {\n            throw MessageCenterStoreError.coreDataUnavailble\n        }\n\n        AirshipLogger.trace(\n            \"Fetching message center with predicate: \\(String(describing: predicate))\"\n        )\n\n        return try await coreData.performWithResult { context in\n            let request: NSFetchRequest<InboxMessageData> =\n                InboxMessageData.fetchRequest()\n\n            request.sortDescriptors = [\n                NSSortDescriptor(\n                    key: \"messageSent\",\n                    ascending: false\n                )\n            ]\n\n            if let predicate = predicate {\n                request.predicate = predicate.toNSPredicate()\n            }\n\n            let fetchedMessages = try context.fetch(request)\n            return fetchedMessages.compactMap { data in data.message() }\n        }\n    }\n\n    nonisolated private func delete(\n        predicate: NSPredicate,\n        useBatch: Bool,\n        context: NSManagedObjectContext\n    ) throws {\n        if useBatch {\n            let request = InboxMessageData.fetchRequest()\n            request.predicate = predicate\n            let deleteRequest = NSBatchDeleteRequest(fetchRequest: request)\n            try context.execute(deleteRequest)\n        } else {\n            let request: NSFetchRequest<InboxMessageData> =\n                InboxMessageData.fetchRequest()\n            request.predicate = predicate\n            request.includesPropertyValues = false\n            let fetchedMessages = try context.fetch(request)\n            fetchedMessages.forEach { message in\n                context.delete(message)\n            }\n        }\n\n    }\n\n    nonisolated private func getOrCreateMessageEntity(\n        messageID: String,\n        context: NSManagedObjectContext\n    ) throws -> InboxMessageData {\n        let request: NSFetchRequest<InboxMessageData> =\n            InboxMessageData.fetchRequest()\n        request.predicate = NSPredicate(format: \"messageID == %@\", messageID)\n        request.fetchLimit = 1\n\n        let response = try context.fetch(request)\n        if let existing = response.first {\n            return existing\n        }\n\n        guard\n            let data = NSEntityDescription.insertNewObject(\n                forEntityName: InboxMessageData.messageDataEntity,\n                into: context\n            ) as? InboxMessageData\n        else {\n            throw MessageCenterStoreError.coreDataError\n        }\n\n        return data\n    }\n\n    func updateMessages(\n        messages: [MessageCenterMessage],\n        lastModifiedTime: String?,\n        updateLastModifiedTime: Bool = true,\n        overwriteAssociatedData: Bool = false\n    ) async throws {\n        guard let coreData = self.coreData else {\n            throw MessageCenterStoreError.coreDataUnavailble\n        }\n\n        try await coreData.perform { context in\n            // Track the response messageIDs so we can remove any messages that are\n            // no longer in the response.\n            try messages.forEach { message in\n                let data = try self.getOrCreateMessageEntity(\n                    messageID: message.id,\n                    context: context\n                )\n\n                data.messageID = message.id\n                data.title = message.title\n                data.contentType = message.contentType.stringValue\n                data.extra = AirshipJSON.object(message.extra.mapValues { .string($0) }).toDataLoggingError()\n                data.messageBodyURL = message.bodyURL\n                data.messageURL = message.messageURL\n                data.unread = message.unread\n                data.messageSent = message.sentDate\n                data.rawMessageObject = message.rawMessageObject.toDataLoggingError()\n                data.messageReporting = message.messageReporting?.toDataLoggingError()\n                data.messageExpiration = message.expirationDate\n                \n                if overwriteAssociatedData {\n                    data.associatedData = message.associatedData.encoded()\n                }\n            }\n\n            // Delete any messages no longer in the listing\n            let messageIDs = messages.map { message in message.id }\n            try self.delete(\n                predicate: NSPredicate(\n                    format: \"NOT(messageID IN %@)\",\n                    messageIDs\n                ),\n                useBatch: !self.inMemory,\n                context: context\n            )\n        }\n        \n        if updateLastModifiedTime {\n            self.setLastMessageListModifiedTime(lastModifiedTime)\n        }\n    }\n}\n\nextension InboxMessageData {\n    fileprivate func message() -> MessageCenterMessage? {\n        guard\n            let title = self.title,\n            let messageID = self.messageID,\n            let messageBodyURL = self.messageBodyURL,\n            let messageReporting = self.messageReporting,\n            let messageURL = self.messageURL,\n            let messageSent = self.messageSent,\n            let rawMessageObject = self.rawMessageObject,\n            let rawJSON = AirshipJSON.fromDataLoggingError(data:rawMessageObject)\n        else {\n            AirshipLogger.error(\"Invalid message data\")\n            return nil\n        }\n        \n        let contentTypeString = contentType ?? rawJSON.object?[\"content_type\"]?.string\n        let contentType: MessageCenterMessage.ContentType = contentTypeString.map {\n            MessageCenterMessage.ContentType.parse($0)\n        } ?? .unknown(nil)\n\n        return MessageCenterMessage(\n            title: title,\n            id: messageID,\n            contentType: contentType,\n            extra: AirshipJSON.fromDataLoggingError(data:self.extra)?.unWrap() as? [String: String] ?? [:],\n            bodyURL: messageBodyURL,\n            expirationDate: self.messageExpiration,\n            messageReporting: AirshipJSON.fromDataLoggingError(data:messageReporting),\n            unread: (self.unread && self.unreadClient),\n            sentDate: messageSent,\n            messageURL: messageURL,\n            rawMessageObject: rawJSON,\n            associatedData: self.associatedData\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/MessageViewAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nprotocol MessageViewAnalytics: ThomasLayoutMessageAnalyticsProtocol {\n}\n\nfinal class DefaultMessageViewAnalytics: MessageViewAnalytics {\n    private static let impressionReportInterval: TimeInterval = 30 * 60 // 30 mins\n    private static let defaultProductID = \"default_native_mc\"\n    \n    private let messageID: ThomasLayoutEventMessageID\n    private let productID: String\n    private let reportingContext: AirshipJSON?\n    private let eventRecorder: any ThomasLayoutEventRecorderProtocol\n    private let eventSource: ThomasLayoutEventSource\n    \n    private let historyStorage: any MessageDisplayHistoryStoreProtocol\n    private let displayContext: AirshipMainActorValue<ThomasLayoutEventContext.Display?>\n    private let sessionID: String\n    private let date: any AirshipDateProtocol\n    \n    private let queue: AirshipAsyncSerialQueue\n    \n    init(\n        message: MessageCenterMessage,\n        eventRecorder: any ThomasLayoutEventRecorderProtocol,\n        historyStorage: any MessageDisplayHistoryStoreProtocol,\n        eventSource: ThomasLayoutEventSource = .airship,\n        date: any AirshipDateProtocol = AirshipDate.shared,\n        sessionID: String = UUID().uuidString,\n        queue: AirshipAsyncSerialQueue = AirshipAsyncSerialQueue()\n    ) {\n        self.messageID = .airship(identifier: message.id, campaigns: nil)\n        self.productID = message.productID ?? Self.defaultProductID\n        self.reportingContext = message.messageReporting\n        self.eventRecorder = eventRecorder\n        self.eventSource = eventSource\n        self.date = date\n        self.historyStorage = historyStorage\n        self.sessionID = sessionID\n        self.displayContext = .init(nil)\n        self.queue = queue\n        \n        queue.enqueue { [messageID = messageID.identifier, sessionID, weak self] in\n            guard let history = await self?.historyStorage.get(scheduleID: messageID) else {\n                return\n            }\n            \n            await MainActor.run {\n                let lastTriggerSessionId = history.lastDisplay?.triggerSessionID\n                self?.displayContext.set(\n                    ThomasLayoutEventContext.Display(\n                        triggerSessionID: lastTriggerSessionId ?? sessionID,\n                        isFirstDisplay: history.lastDisplay == nil,\n                        isFirstDisplayTriggerSessionID: lastTriggerSessionId == history.lastImpression?.triggerSessionID\n                    )\n                )\n            }\n        }\n    }\n    \n    @MainActor\n    func recordEvent(_ event: any ThomasLayoutEvent, layoutContext: ThomasLayoutContext?) {\n        \n        let now = date.now\n        \n        queue.enqueue { @MainActor [weak self] in\n            guard let self = self else { return }\n            \n            if event is ThomasLayoutDisplayEvent {\n                \n                var history = await self.historyStorage.get(scheduleID: messageID.identifier)\n                \n                if let lastDisplay = history.lastDisplay {\n                    if self.sessionID != lastDisplay.triggerSessionID {\n                        self.displayContext.update { value in\n                            value?.isFirstDisplay = false\n                            value?.isFirstDisplayTriggerSessionID = false\n                        }\n                    } else {\n                        self.displayContext.update { value in\n                            value?.isFirstDisplay = false\n                        }\n                    }\n                }\n\n                if (recordImpression(date: now, history: history)) {\n                    history.lastImpression = MessageDisplayHistory\n                        .LastImpression(\n                            date: now,\n                            triggerSessionID: self.sessionID\n                        )\n                }\n                \n                history.lastDisplay = MessageDisplayHistory.LastDisplay(\n                    triggerSessionID: self.sessionID\n                )\n                \n                self.historyStorage.set(history, scheduleID: messageID.identifier)\n            }\n\n            let data = ThomasLayoutEventData(\n                event: event,\n                context: ThomasLayoutEventContext.makeContext(\n                    reportingContext: reportingContext,\n                    experimentsResult: nil,\n                    layoutContext: layoutContext,\n                    displayContext: displayContext.value\n                ),\n                source: eventSource,\n                messageID: messageID,\n                renderedLocale: nil\n            )\n            \n            eventRecorder.recordEvent(inAppEventData: data)\n        }\n    }\n    \n    private func shouldRecordImpression(history: MessageDisplayHistory) -> Bool {\n        if\n            let lastImpression = history.lastImpression,\n            date.now.timeIntervalSince(lastImpression.date) < Self.impressionReportInterval {\n            return false\n        } else {\n            return true\n        }\n    }\n    \n    private func recordImpression(date: Date, history: MessageDisplayHistory) -> Bool {\n        guard shouldRecordImpression(history: history) else { return false }\n        \n        let event = AirshipMeteredUsageEvent(\n            eventID: UUID().uuidString,\n            entityID: self.messageID.identifier,\n            usageType: .inAppExperienceImpression,\n            product: productID,\n            reportingContext: reportingContext,\n            timestamp: date,\n            contactID: nil\n        )\n        \n        self.eventRecorder.recordImpressionEvent(event)\n\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Model/InboxMessageData.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport CoreData\nimport Foundation\n\n/// CoreData class representing the backing data for a UAInboxMessage.\n/// This class should not ordinarily be used directly.\n@objc(UAInboxMessageData)\nclass InboxMessageData: NSManagedObject {\n\n    static let messageDataEntity = \"UAInboxMessage\"\n\n    @nonobjc class func fetchRequest<T>() -> NSFetchRequest<T> {\n        return NSFetchRequest<T>(entityName: InboxMessageData.messageDataEntity)\n    }\n\n    @nonobjc class func batchUpdateRequest() -> NSBatchUpdateRequest {\n        return NSBatchUpdateRequest(\n            entityName: InboxMessageData.messageDataEntity\n        )\n    }\n\n    /// The Airship message ID.\n    /// This ID may be used to match an incoming push notification to a specific message.\n    @NSManaged var messageID: String?\n\n    /// The URL for the message body itself.\n    /// This URL may only be accessed with Basic Auth credentials set to the user ID and password.\n    @NSManaged var messageBodyURL: URL?\n\n    /// The URL for the message.\n    /// This URL may only be accessed with Basic Auth credentials set to the user ID and password.\n    @NSManaged var messageURL: URL?\n\n    /// The data object that contains the message ID, the group ID and the variant ID.\n    @NSManaged var messageReporting: Data?\n\n    /// YES if the message is unread, otherwise NO.\n    @NSManaged var unread: Bool\n\n    /// YES if the message is unread on the client, otherwise NO.\n    @NSManaged var unreadClient: Bool\n\n    /// YES if the message is deleted, otherwise NO.\n    @NSManaged var deletedClient: Bool\n\n    /// The date and time the message was sent (UTC)\n    @NSManaged var messageSent: Date?\n\n    /// The date and time the message will expire.\n    /// A nil value indicates it will never expire.\n    @NSManaged var messageExpiration: Date?\n\n    /// The message title\n    @NSManaged var title: String?\n\n    /// The message's extra dictionary. This dictionary can be populated\n    /// with arbitrary key-value data at the time the message is composed.\n    @NSManaged var extra: Data?\n\n    ///  The raw message dictionary. This is the dictionary that originally created the message.\n    ///  It can contain more values then the message.\n    @NSManaged var rawMessageObject: Data?\n    \n    /// The message content type\n    @NSManaged var contentType: String?\n    \n    /// The message associated data(display history)\n    @NSManaged var associatedData: Data?\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Model/MessageCenterMessage.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Message center message.\npublic struct MessageCenterMessage: Sendable, Equatable, Identifiable, Hashable {\n    private static let productIDKey = \"product_id\"\n\n    /// The message title.\n    public var title: String\n\n    /// The Airship message ID.\n    /// This ID may be used to match an incoming push notification to a specific message.\n    public var id: String\n\n    /// The message's extra dictionary.\n    /// This dictionary can be populated with arbitrary key-value data at the time the message is composed.\n    public var extra: [String: String]\n\n    /// The URL for the message body itself.\n    /// This URL may only be accessed with Basic Auth credentials set to the user ID and password.\n    public var bodyURL: URL\n\n    /// The date and time the message will expire.\n    /// A nil value indicates it will never expire.\n    public var expirationDate: Date?\n\n    /// The date and time the message was sent (UTC).\n    public var sentDate: Date\n\n    /// The unread status of the message.\n    /// `true` if the message is unread, otherwise `false`.\n    public var unread: Bool\n    \n    /// The message center content type\n    public let contentType: ContentType\n\n    /// The reporting data of the message.\n    let messageReporting: AirshipJSON?\n\n    /// The URL for the message.\n    /// This URL may only be accessed with Basic Auth credentials set to the user ID and password.\n    let messageURL: URL\n\n    /// The raw message dictionary.\n    /// This is the dictionary that originally created the message.\n    /// It can contain more values than the message.\n    let rawMessageObject: AirshipJSON\n    \n    /// The message associated data\n    var associatedData: AssociatedData\n    \n    public enum ContentType: Sendable, Hashable, Codable {\n        case html\n        case plain\n        case native(version: Int)\n        case unknown(String?)\n\n        var stringValue: String? {\n            switch self {\n            case .html: return \"text/html\"\n            case .plain: return \"text/plain\"\n            case .native(let version): return \"application/vnd.urbanairship.thomas+json;version=\\(version);\"\n            case .unknown(let value): return value\n            }\n        }\n\n        static let nativeContentTypePrefix: String = \"application/vnd.urbanairship.thomas+json\"\n\n        public func encode(to encoder: any Encoder) throws {\n            var container = encoder.singleValueContainer()\n            try container.encode(self.stringValue)\n        }\n\n        static func parse(_ value: String) -> ContentType {\n            if value == \"text/html\" {\n                return .html\n            } else if value == \"text/plain\" {\n                return .plain\n            } else if value.hasPrefix(Self.nativeContentTypePrefix),\n                      let version = value\n                        .replacingOccurrences(of: \" \", with: \"\")\n                        .components(separatedBy: \";\")\n                        .last(where: { $0.hasPrefix(\"version\") })?\n                        .components(separatedBy: \"=\")\n                        .last\n                        .flatMap(Int.init) {\n                return .native(version: version)\n            } else {\n                return .unknown(value)\n            }\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let value = try decoder.singleValueContainer().decode(String.self)\n            self = Self.parse(value)\n        }\n\n    }\n\n    init(\n        title: String,\n        id: String,\n        contentType: ContentType,\n        extra: [String: String],\n        bodyURL: URL,\n        expirationDate: Date?,\n        messageReporting: AirshipJSON?,\n        unread: Bool,\n        sentDate: Date,\n        messageURL: URL,\n        rawMessageObject: AirshipJSON,\n        associatedData: Data? = nil\n    ) {\n        self.title = title\n        self.id = id\n        self.contentType = contentType\n        self.extra = extra\n        self.bodyURL = bodyURL\n        self.expirationDate = expirationDate\n        self.messageReporting = messageReporting\n        self.unread = unread\n        self.sentDate = sentDate\n        self.messageURL = messageURL\n        self.rawMessageObject = rawMessageObject\n        self.associatedData = associatedData\n            .flatMap { try? JSONDecoder().decode(MessageCenterMessage.AssociatedData.self, from: $0) }\n            ?? AssociatedData()\n    }\n    \n    public static func == (lhs: MessageCenterMessage, rhs: MessageCenterMessage) -> Bool {\n        return lhs.rawMessageObject == rhs.rawMessageObject && lhs.unread == rhs.unread\n    }\n\n    public func hash(into hasher: inout Hasher) {\n        hasher.combine(rawMessageObject)\n        hasher.combine(unread)\n    }\n    \n    struct AssociatedData: Codable, Sendable, Equatable, Hashable {\n        var displayHistory: Data?\n        var viewState: ViewState?\n        \n        func encoded() -> Data? {\n            return try? JSONEncoder().encode(self)\n        }\n        \n        struct ViewState: Codable, Sendable, Equatable, Hashable {\n            var restoreID: String\n            var state: Data?\n        }\n    }\n}\n\nextension MessageCenterMessage {\n    /// The list icon of the message. `nil` if there is none.\n    public var listIcon: String? {\n        guard\n            let rawMessage = self.rawMessageObject.unWrap() as? [String: Any],\n            let icons = rawMessage[\"icons\"] as? [String: String],\n            let listIcon = icons[\"list_icon\"]\n        else {\n            return nil\n        }\n\n        return listIcon\n    }\n\n    /// The subtitle of the message. `nil` if there is none.\n    public var subtitle: String? {\n        return self.extra[\"com.urbanairship.listing.field1\"]\n    }\n\n    /// Parses the message ID.\n    /// - Parameters:\n    ///     - userInfo: The notification user info.\n    /// - Returns: The message ID.\n    public static func parseMessageID(userInfo: [AnyHashable: Any]) -> String? {\n        guard let uamid = userInfo[\"_uamid\"] else {\n            return nil\n        }\n\n        if let uamid = uamid as? [String] {\n            return uamid.first\n        } else if let uamid = uamid as? String {\n            return uamid\n        } else {\n            return nil\n        }\n    }\n\n    /// Tells if the message is expired.\n    /// `true` if the message is expired, otherwise `false`.\n    public var isExpired: Bool {\n        if let messageExpiration = self.expirationDate {\n            let result = messageExpiration.compare(AirshipDate().now)\n            return (result == .orderedAscending || result == .orderedSame)\n        }\n        return false\n    }\n    \n    var productID: String? {\n        return self.rawMessageObject.object?[Self.productIDKey]?.string\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Model/MessageCenterUser.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Model object for holding user data.\npublic struct MessageCenterUser: Codable, Sendable, Equatable {\n\n    /// The username.\n    public var password: String\n\n    /// The password.\n    public var username: String\n\n    /// - Note: for internal use only.  :nodoc:\n    init(username: String, password: String) {\n        self.username = username\n        self.password = password\n    }\n\n    private enum CodingKeys: String, CodingKey {\n        case username = \"user_id\"\n        case password = \"password\"\n    }\n}\n\nextension MessageCenterUser {\n    public var basicAuthString: String {\n        return AirshipUtils.authHeader(\n            username: self.username,\n            password: self.password\n        ) ?? \"\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Model/UAInboxDataMapping.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport CoreData\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif  \n\n@objc(UAInboxDataMappingV2toV4)\nclass UAInboxDataMappingV2toV4: NSEntityMigrationPolicy {\n\n    override func createDestinationInstances(\n        forSource source: NSManagedObject,\n        in mapping: NSEntityMapping,\n        manager: NSMigrationManager\n    ) throws {\n\n        /// extras -> JSON Data\n        /// rawMessageObject -> JSON Data\n        /// messageReporting -> JSON Data\n\n        guard source.entity.name == InboxMessageData.messageDataEntity else {\n            return\n        }\n\n        let messageID = source.value(forKey: \"messageID\") as? String\n        let messageBodyURL = source.value(forKey: \"messageBodyURL\") as? URL\n        let messageURL = source.value(forKey: \"messageURL\") as? URL\n        let messageReporting = source.value(forKey: \"messageReporting\") as? [String: Any]\n        let unread = source.value(forKey: \"unread\") as? Bool\n        let unreadClient = source.value(forKey: \"unreadClient\") as? Bool\n        let deletedClient = source.value(forKey: \"deletedClient\") as? Bool\n        let messageSent = source.value(forKey: \"messageSent\") as? Date\n        let messageExpiration = source.value(forKey: \"messageExpiration\") as? Date\n        let title = source.value(forKey: \"title\") as? String\n        let extra = source.value(forKey: \"extra\") as? [String: String]\n        let rawMessageObject = source.value(forKey: \"rawMessageObject\") as? [String: Any]\n\n\n        let newEntity = NSEntityDescription.insertNewObject(\n            forEntityName: InboxMessageData.messageDataEntity,\n            into: manager.destinationContext\n        )\n\n        newEntity.setValue(messageID, forKey: \"messageID\")\n        newEntity.setValue(messageBodyURL, forKey: \"messageBodyURL\")\n        newEntity.setValue(messageURL, forKey: \"messageURL\")\n        newEntity.setValue(AirshipJSONUtils.toData(messageReporting), forKey: \"messageReporting\")\n        newEntity.setValue(unread, forKey: \"unread\")\n        newEntity.setValue(unreadClient, forKey: \"unreadClient\")\n        newEntity.setValue(deletedClient, forKey: \"deletedClient\")\n        newEntity.setValue(messageSent, forKey: \"messageSent\")\n        newEntity.setValue(messageExpiration, forKey: \"messageExpiration\")\n        newEntity.setValue(title, forKey: \"title\")\n        newEntity.setValue(AirshipJSONUtils.toData(extra), forKey: \"extra\")\n        newEntity.setValue(AirshipJSONUtils.toData(rawMessageObject), forKey: \"rawMessageObject\")\n        \n        manager.associate(sourceInstance: source, withDestinationInstance: newEntity, for: mapping)\n    }\n}\n\n@objc(UAInboxDataMappingV1toV4)\nclass UAInboxDataMappingV1toV4: NSEntityMigrationPolicy {\n\n    override func createDestinationInstances(\n        forSource source: NSManagedObject,\n        in mapping: NSEntityMapping,\n        manager: NSMigrationManager\n    ) throws {\n\n        /// extras -> Json Data\n        /// rawMessageObject -> Json Data\n\n        guard source.entity.name == InboxMessageData.messageDataEntity else {\n            return\n        }\n\n        let messageID = source.value(forKey: \"messageID\") as? String\n        let messageBodyURL = source.value(forKey: \"messageBodyURL\") as? URL\n        let messageURL = source.value(forKey: \"messageURL\") as? URL\n        let unread = source.value(forKey: \"unread\") as? Bool\n        let unreadClient = source.value(forKey: \"unreadClient\") as? Bool\n        let deletedClient = source.value(forKey: \"deletedClient\") as? Bool\n        let messageSent = source.value(forKey: \"messageSent\") as? Date\n        let messageExpiration = source.value(forKey: \"messageExpiration\") as? Date\n        let title = source.value(forKey: \"title\") as? String\n        let extra = source.value(forKey: \"extra\") as? [String: String]\n        let rawMessageObject = source.value(forKey: \"rawMessageObject\") as? [String: Any]\n\n        let newEntity = NSEntityDescription.insertNewObject(\n            forEntityName: InboxMessageData.messageDataEntity,\n            into: manager.destinationContext\n        )\n\n        newEntity.setValue(messageID, forKey: \"messageID\")\n        newEntity.setValue(messageBodyURL, forKey: \"messageBodyURL\")\n        newEntity.setValue(messageURL, forKey: \"messageURL\")\n        newEntity.setValue(unread, forKey: \"unread\")\n        newEntity.setValue(unreadClient, forKey: \"unreadClient\")\n        newEntity.setValue(deletedClient, forKey: \"deletedClient\")\n        newEntity.setValue(messageSent, forKey: \"messageSent\")\n        newEntity.setValue(messageExpiration, forKey: \"messageExpiration\")\n        newEntity.setValue(title, forKey: \"title\")\n        newEntity.setValue(AirshipJSONUtils.toData(extra), forKey: \"extra\")\n        newEntity.setValue(AirshipJSONUtils.toData(rawMessageObject), forKey: \"rawMessageObject\")\n\n        manager.associate(sourceInstance: source, withDestinationInstance: newEntity, for: mapping)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/StateStore/NativeLayoutPersistentDataStore.swift",
    "content": "// Copyright Urban Airship and Contributors\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nfinal class NativeLayoutPersistentDataStore: LayoutDataStorage {\n    let messageID: String\n    \n    private var restoreID: String? = nil\n    private var storage: [String: Data] = [:]\n    \n    private let save: @Sendable (MessageCenterMessage.AssociatedData.ViewState?) -> Void\n    private let fetch: @Sendable () async -> MessageCenterMessage.AssociatedData.ViewState?\n    \n    init(\n        messageID: String,\n        onSave: @Sendable @escaping (MessageCenterMessage.AssociatedData.ViewState?) -> Void,\n        onFetch: @Sendable @escaping () async -> MessageCenterMessage.AssociatedData.ViewState?\n    ) {\n        self.messageID = messageID\n        self.save = onSave\n        self.fetch = onFetch\n    }\n    \n    func prepare(restoreID: String) async {\n        self.restoreID = restoreID\n        \n        guard\n            let saved = await fetch(),\n            saved.restoreID == restoreID\n        else {\n            self.clear()\n            return\n        }\n        \n        if\n            let data = saved.state,\n            let decoded = try? JSONDecoder().decode([String: Data].self, from: data) {\n            self.storage = decoded\n        }\n    }\n    \n    func store(_ state: Data?, key: String) {\n        guard let restoreID else { return }\n        \n        self.storage[key] = state\n        \n        let state = makeViewState(restoreID: restoreID)\n        storeState(state)\n    }\n    \n    func retrieve(_ key: String) -> Data? {\n        //assume storage is preloaded\n        return self.storage[key]\n    }\n    \n    func clear() {\n        self.storage.removeAll()\n        \n        if let restoreID {\n            storeState(.init(restoreID: restoreID))\n        } else {\n            storeState(nil)\n        }\n    }\n    \n    private func storeState(_ state: MessageCenterMessage.AssociatedData.ViewState?) {\n        save(state)\n    }\n    \n    private func makeViewState(restoreID: String) -> MessageCenterMessage.AssociatedData.ViewState? {\n        let data = try? JSONEncoder().encode(self.storage)\n        \n        return .init(\n            restoreID: restoreID,\n            state: data\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Theme/MessageCenterNavigationAppearance.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nimport SwiftUI\n\n/// Resolves the effective navigation styling by prioritizing detected\n/// system appearance over the Airship theme.\ninternal struct MessageCenterNavigationAppearance {\n    let theme: MessageCenterTheme\n    let colorScheme: ColorScheme\n\n    // Detected system properties (optional)\n    var barTintColor: Color?\n    var barBackgroundColor: Color?\n    var titleColor: Color?\n    var titleFont: Font?\n\n    /// Initializer with optional detected properties.\n    init(\n        theme: MessageCenterTheme,\n        colorScheme: ColorScheme,\n        barTintColor: Color? = nil,\n        barBackgroundColor: Color? = nil,\n        titleColor: Color? = nil,\n        titleFont: Font? = nil\n    ) {\n        self.theme = theme\n        self.colorScheme = colorScheme\n        self.barTintColor = barTintColor\n        self.barBackgroundColor = barBackgroundColor\n        self.titleColor = titleColor\n        self.titleFont = titleFont\n    }\n\n    private func resolve(light: Color?, dark: Color?, detected: Color?) -> Color? {\n        colorScheme.airshipResolveColor(light: light, dark: dark) ?? detected\n    }\n\n    var backButtonColor: Color? {\n        resolve(\n            light: theme.backButtonColor,\n            dark: theme.backButtonColorDark,\n            detected: barTintColor)\n    }\n\n    var deleteButtonColor: Color? {\n        resolve(\n            light: theme.deleteButtonTitleColor,\n            dark: theme.deleteButtonTitleColorDark,\n            detected: barTintColor\n        )\n    }\n\n    func editButtonColor(isEditing: Bool) -> Color? {\n        if isEditing {\n            return resolve(\n                light: theme.cancelButtonTitleColor,\n                dark: theme.cancelButtonTitleColorDark,\n                detected: barTintColor\n            )\n        } else {\n            return resolve(\n                light: theme.editButtonTitleColor,\n                dark: theme.editButtonTitleColorDark,\n                detected: barTintColor\n            )\n        }\n    }\n\n    var effectiveBarBackgroundColor: Color? {\n        resolve(\n            light: theme.messageListContainerBackgroundColor,\n            dark: theme.messageListContainerBackgroundColorDark,\n            detected: barBackgroundColor\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Theme/MessageCenterTheme.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic enum SeparatorStyle: Sendable {\n    case none\n    case singleLine\n}\n\n/// Model object representing a custom theme to be applied to the default message center.\n///\n/// To customize the message center theme:\n///\n///     MessageCenterView(\n///         controller: messageCenterController\n///     )\n///     .messageCenterTheme(theme)\n///\npublic struct MessageCenterTheme: Sendable {\n\n    /// The tint color of the \"pull to refresh\" control\n    public var refreshTintColor: Color? = nil\n\n    /// The dark mode tint color of the \"pull to refresh\" control\n    public var refreshTintColorDark: Color? = nil\n\n    /// Whether icons are enabled. Defaults to `false`.\n    public var iconsEnabled: Bool = false\n\n    /// An optional placeholder image to use when icons haven't fully loaded.\n    public var placeholderIcon: Image? = nil\n\n    /// The font to use for message cell titles.\n    public var cellTitleFont: Font? = .headline\n\n    /// The font to use for message cell dates.\n    public var cellDateFont: Font? = .subheadline\n\n    /// The regular color for message cells\n    public var cellColor: Color? = nil\n\n    /// The dark mode color for message cells\n    public var cellColorDark: Color? = nil\n\n    /// The regular color for message cell titles.\n    public var cellTitleColor: Color? = .primary\n\n    /// The dark mode color for message cell titles.\n    public var cellTitleColorDark: Color? = nil\n\n    /// The regular color for message cell dates.\n    public var cellDateColor: Color? = .secondary\n\n    /// The dark mode color for message cell dates.\n    public var cellDateColorDark: Color? = nil\n\n    /// The message cell separator style.\n    public var cellSeparatorStyle: SeparatorStyle?\n\n#if os(macOS)\n    /// The message cell separator color.\n    public var cellSeparatorColor: Color? = Color(.separatorColor)\n#else\n    /// The message cell separator color.\n    public var cellSeparatorColor: Color? = Color(.separator)\n#endif\n\n    /// The dark mode message cell separator color.\n    public var cellSeparatorColorDark: Color? = nil\n\n    /// The message cell tint color.\n    public var cellTintColor: Color? = nil\n\n    /// The dark mode message cell tint color.\n    public var cellTintColorDark: Color? = nil\n\n    /// The background color for the unread indicator.\n    public var unreadIndicatorColor: Color? = nil\n\n    /// The dark mode background color for the unread indicator.\n    public var unreadIndicatorColorDark: Color? = nil\n\n    /// The title color for the \"Select All\" button.\n    public var selectAllButtonTitleColor: Color? = nil\n\n    /// The dark mode title color for the \"Select All\" button.\n    public var selectAllButtonTitleColorDark: Color? = nil\n\n    /// The title color for the \"Delete\" button.\n    public var deleteButtonTitleColor: Color? = nil\n\n    /// The dark mode title color for the \"Delete\" button.\n    public var deleteButtonTitleColorDark: Color? = nil\n\n    /// The title color for the \"Mark Read\" button.\n    public var markAsReadButtonTitleColor: Color? = nil\n\n    /// The dark mode title color for the \"Mark Read\" button.\n    public var markAsReadButtonTitleColorDark: Color? = nil\n\n    /// Whether the delete message button from the message view is enabled. Defaults to `false`.\n    public var hideDeleteButton: Bool? = false\n\n    /// The title color for the \"Edit\" button.\n    public var editButtonTitleColor: Color? = nil\n\n    /// The dark mode title color for the \"Edit\" button.\n    public var editButtonTitleColorDark: Color? = nil\n\n    /// The title color for the \"Cancel\" button.\n    public var cancelButtonTitleColor: Color? = nil\n\n    /// The dark mode title color for the \"Cancel\" button.\n    public var cancelButtonTitleColorDark: Color? = nil\n\n    /// The title color for the \"Done\" button.\n    public var backButtonColor: Color? = nil\n\n    /// The dark mode title color for the \"Done\" button.\n    public var backButtonColorDark: Color? = nil\n\n    /// The navigation bar title\n    public var navigationBarTitle: String? = nil\n\n    /// The background of the message list.\n    public var messageListBackgroundColor: Color? = nil\n\n    /// The dark mode background of the message list.\n    public var messageListBackgroundColorDark: Color? = nil\n\n    /// The background of the message list container.\n    public var messageListContainerBackgroundColor: Color? = nil\n\n    /// The dark mode background of the message list container.\n    public var messageListContainerBackgroundColorDark: Color? = nil\n\n    /// The background of the message view.\n    public var messageViewBackgroundColor: Color? = nil\n\n    /// The dark mode background of the message view.\n    public var messageViewBackgroundColorDark: Color? = nil\n\n    /// The background of the message view container.\n    public var messageViewContainerBackgroundColor: Color? = nil\n\n    /// The dark mode background of the message view container.\n    public var messageViewContainerBackgroundColorDark: Color? = nil\n\n    public init(\n        refreshTintColor: Color? = nil,\n        refreshTintColorDark: Color? = nil,\n        iconsEnabled: Bool = false,\n        placeholderIcon: Image? = nil,\n        cellTitleFont: Font? = nil,\n        cellDateFont: Font? = nil,\n        cellColor: Color? = nil,\n        cellColorDark: Color? = nil,\n        cellTitleColor: Color? = nil,\n        cellTitleColorDark: Color? = nil,\n        cellDateColor: Color? = nil,\n        cellDateColorDark: Color? = nil,\n        cellSeparatorStyle: SeparatorStyle? = nil,\n        cellSeparatorColor: Color? = nil,\n        cellSeparatorColorDark: Color? = nil,\n        cellTintColor: Color? = nil,\n        cellTintColorDark: Color? = nil,\n        unreadIndicatorColor: Color? = nil,\n        unreadIndicatorColorDark: Color? = nil,\n        selectAllButtonTitleColor: Color? = nil,\n        selectAllButtonTitleColorDark: Color? = nil,\n        deleteButtonTitleColor: Color? = nil,\n        deleteButtonTitleColorDark: Color? = nil,\n        markAsReadButtonTitleColor: Color? = nil,\n        markAsReadButtonTitleColorDark: Color? = nil,\n        hideDeleteButton: Bool? = nil,\n        editButtonTitleColor: Color? = nil,\n        editButtonTitleColorDark: Color? = nil,\n        cancelButtonTitleColor: Color? = nil,\n        cancelButtonTitleColorDark: Color? = nil,\n        backButtonColor: Color? = nil,\n        backButtonColorDark: Color? = nil,\n        navigationBarTitle: String? = nil,\n        messageListBackgroundColor: Color? = nil,\n        messageListBackgroundColorDark: Color? = nil,\n        messageListContainerBackgroundColor: Color? = nil,\n        messageListContainerBackgroundColorDark: Color? = nil,\n        messageViewBackgroundColor: Color? = nil,\n        messageViewBackgroundColorDark: Color? = nil,\n        messageViewContainerBackgroundColor: Color? = nil,\n        messageViewContainerBackgroundColorDark: Color? = nil\n    ) {\n        self.refreshTintColor = refreshTintColor\n        self.refreshTintColorDark = refreshTintColorDark\n        self.iconsEnabled = iconsEnabled\n        self.placeholderIcon = placeholderIcon\n        self.cellTitleFont = cellTitleFont\n        self.cellDateFont = cellDateFont\n        self.cellColor = cellColor\n        self.cellColorDark = cellColorDark\n        self.cellTitleColor = cellTitleColor\n        self.cellTitleColorDark = cellTitleColorDark\n        self.cellDateColor = cellDateColor\n        self.cellDateColorDark = cellDateColorDark\n        self.cellSeparatorStyle = cellSeparatorStyle\n        self.cellSeparatorColor = cellSeparatorColor\n        self.cellSeparatorColorDark = cellSeparatorColorDark\n        self.cellTintColor = cellTintColor\n        self.cellTintColorDark = cellTintColorDark\n        self.unreadIndicatorColor = unreadIndicatorColor\n        self.unreadIndicatorColorDark = unreadIndicatorColorDark\n        self.selectAllButtonTitleColor = selectAllButtonTitleColor\n        self.selectAllButtonTitleColorDark = selectAllButtonTitleColorDark\n        self.deleteButtonTitleColor = deleteButtonTitleColor\n        self.deleteButtonTitleColorDark = deleteButtonTitleColorDark\n        self.markAsReadButtonTitleColor = markAsReadButtonTitleColor\n        self.markAsReadButtonTitleColorDark = markAsReadButtonTitleColorDark\n        self.hideDeleteButton = hideDeleteButton\n        self.editButtonTitleColor = editButtonTitleColor\n        self.editButtonTitleColorDark = editButtonTitleColorDark\n        self.cancelButtonTitleColor = cancelButtonTitleColor\n        self.cancelButtonTitleColorDark = cancelButtonTitleColorDark\n        self.backButtonColor = backButtonColor\n        self.backButtonColorDark = backButtonColorDark\n        self.navigationBarTitle = navigationBarTitle\n        self.messageListBackgroundColor = messageListBackgroundColor\n        self.messageListBackgroundColorDark = messageListBackgroundColorDark\n        self.messageListContainerBackgroundColor = messageListContainerBackgroundColor\n        self.messageListContainerBackgroundColorDark = messageListContainerBackgroundColorDark\n        self.messageViewBackgroundColor = messageViewBackgroundColor\n        self.messageViewBackgroundColorDark = messageViewBackgroundColorDark\n        self.messageViewContainerBackgroundColor = messageViewContainerBackgroundColor\n        self.messageViewContainerBackgroundColorDark = messageViewContainerBackgroundColorDark\n    }\n}\n\nextension View {\n    /// Overrides the message center theme\n    /// - Parameters:\n    ///     - theme: The message center theme\n    public func messageCenterTheme(_ theme: MessageCenterTheme) -> some View {\n        environment(\\.airshipMessageCenterTheme, theme)\n    }\n}\n\nstruct MessageCenterThemeKey: EnvironmentKey {\n    static let defaultValue = MessageCenterTheme()\n}\n\nextension EnvironmentValues {\n    /// Airship message center theme environment value\n    public var airshipMessageCenterTheme: MessageCenterTheme {\n        get { self[MessageCenterThemeKey.self] }\n        set { self[MessageCenterThemeKey.self] = newValue }\n    }\n}\n\nextension MessageCenterTheme {\n    /// Loads a message center theme from a plist file\n    /// - Parameters:\n    ///     - plist: The name of the plist in the bundle\n    public static func fromPlist(_ plist: String) throws -> MessageCenterTheme {\n        return try MessageCenterThemeLoader.fromPlist(plist)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Theme/MessageCenterThemeLoader.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct MessageCenterThemeLoader {\n\n\n    static let messageCenterFileName = \"MessageCenterTheme\"\n    static let cellSeparatorStyleNoneKey = \"none\"\n\n    static func defaultPlist() -> MessageCenterTheme? {\n        if let _ = try? plistPath(\n            file: messageCenterFileName,\n            bundle: Bundle.main\n        ) {\n            do {\n                return try fromPlist(messageCenterFileName)\n            } catch {\n                AirshipLogger.error(\n                    \"Unable to load message center theme \\(error)\"\n                )\n            }\n        }\n\n        return nil\n    }\n\n    static func fromPlist(\n        _ file: String,\n        bundle: Bundle = Bundle.main\n    ) throws -> MessageCenterTheme {\n        let path = try plistPath(file: file, bundle: bundle)\n\n        guard let data = FileManager.default.contents(atPath: path) else {\n            throw AirshipErrors.error(\"Failed to load contents of theme.\")\n        }\n\n        let decoder = PropertyListDecoder()\n\n        let config = try decoder.decode(Config.self, from: data)\n        return try config.toMessageCenterTheme(bundle: bundle)\n    }\n\n    static func plistPath(file: String, bundle: Bundle) throws -> String {\n        guard let path = bundle.path(forResource: file, ofType: \"plist\"),\n              FileManager.default.fileExists(atPath: path)\n        else {\n            throw AirshipErrors.error(\"File not found \\(file).\")\n        }\n\n        return path\n    }\n\n    internal struct Config: Decodable {\n        let tintColor: String?\n        let tintColorDark: String?\n        let refreshTintColor: String?\n        let refreshTintColorDark: String?\n\n        let iconsEnabled: Bool?\n        let placeholderIcon: String?\n\n        let cellTitleFont: FontConfig?\n        let cellDateFont: FontConfig?\n        let cellColor: String?\n        let cellColorDark: String?\n        let cellTitleColor: String?\n        let cellTitleColorDark: String?\n        let cellDateColor: String?\n        let cellDateColorDark: String?\n        let cellSeparatorStyle: String?\n        let cellSeparatorColor: String?\n        let cellSeparatorColorDark: String?\n        let cellTintColor: String?\n        let cellTintColorDark: String?\n\n        let unreadIndicatorColor: String?\n        let unreadIndicatorColorDark: String?\n        let selectAllButtonTitleColor: String?\n        let selectAllButtonTitleColorDark: String?\n        let deleteButtonTitleColor: String?\n        let deleteButtonTitleColorDark: String?\n        let markAsReadButtonTitleColor: String?\n        let markAsReadButtonTitleColorDark: String?\n        let hideDeleteButton: Bool?\n        let editButtonTitleColor: String?\n        let editButtonTitleColorDark: String?\n        let cancelButtonTitleColor: String?\n        let cancelButtonTitleColorDark: String?\n        let backButtonColor: String?\n        let backButtonColorDark: String?\n\n        let messageListBackgroundColor: String?\n        let messageListBackgroundColorDark: String?\n        let messageListContainerBackgroundColor: String?\n        let messageListContainerBackgroundColorDark: String?\n\n        let messageViewBackgroundColor: String?\n        let messageViewBackgroundColorDark: String?\n        let messageViewContainerBackgroundColor: String?\n        let messageViewContainerBackgroundColorDark: String?\n\n        let navigationBarTitle: String?\n    }\n\n    struct FontConfig: Decodable {\n        let fontName: String\n        let fontSize: FontSize\n\n        enum FontSize: Decodable {\n            case string(String)\n            case cgFloat(CGFloat)\n\n            init(from decoder: any Decoder) throws {\n                let container = try decoder.singleValueContainer()\n                if let value = try? container.decode(String.self) {\n                    self = .string(value)\n                } else if let value = try? container.decode(CGFloat.self) {\n                    self = .cgFloat(value)\n                } else {\n                    throw AirshipErrors.error(\n                        \"Font size must be able to be parsed into a String or CGFloat\"\n                    )\n                }\n            }\n\n            var size: CGFloat {\n                switch self {\n                case .string(let value):\n                    return CGFloat(Double(value) ?? 0.0)\n                case .cgFloat(let value):\n                    return value\n                }\n            }\n        }\n    }\n}\n\nextension Color {\n    var isClear: Bool {\n        var red: CGFloat = 0\n        var green: CGFloat = 0\n        var blue: CGFloat = 0\n        var opacity: CGFloat = 0\n        AirshipNativeColor(self).getRed(&red, green: &green, blue: &blue, alpha: &opacity)\n        return opacity == 0.0\n    }\n}\n\nextension MessageCenterThemeLoader.FontConfig {\n    internal func toFont() throws -> Font {\n        let size: CGFloat\n        let zeroSizeError = AirshipErrors.error(\"Font size must represent a valid number greater than 0\")\n\n        switch fontSize {\n        case .string(let value):\n            guard let fontSize = Double(value), fontSize > 0.0 else {\n                throw zeroSizeError\n            }\n            size = CGFloat(fontSize)\n        case .cgFloat(let value):\n            guard value > 0.0 else {\n                throw zeroSizeError\n            }\n            size = value\n        }\n\n        return Font.custom(fontName.trimmingCharacters(in: .whitespaces), size: size)\n    }\n}\n\nextension MessageCenterThemeLoader.Config {\n    internal func toMessageCenterTheme(bundle: Bundle = Bundle.main) throws -> MessageCenterTheme {\n        var theme = MessageCenterTheme()\n        theme.refreshTintColor = self.refreshTintColor?.airshipToColor(bundle)\n        theme.refreshTintColorDark = self.refreshTintColorDark?.airshipToColor(bundle)\n        theme.iconsEnabled = self.iconsEnabled ?? false\n        if let placeholderIcon = self.placeholderIcon {\n            theme.placeholderIcon = Image(placeholderIcon)\n        }\n        theme.cellTitleFont = try? self.cellTitleFont?.toFont()\n        theme.cellDateFont = try? self.cellDateFont?.toFont()\n        theme.cellColor = self.cellColor?.airshipToColor(bundle)\n        theme.cellColorDark = self.cellColorDark?.airshipToColor(bundle)\n        theme.cellTitleColor = self.cellTitleColor?.airshipToColor(bundle)\n        theme.cellTitleColorDark = self.cellTitleColorDark?.airshipToColor(bundle)\n        theme.cellDateColor = self.cellDateColor?.airshipToColor(bundle)\n        theme.cellDateColorDark = self.cellDateColorDark?.airshipToColor(bundle)\n        theme.cellSeparatorStyle = self.cellSeparatorStyle?.toSeparatorStyle()\n        theme.cellSeparatorColor = self.cellSeparatorColor?.airshipToColor(bundle)\n        theme.cellSeparatorColorDark = self.cellSeparatorColorDark?.airshipToColor(bundle)\n        theme.cellTintColor = self.cellTintColor?.airshipToColor(bundle)\n        theme.cellTintColorDark = self.cellTintColorDark?.airshipToColor(bundle)\n        theme.unreadIndicatorColor = self.unreadIndicatorColor?.airshipToColor(bundle)\n        theme.unreadIndicatorColorDark = self.unreadIndicatorColorDark?.airshipToColor(bundle)\n        theme.selectAllButtonTitleColor = self.selectAllButtonTitleColor?\n            .airshipToColor(bundle)\n        theme.selectAllButtonTitleColorDark = self.selectAllButtonTitleColorDark?\n            .airshipToColor(bundle)\n        theme.deleteButtonTitleColor = self.deleteButtonTitleColor?.airshipToColor(bundle)\n        theme.deleteButtonTitleColorDark = self.deleteButtonTitleColorDark?\n            .airshipToColor(bundle)\n        theme.markAsReadButtonTitleColor = self.markAsReadButtonTitleColor?\n            .airshipToColor(bundle)\n        theme.markAsReadButtonTitleColorDark = self\n            .markAsReadButtonTitleColorDark?.airshipToColor(bundle)\n        theme.hideDeleteButton = self.hideDeleteButton ?? false\n        theme.editButtonTitleColor = self.editButtonTitleColor?.airshipToColor(bundle)\n        theme.editButtonTitleColorDark = self.editButtonTitleColorDark?\n            .airshipToColor(bundle)\n        theme.cancelButtonTitleColor = self.cancelButtonTitleColor?.airshipToColor(bundle)\n        theme.cancelButtonTitleColorDark = self.cancelButtonTitleColorDark?\n            .airshipToColor(bundle)\n        theme.backButtonColor = self.backButtonColor?.airshipToColor(bundle)\n        theme.backButtonColorDark = self.backButtonColorDark?.airshipToColor(bundle)\n        theme.navigationBarTitle = self.navigationBarTitle\n        theme.messageListBackgroundColor = self.messageListBackgroundColor?.airshipToColor(bundle)\n        theme.messageListBackgroundColorDark = self.messageListBackgroundColorDark?.airshipToColor(bundle)\n        theme.messageListContainerBackgroundColor = self.messageListContainerBackgroundColor?.airshipToColor(bundle)\n        theme.messageListContainerBackgroundColorDark = self.messageListContainerBackgroundColorDark?.airshipToColor(bundle)\n        theme.messageViewBackgroundColor = self.messageViewBackgroundColor?.airshipToColor(bundle)\n        theme.messageViewBackgroundColorDark = self.messageViewBackgroundColorDark?.airshipToColor(bundle)\n        theme.messageViewContainerBackgroundColor = self.messageViewContainerBackgroundColor?.airshipToColor(bundle)\n        theme.messageViewContainerBackgroundColorDark = self.messageViewContainerBackgroundColorDark?.airshipToColor(bundle)\n        return theme\n    }\n}\n\nfileprivate extension String {\n    func toSeparatorStyle() -> SeparatorStyle {\n        let separatorStyle = self.trimmingCharacters(in: .whitespaces)\n        if separatorStyle == MessageCenterThemeLoader.cellSeparatorStyleNoneKey\n        {\n            return .none\n        }\n        return .singleLine\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/ViewModel/MessageCenterListItemViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nclass MessageCenterListItemViewModel: ObservableObject {\n\n    private var cancellables = Set<AnyCancellable>()\n\n    @Published\n    private(set) public var message: MessageCenterMessage\n\n    public init(message: MessageCenterMessage) {\n\n        self.message = message\n        Airship.messageCenter.inbox\n            .messagePublisher\n            .compactMap({ messages in\n                messages.filter { $0.id == message.id }.first\n            })\n            .removeDuplicates()\n            .receive(on: RunLoop.main)\n            .sink { message in\n                self.message = message\n            }\n            .store(in: &self.cancellables)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenter/MessageCenterContent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AppKit)\npublic import AppKit\n#endif\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// The Message Center content view.\n/// This view can be used to construct a custom Message Center. For a more turnkey solution, see `MessageCenterView`.\n///\n/// To use this view, a `MessageCenterController` must be supplied. The controller will be shared between the list and message views,\n/// keeping the state in sync.\n///\n/// ### Using it with your own navigation stack:\n/// ```swift\n///    @StateObject\n///    private var messageCenterController = MessageCenterController()\n///\n///    var body: some View {\n///        NavigationStack(path: $messageCenterController.path) {\n///            MessageCenterContent(controller: messageCenterController)\n///                .navigationDestination(for: MessageCenterController.Route.self) { route in\n///                    switch(route) {\n///                    case .message(let messageID):\n///                        MessageCenterMessageViewWithNavigation(messageID: messageID)\n///                    @unknown default:\n///                        fatalError()\n///                    }\n///                }\n///        }\n///    }\n/// ```\n///\n/// ### Using it in a deprecated NavigationView or UIKIt:\n///```swift\n///     @StateObject\n///     private var messageCenterController = MessageCenterController()\n///\n///     var body: some View {\n///         NavigationView {\n///             ZStack {\n///                 MessageCenterContent(controller: self.messageCenterController)\n///                 NavigationLink(\n///                     destination: Group {\n///                         if case .message(let messageID) = self.messageCenterController.path.last {\n///                             MessageCenterMessageViewWithNavigation(messageID: messageID) {\n///                                 // Clear selection on close\n///                                 self.messageCenterController.path.removeAll()\n///                             }\n///                         } else {\n///                             EmptyView()\n///                         }\n///                     },\n///                     isActive: Binding(\n///                         get: { self.messageCenterController.path.last != nil },\n///                         set: { isActive in\n///                             if !isActive { self.messageCenterController.path.removeAll() }\n///                         }\n///                     )\n///                 ) {\n///                     EmptyView()\n///                 }\n///                 .hidden()\n///             }\n///         }\n///     }\n///```\n@MainActor\npublic struct MessageCenterContent: View {\n\n    /// The message center state\n    @ObservedObject\n    private var controller: MessageCenterController\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n\n    @StateObject\n    private var listViewModel: MessageCenterMessageListViewModel\n    \n    /// Weak reference to the hosting view controller for UIKit appearance detection\n    weak private var hostingController: AirshipNativeViewController?\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - controller: The message center controller.\n    ///   - listViewModel: The message center list view model.\n    public init(\n        controller: MessageCenterController,\n        listViewModel: MessageCenterMessageListViewModel\n    ) {\n        self.controller = controller\n        _listViewModel = .init(wrappedValue: listViewModel)\n    }\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - controller: The message center controller.\n    ///   - hostingController: A weak reference to the hosting controller to apply apperance changes.\n    ///   - predicate: A predicate to filter messages.\n    public init(\n        controller: MessageCenterController,\n        hostingController: AirshipNativeViewController? = nil,\n        predicate: (any MessageCenterPredicate)? = nil\n    ) {\n        self.controller = controller\n        self.hostingController = hostingController\n        _listViewModel = .init(wrappedValue: .init(predicate: predicate))\n    }\n\n    /// The body of the view.\n    @ViewBuilder\n    public var body: some View {\n        let content = MessageCenterListViewWithNavigation(viewModel: self.listViewModel)\n            .airshipOnChangeOf(self.listViewModel.selectedMessageID) { selection in\n                // sync list ID to the controller path\n                if let messageID = selection {\n                    controller.navigate(messageID: messageID)\n                }\n            }\n            .airshipOnChangeOf(controller.path, initial: true) { _ in\n                // Sync controller path to the ID\n                if self.listViewModel.selectedMessageID != controller.currentMessageID {\n                    self.listViewModel.selectedMessageID = controller.currentMessageID\n                }\n            }\n\n#if !os(macOS)\n        if let hostingController = hostingController {\n            content.modifier(\n                MessageCenterUIKitContextModifier(\n                    hostingControllerRef: MessageCenterUIKitAppearance.WeakReference(hostingController)\n                )\n            )\n        } else {\n            content\n        }\n#else\n        content\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenter/MessageCenterController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\npublic import Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The message center controller's possible states.\npublic enum MessageCenterState: Equatable, Sendable {\n    /// The message center is visible, with an optional message ID.\n    case visible(messageID: String?)\n    /// The message center is not visible.\n    case notVisible\n}\n\n/// Controller for the Message Center.\n@MainActor\npublic class MessageCenterController: ObservableObject {\n\n    /// The routes available in the message center.\n    public enum Route: Sendable, Hashable {\n        /// The message route, with the message ID.\n        case message(String)\n    }\n\n    @Published\n    var visibleMessageID: String? = nil\n\n    @Published\n    var isMessageCenterVisible: Bool = false\n\n    /// The navigation path.\n    @Published\n    public var path: [Route] = []\n\n    private var subscriptions: Set<AnyCancellable> = Set()\n\n    private let updateSubject = PassthroughSubject<MessageCenterState, Never>()\n\n    /// Publisher that emits the message center state.\n    public var statePublisher: AnyPublisher<MessageCenterState, Never> {\n        self.updateSubject\n            .removeDuplicates()\n            .eraseToAnyPublisher()\n    }\n\n    /// Navigates to a message.\n    /// - Parameters:\n    ///     - messageID: The message ID to navigate to. A `nil` value will pop to the root view.\n    public func navigate(messageID: String?) {\n        guard self.currentMessageID != messageID else {\n            return\n        }\n        guard let messageID else {\n            self.path = []\n            return\n        }\n        self.path = [.message(messageID)]\n    }\n\n    var currentMessageID: String? {\n        guard case .message(let messageID) = self.path.last else {\n            return nil\n        }\n        return messageID\n    }\n\n    /// Default initializer.\n    public init() {\n        Publishers\n            .CombineLatest($visibleMessageID, $isMessageCenterVisible)\n            .sink {[updateSubject] (visibleMessageID, isMessageCenterVisible) in\n                if  let messageID = visibleMessageID {\n                    updateSubject.send(.visible(messageID: messageID))\n                } else if isMessageCenterVisible {\n                    updateSubject.send(.visible(messageID: nil))\n                } else {\n                    updateSubject.send(.notVisible)\n                }\n            }\n            .store(in: &subscriptions)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenter/MessageCenterNavigationStack.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nstruct MessageCenterNavigationStack: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n    \n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n    \n    /// The message center state\n    @ObservedObject\n    private var controller: MessageCenterController\n    \n    @StateObject\n    private var listViewModel: MessageCenterMessageListViewModel\n    \n#if !os(macOS)\n    @State\n    private var editMode: EditMode = .inactive\n#endif\n    \n    init(controller: MessageCenterController, predicate: (any MessageCenterPredicate)?) {\n        self.controller = controller\n        _listViewModel = .init(wrappedValue: .init(predicate: predicate))\n    }\n    \n    var body: some View {\n        NavigationStack(path: $controller.path) {\n            MessageCenterContent(controller: self.controller, listViewModel: self.listViewModel)\n#if !os(macOS)\n                .environment(\\.editMode, $editMode)\n                .navigationDestination(for: MessageCenterController.Route.self) { route in\n                    switch(route) {\n                    case .message(let messageID):\n                        MessageCenterMessageViewWithNavigation(messageID: messageID, title: nil) {\n                            self.controller.path.removeAll()\n                        }\n                    }\n                }\n#endif\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenter/MessageCenterSplitNavigationView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nstruct MessageCenterNavigationSplitView: View {\n\n    @ObservedObject\n    private var controller: MessageCenterController\n\n    @StateObject\n    private var listViewModel: MessageCenterMessageListViewModel\n\n#if !os(macOS)\n    @State\n    private var editMode: EditMode = .inactive\n#endif\n\n    init(controller: MessageCenterController, predicate: (any MessageCenterPredicate)?) {\n        self.controller = controller\n        _listViewModel = .init(wrappedValue: .init(predicate: predicate))\n    }\n\n    @ViewBuilder\n    private var sidebar: some View {\n        NavigationStack {\n            let content = MessageCenterContent(controller: self.controller, listViewModel: self.listViewModel)\n            content\n#if !os(macOS)\n                .environment(\\.editMode, $editMode)\n                .airshipOnChangeOf(editMode) { editMode in\n                    if !editMode.isEditing, let last = self.listViewModel.selectedMessageID {\n                        DispatchQueue.main.async {\n                            self.listViewModel.selectedMessageID = last\n                        }\n                    }\n                }\n#endif\n        }\n    }\n\n    @ViewBuilder\n    private var detailView: some View {\n        Group {\n            if let messageID = self.controller.currentMessageID {\n                MessageCenterMessageViewWithNavigation(messageID: messageID) {\n                    self.controller.path.removeAll { $0 == .message(messageID) }\n                }\n                .id(messageID)\n            } else {\n                Text(\"Select a message\")\n                    .font(.title)\n                    .foregroundColor(.secondary)\n            }\n        }\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        NavigationSplitView {\n            self.sidebar\n        } detail: {\n            NavigationStack {\n                self.detailView\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenter/MessageCenterUIKitAppearance.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(UIKit)\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Detects and bridges UIKit navigation appearance to SwiftUI Message Center\ninternal struct MessageCenterUIKitAppearance {\n\n    // MARK: - Appearance Data Model\n\n    /// Captured UIKit appearance data\n    struct DetectedAppearance: Equatable {\n        var navigationBarTintColor: Color?\n        var navigationBarBackgroundColor: Color?\n        var navigationTitleColor: Color?\n        var navigationTitleFont: Font?\n        var navigationLargeTitleColor: Color?\n        var navigationLargeTitleFont: Font?\n        var navigationBarIsTranslucent: Bool = true\n        var prefersLargeTitles: Bool = false\n        var navigationTitle: String?\n\n        /// Simplified equality that compares only the non-Font properties\n        /// Font instances don't support Equatable, so we exclude them from comparison\n        static func == (lhs: DetectedAppearance, rhs: DetectedAppearance) -> Bool {\n            // Early return for boolean properties\n            if lhs.navigationBarIsTranslucent != rhs.navigationBarIsTranslucent ||\n               lhs.prefersLargeTitles != rhs.prefersLargeTitles ||\n               lhs.navigationTitle != rhs.navigationTitle {\n                return false\n            }\n\n            // Compare color properties separately to reduce type-checking load\n            return lhs.compareColors(rhs)\n        }\n\n        private func compareColors(_ other: DetectedAppearance) -> Bool {\n            navigationBarTintColor == other.navigationBarTintColor &&\n                navigationBarBackgroundColor == other.navigationBarBackgroundColor &&\n                navigationTitleColor == other.navigationTitleColor &&\n                navigationLargeTitleColor == other.navigationLargeTitleColor\n        }\n\n        // Convert UIKit appearance to this model\n        @MainActor\n        static func from(navigationBar: UINavigationBar?, navigationItem: UINavigationItem?) -> DetectedAppearance {\n            var appearance = DetectedAppearance()\n\n            // Extract tint color (affects back buttons and bar button items)\n            if let tintColor = navigationBar?.tintColor {\n                appearance.navigationBarTintColor = Color(tintColor)\n            }\n\n            // Prioritize navigation item's appearance over navigation bar's appearance\n            let standardAppearance = navigationItem?.standardAppearance ?? navigationBar?.standardAppearance\n\n            // Extract from standard appearance\n            if let standardAppearance = standardAppearance {\n                // Background color\n                if let backgroundColor = standardAppearance.backgroundColor {\n                    appearance.navigationBarBackgroundColor = Color(backgroundColor)\n                }\n\n                // Title attributes\n                if let titleColor = standardAppearance.titleTextAttributes[.foregroundColor] as? UIColor {\n                    appearance.navigationTitleColor = Color(titleColor)\n                }\n                if let titleFont = standardAppearance.titleTextAttributes[.font] as? UIFont {\n                    appearance.navigationTitleFont = Font(titleFont)\n                }\n\n                // Large title attributes\n                if let largeTitleColor = standardAppearance.largeTitleTextAttributes[.foregroundColor] as? UIColor {\n                    appearance.navigationLargeTitleColor = Color(largeTitleColor)\n                }\n                if let largeTitleFont = standardAppearance.largeTitleTextAttributes[.font] as? UIFont {\n                    appearance.navigationLargeTitleFont = Font(largeTitleFont)\n                }\n            }\n\n            // Extract other properties\n            appearance.navigationBarIsTranslucent = navigationBar?.isTranslucent ?? true\n#if !os(tvOS)\n            appearance.prefersLargeTitles = navigationBar?.prefersLargeTitles ?? false\n#endif\n            // Extract title from navigation item\n            appearance.navigationTitle = navigationItem?.title\n\n            return appearance\n        }\n\n    }\n\n    // MARK: - Environment Keys\n\n    struct DetectedAppearanceKey: EnvironmentKey {\n        static let defaultValue: DetectedAppearance? = nil\n    }\n\n    // MARK: - Weak Reference Wrapper\n\n    /// Weak reference wrapper to prevent retain cycles\n    final class WeakReference<T: AnyObject> {\n        weak var value: T?\n\n        init(_ value: T?) {\n            self.value = value\n        }\n    }\n}\n\n// MARK: - Environment Extensions\n\nextension EnvironmentValues {\n    /// The detected UIKit appearance from parent navigation\n    var messageCenterDetectedAppearance: MessageCenterUIKitAppearance.DetectedAppearance? {\n        get { self[MessageCenterUIKitAppearance.DetectedAppearanceKey.self] }\n        set { self[MessageCenterUIKitAppearance.DetectedAppearanceKey.self] = newValue }\n    }\n}\n\n// MARK: - Appearance Detector View\n\ninternal struct MessageCenterAppearanceDetector: UIViewRepresentable {\n    @Binding var detectedAppearance: MessageCenterUIKitAppearance.DetectedAppearance?\n    let hostingControllerRef: MessageCenterUIKitAppearance.WeakReference<UIViewController>\n\n    func makeUIView(context: Context) -> UIView {\n        let view = IntrospectionView()\n        view.onAppearanceDetected = { appearance in\n            DispatchQueue.main.async {\n                self.detectedAppearance = appearance\n            }\n        }\n        view.hostingControllerRef = hostingControllerRef\n        return view\n    }\n\n    func updateUIView(_ uiView: UIView, context: Context) {\n        if let introspectionView = uiView as? IntrospectionView {\n            introspectionView.detectAppearance()\n        }\n    }\n\n    class IntrospectionView: UIView {\n        var onAppearanceDetected: ((MessageCenterUIKitAppearance.DetectedAppearance) -> Void)?\n        var hostingControllerRef: MessageCenterUIKitAppearance.WeakReference<UIViewController>?\n\n        override func didMoveToWindow() {\n            super.didMoveToWindow()\n            if window != nil {\n                detectAppearance()\n            }\n        }\n\n        @MainActor\n        func detectAppearance() {\n            guard let hostingController = hostingControllerRef?.value,\n                  let navController = hostingController.navigationController else {\n                return\n            }\n\n            let appearance = MessageCenterUIKitAppearance.DetectedAppearance.from(\n                navigationBar: navController.navigationBar,\n                navigationItem: hostingController.navigationItem\n            )\n\n            onAppearanceDetected?(appearance)\n        }\n    }\n}\n\n// MARK: - View Modifier for Applying Detected Appearance\n\ninternal struct MessageCenterApplyDetectedAppearance: ViewModifier {\n    @Environment(\\.messageCenterDetectedAppearance) var detectedAppearance\n\n    func body(content: Content) -> some View {\n        if let appearance = detectedAppearance {\n            content\n                .airshipApplyIf(appearance.navigationBarTintColor != nil) { view in\n                    // Apply navigation bar tint color (affects back button and bar items)\n                    view.accentColor(appearance.navigationBarTintColor)\n                        .tint(appearance.navigationBarTintColor)\n                }\n                .airshipApplyIf(appearance.navigationBarBackgroundColor != nil) { view in\n                    // Apply navigation bar background color\n                    view.toolbarBackground(appearance.navigationBarBackgroundColor!, for: .navigationBar)\n                        .toolbarBackground(.visible, for: .navigationBar)\n                }\n#if !os(tvOS)\n                .navigationBarTitleDisplayMode(appearance.prefersLargeTitles ? .large : .inline)\n#endif\n        } else {\n            content\n        }\n    }\n}\n\n\n// MARK: - View Extension for Appearance\n\nextension View {\n    /// Detects UIKit navigation appearance and applies it to Message Center\n    func applyUIKitNavigationAppearance() -> some View {\n        self.modifier(MessageCenterApplyDetectedAppearance())\n    }\n}\n\nextension MessageCenterUIKitAppearance.DetectedAppearance {\n    /// Factory method to convert detected UIKit data into the platform-agnostic NavigationAppearance\n    @MainActor\n    func resolveAppearance(theme: MessageCenterTheme, colorScheme: ColorScheme) -> MessageCenterNavigationAppearance {\n        return MessageCenterNavigationAppearance(\n            theme: theme,\n            colorScheme: colorScheme,\n            barTintColor: self.navigationBarTintColor,\n            barBackgroundColor: self.navigationBarBackgroundColor,\n            titleColor: self.navigationTitleColor,\n            titleFont: self.navigationTitleFont\n        )\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenter/MessageCenterView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The main view for the Airship Message Center. This view provides a navigation stack.\n/// If you wish to provide your own navigation, see `MessageCenterContent`.\npublic struct MessageCenterView: View {\n\n    /// The navigation style.\n    public enum NavigationStyle: Sendable {\n        /// A navigation style that uses a split view on larger devices and a stack view on smaller devices.\n        case split\n        /// A navigation style that uses a stack view.\n        case stack\n        /// The default navigation style. Defers to `split` on larger devices and `stack` on smaller devices.\n        case auto\n    }\n\n    private let navigationStyle: NavigationStyle\n\n    @ObservedObject\n    private var controller: MessageCenterController\n\n    @Environment(\\.airshipMessageCenterPredicate)\n    private var predicate\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - navigationStyle: The navigation style. Defaults to `auto`.\n    ///   - controller: The message center controller. If `nil` the default controller will be used.\n    public init(navigationStyle: NavigationStyle = .auto, controller: MessageCenterController? = nil) {\n        self.navigationStyle = navigationStyle\n        self.controller = controller ?? (Airship.isFlying ? Airship.messageCenter.controller : MessageCenterController())\n    }\n\n    private var shouldUseSplit: Bool {\n        switch navigationStyle {\n        case .split:\n            return true\n        case .stack:\n            return false\n        case .auto:\n#if canImport(UIKit)\n            return UIDevice.current.userInterfaceIdiom == .pad\n#else\n            return true // fallback for macOS, etc.\n#endif\n        }\n    }\n\n    /// The body of the view.\n    public var body: some View {\n        Group {\n            if shouldUseSplit {\n                MessageCenterNavigationSplitView(controller: controller, predicate: self.predicate)\n            } else {\n                MessageCenterNavigationStack(controller: controller, predicate: self.predicate)\n            }\n        }\n    }\n}\n\nextension EnvironmentValues {\n    var messageCenterDismissAction: (@MainActor @Sendable () -> Void)? {\n        get { self[MessageCenterDismissActionKey.self] }\n        set { self[MessageCenterDismissActionKey.self] = newValue }\n    }\n}\n\nprivate struct MessageCenterDismissActionKey: EnvironmentKey {\n    static let defaultValue: (@MainActor @Sendable () -> Void)? = nil\n}\n\ninternal extension View {\n    func addMessageCenterDismissAction(action: (@MainActor @Sendable () -> Void)?) -> some View {\n        environment(\\.messageCenterDismissAction, action)\n    }\n}\n\n#if canImport(UIKit)\nstruct MessageCenterUIKitContextModifier: ViewModifier {\n    let hostingControllerRef: MessageCenterUIKitAppearance.WeakReference<UIViewController>\n    @State private var detectedAppearance: MessageCenterUIKitAppearance.DetectedAppearance?\n\n    func body(content: Content) -> some View {\n        content\n            .environment(\\.messageCenterDetectedAppearance, detectedAppearance)\n            .applyUIKitNavigationAppearance()\n            .background(\n                MessageCenterAppearanceDetector(\n                    detectedAppearance: $detectedAppearance,\n                    hostingControllerRef: hostingControllerRef\n                )\n                .frame(width: 0, height: 0)\n                .hidden()\n            )\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageCenterViewController.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AppKit)\npublic import AppKit\n#endif\n\n/// View controller for Message Center view\npublic class MessageCenterViewControllerFactory: NSObject {\n\n    /// Makes a message view controller with the given theme.\n    /// - Parameters:\n    ///     - theme: The message center theme.\n    ///     - predicate: The message center predicate.\n    ///     - controller: The Message Center controller\n    ///     - dismissAction: Optional action to dismiss the view controller.\n    /// - Returns: A view controller.\n    @MainActor\n    public class func make(\n        theme: MessageCenterTheme? = nil,\n        predicate: (any MessageCenterPredicate)? = nil,\n        controller: MessageCenterController,\n        dismissAction: (@MainActor @Sendable () -> Void)? = nil\n    ) -> AirshipNativeViewController {\n        let theme = theme ?? MessageCenterTheme()\n        return MessageCenterViewController(\n            rootView: MessageCenterView(\n                controller: controller\n            )\n            .messageCenterTheme(theme)\n            .messageCenterPredicate(predicate)\n            .addMessageCenterDismissAction(\n                action: dismissAction\n            )\n        )\n    }\n\n    /// Makes a message view controller with the given theme.\n    /// - Parameters:\n    ///     - themePlist: A path to a theme plist\n    ///     - controller: The Message Center controller\n    ///     - dismissAction: Optional action to dismiss the view controller.\n    /// - Returns: A view controller.\n    @MainActor\n    public class func make(\n        themePlist: String?,\n        controller: MessageCenterController,\n        dismissAction: (@Sendable () -> Void)? = nil\n    ) throws -> AirshipNativeViewController {\n\n        if let themePlist = themePlist {\n            return make(\n                theme: try MessageCenterThemeLoader.fromPlist(themePlist),\n                controller: controller,\n                dismissAction: dismissAction\n            )\n        } else {\n            return make(\n                controller: controller,\n                dismissAction: dismissAction\n            )\n        }\n    }\n    \n    /// Makes a message view controller with the given theme.\n    /// - Parameters:\n    ///     - themePlist: A path to a theme plist\n    ///     - predicate: The message center predicate\n    ///     - controller: The Message Center controller\n    ///     - dismissAction: Optional action to dismiss the view controller.\n    /// - Returns: A view controller.\n    @MainActor\n    public class func make(\n        themePlist: String?,\n        predicate: (any MessageCenterPredicate)?,\n        controller: MessageCenterController,\n        dismissAction: (@Sendable () -> Void)? = nil\n    ) throws -> AirshipNativeViewController {\n\n        if let themePlist = themePlist {\n            return make(\n                theme: try MessageCenterThemeLoader.fromPlist(themePlist),\n                predicate: predicate,\n                controller: controller,\n                dismissAction: dismissAction\n            )\n        } else {\n            return make(\n                predicate: predicate,\n                controller: controller,\n                dismissAction: dismissAction\n            )\n        }\n    }\n}\n\nprivate class MessageCenterViewController<Content>: AirshipNativeHostingController<Content> where Content: View {\n\n    override init(rootView: Content) {\n        super.init(rootView: rootView)\n\n#if os(macOS)\n        self.view.wantsLayer = true\n        self.view.layer?.backgroundColor = NSColor.clear.cgColor\n#else\n        self.view.backgroundColor = .clear\n#endif\n    }\n\n    required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageList/MessageCenterListItemView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct MessageCenterListItemView: View {\n    @Environment(\\.airshipMessageCenterListItemStyle)\n    private var itemStyle\n\n    @ObservedObject\n    var viewModel: MessageCenterListItemViewModel\n\n    @ViewBuilder\n    var body: some View {\n        let configuration = ListItemViewStyleConfiguration(\n            message: self.viewModel.message\n        )\n        itemStyle.makeBody(configuration: configuration)\n    }\n}\n\nextension View {\n    /// Sets the list item style for the Message Center.\n    /// - Parameters:\n    ///     - style: The style to apply.\n    public func messageCenterItemViewStyle<S>(\n        _ style: S\n    ) -> some View where S: MessageCenterListItemViewStyle {\n        self.environment(\n            \\.airshipMessageCenterListItemStyle,\n            AnyListItemViewStyle(style: style)\n        )\n    }\n}\n\n/// The configuration for a Message Center list item view.\npublic struct ListItemViewStyleConfiguration {\n    /// The message associated with the list item.\n    public let message: MessageCenterMessage\n}\n\n/// A protocol that defines the style for a Message Center list item view.\npublic protocol MessageCenterListItemViewStyle: Sendable {\n    associatedtype Body: View\n\n    typealias Configuration = ListItemViewStyleConfiguration\n\n    /// Creates the view body for the list item.\n    /// - Parameters:\n    ///   - configuration: The configuration for the list item.\n    /// - Returns: The view body.\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension MessageCenterListItemViewStyle where Self == DefaultListItemViewStyle {\n    /// The default list item style.\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default style for a Message Center list item view.\npublic struct DefaultListItemViewStyle: MessageCenterListItemViewStyle {\n    @ViewBuilder\n    /// Creates the view body for the list item.\n    /// - Parameters:\n    ///   - configuration: The configuration for the list item.\n    /// - Returns: The view body.\n    public func makeBody(configuration: Configuration) -> some View {\n        MessageCenterListContentView(message: configuration.message)\n    }\n}\n\nstruct AnyListItemViewStyle: MessageCenterListItemViewStyle {\n    @ViewBuilder\n    private let _makeBody: @Sendable (Configuration) -> AnyView\n\n    init<S: MessageCenterListItemViewStyle>(style: S) {\n        _makeBody = { configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct ListItemViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyListItemViewStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipMessageCenterListItemStyle: AnyListItemViewStyle {\n        get { self[ListItemViewStyleKey.self] }\n        set { self[ListItemViewStyleKey.self] = newValue }\n    }\n}\n\nprivate struct MessageCenterListContentView: View {\n\n#if os(tvOS)\n    private static let iconWidth: Double = 100.0\n    private static let noIconSpacerWidth: Double = 30.0\n#else\n    private static let iconWidth: Double = 60.0\n    private static let noIconSpacerWidth: Double = 20.0\n#endif\n\n    private static let unreadIndicatorSize: Double = 8.0\n    private static let placeHolderImageName: String = \"photo\"\n    private static let unreadIndicatorImageName: String = \"circle.fill\"\n\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n\n    let message: MessageCenterMessage\n\n    @ViewBuilder\n    func makeIcon() -> some View {\n        if let listIcon = self.message.listIcon {\n            AirshipAsyncImage(url: listIcon) { image, _ in\n                image.resizable()\n                    .scaledToFit()\n                    .frame(width: MessageCenterListContentView.iconWidth)\n            } placeholder: {\n                return makeImagePlaceHolder()\n            }\n        } else {\n            makeImagePlaceHolder()\n        }\n    }\n\n    private func makeImagePlaceHolder() -> some View {\n        let placeHolderImage = theme.placeholderIcon ?? Image(\n            systemName: MessageCenterListContentView.placeHolderImageName\n        )\n\n        return placeHolderImage\n            .resizable()\n            .scaledToFit()\n            .foregroundColor(.primary)\n            .frame(width: MessageCenterListContentView.iconWidth)\n    }\n\n    @ViewBuilder\n    func makeUnreadIndicator() -> some View {\n        if message.unread {\n            let foregroundColor = colorScheme.airshipResolveColor(\n                light: theme.unreadIndicatorColor,\n                dark: theme.unreadIndicatorColorDark\n            ) ?? colorScheme.airshipResolveColor(\n                light: theme.cellTintColor,\n                dark: theme.cellTintColorDark\n            )\n            Image(systemName: MessageCenterListContentView.unreadIndicatorImageName)\n                .foregroundColor(\n                    foregroundColor\n                )\n                .frame(\n                    width: MessageCenterListContentView.unreadIndicatorSize,\n                    height: MessageCenterListContentView.unreadIndicatorSize\n                )\n        }\n    }\n\n    @ViewBuilder\n    func makeMessageInfo() -> some View {\n        VStack(alignment: .leading) {\n            Text(self.message.title)\n                .font(theme.cellTitleFont)\n                .foregroundColor(colorScheme.airshipResolveColor(light: theme.cellTitleColor, dark: theme.cellTitleColorDark))\n                .accessibilityHidden(true)\n\n            if let subtitle = self.message.subtitle {\n                Text(subtitle)\n                    .font(.subheadline)\n                    .accessibilityHidden(true)\n\n            }\n\n            Text(self.message.sentDate, style: .date)\n                .font(theme.cellDateFont)\n                .foregroundColor(colorScheme.airshipResolveColor(light: theme.cellDateColor, dark: theme.cellDateColorDark))\n                .accessibilityHidden(true)\n        }\n    }\n\n    @ViewBuilder\n    var body: some View {\n        HStack(alignment: .top) {\n            if (theme.iconsEnabled) {\n                makeIcon()\n#if !os(tvOS)\n                    .padding(.trailing)\n#endif\n                    .overlay(makeUnreadIndicator(), alignment: .topLeading)\n            } else {\n                Spacer().frame(\n                    width: MessageCenterListContentView.noIconSpacerWidth\n                )\n                .overlay(makeUnreadIndicator(), alignment: .topLeading)\n            }\n\n            makeMessageInfo()\n            Spacer()\n        }\n#if os(tvOS)\n        .padding()\n#else\n        .padding(8)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageList/MessageCenterListView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// A view that displays a list of messages.\npublic struct MessageCenterListView: View {\n\n#if !os(macOS)\n    @Environment(\\.editMode)\n    private var editMode\n#endif\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n\n    @StateObject\n    private var viewModel = MessageCenterMessageListViewModel()\n\n    @State\n    private var listOpacity = 0.0\n\n    @State\n    private var isRefreshing = false\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - viewModel: The message center list view model.\n    public init(viewModel: MessageCenterMessageListViewModel) {\n        _viewModel = .init(wrappedValue: viewModel)\n    }\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - predicate: A predicate to filter messages.\n    public init(predicate: (any MessageCenterPredicate)? = nil) {\n        _viewModel = .init(wrappedValue: .init(predicate: predicate))\n    }\n\n    @ViewBuilder\n    private func makeCellContent(\n        item: MessageCenterListItemViewModel,\n        messageID: String\n    ) -> some View {\n        MessageCenterListItemView(viewModel: item)\n    }\n    \n    @ViewBuilder\n    private func makeCell(\n        item: MessageCenterListItemViewModel,\n        messageID: String\n    ) -> some View {\n        let accessibilityLabel = String(\n            format: item.message.unread ? \"ua_message_unread_description\".messageCenterLocalizedString : \"ua_message_description\".messageCenterLocalizedString,\n            item.message.title,\n            AirshipDateFormatter.string(fromDate: item.message.sentDate, format: .relativeShortDate)\n        )\n\n        let cell = makeCellContent(item: item, messageID: messageID)\n            .accessibilityLabel(\n                accessibilityLabel\n            ).accessibilityHint(\n                \"ua_message_cell_description\".messageCenterLocalizedString\n            )\n\n        cell.listRowBackground(\n            colorScheme.airshipResolveColor(light: theme.cellColor, dark: theme.cellColorDark)\n        )\n#if !os(tvOS)\n        .listRowSeparator(\n            (theme.cellSeparatorStyle == SeparatorStyle.none)\n            ? .hidden : .automatic\n        )\n        .listRowSeparatorTint(colorScheme.airshipResolveColor(light: theme.cellSeparatorColor, dark: theme.cellSeparatorColorDark))\n#endif\n    }\n\n    @ViewBuilder\n    private func makeCell(messageID: String) -> some View {\n        if let item = self.viewModel.messageItem(forID: messageID) {\n#if !os(tvOS)\n            makeCell(item: item, messageID: messageID)\n#else\n            /**\n             * List items are not selectable by tvOS without a focusable element\n             */\n            Button(\n                action: {\n                    self.viewModel.selectedMessageID = messageID\n                }) {\n                    makeCell(item: item, messageID: messageID)\n                }\n                .buttonStyle(.plain)\n#endif\n        } else {\n            EmptyView()\n        }\n    }\n\n    private var isEditMode: Bool {\n#if os(macOS)\n        false\n#else\n        self.editMode?.wrappedValue.isEditing ?? false\n#endif\n    }\n\n    @ViewBuilder\n    private func makeList() -> some View {\n        let binding: Binding<Set<String>> = .init(\n            get: {\n                if isEditMode {\n                    return self.viewModel.editModeSelection\n                } else {\n                    var set = Set<String>()\n                    if let selectedMessageID = viewModel.selectedMessageID {\n                        set.insert(selectedMessageID)\n                    }\n                    return set\n                }\n            },\n            set: {\n                if isEditMode {\n                    self.viewModel.editModeSelection = $0\n                } else {\n                    self.viewModel.selectedMessageID = $0.first\n                }\n            }\n        )\n\n        List(selection: binding) {\n            ForEach(self.viewModel.messages) { message in\n                makeCell(messageID: message.id)\n            }\n            .onDelete { offsets in\n                self.viewModel.delete(\n                    messages: Set(offsets.map { self.viewModel.messages[$0].id })\n                )\n            }\n        }\n        .refreshable {\n            await self.viewModel.refresh()\n        }\n        .disabled(self.viewModel.messages.isEmpty)\n    }\n\n    @ViewBuilder\n    private func emptyMessageListMessage() -> some View {\n        let refreshColor = colorScheme.airshipResolveColor(\n            light: theme.refreshTintColor,\n            dark: theme.refreshTintColorDark\n        )\n\n        VStack {\n            Button {\n                Task { @MainActor in\n                    isRefreshing = true\n                    await self.viewModel.refresh()\n                    isRefreshing = false\n                }\n            } label: {\n                ZStack {\n                    if isRefreshing {\n                        ProgressView()\n                    } else {\n                        Image(systemName: \"arrow.clockwise\")\n                            .foregroundColor(refreshColor ?? .primary)\n                    }\n                }\n                .frame(height: 44)\n                .background(Color.airshipTappableClear)\n            }\n            .disabled(isRefreshing)\n\n            Text(\"ua_empty_message_list\".messageCenterLocalizedString)\n                .foregroundColor(refreshColor ?? .primary)\n        }\n        .opacity(1.0 - self.listOpacity)\n    }\n\n    @ViewBuilder\n    /// The body of the view.\n    public var body: some View {\n        let listBackgroundColor = colorScheme.airshipResolveColor(\n            light: theme.messageListBackgroundColor,\n            dark: theme.messageListBackgroundColorDark\n        )\n\n        ZStack {\n            if !self.viewModel.messagesLoaded {\n                ProgressView().opacity(1.0 - self.listOpacity)\n            } else if viewModel.messages.isEmpty {\n                emptyMessageListMessage()\n            } else {\n                makeList()\n                    .opacity(self.listOpacity)\n                    .listBackground(listBackgroundColor)\n                    .animation(.easeInOut(duration: 0.5), value: self.listOpacity)\n                    .padding(.bottom, 60) // small spacing at bottom to avoid tab bars\n            }\n        }\n        .airshipOnChangeOf(self.viewModel.messages) { messages in\n            if messages.isEmpty {\n                self.listOpacity = 0.0\n            } else {\n                self.listOpacity = 1.0\n            }\n        }\n    }\n}\n\nfileprivate extension View {\n    @ViewBuilder\n    func listBackground(_ color: Color?) -> some View {\n        if let color {\n#if !os(tvOS)\n            self.scrollContentBackground(.hidden).background(color)\n#endif\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageList/MessageCenterListViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// A view model for the message center list.\n@MainActor\npublic class MessageCenterMessageListViewModel: ObservableObject {\n\n    /// The list of messages.\n    @Published\n    public private(set) var messages: [MessageCenterMessage] = []\n\n    /// The set of selected message IDs in edit mode.\n    @Published\n    public var editModeSelection: Set<String> = []\n\n    /// The selected message ID.\n    @Published\n    public var selectedMessageID: String? = nil\n\n    /// A flag indicating if the messages have been loaded.\n    @Published\n    public private(set) var messagesLoaded: Bool = false\n\n    private var messageItems: [String: MessageCenterListItemViewModel] = [:]\n    private var updates = Set<AnyCancellable>()\n    private let messageCenter: (any MessageCenter)?\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - predicate: A predicate to filter messages.\n    public init(predicate: (any MessageCenterPredicate)? = nil) {\n        if Airship.isFlying {\n            messageCenter = Airship.messageCenter\n        } else {\n            messageCenter = nil\n        }\n\n        self.messageCenter?.inbox.messagePublisher\n            .receive(on: RunLoop.main)\n            .sink { [weak self] incoming in\n                guard let self else { return }\n                self.objectWillChange.send()\n\n                self.messagesLoaded = true\n\n                var incomings: [MessageCenterMessage] = []\n                incoming.filter {\n                    predicate?.evaluate(message: $0) ?? true\n                }\n                .forEach { message in\n                    incomings.append(message)\n                    if self.messageItems[message.id] == nil {\n                        self.messageItems[message.id] =\n                        MessageCenterListItemViewModel(\n                            message: message\n                        )\n                    }\n                }\n\n                let incomingIDs = incomings.map { $0.id }\n                Set(self.messageItems.keys)\n                    .subtracting(incomingIDs)\n                    .forEach {\n                        self.messageItems.removeValue(forKey: $0)\n                    }\n                self.messages = incomings\n            }\n            .store(in: &self.updates)\n\n        Task {\n            await self.refresh()\n        }\n    }\n\n    func messageItem(forID: String) -> MessageCenterListItemViewModel? {\n        return self.messageItems[forID]\n    }\n\n    /// Refreshes the list of messages.\n    public func refresh() async {\n        await self.messageCenter?.inbox.refreshMessages()\n    }\n\n    /// Marks a set of messages as read.\n    /// - Parameters:\n    ///   - messages: A set of message IDs to mark as read.\n    public func markRead(messages: Set<String>) {\n        Task {\n            await self.messageCenter?.inbox\n                .markRead(\n                    messageIDs: Array(messages)\n                )\n        }\n    }\n\n    /// Deletes a set of messages.\n    /// - Parameters:\n    ///   - messages: A set of message IDs to delete.\n    public func delete(messages: Set<String>) {\n        Task {\n            await self.messageCenter?.inbox\n                .delete(\n                    messageIDs: Array(messages)\n                )\n        }\n    }\n\n    /// Selects all messages in edit mode.\n    public func editModeSelectAll() {\n        editModeSelection = Set(messages.map { $0.id })\n    }\n\n    /// Clears the selection in edit mode.\n    public func editModeClearAll() {\n        editModeSelection.removeAll()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageList/MessageCenterListViewWithNavigation.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// A view that displays a list of messages as well as modifies the toolbars and navigation title.\n@MainActor\npublic struct MessageCenterListViewWithNavigation: View {\n    \n    @Environment(\\.messageCenterDismissAction)\n    private var dismissAction: (@MainActor @Sendable () -> Void)?\n    \n#if !os(macOS)\n    @Environment(\\.editMode)\n    private var editMode\n    \n    @Environment(\\.messageCenterDetectedAppearance)\n    private var detectedAppearance\n#endif\n    \n    @Environment(\\.colorScheme)\n    private var colorScheme\n    \n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n    \n    @StateObject\n    private var viewModel: MessageCenterMessageListViewModel\n    \n    \n    /// Initializer.\n    /// - Parameters:\n    ///   - viewModel: The message center list view model.\n    public init(viewModel: MessageCenterMessageListViewModel) {\n        _viewModel = .init(wrappedValue: viewModel)\n    }\n    \n    /// Initializer.\n    /// - Parameters:\n    ///   - predicate: A predicate to filter messages.\n    public init(predicate: (any MessageCenterPredicate)? = nil) {\n        _viewModel = .init(wrappedValue: .init(predicate: predicate))\n    }\n    \n    private var navigationBarAppearance: MessageCenterNavigationAppearance {\n#if !os(macOS)\n        if let detectedAppearance {\n            return detectedAppearance.resolveAppearance(theme: theme, colorScheme: colorScheme)\n        }\n#endif\n        return MessageCenterNavigationAppearance(theme: theme, colorScheme: colorScheme)\n    }\n    \n#if !os(macOS)\n    private var isEditMode: Bool {\n        self.editMode?.wrappedValue.isEditing ?? false\n    }\n#endif\n    \n#if !os(tvOS) && !os(macOS)\n    private func editButton() -> some View {\n        return EditButton()\n            .foregroundColor(navigationBarAppearance.editButtonColor(isEditing: isEditMode))\n            .accessibilityHint(\"ua_edit_messages_description\".messageCenterLocalizedString)\n    }\n#endif\n    \n    private func markRead(messages: Set<String>) {\n#if !os(macOS)\n        withAnimation {\n            self.editMode?.wrappedValue = .inactive\n        }\n#endif\n        self.viewModel.markRead(messages: messages)\n    }\n    \n    private func delete(messages: Set<String>) {\n#if !os(macOS)\n        withAnimation {\n            self.editMode?.wrappedValue = .inactive\n        }\n#endif\n        self.viewModel.delete(messages: messages)\n    }\n    \n    private func markDeleteButton() -> some View {\n        Button(\n            \"ua_mark_messages_read\".messageCenterLocalizedString,\n            systemImage: \"trash\",\n            role: .destructive\n        ) {\n            self.viewModel.delete(messages: self.viewModel.editModeSelection)\n        }\n        .tint(\n            colorScheme.airshipResolveColor(\n                light: theme.deleteButtonTitleColor,\n                dark: theme.deleteButtonTitleColorDark\n            )\n        )\n        .accessibilityHint(\"ua_delete_messages\".messageCenterLocalizedString)\n        .disabled(self.viewModel.editModeSelection.isEmpty)\n    }\n    \n    @ViewBuilder\n    private func markReadButton() -> some View {\n        Button(\n            \"ua_mark_messages_read\".messageCenterLocalizedString,\n            systemImage: \"envelope.open\"\n        ) {\n            markRead(messages: self.viewModel.editModeSelection)\n        }\n        .tint(\n            colorScheme.airshipResolveColor(\n                light: theme.markAsReadButtonTitleColor,\n                dark: theme.markAsReadButtonTitleColorDark\n            )\n        )\n        .disabled(self.viewModel.editModeSelection.isEmpty)\n        .accessibilityHint(\"ua_mark_messages_read\".messageCenterLocalizedString)\n    }\n    \n    @ViewBuilder\n    private func selectButton() -> some View {\n        if self.viewModel.editModeSelection.count == self.viewModel.messages.count {\n            selectNone()\n        } else {\n            selectAll()\n        }\n    }\n    \n    private func selectAll() -> some View {\n        Button(\n            \"ua_select_all_messages\".messageCenterLocalizedString\n        ) {\n            self.viewModel.editModeSelectAll()\n        }\n        .tint(\n            colorScheme.airshipResolveColor(\n                light: theme.selectAllButtonTitleColor,\n                dark: theme.selectAllButtonTitleColorDark\n            )\n        )\n        .accessibilityHint(\"ua_select_all_messages\".messageCenterLocalizedString)\n    }\n    \n    private func selectNone() -> some View {\n        Button(\n            \"ua_select_none_messages\".messageCenterLocalizedString\n        ) {\n            self.viewModel.editModeClearAll()\n        }\n        .tint(\n            colorScheme.airshipResolveColor(\n                light: theme.selectAllButtonTitleColor,\n                dark: theme.selectAllButtonTitleColorDark\n            )\n        )\n        .accessibilityHint(\"ua_select_none_messages\".messageCenterLocalizedString)\n    }\n    \n    /// The body of the view.\n    public var body: some View {\n        let containerColor: Color? = colorScheme.airshipResolveColor(\n            light: theme.messageListContainerBackgroundColor,\n            dark: theme.messageListContainerBackgroundColorDark\n        )\n        \n        let content = MessageCenterListView(viewModel: self.viewModel)\n            .frame(maxHeight: .infinity)\n#if !os(macOS)\n            .applyUIKitNavigationAppearance()\n#endif\n            .navigationTitle(\n                theme.navigationBarTitle ?? \"ua_message_center_title\".messageCenterLocalizedString\n            )\n            .toolbar {\n#if os(iOS)\n                if #available(iOS 26.0, *) {\n                    ToolbarItemGroup(placement: .topBarTrailing) {\n                        editButton()\n                    }\n                } else {\n                    ToolbarItemGroup(placement: .navigationBarTrailing) {\n                        editButton()\n                    }\n                }\n                \n                ToolbarItemGroup(placement: .bottomBar) {\n                    selectButton()\n                    Spacer()\n                    markReadButton()\n                    markDeleteButton()\n                }\n#elseif !os(tvOS) && !os(macOS)\n                ToolbarItemGroup(placement: .navigationBarTrailing) {\n#if !os(macOS)\n                    editButton()\n#endif\n                }\n                ToolbarItemGroup(placement: .bottomBar) {\n                    selectButton()\n                    Spacer()\n                    markReadButton()\n                    Spacer()\n                    markDeleteButton()\n                }\n#endif\n                \n                if navigationBarAppearance.titleColor != nil || navigationBarAppearance.titleFont != nil {\n                    ToolbarItemGroup(placement: .principal) {\n                        // Custom title with detected color\n                        Text(theme.navigationBarTitle ?? \"ua_message_center_title\".messageCenterLocalizedString)\n                            .foregroundColor(navigationBarAppearance.titleColor)\n                            .airshipApplyIf(navigationBarAppearance.titleFont != nil) { text in\n                                text.font(navigationBarAppearance.titleFont)\n                            }\n                    }\n                }\n            }\n#if !os(tvOS) && !os(macOS)\n            .toolbar(isEditMode ? .visible : .hidden, for: .bottomBar)\n#endif\n        \n#if !os(macOS)\n            .airshipApplyIf(containerColor != nil) { view in\n                let visibility: Visibility = if #available(iOS 26.0, *) {\n                    .automatic\n                } else {\n                    .visible\n                }\n                view.toolbarBackground(containerColor!, for: .navigationBar)\n                    .toolbarBackground(visibility, for: .navigationBar)\n            }\n            .airshipApplyIf(dismissAction != nil) { view in\n                view.toolbar {\n                    ToolbarItem(placement: .navigationBarLeading) {\n                        MessageCenterBackButton(dismissAction: dismissAction)\n                    }\n                }\n            }\n#endif\n        \n#if !os(macOS)\n        if #available(iOS 26.0, *) {\n            content.toolbar(\n                isEditMode ? .hidden : .automatic,\n                for: .tabBar\n            )\n            .ignoresSafeArea(edges: .bottom)\n        } else {\n            content\n        }\n#else\n        \n        content\n        \n#endif\n        \n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageView/MessageCenterMessageError.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Errors that can occur when loading Message Center messages.\npublic enum MessageCenterMessageError: Error {\n\n    /// No message exists in the inbox for the provided message ID.\n    case messageGone\n\n    /// A network failure occurred while fetching the message or inbox data.\n    case failedToFetchMessage\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageView/MessageCenterMessageView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(WebKit)\nimport WebKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The Message Center message view.\n@MainActor\npublic struct MessageCenterMessageView: View {\n\n    @Environment(\\.airshipMessageViewStyle)\n    private var style\n\n    /// The message's ID\n    @StateObject\n    private var viewModel: MessageCenterMessageViewModel\n\n    /// The dismiss action callback\n    private let dismissAction: (@MainActor @Sendable () -> Void)?\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - viewModel: The message center message view model.\n    ///   - dismissAction: A dismiss action.\n    public init(\n        viewModel: MessageCenterMessageViewModel,\n        dismissAction: (@MainActor @Sendable () -> Void)? = nil\n    ) {\n        _viewModel = .init(wrappedValue: viewModel)\n        self.dismissAction = dismissAction\n    }\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - messageID: The message ID.\n    ///   - dismissAction: A dismiss action.\n    public init(\n        messageID: String,\n        dismissAction: (@MainActor @Sendable () -> Void)? = nil\n    ) {\n        _viewModel = .init(wrappedValue: .init(messageID: messageID))\n        self.dismissAction = dismissAction\n    }\n\n    @ViewBuilder\n    /// The body of the view.\n    public var body: some View {\n        let configuration = MessageViewStyleConfiguration(\n            viewModel: viewModel,\n            dismissAction: dismissAction\n        )\n\n        style.makeBody(configuration: configuration)\n    }\n    \n    enum DisplayPhase {\n        case loading\n        case error(any Error)\n        case loaded\n    }\n}\n\nextension View {\n    /// Sets the style for the Message Center message view.\n    /// - Parameters:\n    ///     - style: The style to apply.\n    public func messageCenterMessageViewStyle<S>(\n        _ style: S\n    ) -> some View where S: MessageViewStyle {\n        self.environment(\\.airshipMessageViewStyle, AnyMessageViewStyle(style: style))\n    }\n}\n\n/// The configuration for a Message Center message view.\npublic struct MessageViewStyleConfiguration: Sendable {\n    /// The message view model.\n    public let viewModel: MessageCenterMessageViewModel\n    /// The dismiss action.\n    public let dismissAction: (@MainActor @Sendable () -> Void)?\n}\n\n/// A protocol that defines the style for a Message Center message view.\npublic protocol MessageViewStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = MessageViewStyleConfiguration\n    @MainActor\n    /// Creates the view body for the message view.\n    /// - Parameters:\n    ///   - configuration: The configuration for the message view.\n    /// - Returns: The view body.\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension MessageViewStyle where Self == DefaultMessageViewStyle {\n\n    /// The default message view style.\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default style for a Message Center message view.\npublic struct DefaultMessageViewStyle: MessageViewStyle {\n    @ViewBuilder\n    @MainActor\n    /// Creates the view body for the message view.\n    /// - Parameters:\n    ///   - configuration: The configuration for the message view.\n    /// - Returns: The view body.\n    public func makeBody(configuration: Configuration) -> some View {\n        MessageCenterMessageContentView(\n            viewModel: configuration.viewModel,\n            dismissAction: configuration.dismissAction\n        )\n    }\n}\n\nstruct AnyMessageViewStyle: MessageViewStyle {\n    @ViewBuilder\n    private let _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: MessageViewStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct MessageViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyMessageViewStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipMessageViewStyle: AnyMessageViewStyle {\n        get { self[MessageViewStyleKey.self] }\n        set { self[MessageViewStyleKey.self] = newValue }\n    }\n}\n\nprivate struct MessageCenterMessageContentView: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n\n    @State\n    private var messageLoadingPhase: MessageCenterMessageView.DisplayPhase = .loading\n    \n    @State\n    private var opacity = 0.0\n    \n    @State\n    private var contentType: MessageCenterMessage.ContentType = .unknown(nil)\n\n    @ObservedObject\n    var viewModel: MessageCenterMessageViewModel\n    let dismissAction: (@MainActor @Sendable () -> Void)?\n\n    init(\n        viewModel: MessageCenterMessageViewModel,\n        dismissAction: (@MainActor @Sendable () -> Void)?\n    ) {\n        self.viewModel = viewModel\n        self.dismissAction = dismissAction\n        self.contentType = viewModel.message?.contentType ?? .unknown(nil)\n    }\n\n    @MainActor\n    private static func makeRequest(\n        viewModel: MessageCenterMessageViewModel\n    ) async throws -> URLRequest {\n        guard let message = await viewModel.fetchMessage(),\n              let user = await Airship.messageCenter.inbox.user\n        else {\n            throw AirshipErrors.error(\"\")\n        }\n\n        var request = URLRequest(url: message.bodyURL)\n        request.setValue(\n            user.basicAuthString,\n            forHTTPHeaderField: \"Authorization\"\n        )\n        request.timeoutInterval = 120\n        return request\n    }\n\n#if canImport(WebKit)\n    @MainActor\n    private func makeExtensionDelegate(\n        messageID: String\n    ) async throws -> MessageCenterNativeBridgeExtension {\n        guard let message = await viewModel.fetchMessage(),\n              let user = await Airship.messageCenter.inbox.user\n        else {\n            throw AirshipErrors.error(\"\")\n        }\n\n        return MessageCenterNativeBridgeExtension(\n            message: message,\n            user: user\n        )\n    }\n#endif\n\n    var body: some View {\n        let backgroundColor = self.colorScheme.airshipResolveColor(\n            light: self.theme.messageViewBackgroundColor,\n            dark: self.theme.messageViewBackgroundColorDark\n        )\n\n        ZStack {\n            if let backgroundColor {\n                backgroundColor.ignoresSafeArea()\n            }\n            \n            messageContent()\n                .opacity(self.opacity)\n                .onReceive(Just(messageLoadingPhase)) { _ in\n                    if case .loaded = self.messageLoadingPhase {\n                        self.opacity = 1.0\n                        if Airship.isFlying {\n                            Task {\n                                await viewModel.markRead()\n                            }\n                        }\n                    }\n                }\n                .animation(.easeInOut(duration: 0.5), value: self.opacity)\n            \n            if case .loading = self.messageLoadingPhase {\n                ProgressView().task {\n                    do {\n                        let message = try await viewModel.fetchMessageThrowing()\n                        self.contentType = message.contentType\n                    } catch {\n                        self.messageLoadingPhase = .error(error)\n                    }\n                }\n            } else if case .error(let error) = self.messageLoadingPhase {\n                if let error = error as? MessageCenterMessageError,\n                   error == .messageGone\n                {\n                    VStack {\n                        Text(\n                            \"ua_mc_no_longer_available\".messageCenterLocalizedString\n                        )\n                        .font(.headline)\n                        .foregroundColor(.primary)\n                    }\n                } else {\n                    VStack {\n                        Text(\"ua_mc_failed_to_load\".messageCenterLocalizedString)\n                            .font(.headline)\n                            .foregroundColor(.primary)\n\n                        Button(\"ua_retry_button\".messageCenterLocalizedString) {\n                            self.messageLoadingPhase = .loading\n                        }\n                    }\n                }\n            }\n        }\n    }\n    \n    @ViewBuilder\n    private func messageContent() -> some View {\n        switch self.contentType {\n        case .html, .plain, .unknown:\n            webBasedMessageView()\n        case .native:\n            thomasMessageView()\n        }\n    }\n    \n    @ViewBuilder\n    private func webBasedMessageView() -> some View {\n#if canImport(WebKit)\n        MessageCenterWebView(\n            phase: self.$messageLoadingPhase,\n            nativeBridgeExtension: {\n                try await makeExtensionDelegate(messageID: viewModel.messageID)\n            },\n            request: {\n                try await Self.makeRequest(viewModel: self.viewModel)\n            },\n            dismiss: {\n                await MainActor.run {\n                    dismiss()\n                }\n            }\n        )\n#else\n        Text(\"ua_mc_failed_to_load\".messageCenterLocalizedString)\n            .font(.headline)\n            .foregroundColor(.primary)\n#endif\n    }\n    \n    @ViewBuilder\n    private func thomasMessageView() -> some View {\n        if let analytics = viewModel.makeAnalytics(onDismiss: { [action = dismissAction] in action?() } ) {\n            MessageCenterThomasView(\n                phase: self.$messageLoadingPhase,\n                layoutRequest: { try await Self.makeRequest(viewModel: viewModel) },\n                analytics: analytics,\n                dismissHandle: self.viewModel.thomasDismissHandle,\n//                stateStorage: viewModel.getOrCreateNativeStateStorage() //disables state restoring for all message center views\n            )\n        } else {\n            EmptyView()\n        }\n    }\n\n    private func dismiss() {\n        self.dismissAction?()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageView/MessageCenterMessageViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// A view model for a message.\n@MainActor\npublic final class MessageCenterMessageViewModel: ObservableObject {\n    /// The message ID.\n    public let messageID: String\n\n    /// The message.\n    @Published\n    public var message: MessageCenterMessage? = nil\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - messageID: The message ID.\n    public init(messageID: String) {\n        self.messageID = messageID\n    }\n\n    private var fetchMessageTask: Task<MessageCenterMessage, any Error>? = nil\n    \n    private var nativeAnalytics: ThomasDisplayListener? = nil\n    let thomasDismissHandle: ThomasDismissHandle = .init()\n\n    func makeAnalytics(\n        messageCenter: DefaultMessageCenter = Airship.internalMessageCenter,\n        onDismiss: @MainActor @escaping () -> Void\n    ) -> ThomasDisplayListener? {\n        guard let message else { return nil }\n\n        if let cached = nativeAnalytics {\n            return cached\n        }\n\n        let result = ThomasDisplayListener(\n            analytics: DefaultMessageViewAnalytics(\n                message: message,\n                eventRecorder: ThomasLayoutEventRecorder(\n                    airshipAnalytics: messageCenter.analytics,\n                    meteredUsage: messageCenter.meteredUsage\n                ),\n                historyStorage: MessageDisplayHistoryStore(\n                    storageGetter: { messageID in\n                        await messageCenter.internalInbox.message(forID: messageID)?.associatedData.displayHistory\n                    },\n                    storageSetter: { messageID, data in\n                        await messageCenter.internalInbox.saveDisplayHistory(for: messageID, history: data)\n                    })\n            ),\n            onDismiss: { _ in\n                onDismiss()\n            }\n        )\n\n        nativeAnalytics = result\n        return result\n    }\n    \n    func getOrCreateNativeStateStorage(\n        messageCenter: DefaultMessageCenter = Airship.internalMessageCenter\n    ) -> any LayoutDataStorage {\n        return messageCenter.internalInbox.getNativeStateStorage(for: messageID)\n    }\n\n    /// Fetches the message.\n    /// - Returns: The message.\n    @MainActor\n    public func fetchMessage() async -> MessageCenterMessage? {\n        return try? await fetchMessageThrowing()\n    }\n    \n    /// Fetches the message.\n    ///  - Throws: An error of type `MessageCenterMessageError`\n    /// - Returns: The message.\n    @MainActor\n    public func fetchMessageThrowing() async throws -> MessageCenterMessage {\n        _ = try await fetchMessageTask?.value\n\n        if let message = message {\n            return message\n        }\n\n        let task = Task {\n            var message = await Airship.messageCenter.inbox.message(\n                forID: messageID\n            )\n            \n            do {\n                if message == nil {\n                    try await Airship.messageCenter.inbox.refreshMessagesThrowing()\n                    message = await Airship.messageCenter.inbox.message(\n                        forID: messageID\n                    )\n                }\n            } catch {\n                throw MessageCenterMessageError.failedToFetchMessage\n            }\n            \n            if let message {\n                return message\n            } else {\n                throw MessageCenterMessageError.messageGone\n            }\n        }\n        \n        self.fetchMessageTask = task\n\n        let result = try await task.value\n        self.message = result\n        \n        return result\n    }\n\n    /// Marks the message as read.\n    /// - Returns: `true` if the message was marked as read, `false` otherwise.\n    @discardableResult\n    public func markRead() async -> Bool {\n        guard let message = await fetchMessage() else { return false }\n        await Airship.messageCenter.inbox.markRead(messageIDs: [message.id])\n        return true\n    }\n\n    /// Deletes the message.\n    /// - Returns: `true` if the message was deleted, `false` otherwise.\n    @discardableResult\n    public func delete() async -> Bool {\n        guard let message = await fetchMessage() else { return false }\n        await Airship.messageCenter.inbox.delete(messageIDs: [message.id])\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageView/MessageCenterMessageViewWithNavigation.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// A view that displays a message as well as modifies the toolbars and navigation title.\n@MainActor\npublic struct MessageCenterMessageViewWithNavigation: View {\n\n    @Environment(\\.presentationMode)\n    private var presentationMode: Binding<PresentationMode>\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n\n#if !os(macOS)\n    @Environment(\\.messageCenterDetectedAppearance)\n    private var detectedAppearance\n#endif\n\n    @State\n    private var opacity = 0.0\n\n    @StateObject\n    private var messageViewModel: MessageCenterMessageViewModel\n\n    private let showBackButton: Bool?\n    private let title: String?\n    private let dismissAction: (@MainActor () -> Void)?\n\n    @State\n    private var isDismissed = false // Add this state\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - messageID: The message ID.\n    ///   - title: The title to use until the message is loaded.\n    ///   - showBackButton: Flag to show or hide the back button. If not set, back button will be displayed if it has a presentationMode.\n    ///   - dismissAction: A dismiss action.\n    public init(\n        messageID: String,\n        title: String? = nil,\n        showBackButton: Bool? = nil,\n        dismissAction: (@MainActor () -> Void)? = nil\n    ) {\n        _messageViewModel = .init(wrappedValue: .init(messageID: messageID))\n        self.title = title\n        self.showBackButton = showBackButton\n        self.dismissAction = dismissAction\n    }\n\n    /// Initializer.\n    /// - Parameters:\n    ///   - viewModel: The message center message view model.\n    ///   - title: The title to use until the message is loaded.\n    ///   - showBackButton: Flag to show or hide the back button. If not set, back button will be displayed if it has a presentationMode.\n    ///   - dismissAction: A dismiss action.\n    public init(\n        viewModel: MessageCenterMessageViewModel,\n        title: String? = nil,\n        showBackButton: Bool? = nil,\n        dismissAction: (@MainActor () -> Void)? = nil\n    ) {\n        _messageViewModel = .init(wrappedValue: viewModel)\n        self.title = title\n        self.showBackButton = showBackButton\n        self.dismissAction = dismissAction\n    }\n\n\n    private var navigationBarAppearance: MessageCenterNavigationAppearance {\n#if !os(macOS)\n        if let detectedAppearance {\n            return detectedAppearance.resolveAppearance(theme: theme, colorScheme: colorScheme)\n        }\n#endif\n        return MessageCenterNavigationAppearance(theme: theme, colorScheme: colorScheme)\n    }\n\n    private var shouldShowBackButton: Bool {\n        if let showBackButton {\n            return showBackButton\n        }\n\n        return showBackButton ?? self.presentationMode.wrappedValue.isPresented\n    }\n\n    /// The body of the view.\n    public var body: some View {\n        let containerColor = navigationBarAppearance.barBackgroundColor\n        \n        MessageCenterMessageView(\n            viewModel: self.messageViewModel,\n            dismissAction: dismiss\n        )\n#if !os(macOS)\n        .applyUIKitNavigationAppearance()\n#endif\n        .navigationBarBackButtonHidden(true) // Hide the default back button\n#if !os(tvOS) && !os(macOS)\n        .navigationBarTitleDisplayMode(.inline)\n#endif\n        .navigationTitle(self.messageViewModel.message?.title ?? self.title ?? \"\")\n        .toolbar {\n            if shouldShowBackButton {\n#if !os(macOS)\n                ToolbarItemGroup(placement: .navigationBarLeading) {\n                    MessageCenterBackButton(dismissAction: dismiss)\n                }\n#else\n                ToolbarItemGroup(placement: .automatic) {\n                    MessageCenterBackButton(dismissAction: dismiss)\n                }\n#endif\n            }\n            \n#if os(iOS)\n            if #available(iOS 26.0, *) {\n                ToolbarItemGroup(placement: .topBarTrailing) {\n                    deleteButton\n                }\n            } else {\n                ToolbarItemGroup(placement: .navigationBarTrailing) {\n                    deleteButton\n                }\n            }\n#elseif !os(macOS)\n            ToolbarItemGroup(placement: .navigationBarTrailing) {\n                deleteButton\n            }\n#else\n            ToolbarItemGroup(placement: .automatic) {\n                deleteButton\n            }\n#endif\n            \n            if navigationBarAppearance.titleColor != nil || navigationBarAppearance.titleFont != nil {\n                ToolbarItemGroup(placement: .principal) {\n                    // Custom title with detected color\n                    Text(self.messageViewModel.message?.title ?? self.title ?? \"\")\n                        .foregroundColor(navigationBarAppearance.titleColor)\n                        .airshipApplyIf(navigationBarAppearance.titleFont != nil) { text in\n                            text.font(navigationBarAppearance.titleFont)\n                        }\n                }\n            }\n        }\n        .airshipApplyIf(containerColor != nil) { view in\n            let visibility: Visibility = if #available(iOS 26.0, *) {\n                .automatic\n            } else {\n                .visible\n            }\n#if !os(macOS)\n            view.toolbarBackground(containerColor!, for: .navigationBar)\n                .toolbarBackground(visibility, for: .navigationBar)\n#endif\n        }\n    }\n\n    @ViewBuilder\n    private var deleteButton: some View {\n        if theme.hideDeleteButton != true {\n            Button(\n                \"ua_delete_message\".messageCenterLocalizedString,\n                systemImage: \"trash\",\n                role: .destructive\n            ) {\n                Task {\n                    await messageViewModel.delete()\n                }\n                dismiss()\n            }\n            .tint(navigationBarAppearance.deleteButtonColor)\n            .disabled(messageViewModel.message == nil)\n        }\n    }\n\n    private func dismiss() {\n        guard !isDismissed else { return }\n        isDismissed = true\n\n        if let dismissAction = self.dismissAction {\n            dismissAction()\n        }\n        messageViewModel.thomasDismissHandle.dismiss()\n        presentationMode.wrappedValue.dismiss()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageView/MessageCenterThomasView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct MessageCenterThomasView: View {\n    \n    @Binding\n    var phase: MessageCenterMessageView.DisplayPhase\n    \n    @StateObject\n    private var viewModel: ViewModel\n    \n    init(\n        phase: Binding<MessageCenterMessageView.DisplayPhase>,\n        layoutRequest: @escaping () async throws -> URLRequest,\n        analytics: ThomasDisplayListener,\n        dismissHandle: ThomasDismissHandle,\n        stateStorage: (any LayoutDataStorage)? = nil\n    ) {\n        self._phase = phase\n        self._viewModel = StateObject(\n            wrappedValue: ViewModel(\n                request: layoutRequest,\n                analytics: analytics,\n                dismissHandle: dismissHandle,\n                stateStorage: stateStorage\n            )\n        )\n    }\n    \n    var body: some View {\n        if let layout = viewModel.layout {\n            AirshipSimpleLayoutView(\n                layout: layout,\n                viewModel: viewModel.layoutViewModel\n            )\n        } else {\n            Color.clear.task {\n                switch phase {\n                case .loaded: return\n                default: self.phase = await viewModel.loadLayout()\n                }\n            }\n        }\n    }\n}\n\n@MainActor\nprivate final class ViewModel: ObservableObject {\n    private let layoutRequest: () async throws -> URLRequest\n    private let stateStorage: (any LayoutDataStorage)?\n\n    @Published\n    private(set) var layout: AirshipLayout? = nil\n\n    let analyticsRecorder: any ThomasDelegate\n    let dismissHandle: ThomasDismissHandle\n    let layoutViewModel: AirshipSimpleLayoutViewModel\n\n    init(\n        request: @escaping () async throws -> URLRequest,\n        analytics: ThomasDisplayListener,\n        dismissHandle: ThomasDismissHandle,\n        stateStorage: (any LayoutDataStorage)? = nil\n    ) {\n        self.layoutRequest = request\n        self.analyticsRecorder = analytics\n        self.dismissHandle = dismissHandle\n        self.stateStorage = stateStorage\n        self.layoutViewModel = AirshipSimpleLayoutViewModel(\n            delegate: analytics,\n            dismissHandle: dismissHandle,\n            stateStorage: stateStorage\n        )\n    }\n    \n    func loadLayout() async -> MessageCenterMessageView.DisplayPhase {\n        if let layout {\n            await preloadData(for: layout)\n            return .loaded\n        }\n        \n        do {\n            let request = try await self.layoutRequest()\n            let (data, _) = try await URLSession.airshipSecureSession.data(for: request)\n            let downloaded = try JSONDecoder().decode(AirshipLayout.self, from: data)\n            await preloadData(for: downloaded)\n            self.layout = downloaded\n        } catch {\n            return .error(error)\n        }\n        \n        return .loaded\n    }\n\n    func dismiss() {\n        self.dismissHandle.dismiss()\n    }\n    \n    private func preloadData(for layout: AirshipLayout) async {\n        await self.stateStorage?.prepare(restoreID: \"static\") //TODO: replace with the actual\n    }\n}\n\nextension View {\n    func also(_ action: (Self) -> ()) -> some View {\n        action(self)\n        return self\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/MessageView/MessageCenterWebView.swift",
    "content": "import Combine\nimport Foundation\nimport SwiftUI\n\n#if canImport(WebKit)\nimport WebKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n#if canImport(WebKit)\nstruct MessageCenterWebView: AirshipNativeViewRepresentable {\n\n#if os(macOS)\n    typealias NSViewType = WKWebView\n    func makeNSView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateNSView(_ nsView: WKWebView, context: Context) {\n        updateView(nsView, context: context)\n    }\n#else\n    typealias UIViewType = WKWebView\n\n    func makeUIView(context: Context) -> WKWebView {\n        return makeWebView(context: context)\n    }\n\n    func updateUIView(_ uiView: WKWebView, context: Context) {\n        updateView(uiView, context: context)\n    }\n#endif\n\n    @Binding\n    var phase: MessageCenterMessageView.DisplayPhase\n    let nativeBridgeExtension:\n    (() async throws -> MessageCenterNativeBridgeExtension)?\n\n    let request: () async throws -> URLRequest\n\n    let dismiss: () async -> Void\n\n    @State\n    private var isWebViewLoading: Bool = false\n\n    private var isLoading: Bool {\n        guard case .loading = self.phase else {\n            return false\n        }\n        return true\n    }\n\n    func makeWebView(context: Context) -> WKWebView {\n        let configuration = WKWebViewConfiguration()\n\n#if !os(macOS)\n        configuration.allowsInlineMediaPlayback = true\n        configuration.dataDetectorTypes = .all\n#endif\n\n        let webView = WKWebView(\n            frame: CGRect.zero,\n            configuration: configuration\n        )\n        webView.allowsLinkPreview = false\n        webView.navigationDelegate = context.coordinator.nativeBridge\n\n        if #available(iOS 16.4, *) {\n            webView.isInspectable = Airship.isFlying && Airship.config.airshipConfig.isWebViewInspectionEnabled\n        }\n\n        return webView\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self)\n    }\n\n    func updateView(_ uiView: WKWebView, context: Context) {\n        Task {\n            await checkLoad(\n                webView: uiView,\n                coordinator: context.coordinator\n            )\n        }\n    }\n\n    @MainActor\n    func checkLoad(webView: WKWebView, coordinator: Coordinator) async {\n        if isLoading, !isWebViewLoading {\n            await self.load(webView: webView, coordinator: coordinator)\n        }\n    }\n\n    @MainActor\n    func load(webView: WKWebView, coordinator: Coordinator) async {\n        self.phase = .loading\n\n        do {\n            let delegate = try await self.nativeBridgeExtension?()\n            coordinator.nativeBridgeExtensionDelegate = delegate\n\n            let request = try await self.request()\n            _ = webView.load(request)\n            self.isWebViewLoading = true\n        } catch {\n            self.phase = .error(error)\n        }\n    }\n\n    @MainActor\n    private func pageFinished(error: (any Error)? = nil) async {\n        self.isWebViewLoading = false\n\n        if let error = error {\n            self.phase = .error(error)\n        } else {\n            self.phase = .loaded\n        }\n    }\n\n    class Coordinator: NSObject, AirshipWKNavigationDelegate,\n                       JavaScriptCommandDelegate,\n                       NativeBridgeDelegate\n    {\n\n\n        private let parent: MessageCenterWebView\n        private let challengeResolver: ChallengeResolver\n        let nativeBridge: NativeBridge\n        var nativeBridgeExtensionDelegate: (any NativeBridgeExtensionDelegate)? {\n            didSet {\n                self.nativeBridge.nativeBridgeExtensionDelegate = self.nativeBridgeExtensionDelegate\n            }\n        }\n\n        init(_ parent: MessageCenterWebView, resolver: ChallengeResolver = .shared) {\n            self.parent = parent\n            self.nativeBridge = NativeBridge()\n            self.challengeResolver = resolver\n            super.init()\n            self.nativeBridge.forwardNavigationDelegate = self\n            self.nativeBridge.javaScriptCommandDelegate = self\n            self.nativeBridge.nativeBridgeDelegate = self\n        }\n\n        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!)\n        {\n            Task { @MainActor in\n                await parent.pageFinished()\n            }\n        }\n\n        func webViewWebContentProcessDidTerminate(_ webView: WKWebView) {\n            Task { @MainActor in\n                await parent.load(webView: webView, coordinator: self)\n            }\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            didFail navigation: WKNavigation!,\n            withError error: any Error\n        ) {\n            Task { @MainActor in\n                await parent.pageFinished(error: error)\n            }\n        }\n\n        func webView(\n            _ webView: WKWebView,\n            respondTo challenge: URLAuthenticationChallenge)\n        async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n\n            return await challengeResolver.resolve(challenge)\n        }\n\n        func performCommand(_ command: JavaScriptCommand, webView: WKWebView) -> Bool {\n            return false\n        }\n\n        nonisolated func close() {\n            Task { @MainActor in\n                await parent.dismiss()\n            }\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Source/Views/Shared/MessageCenterBackButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nstruct MessageCenterBackButton: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @Environment(\\.airshipMessageCenterTheme)\n    private var theme\n\n    @Environment(\\.airshipMessageCenterPredicate)\n    private var predicate\n\n    var dismissAction: (@MainActor @Sendable () -> Void)?\n\n    @ViewBuilder\n    public var body: some View {\n\n        let backButtonColor = colorScheme.airshipResolveColor(\n            light: theme.backButtonColor,\n            dark: theme.backButtonColorDark\n        )\n\n        Button(action: {\n            self.dismissAction?()\n        }) {\n            Image(systemName: \"chevron.backward\")\n                .scaleEffect(0.68)\n                .font(Font.title.weight(.medium))\n                .foregroundColor(backButtonColor)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Tests/MessageCenterAPIClientTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n@testable import AirshipMessageCenter\n\nfinal class MessageCenterAPIClientTest: XCTestCase {\n\n    private var client: MessageCenterAPIClient! = nil\n    private let session = TestAirshipRequestSession()\n    private let user = MessageCenterUser(\n        username: \"username\",\n        password: \"password\"\n    )\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let messages = [\n        MessageCenterMessage(\n            title: \"Foo message\",\n            id: \"foo\",\n            contentType: .html,\n            extra: [:],\n            bodyURL: URL(string: \"anyurl.com\")!,\n            expirationDate: nil,\n            messageReporting: [\"foo\": \"reporting\"],\n            unread: true,\n            sentDate: Date(),\n            messageURL: URL(string: \"anyurl.com\")!,\n            rawMessageObject: [:]\n        )\n    ]\n\n    override func setUpWithError() throws {\n        self.client = MessageCenterAPIClient(\n            config: .testConfig(),\n            session: session\n        )\n    }\n\n    /// Tests retrieving the message list with success.\n    func testRetrieveMessageListSuccess() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        let messageResponse: String = \"\"\"\n            {\n                \"messages\": [\n                    {\n                        \"message_id\": \"some_mesg_id\",\n                        \"message_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/\",\n                        \"message_body_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/body/\",\n                        \"message_read_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/read/\",\n                        \"unread\": true,\n                        \"message_sent\": \"2010-09-05 12:13 -0000\",\n                        \"title\": \"Message title\",\n                        \"extra\": {\n                            \"some_key\": \"some_value\"\n                        },\n                        \"message_reporting\": { \"cool\": \"story\" },\n                        \"content_type\": \"text/html\",\n                        \"content_size\": \"128\"\n                    }\n                ]\n            }\n            \"\"\"\n\n        self.session.data = messageResponse.data(using: .utf8)\n\n        let response = try await self.client.retrieveMessageList(\n            user: self.user,\n            channelID: \"some channel\",\n            lastModified: \"some modified date\"\n        )\n\n        let messages = response.result!\n        let message = messages[0] as MessageCenterMessage\n        XCTAssertEqual(message.id, \"some_mesg_id\")\n        XCTAssertEqual(message.title, \"Message title\")\n        XCTAssertEqual(message.contentType, .html)\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/user/username/messages/\",\n            request.url!.absoluteString\n        )\n        XCTAssertEqual(\"GET\", request.method)\n\n        let expectedHeaders = [\n            \"X-UA-Channel-ID\": \"some channel\",\n            \"If-Modified-Since\": \"some modified date\",\n            \"Accept\": \"application/vnd.urbanairship+json; version=3;\"\n        ]\n\n        XCTAssertEqual(expectedHeaders, request.headers)\n    }\n\n    /// Tests retrieving the message list with missing body failure\n    func testRetrieveMessageMissingBody() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        do {\n            let _ = try await self.client.retrieveMessageList(\n                user: self.user,\n                channelID: \"some channel\",\n                lastModified: nil\n            )\n            XCTFail(\"Expected error\")\n        } catch {\n            XCTAssertNotNil(error)\n        }\n    }\n\n    /// Tests retrieving the message list with status code failure\n    func testRetrieveMessageFailure() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 500,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n\n        let response = try await self.client.retrieveMessageList(\n            user: self.user,\n            channelID: \"some channel\",\n            lastModified: nil\n        )\n\n        XCTAssertEqual(response.statusCode, 500)\n        XCTAssertNil(response.result)\n    }\n\n    /// Tests retrieving the message list with parsing failure\n    func testRetrieveMessageJSONParseFailure() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n\n        do {\n            let _ = try await self.client.retrieveMessageList(\n                user: self.user,\n                channelID: \"some channel\",\n                lastModified: nil\n            )\n            XCTFail(\"Expected error\")\n        } catch {\n            XCTAssertNotNil(error)\n        }\n    }\n\n    /// Tests batch mark as read success.\n    func testBatchMarkAsReadSuccess() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n        let _ = try await self.client.performBatchMarkAsRead(\n            forMessages: self.messages,\n            user: self.user,\n            channelID: \"some channel\"\n        )\n\n        let request = self.session.lastRequest!\n        let requestPayload = try JSONSerialization.jsonObject(\n            with: request.body!\n        )\n\n        let expected: [String: AnyHashable] = [\n            \"messages\": [[\"foo\": \"reporting\"]]\n        ]\n\n        XCTAssertEqual(\n            expected as NSDictionary,\n            requestPayload as! NSDictionary\n        )\n    }\n\n    /// Tests batch mark as read failure.\n    func testBatchMarkAsReadFailure() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 500,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n        let response = try await self.client.performBatchMarkAsRead(\n            forMessages: self.messages,\n            user: self.user,\n            channelID: \"some channel\"\n        )\n        XCTAssertEqual(500, response.statusCode)\n    }\n\n    /// Tests batch delete success.\n    func testBatchDeleteSuccess() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n        let _ = try await self.client.performBatchDelete(\n            forMessages: self.messages,\n            user: self.user,\n            channelID: \"some channel\"\n        )\n\n        let request = self.session.lastRequest!\n        let requestPayload = try JSONSerialization.jsonObject(\n            with: request.body!\n        )\n\n        let expected: [String: AnyHashable] = [\n            \"messages\": [[\"foo\": \"reporting\"]]\n        ]\n\n        XCTAssertEqual(\n            expected as NSDictionary,\n            requestPayload as! NSDictionary\n        )\n    }\n\n    /// Tests batch delete failure.\n    func testBatchDeleteAsReadFailure() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 500,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n        let response = try await self.client.performBatchDelete(\n            forMessages: self.messages,\n            user: self.user,\n            channelID: \"some channel\"\n        )\n        XCTAssertEqual(500, response.statusCode)\n    }\n\n    /// Tests creating user with success.\n    func testCreateUserSuccess() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 201,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = try AirshipJSONUtils.data(\n            [\n                \"user_id\": \"some user id\",\n                \"password\": \"some password\",\n            ]\n        )\n\n        let response = try await self.client.createUser(\n            withChannelID: \"some channel\"\n        )\n        let request = self.session.lastRequest!\n        XCTAssertEqual(response.statusCode, 201)\n        XCTAssertNotNil(response.result)\n        XCTAssertEqual(response.result?.username, \"some user id\")\n        XCTAssertEqual(response.result?.password, \"some password\")\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/user/\",\n            request.url?.absoluteString\n        )\n        XCTAssertEqual(\n            AirshipRequestAuth.channelAuthToken(identifier: \"some channel\"),\n            request.auth\n        )\n        XCTAssertEqual(\"POST\", request.method)\n\n        let requestPayload = try JSONSerialization.jsonObject(\n            with: request.body!\n        )\n\n        let expected: [String: AnyHashable] = [\n            \"ios_channels\": [\"some channel\"]\n        ]\n\n        XCTAssertEqual(\n            expected as NSDictionary,\n            requestPayload as! NSDictionary\n        )\n    }\n\n    /// Tests creating user with status code failure\n    func testCreateUserFailure() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 400,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n        self.session.data = \"{\\\"ok\\\":true}\".data(using: .utf8)\n        let response = try await self.client.createUser(\n            withChannelID: \"channelID\"\n        )\n        XCTAssertEqual(response.statusCode, 400)\n        XCTAssertNil(response.result)\n    }\n\n    /// Tests create user with parsing failure\n    func testCreateUserFailureJSONParseError() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        self.session.data = try AirshipJSONUtils.data([:])\n        do {\n            let _ = try await self.client.createUser(withChannelID: \"channelID\")\n            XCTFail(\"Expected error\")\n        } catch {\n            XCTAssertNotNil(error)\n        }\n    }\n\n    /// Tests updating user with success.\n    func testUpdateUserSuccess() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        let response = try await self.client.updateUser(\n            self.user,\n            channelID: \"some channel\"\n        )\n\n        let request = self.session.lastRequest!\n        XCTAssertEqual(response.statusCode, 200)\n        XCTAssertNil(response.result)\n        XCTAssertEqual(\n            \"https://device-api.urbanairship.com/api/user/username\",\n            request.url!.absoluteString\n        )\n        XCTAssertEqual(\"POST\", request.method)\n\n        let requestPayload = try JSONSerialization.jsonObject(\n            with: request.body!\n        )\n        let expected: [String: AnyHashable] = [\n            \"ios_channels\": [\n                \"add\": [\"some channel\"]\n            ]\n        ]\n\n        XCTAssertEqual(\n            expected as NSDictionary,\n            requestPayload as! NSDictionary\n        )\n    }\n\n    func testRetrieveMessageListInvalidContentTypeBecomesUnknown() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        let messageResponse: String = \"\"\"\n            {\n                \"messages\": [\n                    {\n                        \"message_id\": \"some_mesg_id\",\n                        \"message_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/\",\n                        \"message_body_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/body/\",\n                        \"message_read_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/read/\",\n                        \"unread\": true,\n                        \"message_sent\": \"2010-09-05 12:13 -0000\",\n                        \"title\": \"Message title\",\n                        \"extra\": {\n                            \"some_key\": \"some_value\"\n                        },\n                        \"message_reporting\": { \"cool\": \"story\" },\n                        \"content_type\": \"application/x-unknown\",\n                        \"content_size\": \"128\"\n                    }\n                ]\n            }\n            \"\"\"\n\n        self.session.data = messageResponse.data(using: .utf8)\n\n        let response = try await self.client.retrieveMessageList(\n            user: self.user,\n            channelID: \"some channel\",\n            lastModified: nil\n        )\n\n        let messages = response.result!\n        XCTAssertEqual(messages.count, 1)\n        XCTAssertEqual(messages[0].contentType, .unknown(\"application/x-unknown\"))\n    }\n\n    func testRetrieveMessageListMissingContentTypeBecomesUnknown() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 200,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n\n        let messageResponse: String = \"\"\"\n            {\n                \"messages\": [\n                    {\n                        \"message_id\": \"some_mesg_id\",\n                        \"message_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/\",\n                        \"message_body_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/body/\",\n                        \"message_read_url\": \"https://go.urbanairship.com/api/user/userId/messages/message/some_mesg_id/read/\",\n                        \"unread\": true,\n                        \"message_sent\": \"2010-09-05 12:13 -0000\",\n                        \"title\": \"Message title\",\n                        \"extra\": {\n                            \"some_key\": \"some_value\"\n                        },\n                        \"message_reporting\": { \"cool\": \"story\" },\n                        \"content_size\": \"128\"\n                    }\n                ]\n            }\n            \"\"\"\n\n        self.session.data = messageResponse.data(using: .utf8)\n\n        let response = try await self.client.retrieveMessageList(\n            user: self.user,\n            channelID: \"some channel\",\n            lastModified: nil\n        )\n\n        let messages = response.result!\n        XCTAssertEqual(messages.count, 1)\n        XCTAssertEqual(messages[0].contentType, .unknown(nil))\n    }\n\n    /// Tests creating user with status code failure\n    func testUpdateUserFailure() async throws {\n        self.session.response = HTTPURLResponse(\n            url: URL(string: \"www.anyurl.com\")!,\n            statusCode: 400,\n            httpVersion: nil,\n            headerFields: [:]\n        )\n        do {\n            let response = try await self.client.updateUser(\n                self.user,\n                channelID: \"some channel\"\n            )\n            XCTAssertEqual(response.statusCode, 400)\n            XCTAssertNil(response.result)\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Tests/MessageCenterListTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport XCTest\n\n@testable import AirshipCore\n@testable import AirshipMessageCenter\n\n@MainActor\nfinal class MessageCenterListTest: XCTestCase {\n\n    private var disposables = Set<AnyCancellable>()\n    private let dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let config: RuntimeConfig = .testConfig()\n\n    private lazy var store: MessageCenterStore = {\n        let modelURL = AirshipMessageCenterResources.bundle\n            .url(\n                forResource: \"UAInbox\",\n                withExtension: \"momd\"\n            )\n        if let modelURL = modelURL {\n            let storeName = String(\n                format: \"Inbox-%@.sqlite\",\n                self.config.appCredentials.appKey\n            )\n            let coreData = UACoreData(\n                name: \"UAInbox\",\n                modelURL: modelURL,\n                inMemory: true,\n                stores: [storeName]\n            )\n            return MessageCenterStore(\n                config: self.config,\n                dataStore: self.dataStore,\n                coreData: coreData,\n                date: self.date\n            )\n        }\n        return MessageCenterStore(\n            config: self.config,\n            dataStore: self.dataStore,\n            date: self.date\n        )\n    }()\n\n    private let channel = TestChannel()\n    private let workManager: TestWorkManager = TestWorkManager()\n    private let client: TestMessageCenterAPIClient = TestMessageCenterAPIClient()\n    private let sleeper = TestTaskSleeper()\n    private let notificationCenter = NotificationCenter()\n    private let date = UATestDate(offset: 0, dateOverride: Date())\n\n\n    private lazy var inbox = DefaultMessageCenterInbox(\n        channel: channel,\n        client: client,\n        config: config,\n        store: store,\n        notificationCenter: notificationCenter,\n        date: date,\n        workManager: workManager,\n        taskSleeper: sleeper\n    )\n\n    func testMessageCenterInboxUser() async throws {\n\n        let expectedUser = MessageCenterUser(\n            username: \"AnyName\",\n            password: \"AnyPassword\"\n        )\n\n        // Save user\n        await store.saveUser(expectedUser, channelID: \"987654433\")\n\n        self.inbox.enabled = true\n        var user = await self.inbox.user\n        XCTAssertNotNil(user)\n        XCTAssertEqual(user!.username, expectedUser.username)\n        XCTAssertEqual(user!.password, expectedUser.password)\n\n        self.inbox.enabled = false\n        user = await self.inbox.user\n        XCTAssertNil(user)\n\n        // Reset User\n        await store.resetUser()\n\n        let resetedUser = await self.inbox.user\n        XCTAssertNil(resetedUser)\n    }\n\n    func testMessageCenterIdenityHint() async throws {\n        let user = MessageCenterUser(\n            username: \"AnyName\",\n            password: \"AnyPassword\"\n        )\n\n        // Save user\n        await store.saveUser(user, channelID: \"987654433\")\n\n        self.inbox.enabled = true\n\n        XCTAssertEqual(1, self.channel.extenders.count)\n        let payload = await self.channel.channelPayload\n        XCTAssertEqual(user.username, payload.identityHints?.userID)\n    }\n\n    func testMessageCenterIdenityHintRestoreMessageCenterDisabled() async throws {\n        self.channel.extenders.removeAll()\n        var airshipConfig = AirshipConfig()\n        airshipConfig.restoreMessageCenterOnReinstall = false\n\n        let user = MessageCenterUser(\n            username: \"AnyName\",\n            password: \"AnyPassword\"\n        )\n\n        // Save user\n        await store.saveUser(user, channelID: \"987654433\")\n\n        let inbox = DefaultMessageCenterInbox(\n            channel: channel,\n            client: client,\n            config: .testConfig(airshipConfig: airshipConfig),\n            store: store,\n            workManager: workManager\n        )\n\n        inbox.enabled = true\n\n        XCTAssertEqual(1, self.channel.extenders.count)\n        let payload = await self.channel.channelPayload\n        XCTAssertNil(payload.identityHints?.userID)\n    }\n\n    func testRestoreMessageCenterDisabled() async throws {\n        self.channel.extenders.removeAll()\n        var airshipConfig = AirshipConfig()\n        airshipConfig.restoreMessageCenterOnReinstall = false\n\n        let user = MessageCenterUser(\n            username: \"AnyName\",\n            password: \"AnyPassword\"\n        )\n\n        // Save user\n        await store.saveUser(user, channelID: \"987654433\")\n\n        let inbox = DefaultMessageCenterInbox(\n            channel: channel,\n            client: client,\n            config: .testConfig(airshipConfig: airshipConfig),\n            store: store,\n            workManager: workManager\n        )\n\n        inbox.enabled = true\n\n        let fromInbox = await self.inbox.user\n        let fromStore = await self.store.user\n\n        XCTAssertNil(fromInbox)\n        XCTAssertNil(fromStore)\n    }\n\n    func testMessageRetrieve() async throws {\n        self.inbox.enabled = true\n\n        try await self.store.updateMessages(\n            messages: MessageCenterMessage.generateMessages(3),\n            lastModifiedTime: \"\"\n        )\n\n        let messages = await self.inbox.messages\n\n        XCTAssertNotNil(messages)\n        XCTAssertEqual(messages.count, 3)\n\n    }\n\n    func testMessageRetrieveWithId() async throws {\n        self.inbox.enabled = true\n\n        let messages = MessageCenterMessage.generateMessages(1)\n        try await self.store.updateMessages(\n            messages: messages,\n            lastModifiedTime: \"\"\n        )\n\n        let message = try XCTUnwrap(messages.first)\n\n        let fetchedMessage = await self.inbox.message(forID: message.id)\n\n        XCTAssertNotNil(fetchedMessage)\n        XCTAssertEqual(message.id, fetchedMessage?.id)\n        XCTAssertEqual(message.sentDate, fetchedMessage?.sentDate)\n        XCTAssertEqual(message.bodyURL, fetchedMessage?.bodyURL)\n        XCTAssertEqual(message.expirationDate, fetchedMessage?.expirationDate)\n        XCTAssertEqual(message.messageURL, fetchedMessage?.messageURL)\n\n    }\n\n    @MainActor\n    func testUpdateMessages() async throws {\n        self.inbox.enabled = true\n\n\n\n        let messages = MessageCenterMessage.generateMessages(1)\n        let message = try XCTUnwrap(messages.first)\n\n        // The message does not exists on the store yet\n        let fetchedMessage = await self.inbox.message(forID: message.id)\n        XCTAssertNil(fetchedMessage)\n\n        let expectation = self.expectation(\n            description: \"waiting for message publisher\"\n        )\n        self.inbox.messagePublisher\n            .receive(on: RunLoop.main)\n            .sink { _ in\n                expectation.fulfill()\n            }\n            .store(in: &disposables)\n\n        // Add the message to the store\n        try await self.store.updateMessages(\n            messages: messages,\n            lastModifiedTime: \"\"\n        )\n\n        await fulfillment(of: [expectation], timeout: 3.0)\n\n        let updatedMessage = await self.inbox.message(forID: message.id)\n        XCTAssertNotNil(updatedMessage)\n    }\n\n    func testRefreshMessages() async throws {\n        self.channel.identifier = UUID().uuidString\n\n        let expectations = self.expectation(description: \"client called\")\n        expectations.expectedFulfillmentCount = 2\n\n        var messageUpdates = self.inbox.messageUpdates.makeAsyncIterator()\n        var messageUpdate = await messageUpdates.next()\n        XCTAssertEqual(messageUpdate, [])\n\n        var unreadCountUpdates = self.inbox.unreadCountUpdates.makeAsyncIterator()\n        var unreadCountUpdate = await unreadCountUpdates.next()\n        XCTAssertEqual(unreadCountUpdate, 0)\n\n\n        let messages = MessageCenterMessage.generateMessages(1)\n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n\n        self.client.onCreateUser = { channelID in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            expectations.fulfill()\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.client.onRetrieve = { user, channelID, lastModified in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            XCTAssertEqual(user, mcUser)\n            XCTAssertNil(lastModified)\n\n            expectations.fulfill()\n            return AirshipHTTPResponse(\n                result: messages,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        let result = await self.inbox.refreshMessages()\n        XCTAssertTrue(result)\n        XCTAssertFalse(self.workManager.workRequests.last!.requiresNetwork)\n        XCTAssertEqual(self.workManager.workRequests.last!.conflictPolicy, .replace)\n        await self.fulfillment(of: [expectations])\n\n        messageUpdate = await messageUpdates.next()\n        XCTAssertEqual(messageUpdate, messages)\n\n        unreadCountUpdate = await unreadCountUpdates.next()\n        XCTAssertEqual(unreadCountUpdate, 1)\n    }\n\n    func testRefreshMessagesThrowingSuccess() async throws {\n        self.channel.identifier = UUID().uuidString\n\n        let expectations = self.expectation(description: \"client called\")\n        expectations.expectedFulfillmentCount = 2\n\n        let messages = MessageCenterMessage.generateMessages(1)\n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n\n        self.client.onCreateUser = { channelID in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            expectations.fulfill()\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.client.onRetrieve = { user, channelID, lastModified in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            XCTAssertEqual(user, mcUser)\n            XCTAssertNil(lastModified)\n\n            expectations.fulfill()\n            return AirshipHTTPResponse(\n                result: messages,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        try await self.inbox.refreshMessagesThrowing()\n        await self.fulfillment(of: [expectations])\n\n        let inboxMessages = await self.inbox.messages\n        XCTAssertEqual(inboxMessages, messages)\n    }\n\n    func testRefreshMessagesThrowingThrowsDisabled() async throws {\n        self.inbox.enabled = false\n        self.workManager.autoLaunchRequests = true\n\n        do {\n            try await self.inbox.refreshMessagesThrowing()\n            XCTFail(\"Expected MessageCenterInboxError.disabled\")\n        } catch let error as MessageCenterInboxError {\n            XCTAssertEqual(error, .disabled)\n        } catch {\n            XCTFail(\"Unexpected error: \\(error)\")\n        }\n    }\n\n    func testRefreshMessagesThrowingThrowsFailedToFetchWhenNoChannel() async throws {\n        self.channel.identifier = nil\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        do {\n            try await self.inbox.refreshMessagesThrowing()\n            XCTFail(\"Expected MessageCenterInboxError.failedToFetchMessage\")\n        } catch let error as MessageCenterInboxError {\n            XCTAssertEqual(error, .failedToFetchMessage)\n        } catch {\n            XCTFail(\"Unexpected error: \\(error)\")\n        }\n    }\n\n    func testRefreshMessagesThrowingThrowsFailedToFetchWhenRetrieveFails() async throws {\n        self.channel.identifier = UUID().uuidString\n\n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n\n        self.client.onCreateUser = { channelID in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.client.onRetrieve = { user, channelID, lastModified in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            XCTAssertEqual(user, mcUser)\n            XCTAssertNil(lastModified)\n\n            return AirshipHTTPResponse(\n                result: [],\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        do {\n            try await self.inbox.refreshMessagesThrowing()\n            XCTFail(\"Expected MessageCenterInboxError.failedToFetchMessage\")\n        } catch let error as MessageCenterInboxError {\n            XCTAssertEqual(error, .failedToFetchMessage)\n        } catch {\n            XCTFail(\"Unexpected error: \\(error)\")\n        }\n    }\n\n    func testRefreshMessagesWithTimeout() async throws {\n        self.channel.identifier = UUID().uuidString\n\n        let expectations = self.expectation(description: \"client called\")\n        expectations.expectedFulfillmentCount = 2\n\n        let messages = MessageCenterMessage.generateMessages(1)\n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n\n        self.client.onCreateUser = { channelID in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            expectations.fulfill()\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.client.onRetrieve = { user, channelID, lastModified in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            XCTAssertEqual(user, mcUser)\n            XCTAssertNil(lastModified)\n\n            expectations.fulfill()\n            return AirshipHTTPResponse(\n                result: messages,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        let result = try await self.inbox.refreshMessages(timeout: 4.0)\n        XCTAssertTrue(result)\n        XCTAssertFalse(self.workManager.workRequests.last!.requiresNetwork)\n        XCTAssertEqual(self.workManager.workRequests.last!.conflictPolicy, .replace)\n        await self.fulfillment(of: [expectations])\n    }\n    \n    func testRefreshMessagesNoChannel() async throws {\n        self.channel.identifier = nil\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        let result = await self.inbox.refreshMessages()\n        XCTAssertFalse(result)\n    }\n\n    func testRefreshMessagesUserCreationFailed() async throws {\n        self.channel.identifier = UUID().uuidString\n\n        self.client.onCreateUser = { channelID in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            return AirshipHTTPResponse(\n                result: nil,\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        let result = await self.inbox.refreshMessages()\n        XCTAssertFalse(result)\n    }\n\n    func testRefreshMessagesRetrieveFailed() async throws {\n        self.channel.identifier = UUID().uuidString\n\n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n\n        self.client.onCreateUser = { channelID in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        self.client.onRetrieve = { user, channelID, lastModified in\n            XCTAssertEqual(channelID, self.channel.identifier)\n            XCTAssertEqual(user, mcUser)\n            XCTAssertNil(lastModified)\n\n            return AirshipHTTPResponse(\n                result: [],\n                statusCode: 400,\n                headers: [:]\n            )\n        }\n\n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n\n        let result = await self.inbox.refreshMessages()\n        XCTAssertFalse(result)\n    }\n    \n    func testRefreshOnMessageExpiresOnAfterUpdate() async throws {\n        var sleeps = await self.sleeper.sleepUpdates.makeStream().makeAsyncIterator()\n        self.channel.identifier = UUID().uuidString\n        \n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n        \n        let message = MessageCenterMessage.generateMessage(\n            sentDate: self.date.now.advanced(by: -1),\n            expiry: self.date.now.advanced(by: 1)\n        )\n\n        self.client.onCreateUser = { _ in\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n\n        var refreshes = AsyncStream<Bool> { continuation in\n            let responses = AirshipAtomicValue([[message], []])\n            self.client.onRetrieve = { _, _, _ in\n                defer {\n                    continuation.yield(true)\n                }\n\n                let response = responses.value.first\n                responses.update { responses in\n                    var updated = responses\n                    if !updated.isEmpty {\n                        updated.removeFirst()\n                    }\n                    return updated\n                }\n\n                return AirshipHTTPResponse(\n                    result: response ?? [],\n                    statusCode: 200,\n                    headers: [:]\n                )\n            }\n        }.makeAsyncIterator()\n\n\n        XCTAssert(self.workManager.workRequests.isEmpty)\n        \n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n        await self.inbox.refreshMessages()\n        _ = await refreshes.next()\n\n        var fetched = await self.inbox.message(forID: message.id)\n        XCTAssertNotNil(fetched)\n\n        let sleep = await sleeps.next()\n        XCTAssertEqual(1, sleep)\n        _ = await refreshes.next()\n\n        fetched = await self.inbox.message(forID: message.id)\n        XCTAssertNil(fetched)\n    }\n    \n    func testRefreshOnMessageExpiresTakesEarliestDate() async throws {\n        self.channel.identifier = UUID().uuidString\n        \n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n        \n        let messages = [\n            MessageCenterMessage.generateMessage(\n                sentDate: self.date.now.advanced(by: -1),\n                expiry: self.date.now.advanced(by: 2)\n            ),\n            MessageCenterMessage.generateMessage(\n                sentDate: self.date.now.advanced(by: -1),\n                expiry: self.date.now.advanced(by: 3)\n            )\n        ]\n        \n        let refresh = self.expectation(description: \"client called\")\n        refresh.assertForOverFulfill = false\n        \n        self.client.onCreateUser = { _ in\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n        \n        var isRefreshed = false\n        self.client.onRetrieve = { _, _, _ in\n            defer { isRefreshed = true }\n            \n            refresh.fulfill()\n            return AirshipHTTPResponse(\n                result: isRefreshed ? [] : messages,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n        \n        \n        XCTAssert(self.workManager.workRequests.isEmpty)\n        \n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n        \n        await self.inbox.refreshMessages()\n        \n        await fulfillment(of: [refresh], timeout: 5)\n        \n        let saved = await self.inbox.message(forID: messages.first!.id)\n        XCTAssertNotNil(saved)\n        \n        try await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)\n\n        XCTAssertEqual(3, self.workManager.workRequests.count)\n    }\n    \n    func testNoRefreshWithNoExpirationDate() async throws {\n        self.channel.identifier = UUID().uuidString\n        \n        let mcUser = MessageCenterUser(\n            username: UUID().uuidString,\n            password: UUID().uuidString\n        )\n        \n        let messages = [\n            MessageCenterMessage.generateMessage(\n                sentDate: self.date.now.advanced(by: -1)\n            ),\n            MessageCenterMessage.generateMessage(\n                sentDate: self.date.now.advanced(by: -2)\n            )\n        ]\n        \n        let refresh = self.expectation(description: \"client called\")\n        refresh.assertForOverFulfill = false\n        \n        self.client.onCreateUser = { _ in\n            return AirshipHTTPResponse(\n                result: mcUser,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n        \n        var isRefreshed = false\n        self.client.onRetrieve = { _, _, _ in\n            defer { isRefreshed = true }\n            \n            refresh.fulfill()\n            return AirshipHTTPResponse(\n                result: isRefreshed ? [] : messages,\n                statusCode: 200,\n                headers: [:]\n            )\n        }\n        \n        XCTAssert(self.workManager.workRequests.isEmpty)\n        \n        self.inbox.enabled = true\n        self.workManager.autoLaunchRequests = true\n        \n        await self.inbox.refreshMessages()\n        \n        await fulfillment(of: [refresh], timeout: 5)\n        \n        let saved = await self.inbox.message(forID: messages.first!.id)\n        XCTAssertNotNil(saved)\n        \n        self.date.advance(by: 1)\n\n        XCTAssertEqual(2, self.workManager.workRequests.count)\n    }\n}\n\n\nfileprivate final class TestMessageCenterAPIClient : MessageCenterAPIClientProtocol, @unchecked Sendable {\n    var onRetrieve: ((MessageCenterUser, String, String?) async throws -> AirshipHTTPResponse<[MessageCenterMessage]>)?\n    var onDelete: (([MessageCenterMessage], MessageCenterUser, String) async throws -> AirshipHTTPResponse<Void>)?\n    var onRead: (([MessageCenterMessage], MessageCenterUser, String) async throws -> AirshipHTTPResponse<Void>)?\n    var onCreateUser: ((String) async throws -> AirshipHTTPResponse<MessageCenterUser>)?\n    var onUpdateUser: ((MessageCenterUser, String) async throws -> AirshipHTTPResponse<Void>)?\n\n    func retrieveMessageList(user: MessageCenterUser, channelID: String, lastModified: String?) async throws -> AirshipHTTPResponse<[MessageCenterMessage]> {\n        return try await self.onRetrieve!(user, channelID, lastModified)\n    }\n    \n    func performBatchDelete(forMessages messages: [MessageCenterMessage], user: MessageCenterUser, channelID: String) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.onDelete!(messages, user, channelID)\n    }\n    \n    func performBatchMarkAsRead(forMessages messages: [MessageCenterMessage], user: MessageCenterUser, channelID: String) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.onRead!(messages, user, channelID)\n    }\n    \n    func createUser(withChannelID channelID: String) async throws -> AirshipHTTPResponse<MessageCenterUser> {\n        return try await self.onCreateUser!(channelID)\n    }\n    \n    func updateUser(_ user: MessageCenterUser, channelID: String) async throws -> AirshipHTTPResponse<Void> {\n        return try await self.onUpdateUser!(user, channelID)\n    }\n    \n}\n\nactor TestTaskSleeper : AirshipTaskSleeper {\n    var sleepUpdates: AirshipAsyncChannel<TimeInterval> = AirshipAsyncChannel()\n    var sleeps : [TimeInterval] = []\n\n    func sleep(timeInterval: TimeInterval) async throws {\n        sleeps.append(timeInterval)\n        await sleepUpdates.send(timeInterval)\n        await Task.yield()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Tests/MessageCenterMessageTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\nimport AirshipCore\n@testable import AirshipMessageCenter\n\nfinal class MessageCenterMessageTest: XCTestCase {\n    func testHashing() throws {\n        let date = Date()\n        let m1 = MessageCenterMessage(title: \"title\",\n                                      id: \"identifier\",\n                                      contentType: .html,\n                                      extra: [\"cool\": \"story\"],\n                                      bodyURL: URL(string: \"www.myspace.com\")!,\n                                      expirationDate: date,\n                                      messageReporting: [\"any\": \"thing\"],\n                                      unread: true,\n                                      sentDate: date,\n                                      messageURL:  URL(string: \"www.myspace.com\")!,\n                                      rawMessageObject: [\"raw\": \"message object\"])\n\n        let m2 = MessageCenterMessage(title: \"title\",\n                                      id: \"identifier\",\n                                      contentType: .html,\n                                      extra: [\"cool\": \"story\"],\n                                      bodyURL: URL(string: \"www.myspace.com\")!,\n                                      expirationDate: date,\n                                      messageReporting: [\"any\": \"thing\"],\n                                      unread: true,\n                                      sentDate: date,\n                                      messageURL:  URL(string: \"www.myspace.com\")!,\n                                      rawMessageObject: [\"raw\": \"message object\"])\n\n        XCTAssertTrue(m1 == m2)\n\n        var dictionary = [MessageCenterMessage: String]()\n        dictionary[m1] = \"keyed with m1\"\n        dictionary[m2] = \"keyed with m2\"\n        XCTAssertEqual(dictionary.count, 1, \"dictionary should only contain one entry since m1 and m2 are equal.\")\n    }\n    \n    func testEqualityConsidersUnreadState() {\n        let date = Date()\n        let unread = MessageCenterMessage(\n            title: \"title\",\n            id: \"identifier\",\n            contentType: .html,\n            extra: [:],\n            bodyURL: URL(string: \"www.myspace.com\")!,\n            expirationDate: date,\n            messageReporting: nil,\n            unread: true,\n            sentDate: date,\n            messageURL: URL(string: \"www.myspace.com\")!,\n            rawMessageObject: [\"raw\": \"message object\"]\n        )\n\n        let read = MessageCenterMessage(\n            title: \"title\",\n            id: \"identifier\",\n            contentType: .html,\n            extra: [:],\n            bodyURL: URL(string: \"www.myspace.com\")!,\n            expirationDate: date,\n            messageReporting: nil,\n            unread: false,\n            sentDate: date,\n            messageURL: URL(string: \"www.myspace.com\")!,\n            rawMessageObject: [\"raw\": \"message object\"]\n        )\n\n        XCTAssertNotEqual(unread, read)\n        XCTAssertNotEqual(unread.hashValue, read.hashValue)\n\n        var readCopy = unread\n        readCopy.unread = false\n        XCTAssertEqual(read, readCopy)\n        XCTAssertEqual(read.hashValue, readCopy.hashValue)\n    }\n\n    func testContentTypeDecoding() throws {\n        let validCases: [String: MessageCenterMessage.ContentType] = [\n            \"text/html\": .html,\n            \"text/plain\": .plain,\n            \"application/vnd.urbanairship.thomas+json;version=1\": .native(version: 1),\n            \"application/vnd.urbanairship.thomas+json; version=2\": .native(version: 2),\n            \"application/vnd.urbanairship.thomas+json; version=3; foo=bar\": .native(version: 3),\n        ]\n\n        let unknownCases: [String] = [\n            \"\",\n            \"text/json\",\n            \"garbage_value\",\n            \"application/vnd.urbanairship.thomas+json\",\n            \"application/vnd.urbanairship.thomas+json;\",\n            \"application/vnd.urbanairship.thomas+json;version=nan\",\n            \"application/vnd.urbanairship.thomas+json;garbage version=1\"\n        ]\n\n        for (input, expected) in validCases {\n            let data = try JSONEncoder().encode(input)\n            let result = try JSONDecoder().decode(MessageCenterMessage.ContentType.self, from: data)\n\n            XCTAssertEqual(\n                result,\n                expected,\n                \"Failed to decode valid input: '\\(input)'\"\n            )\n\n            let roundTripped = try JSONDecoder().decode(\n                MessageCenterMessage.ContentType.self,\n                from: JSONEncoder().encode(result)\n            )\n            XCTAssertEqual(\n                roundTripped,\n                expected,\n                \"Round-trip Codable failed for '\\(input)'\"\n            )\n        }\n\n        for input in unknownCases {\n            let data = try JSONEncoder().encode(input)\n            let result = try JSONDecoder().decode(MessageCenterMessage.ContentType.self, from: data)\n            XCTAssertEqual(\n                result,\n                .unknown(input),\n                \"Expected .unknown for unrecognized input: '\\(input)'\"\n            )\n        }\n    }\n\n    func testMessageProductIDNilWhenNotProvided() throws {\n        let date = Date()\n        let message = MessageCenterMessage(\n            title: \"title\",\n            id: \"identifier\",\n            contentType: .native(version: 1),\n            extra: [:],\n            bodyURL: URL(string: \"www.myspace.com\")!,\n            expirationDate: date,\n            messageReporting: [\"any\": \"thing\"],\n            unread: true,\n            sentDate: date,\n            messageURL: URL(string: \"www.myspace.com\")!,\n            rawMessageObject: [\"raw\": \"message object\"]\n        )\n\n        XCTAssertNil(message.productID)\n    }\n\n    func testNativeMessageCenterUsesExplicitProductIDWhenProvided() throws {\n        let date = Date()\n        let message = MessageCenterMessage(\n            title: \"title\",\n            id: \"identifier\",\n            contentType: .native(version: 1),\n            extra: [:],\n            bodyURL: URL(string: \"www.myspace.com\")!,\n            expirationDate: date,\n            messageReporting: [\"any\": \"thing\"],\n            unread: true,\n            sentDate: date,\n            messageURL: URL(string: \"www.myspace.com\")!,\n            rawMessageObject: [\n                \"raw\": \"message object\",\n                \"product_id\": \"custom_product_id\"\n            ]\n        )\n\n        XCTAssertEqual(message.productID, \"custom_product_id\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Tests/MessageCenterStoreTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport XCTest\n\n@testable import AirshipCore\n@testable import AirshipMessageCenter\n\nfinal class MessageCenterStoreTest: XCTestCase {\n    private var dataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private let config: RuntimeConfig = .testConfig()\n        \n    private lazy var store: MessageCenterStore = {\n        let modelURL = AirshipMessageCenterResources.bundle\n            .url(\n                forResource: \"UAInbox\",\n                withExtension: \"momd\"\n            )\n        if let modelURL = modelURL {\n            let storeName = String(\n                format: \"Inbox-%@.sqlite\",\n                self.config.appCredentials.appKey\n            )\n            let coreData = UACoreData(\n                name: \"UAInbox\",\n                modelURL: modelURL,\n                inMemory: true,\n                stores: [storeName]\n            )\n            return MessageCenterStore(\n                config: self.config,\n                dataStore: self.dataStore,\n                coreData: coreData\n            )\n        }\n        return MessageCenterStore(\n            config: self.config,\n            dataStore: self.dataStore\n        )\n    }()\n\n    func testMessageCenterStoreSaveAndResetUser() async throws {\n\n        let expectedUser = MessageCenterUser(\n            username: \"AnyName\",\n            password: \"AnyPassword\"\n        )\n\n        // Save user\n        await store.saveUser(expectedUser, channelID: \"987654433\")\n\n        let user = await store.user\n        XCTAssertNotNil(user)\n        XCTAssertEqual(user!.username, expectedUser.username)\n        XCTAssertEqual(user!.password, expectedUser.password)\n\n        // Reset User\n        await store.resetUser()\n\n        let resetedUser = await store.user\n        XCTAssertNil(resetedUser)\n    }\n\n    func testUserRequiredUpdate() async throws {\n        // Set setUserRequireUpdate true\n        await store.setUserRequireUpdate(true)\n        var requiredUpdate = await store.userRequiredUpdate\n        XCTAssertTrue(requiredUpdate)\n\n        // Set Required update false\n        await store.setUserRequireUpdate(false)\n        requiredUpdate = await store.userRequiredUpdate\n        XCTAssertFalse(requiredUpdate)\n    }\n\n    func testFetchMessages() async throws {\n        let messages = MessageCenterMessage.generateMessages(3)\n\n        try await store.updateMessages(\n            messages: messages,\n            lastModifiedTime: \"\"\n        )\n    }\n\n    func testSyncMessages() async throws {\n        let generated = MessageCenterMessage.generateMessages(5)\n        var messages = Array(generated[0...2])\n\n        try await store.updateMessages(\n            messages: messages,\n            lastModifiedTime: \"\"\n        )\n\n        var fetchedMessage = await store.messages\n        XCTAssertEqual(messages, fetchedMessage)\n\n        messages.remove(at: 0)\n        messages.append(contentsOf: generated[3...4])\n\n        try await store.updateMessages(\n            messages: messages,\n            lastModifiedTime: \"\"\n        )\n\n        fetchedMessage = await store.messages\n        XCTAssertEqual(messages, fetchedMessage)\n    }\n}\n\nextension MessageCenterMessage {\n\n    static func generateMessage(\n        sentDate: Date = Date(),\n        expiry: Date? = nil\n    ) -> MessageCenterMessage {\n        return MessageCenterMessage(\n            title: UUID().uuidString,\n            id: UUID().uuidString,\n            contentType: .html,\n            extra: [UUID().uuidString: UUID().uuidString],\n            bodyURL: URL(\n                string: \"https://www.some-url.fr/\\(UUID().uuidString)\"\n            )!,\n            expirationDate: expiry,\n            messageReporting: [UUID().uuidString: .string(UUID().uuidString)],\n            unread: true,\n            sentDate: sentDate,\n            messageURL: URL(\n                string: \"https://some-url.fr/\\(UUID().uuidString)\"\n            )!,\n            rawMessageObject: [:]\n        )\n    }\n\n    static func generateMessages(_ count: Int) -> [MessageCenterMessage] {\n        // Sets the sent date to make the order predictable\n        let date = Date()\n        return (0..<count)\n            .map { index in\n                generateMessage(\n                    sentDate: date.advanced(by: Double(-index))\n                )\n            }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Tests/MessageCenterThemeLoaderTest.swift",
    "content": "///* Copyright Airship and Contributors */\n//\n//import SwiftUI\n//\n//#if canImport(AirshipCore)\n//import AirshipCore\n//#endif\n//\n//import XCTest\n//@testable import AirshipMessageCenter\n//class MessageCenterThemeLoaderTests: XCTestCase {\n//\n//    var themeLoader: MessageCenterThemeLoader!\n//\n//    override func setUp() {\n//        super.setUp()\n//        themeLoader = MessageCenterThemeLoader()\n//    }\n//\n//    override func tearDown() {\n//        themeLoader = nil\n//        super.tearDown()\n//    }\n//\n//    func testFromPlist() {\n//        let testBundle = Bundle(for: type(of: self))\n//\n//        do {\n//            let theme = try MessageCenterThemeLoader.fromPlist(\"ValidTestMessageCenterTheme\", bundle: testBundle)\n//\n//            let expectedRefreshTintColor = Color(AirshipColorUtils.color(\"#990099\")!)\n//            let expectedRefreshTintColorDark = Color(AirshipColorUtils.color(\"#000001\")!)\n//            let expectedCellColor = Color(AirshipColorUtils.color(\"#009900\")!)\n//            let expectedCellColorDark = Color(AirshipColorUtils.color(\"#000002\")!)\n//            let expectedCellTitleColor = Color(AirshipColorUtils.color(\"#000099\")!)\n//            let expectedCellTitleColorDark = Color(AirshipColorUtils.color(\"#000003\")!)\n//            let expectedCellDateColor = Color(AirshipColorUtils.color(\"#999900\")!)\n//            let expectedCellDateColorDark = Color(AirshipColorUtils.color(\"#000004\")!)\n//            let expectedCellSeparatorColorDark = Color(AirshipColorUtils.color(\"#000005\")!)\n//            let expectedCellTintColor = Color(AirshipColorUtils.color(\"#990000\")!)\n//            let expectedCellTintColorDark = Color(AirshipColorUtils.color(\"#000006\")!)\n//            let expectedUnreadIndicatorColor = Color(AirshipColorUtils.color(\"#009999\")!)\n//            let expectedUnreadIndicatorColorDark = Color(AirshipColorUtils.color(\"#000007\")!)\n//            let expectedSelectAllButtonTitleColor = Color(AirshipColorUtils.color(\"#999999\")!)\n//            let expectedSelectAllButtonTitleColorDark = Color(AirshipColorUtils.color(\"#000008\")!)\n//            let expectedDeleteButtonTitleColor = Color(AirshipColorUtils.color(\"#123456\")!)\n//            let expectedDeleteButtonTitleColorDark = Color(AirshipColorUtils.color(\"#000009\")!)\n//            let expectedMarkAsReadButtonTitleColor = Color(AirshipColorUtils.color(\"#654321\")!)\n//            let expectedMarkAsReadButtonTitleColorDark = Color(AirshipColorUtils.color(\"#000010\")!)\n//            let expectedEditButtonTitleColor = Color(AirshipColorUtils.color(\"#abcdef\")!)\n//            let expectedEditButtonTitleColorDark = Color(AirshipColorUtils.color(\"#000011\")!)\n//            let expectedCancelButtonTitleColor = Color(AirshipColorUtils.color(\"#fedcba\")!)\n//            let expectedCancelButtonTitleColorDark = Color(AirshipColorUtils.color(\"#000012\")!)\n//            let expectedBackButtonColor = Color(AirshipColorUtils.color(\"#112233\")!)\n//            let expectedBackButtonColorDark = Color(AirshipColorUtils.color(\"#000013\")!)\n//            let expectedMessageListBackgroundColor = Color(AirshipColorUtils.color(\"#334455\")!)\n//            let expectedMessageListBackgroundColorDark = Color(AirshipColorUtils.color(\"#000014\")!)\n//            let expectedMessageListContainerBackgroundColor = Color(AirshipColorUtils.color(\"#556677\")!)\n//            let expectedMessageListContainerBackgroundColorDark = Color(AirshipColorUtils.color(\"#000015\")!)\n//\n//            XCTAssertNotNil(theme)\n//\n//            XCTAssertEqual(theme.refreshTintColor, expectedRefreshTintColor)\n//            XCTAssertEqual(theme.refreshTintColorDark, expectedRefreshTintColorDark)\n//            XCTAssertEqual(theme.iconsEnabled, true)\n//            XCTAssertEqual(theme.placeholderIcon, Image(\"placeholderIcon\"))\n//            XCTAssertEqual(theme.cellTitleFont, Font.custom(\"cellTitleFont\", size: 16))\n//            XCTAssertEqual(theme.cellDateFont, Font.custom(\"cellDateFont\", size: 14))\n//            XCTAssertEqual(theme.cellColor, expectedCellColor)\n//            XCTAssertEqual(theme.cellColorDark, expectedCellColorDark)\n//            XCTAssertEqual(theme.cellTitleColor, expectedCellTitleColor)\n//            XCTAssertEqual(theme.cellTitleColorDark, expectedCellTitleColorDark)\n//            XCTAssertEqual(theme.cellDateColor, expectedCellDateColor)\n//            XCTAssertEqual(theme.cellDateColorDark, expectedCellDateColorDark)\n//            XCTAssertEqual(theme.cellSeparatorStyle, AirshipMessageCenter.SeparatorStyle.none)\n//            XCTAssertEqual(theme.cellSeparatorColor, Color(\"testNamedColor\", bundle: testBundle))\n//            XCTAssertEqual(theme.cellSeparatorColorDark, expectedCellSeparatorColorDark)\n//            XCTAssertEqual(theme.cellTintColor, expectedCellTintColor)\n//            XCTAssertEqual(theme.cellTintColorDark, expectedCellTintColorDark)\n//            XCTAssertEqual(theme.unreadIndicatorColor, expectedUnreadIndicatorColor)\n//            XCTAssertEqual(theme.unreadIndicatorColorDark, expectedUnreadIndicatorColorDark)\n//            XCTAssertEqual(theme.selectAllButtonTitleColor, expectedSelectAllButtonTitleColor)\n//            XCTAssertEqual(theme.selectAllButtonTitleColorDark, expectedSelectAllButtonTitleColorDark)\n//            XCTAssertEqual(theme.deleteButtonTitleColor, expectedDeleteButtonTitleColor)\n//            XCTAssertEqual(theme.deleteButtonTitleColorDark, expectedDeleteButtonTitleColorDark)\n//            XCTAssertEqual(theme.markAsReadButtonTitleColor, expectedMarkAsReadButtonTitleColor)\n//            XCTAssertEqual(theme.markAsReadButtonTitleColorDark, expectedMarkAsReadButtonTitleColorDark)\n//            XCTAssertEqual(theme.hideDeleteButton, true)\n//            XCTAssertEqual(theme.editButtonTitleColor, expectedEditButtonTitleColor)\n//            XCTAssertEqual(theme.editButtonTitleColorDark, expectedEditButtonTitleColorDark)\n//            XCTAssertEqual(theme.cancelButtonTitleColor, expectedCancelButtonTitleColor)\n//            XCTAssertEqual(theme.cancelButtonTitleColorDark, expectedCancelButtonTitleColorDark)\n//            XCTAssertEqual(theme.backButtonColor, expectedBackButtonColor)\n//            XCTAssertEqual(theme.backButtonColorDark, expectedBackButtonColorDark)\n//            XCTAssertEqual(theme.navigationBarTitle, \"Test Navigation Bar Title\")\n//            XCTAssertEqual(theme.messageListBackgroundColor, expectedMessageListBackgroundColor)\n//            XCTAssertEqual(theme.messageListBackgroundColorDark, expectedMessageListBackgroundColorDark)\n//            XCTAssertEqual(theme.messageListContainerBackgroundColor, expectedMessageListContainerBackgroundColor)\n//            XCTAssertEqual(theme.messageListContainerBackgroundColorDark, expectedMessageListContainerBackgroundColorDark)\n//        } catch {\n//            XCTFail(\"Failed to load theme from plist: \\(error)\")\n//        }\n//    }\n//\n//\n//    /// Tests that fonts of multiple sizings can be converted properly into CGFloat\n//    /// Users can specify font size in plist at String or Number\n//    func testFontConfigToFont() {\n//        /// Test CGFloat\n//        let fontConfigCGFloat = MessageCenterThemeLoader.FontConfig(fontName: \"testFont\", fontSize: .cgFloat(CGFloat(10)))\n//\n//        do {\n//            let font = try fontConfigCGFloat.toFont()\n//            XCTAssertEqual(font, Font.custom(\"testFont\", size: 10))\n//        } catch {\n//            XCTFail(\"Failed to convert FontConfig to Font: \\(error)\")\n//        }\n//\n//        /// Test string\n//        let fontConfigString = MessageCenterThemeLoader.FontConfig(fontName: \"testFont\", fontSize: .string(\"12\"))\n//\n//        do {\n//            let font = try fontConfigString.toFont()\n//            XCTAssertEqual(font, Font.custom(\"testFont\", size: 12))\n//        } catch {\n//            XCTFail(\"Failed to convert FontConfig to Font: \\(error)\")\n//        }\n//    }\n//\n//    func testStringOrNamedToColor() {\n//        let testBundle = Bundle(for: type(of: self))\n//        /// This is testing the implementation:\n//        /// we want to be sure both conversions from hex OR named color name work as expected\n//        let colorHexString = \"#990099\"\n//        let expectedColor = Color(AirshipColorUtils.color(colorHexString)!)\n//        let hexColor = colorHexString.airshipToColor(testBundle)\n//        XCTAssertEqual(hexColor, expectedColor)\n//\n//        let colorName = \"testNamedColor\"\n//        let color = colorName.airshipToColor(testBundle)\n//        XCTAssertEqual(color, Color(colorName, bundle:testBundle))\n//    }\n//}\n"
  },
  {
    "path": "Airship/AirshipMessageCenter/Tests/MessageViewAnalyticsTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n@testable import AirshipCore\n@testable import AirshipMessageCenter\nimport Foundation\n\nstruct DefaultMessageViewAnalyticsTests {\n\n    let mockRecorder = MockThomasLayoutEventRecorder()\n    let historyStorage = MockDispalyHistoryStorage()\n    let clock = UATestDate(offset: 0, dateOverride: Date(timeIntervalSince1970: 0))\n    let operationsQueue = AirshipAsyncSerialQueue()\n    let mockEvent = ThomasLayoutFormDisplayEvent(data: .init(identifier: \"test\", formType: \"test\"))\n\n    @Test(\"Record event captures correct data with default Airship source\")\n    @MainActor\n    func recordEventDefaults() async throws {\n        \n        let messageID = \"test-message-id\"\n        let message = createStubMessage(id: messageID, reporting: [\"test\": \"reporting\"])\n        \n        let analytics = makeAnalytics(message: message)\n        \n        let layoutContext = ThomasLayoutContext()\n\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: layoutContext)\n        await operationsQueue.waitForCurrentOperations()\n\n        let capturedData = try #require(mockRecorder.lastCapturedData, \"No event data was captured\")\n\n        #expect(capturedData.event is ThomasLayoutDisplayEvent)\n        #expect(capturedData.source == .airship)\n        if case .airship(let identifier, let campaigns) = capturedData.messageID {\n            #expect(identifier == messageID)\n            #expect(campaigns == nil)\n        } else {\n            Issue.record(\"Message ID should be of type .airship\")\n        }\n        \n        #expect(capturedData.context!.reportingContext == [\"test\": \"reporting\"])\n    }\n    \n    @Test(\"Record events with no saved history\")\n    @MainActor\n    func recordEventNoHistory() async throws {\n        \n        let messageID = \"test-message-id\"\n        let message = createStubMessage(id: messageID)\n        \n        let analytics = makeAnalytics(message: message)\n        \n        let layoutContext = ThomasLayoutContext()\n        analytics.recordEvent(mockEvent, layoutContext: layoutContext)\n        await operationsQueue.waitForCurrentOperations()\n\n        let capturedData = try #require(mockRecorder.lastCapturedData, \"No event data was captured\")\n        #expect(capturedData.context?.display?.isFirstDisplay == true)\n        #expect(capturedData.context?.display?.isFirstDisplayTriggerSessionID == true)\n        \n    }\n    \n    @Test(\"Record event with saved history from previous session\")\n    @MainActor\n    func recordEventWithPreviousSession() async throws {\n        let messageID = \"test-message-id\"\n        \n        let message = createStubMessage(id: messageID)\n        \n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n        \n        let layoutContext = ThomasLayoutContext()\n        self.historyStorage.currentValue = MessageDisplayHistory(\n            lastImpression: .init(date: clock.now.advanced(by: -100), triggerSessionID: \"impression-session\"),\n            lastDisplay: .init(triggerSessionID: \"previous-session\")\n        )\n        analytics.recordEvent(mockEvent, layoutContext: layoutContext)\n        await operationsQueue.waitForCurrentOperations()\n        \n        let capturedData = try #require(mockRecorder.lastCapturedData, \"No event data was captured\")\n        #expect(capturedData.context?.display?.isFirstDisplay == false)\n        #expect(capturedData.context?.display?.isFirstDisplayTriggerSessionID == false)\n    }\n    \n    @Test(\"Record event with history same session\")\n    @MainActor\n    func recordEventWithHistorySameSession() async throws {\n        let messageID = \"test-message-id\"\n        \n        let message = createStubMessage(id: messageID)\n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n        let layoutContext = ThomasLayoutContext()\n        \n        self.historyStorage.currentValue = MessageDisplayHistory(\n            lastImpression: .init(date: clock.now.advanced(by: -100), triggerSessionID: \"last-session\"),\n            lastDisplay: .init(triggerSessionID: \"last-session\")\n        )\n        analytics.recordEvent(mockEvent, layoutContext: layoutContext)\n        await operationsQueue.waitForCurrentOperations()\n        \n        let capturedData = try #require(mockRecorder.lastCapturedData, \"No event data was captured\")\n        #expect(capturedData.context?.display?.isFirstDisplay == false)\n        #expect(capturedData.context?.display?.isFirstDisplayTriggerSessionID == true)\n    }\n    \n    @Test(\"Record generic event\")\n    @MainActor\n    func recordGenericEvent() async throws {\n        let messageID = \"test-message-id\"\n        \n        let message = createStubMessage(id: messageID)\n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n        let layoutContext = ThomasLayoutContext()\n        \n        analytics.recordEvent(mockEvent, layoutContext: layoutContext)\n        await operationsQueue.waitForCurrentOperations()\n        \n        #expect(mockRecorder.events.count == 1)\n        #expect(mockRecorder.impressions.isEmpty)\n        #expect(historyStorage.getCalls(for: .get) == 1)\n        #expect(historyStorage.getCalls(for: .set) == 0)\n    }\n    \n    @Test(\"Record record first impression\")\n    @MainActor\n    func recordFirstImpression() async throws {\n        let messageID = \"test-message-id\"\n        \n        let message = createStubMessage(\n            id: messageID,\n            reporting: [\"test\": \"reporting\"],\n            productID: \"product-id\"\n        )\n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n        let layoutContext = ThomasLayoutContext()\n        \n        clock.offset = 100\n        \n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: layoutContext)\n        await operationsQueue.waitForCurrentOperations()\n        \n        let impression = try #require(mockRecorder.lastRecordedImpression)\n        #expect(impression.entityID == messageID)\n        #expect(impression.usageType == .inAppExperienceImpression)\n        #expect(impression.reportingContext == .object([\"test\": .string(\"reporting\")]))\n        #expect(impression.product == \"product-id\")\n        #expect(impression.timestamp == clock.now)\n        \n        let history = try #require(historyStorage.currentValue)\n        #expect(history.lastImpression?.date == clock.now)\n        #expect(history.lastImpression?.triggerSessionID == \"current-session\")\n        #expect(history.lastDisplay?.triggerSessionID == \"current-session\")\n        \n    }\n\n    @Test(\"Record native message impression uses default product ID\")\n    @MainActor\n    func recordNativeFirstImpressionUsesDefaultProductID() async throws {\n        let messageID = \"test-native-message-id\"\n\n        let message = createStubMessage(\n            id: messageID,\n            reporting: [\"test\": \"reporting\"],\n            contentType: .native(version: 1)\n        )\n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n\n        clock.offset = 100\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: ThomasLayoutContext())\n        await operationsQueue.waitForCurrentOperations()\n\n        let impression = try #require(mockRecorder.lastRecordedImpression)\n        #expect(impression.entityID == messageID)\n        #expect(impression.usageType == .inAppExperienceImpression)\n        #expect(impression.reportingContext == .object([\"test\": .string(\"reporting\")]))\n        #expect(impression.product == \"default_native_mc\")\n        #expect(impression.timestamp == clock.now)\n    }\n    \n    @Test(\"Record impression timeout\")\n    @MainActor\n    func recordImpressionTimeout() async throws {\n        let messageID = \"test-message-id\"\n        \n        let message = createStubMessage(id: messageID)\n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n        \n        clock.offset = 100\n        let impression = MessageDisplayHistory.LastImpression(date: clock.now, triggerSessionID: \"other-session\")\n        historyStorage.currentValue = MessageDisplayHistory(\n            lastImpression: impression\n        )\n        \n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        await operationsQueue.waitForCurrentOperations()\n        #expect(mockRecorder.lastRecordedImpression == nil)\n        #expect(historyStorage.currentValue?.lastImpression == impression)\n        #expect(historyStorage.currentValue?.lastDisplay?.triggerSessionID == \"current-session\")\n        \n        clock.offset += 30 * 60 - 1\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        await operationsQueue.waitForCurrentOperations()\n        #expect(mockRecorder.lastRecordedImpression == nil)\n        #expect(historyStorage.currentValue?.lastImpression == impression)\n        #expect(historyStorage.currentValue?.lastDisplay?.triggerSessionID == \"current-session\")\n        \n        clock.offset += 1\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        await operationsQueue.waitForCurrentOperations()\n        #expect(mockRecorder.lastRecordedImpression != nil)\n        #expect(historyStorage.currentValue?.lastImpression?.date == clock.now)\n        #expect(historyStorage.currentValue?.lastDisplay?.triggerSessionID == \"current-session\")\n    }\n    \n    @Test(\"Impression updates display context\")\n    @MainActor\n    func impressionUpdatesDisplayContext() async throws {\n        let messageID = \"test-message-id\"\n        \n        let message = createStubMessage(id: messageID)\n        let analytics = makeAnalytics(message: message, sessionID: \"current-session\")\n        await operationsQueue.waitForCurrentOperations()\n        \n        clock.offset = 100\n        \n        #expect(mockRecorder.events.count == 0)\n        #expect(historyStorage.getCalls(for: .set) == 0)\n        \n        //first event\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        await operationsQueue.waitForCurrentOperations()\n        \n        #expect(mockRecorder.events.count == 1)\n        #expect(historyStorage.getCalls(for: .set) == 1)\n        #expect(mockRecorder.lastRecordedImpression != nil)\n        #expect(mockRecorder.lastCapturedData?.context?.display?.isFirstDisplay == true)\n        \n        //second event\n        clock.offset += 100\n        analytics.recordEvent(ThomasLayoutDisplayEvent(), layoutContext: nil)\n        await operationsQueue.waitForCurrentOperations()\n        #expect(mockRecorder.events.count == 2)\n        #expect(mockRecorder.impressions.count == 1)\n        #expect(mockRecorder.lastCapturedData?.context?.display?.isFirstDisplay == false)\n        #expect(historyStorage.getCalls(for: .set) == 2)\n        \n    }\n    \n    // MARK: - Helpers\n    private func createStubMessage(\n        id: String,\n        reporting: AirshipJSON? = nil,\n        productID: String? = nil,\n        contentType: MessageCenterMessage.ContentType = .html\n    ) -> MessageCenterMessage {\n        let rawJSON: AirshipJSON = productID.map { id in [\"product_id\": .string(id)] } ?? .null\n\n        return MessageCenterMessage(\n            title: \"Test Title\",\n            id: id,\n            contentType: contentType,\n            extra: [:],\n            bodyURL: .init(string: \"https://test.url\")!,\n            expirationDate: nil,\n            messageReporting: reporting,\n            unread: true,\n            sentDate: Date(),\n            messageURL: .init(string: \"https://test.url\")!,\n            rawMessageObject: rawJSON\n        )\n    }\n    \n    private func makeAnalytics(\n        message: MessageCenterMessage,\n        sessionID: String = \"test-session-id\"\n    ) -> DefaultMessageViewAnalytics {\n        return DefaultMessageViewAnalytics(\n            message: message,\n            eventRecorder: mockRecorder,\n            historyStorage: historyStorage,\n            date: clock,\n            sessionID: sessionID,\n            queue: operationsQueue\n        )\n    }\n}\n\nfinal class MockThomasLayoutEventRecorder: ThomasLayoutEventRecorderProtocol, @unchecked Sendable {\n    private(set) var lastRecordedImpression: AirshipMeteredUsageEvent? = nil\n    private(set) var impressions: [AirshipMeteredUsageEvent] = []\n    func recordImpressionEvent(_ event: AirshipMeteredUsageEvent) {\n        lastRecordedImpression = event\n        impressions.append(event)\n    }\n    \n    private(set) var lastCapturedData: ThomasLayoutEventData? = nil\n    private(set) var events: [ThomasLayoutEventData] = []\n    func recordEvent(inAppEventData: ThomasLayoutEventData) {\n        lastCapturedData = inAppEventData\n        events.append(inAppEventData)\n    }\n}\n\nfinal class MockDispalyHistoryStorage: MessageDisplayHistoryStoreProtocol, @unchecked Sendable {\n    enum CallType {\n        case get, set\n    }\n    \n    var currentValue: MessageDisplayHistory? = MessageDisplayHistory()\n    private var recordedCalls: [CallType: Int] = [:]\n    \n    func getCalls(for callType: CallType) -> Int {\n        recordedCalls[callType, default: 0]\n    }\n    \n    func set(_ history: MessageDisplayHistory, scheduleID: String) {\n        currentValue = history\n        recordedCalls[.set, default: 0] += 1\n    }\n    \n    func get(scheduleID: String) async -> MessageDisplayHistory {\n        recordedCalls[.get, default: 0] += 1\n        return currentValue!\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/Events/Templates/UAAccountEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Account template\n@objc\npublic final class UACustomEventAccountTemplate: NSObject {\n\n    fileprivate var template: CustomEvent.AccountTemplate\n\n    private init(template: CustomEvent.AccountTemplate) {\n        self.template = template\n    }\n\n    @objc\n    public static func registered() -> UACustomEventAccountTemplate {\n        self.init(template: .registered)\n    }\n\n    @objc\n    public static func loggedIn() -> UACustomEventAccountTemplate {\n        self.init(template: .loggedIn)\n    }\n\n    @objc\n    public static func loggedOut() -> UACustomEventAccountTemplate {\n        self.init(template: .loggedOut)\n    }\n}\n\n@objc\npublic final class UACustomEventAccountProperties: NSObject {\n\n    /// User ID.\n    @objc\n    public var userID: String?\n\n    /// The event's category.\n    @objc\n    public var category: String?\n\n    /// The event's type.\n    @objc\n    public var type: String?\n\n    /// If the value is a lifetime value or not.\n    @objc\n    public var isLTV: Bool\n\n    @objc\n    public init(category: String? = nil, type: String? = nil, isLTV: Bool = false, userID: String? = nil) {\n        self.category = category\n        self.type = type\n        self.isLTV = isLTV\n        self.userID = userID\n    }\n\n    fileprivate var properties: CustomEvent.AccountProperties {\n        return .init(\n            category: self.category,\n            type: self.type,\n            isLTV: self.isLTV,\n            userID: self.userID\n        )\n    }\n}\n\n@objc\npublic extension UACustomEvent {\n    @objc\n    convenience init(accountTemplate: UACustomEventAccountTemplate) {\n        let customEvent = CustomEvent(accountTemplate: accountTemplate.template)\n        self.init(event: customEvent)\n    }\n\n    @objc\n    convenience init(accountTemplate: UACustomEventAccountTemplate, properties: UACustomEventAccountProperties) {\n        let customEvent = CustomEvent(\n            accountTemplate: accountTemplate.template,\n            properties: properties.properties\n        )\n        self.init(event: customEvent)\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/Events/Templates/UAMediaEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Media template\n@objc\npublic final class UACustomEventMediaTemplate: NSObject {\n\n    fileprivate var template: CustomEvent.MediaTemplate\n\n    private init(template: CustomEvent.MediaTemplate) {\n        self.template = template\n    }\n\n    @objc\n    public static func browsed() -> UACustomEventMediaTemplate {\n        self.init(template: .browsed)\n    }\n\n    @objc\n    public static func consumed() -> UACustomEventMediaTemplate {\n        self.init(template: .consumed)\n    }\n\n    @objc\n    public static func shared(source: String?, medium: String?) -> UACustomEventMediaTemplate {\n        self.init(template: .shared(source: source, medium: medium))\n    }\n\n    @objc\n    public static func starred() -> UACustomEventMediaTemplate {\n        self.init(template: .starred)\n    }\n}\n\n@objc\npublic class UACustomEventMediaProperties: NSObject {\n\n    /// The event's ID.\n    @objc\n    public var id: String?\n\n    /// The event's category.\n    @objc\n    public var category: String?\n\n    /// The event's type.\n    @objc\n    public var type: String?\n\n    /// The event's description.\n    @objc\n    public var eventDescription: String?\n\n    /// The event's author.\n    @objc\n    public var author: String?\n\n    /// The event's published date.\n    @objc\n    public var publishedDate: Date?\n\n    /// If the event is a feature\n    @objc\n    public var isFeature: NSNumber?\n\n    /// If the value is a lifetime value or not.\n    @objc\n    public var isLTV: Bool\n\n    @objc\n    public init(id: String? = nil, category: String? = nil, type: String? = nil, eventDescription: String? = nil, isLTV: Bool = false, author: String? = nil, publishedDate: Date? = nil, isFeature: NSNumber? = nil) {\n        self.id = id\n        self.category = category\n        self.type = type\n        self.eventDescription = eventDescription\n        self.author = author\n        self.publishedDate = publishedDate\n        self.isFeature = isFeature\n        self.isLTV = isLTV\n    }\n\n    fileprivate var properties: CustomEvent.MediaProperties {\n        return CustomEvent.MediaProperties(\n            id: self.id,\n            category: self.category,\n            type: self.type,\n            eventDescription: self.eventDescription,\n            isLTV: self.isLTV,\n            author: self.author,\n            publishedDate: self.publishedDate,\n            isFeature: self.isFeature?.boolValue\n        )\n    }\n}\n\n@objc\npublic extension UACustomEvent {\n    @objc\n    convenience init(mediaTemplate: UACustomEventMediaTemplate) {\n        let customEvent = CustomEvent(mediaTemplate: mediaTemplate.template)\n        self.init(event: customEvent)\n    }\n\n    @objc\n    convenience init(mediaTemplate: UACustomEventMediaTemplate, properties: UACustomEventMediaProperties) {\n        let customEvent = CustomEvent(\n            mediaTemplate: mediaTemplate.template,\n            properties: properties.properties\n        )\n        self.init(event: customEvent)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/Events/Templates/UARetailEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Retail template\n@objc\npublic final class UACustomEventRetailTemplate: NSObject {\n\n    fileprivate var template: CustomEvent.RetailTemplate\n\n    private init(template: CustomEvent.RetailTemplate) {\n        self.template = template\n    }\n\n    @objc\n    public static func browsed() -> UACustomEventRetailTemplate {\n        self.init(template: .browsed)\n    }\n\n    @objc\n    public static func addedToCart() -> UACustomEventRetailTemplate {\n        self.init(template: .addedToCart)\n    }\n\n    @objc\n    public static func shared(source: String?, medium: String?) -> UACustomEventRetailTemplate {\n        self.init(template: .shared(source: source, medium: medium))\n    }\n\n    @objc\n    public static func starred() -> UACustomEventRetailTemplate {\n        self.init(template: .starred)\n    }\n\n    @objc\n    public static func purchased() -> UACustomEventRetailTemplate {\n        self.init(template: .purchased)\n    }\n\n    @objc\n    public static func wishlist(identifier: String?, name: String?) -> UACustomEventRetailTemplate {\n        self.init(template: .wishlist(id: identifier, name: name))\n    }\n}\n\n@objc\npublic class UACustomEventRetailProperties: NSObject {\n\n    /// The event's ID.\n    @objc\n    public var id: String?\n\n    /// The event's category.\n    @objc\n    public var category: String?\n\n    /// The event's type.\n    @objc\n    public var type: String?\n\n    /// The event's description.\n    @objc\n    public var eventDescription: String?\n\n    /// The brand.\n    @objc\n    public var brand: String?\n\n    /// If its a new item or not.\n    @objc\n    public var isNewItem: NSNumber?\n\n    /// The currency.\n    @objc\n    public var currency: String?\n\n    /// If the value is a lifetime value or not.\n    @objc\n    public var isLTV: Bool\n\n    @objc\n    public init(id: String? = nil, category: String? = nil, type: String? = nil, eventDescription: String? = nil, isLTV: Bool = false, brand: String? = nil, isNewItem: NSNumber? = nil, currency: String? = nil) {\n        self.id = id\n        self.category = category\n        self.type = type\n        self.eventDescription = eventDescription\n        self.isLTV = isLTV\n        self.brand = brand\n        self.isNewItem = isNewItem\n        self.currency = currency\n    }\n\n    fileprivate var properties: CustomEvent.RetailProperties {\n        return CustomEvent.RetailProperties(\n            id: self.id,\n            category: self.category,\n            type: self.type,\n            eventDescription: self.eventDescription,\n            isLTV: self.isLTV,\n            brand: self.brand,\n            isNewItem: self.isNewItem?.boolValue,\n            currency: self.currency\n        )\n    }\n}\n\n@objc\npublic extension UACustomEvent {\n    @objc\n    convenience init(retailTemplate: UACustomEventRetailTemplate) {\n        let customEvent = CustomEvent(retailTemplate: retailTemplate.template)\n        self.init(event: customEvent)\n    }\n\n    @objc\n    convenience init(retailTemplate: UACustomEventRetailTemplate, properties: UACustomEventRetailProperties) {\n        let customEvent = CustomEvent(\n            retailTemplate: retailTemplate.template,\n            properties: properties.properties\n        )\n        self.init(event: customEvent)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/Events/Templates/UASearchEventTemplate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Search template\n@objc\npublic class UACustomEventSearchTemplate: NSObject {\n\n    fileprivate var template: CustomEvent.SearchTemplate\n\n    private init(template: CustomEvent.SearchTemplate) {\n        self.template = template\n    }\n\n    @objc\n    public static func search() -> UACustomEventSearchTemplate {\n        return UACustomEventSearchTemplate(template: .search)\n    }\n}\n\n@objc\npublic class UACustomEventSearchProperties: NSObject {\n\n    /// The event's ID.\n    @objc\n    public var id: String?\n\n    /// The search query.\n    @objc\n    public var query: String?\n\n    /// The total search results\n    @objc\n    public var totalResults: NSNumber?\n\n    /// The event's category.\n    @objc\n    public var category: String?\n\n    /// The event's type.\n    @objc\n    public var type: String?\n\n    /// If the value is a lifetime value or not.\n    @objc\n    public var isLTV: Bool\n\n    @objc\n    public init(id: String? = nil, query: String? = nil, totalResults: NSNumber? = nil, category: String? = nil, type: String? = nil, isLTV: Bool = false) {\n        self.id = id\n        self.query = query\n        self.totalResults = totalResults\n        self.category = category\n        self.type = type\n        self.isLTV = isLTV\n    }\n\n    fileprivate var properties: CustomEvent.SearchProperties {\n        CustomEvent.SearchProperties(\n            id: self.id,\n            category: self.category,\n            type: self.type,\n            isLTV: self.isLTV,\n            query: self.query,\n            totalResults: self.totalResults?.intValue\n        )\n    }\n}\n\n@objc\npublic extension UACustomEvent {\n    @objc\n    convenience init(searchTemplate: UACustomEventSearchTemplate) {\n        let customEvent = CustomEvent(searchTemplate: searchTemplate.template)\n        self.init(event: customEvent)\n    }\n\n    @objc\n    convenience init(searchTemplate: UACustomEventSearchTemplate, properties: UACustomEventSearchProperties) {\n        let customEvent = CustomEvent(\n            searchTemplate: searchTemplate.template,\n            properties: properties.properties\n        )\n        self.init(event: customEvent)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/Events/UACustomEvent.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// CustomEvent captures information regarding a custom event for\n/// Analytics.\n@objc\npublic class UACustomEvent: NSObject {\n    \n    var customEvent: CustomEvent\n\n    /**\n     * The event's value. The value must be between -2^31 and\n     * 2^31 - 1 or it will invalidate the event.\n     */\n    @objc\n    public var eventValue: Decimal {\n        get {\n           return customEvent.eventValue\n        }\n        set {\n            customEvent.eventValue = newValue\n        }\n    }\n\n    /**\n     * The event's name. The name's length must not exceed 255 characters or it will\n     * invalidate the event.\n     */\n    @objc\n    public var eventName: String {\n        get {\n           return customEvent.eventName\n        }\n        set {\n            customEvent.eventName = newValue\n        }\n    }\n\n    /**\n     * The event's transaction ID. The ID's length must not exceed 255 characters or it will\n     * invalidate the event.\n     */\n    @objc\n    public var transactionID: String? {\n        get {\n           return customEvent.transactionID\n        }\n        set {\n            customEvent.transactionID = newValue\n        }\n    }\n\n    /**\n     * The event's interaction type. The type's length must not exceed 255 characters or it will\n     * invalidate the event.\n     */\n    @objc\n    public var interactionType: String? {\n        get {\n           return customEvent.interactionType\n        }\n        set {\n            customEvent.interactionType = newValue\n        }\n    }\n\n    /**\n     * The event's interaction ID. The ID's length must not exceed 255 characters or it will\n     * invalidate the event.\n     */\n    @objc\n    public var interactionID: String? {\n        get {\n           return customEvent.interactionID\n        }\n        set {\n            customEvent.interactionID = newValue\n        }\n    }\n\n    /**\n     * The event's properties. Properties must be valid JSON.\n     */\n    @objc\n    public var properties: [String: Any] {\n        get {\n            return customEvent.properties.mapValues { $0.unWrap() as Any }\n        }\n    }\n\n    /// Default constructor.\n    /// - Parameter name: The name of the event. The event's name must not exceed\n    /// 255 characters or it will invalidate the event.\n    @objc\n    public convenience init(name: String) {\n        let customEvent = CustomEvent(name: name)\n        self.init(event: customEvent)\n    }\n\n    /// Default constructor.\n    /// - Parameter name: The name of the event. The event's name must not exceed\n    /// 255 characters or it will invalidate the event.\n    /// - Parameter value: The event value. The value must be between -2^31 and\n    /// 2^31 - 1 or it will invalidate the event. Defaults to 1.\n    @objc\n    public convenience init(name: String, value: Double) {\n        let customEvent = CustomEvent(name: name, value: value)\n        self.init(event: customEvent)\n    }\n\n    /// Default constructor.\n    /// - Parameter name: The name of the event. The event's name must not exceed\n    /// 255 characters or it will invalidate the event.\n    /// - Parameter decimalValue: The event value. The value must be between -2^31 and\n    /// 2^31 - 1 or it will invalidate the event. Defaults to 1.\n    @objc\n    public convenience init(name: String, decimalValue: Decimal) {\n        let customEvent = CustomEvent(name: name, decimalValue: decimalValue)\n        self.init(event: customEvent)\n    }\n\n    init(event: CustomEvent) {\n        self.customEvent = event\n    }\n    \n    @objc\n    public func isValid() -> Bool {\n        return customEvent.isValid()\n    }\n\n    /**\n     * Adds the event to analytics.\n     */\n    @objc\n    public func track() {\n        customEvent.track()\n    }\n\n    /// Sets a property string value.\n    /// - Parameters:\n    ///     - string: The string value to set.\n    ///     - forKey: The properties key\n    @objc\n    public func setProperty(\n        string: String,\n        forKey key: String\n    ) {\n        customEvent.setProperty(string: string, forKey: key)\n    }\n\n    /// Removes a property.\n    /// - Parameters:\n    ///     - forKey: The properties key\n    @objc\n    public func removeProperty(\n        forKey key: String\n    ) {\n        customEvent.removeProperty(forKey: key)\n    }\n\n    /// Sets a property double value.\n    /// - Parameters:\n    ///     - double: The double value to set.\n    ///     - forKey: The properties key\n    @objc\n    public func setProperty(\n        double: Double,\n        forKey key: String\n    ) {\n        customEvent.setProperty(double: double, forKey: key)\n    }\n\n    /// Sets a property bool value.\n    /// - Parameters:\n    ///     - bool: The bool value to set.\n    ///     - forKey: The properties key\n    @objc\n    public func setProperty(\n        bool: Bool,\n        forKey key: String\n    ) {\n        customEvent.setProperty(bool: bool, forKey: key)\n    }\n\n    /// Sets a property value.\n    /// - Parameters:\n    ///     - value: The value to set.\n    ///     - forKey: The properties key\n    @objc\n    public func setProperty(\n        value: Any,\n        forKey key: String\n    ) throws {\n        try customEvent.setProperty(value: value, forKey: key)\n    }\n\n    /// Sets a property value.\n    /// - Parameters:\n    ///     - value: The values to set. The value must result in a JSON object or an error will be thrown.\n    @objc\n    public func setProperties(_ object: Any) throws {\n        try customEvent.setProperties(object)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/UAAnalytics.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The Analytics object provides an interface to the Airship Analytics API.\n@objc\npublic final class UAAnalytics: NSObject, Sendable {\n\n    override init() {\n        super.init()\n    }\n    \n    /// The current session ID.\n    @objc\n    public var sessionID: String {\n        get {\n            return Airship.analytics.sessionID\n        }\n    }\n\n    /// Initiates screen tracking for a specific app screen, must be called once per tracked screen.\n    /// - Parameter screen: The screen's identifier.\n    @objc\n    @MainActor\n    public func trackScreen(_ screen: String?) {\n        Airship.analytics.trackScreen(screen)\n    }\n    \n    @objc\n    public func recordCustomEvent(_ event: UACustomEvent) {\n        Airship.analytics.recordCustomEvent(event.customEvent)\n    }\n    \n    @objc\n    public func associateDeviceIdentifier(_ associatedIdentifiers: UAAssociatedIdentifiers) {\n        let identifiers = AssociatedIdentifiers.init(identifiers: associatedIdentifiers.identifiers)\n        Airship.analytics.associateDeviceIdentifiers(identifiers)\n    }\n    \n    @objc\n    public func currentAssociatedDeviceIdentifiers() -> UAAssociatedIdentifiers {\n        let identifiers = Airship.analytics.currentAssociatedDeviceIdentifiers()\n        return UAAssociatedIdentifiers.init(identifiers: identifiers.allIDs)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Analytics/UAAssociatedIdentifiers.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@objc\npublic final class UAAssociatedIdentifiers: NSObject {\n\n    var identifiers: [String: String]\n    \n    init(identifiers: [String: String]) {\n        self.identifiers = identifiers\n    }\n    \n    @objc\n    public func set(identifier: String?, key: String) {\n        self.identifiers[key] = identifier\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Automation/UAInAppAutomation.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\nimport AirshipAutomation\n#endif\n\n/**\n * In-App Automation\n */\n@objc\npublic final class UAInAppAutomation: NSObject, Sendable {\n\n    override init() {\n        super.init()\n    }\n\n    /// In-App messaging\n    @objc\n    public let inAppMessaging: UAInAppMessaging = UAInAppMessaging()\n\n    /// Paused state of in-app automation.\n    @objc\n    @MainActor\n    public var isPaused: Bool {\n        get {\n            return Airship.inAppAutomation.isPaused\n        }\n        set {\n            Airship.inAppAutomation.isPaused = newValue\n        }\n    }\n\n    /// Display interval\n    @objc\n    @MainActor\n    public var displayInterval: TimeInterval {\n        get {\n            return Airship.inAppAutomation.inAppMessaging.displayInterval\n        }\n        set {\n            Airship.inAppAutomation.inAppMessaging.displayInterval = newValue\n        }\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Automation/UAInAppMessaging.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\nimport AirshipAutomation\n#endif\n\n/// In-App messaging\npublic final class UAInAppMessaging: NSObject, Sendable {\n\n    /// Display interval\n    @MainActor\n    public var displayInterval: TimeInterval {\n        get {\n            Airship.inAppAutomation.inAppMessaging.displayInterval\n        }\n        set {\n            Airship.inAppAutomation.inAppMessaging.displayInterval = newValue\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Channel/UAAttributesEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Attributes editor.\n@objc\npublic class UAAttributesEditor: NSObject {\n    \n    var editor: AttributesEditor?\n    \n    /**\n     * Removes an attribute.\n     * - Parameters:\n     *   - attribute: The attribute.\n     */\n    @objc(removeAttribute:)\n    public func remove(_ attribute: String) {\n        self.editor?.remove(attribute)\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - date: The value\n     *   - attribute: The attribute\n     */\n    @objc(setDate:attribute:)\n    public func set(date: Date, attribute: String) {\n        self.editor?.set(date: date, attribute: attribute)\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - number: The value.\n     *   - attribute: The attribute.\n     */\n    @objc(setNumber:attribute:)\n    public func set(number: NSNumber, attribute: String) {\n        self.editor?.set(number: number, attribute: attribute)\n    }\n\n    /**\n     * Sets the attribute.\n     * - Parameters:\n     *   - string: The value.\n     *   - attribute: The attribute.\n     */\n    @objc(setString:attribute:)\n    public func set(string: String, attribute: String) {\n        self.editor?.set(string: string, attribute: attribute)\n    }\n\n    /**\n     * Applies the attribute changes.\n     */\n    @objc\n    public func apply() {\n        self.editor?.apply()\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Channel/UAChannel.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Provides an interface to the channel functionality.\n@objc\npublic final class UAChannel: NSObject, Sendable {\n\n    override init() {\n        super.init()\n    }\n\n    @objc\n    public var identifier: String? {\n        return Airship.channel.identifier\n    }\n\n    /// Device tags\n    @objc\n    public var tags: [String] {\n        get {\n            return Airship.channel.tags\n        }\n\n        set {\n            Airship.channel.tags = newValue\n        }\n\n    }\n\n    @objc\n    public func editTags() -> UATagEditor? {\n        let tagEditor = UATagEditor()\n        tagEditor.editor = Airship.channel.editTags()\n        return tagEditor\n    }\n\n    @objc\n    public func editTagGroups() -> UATagGroupsEditor {\n        let tagGroupsEditor = UATagGroupsEditor()\n        tagGroupsEditor.editor = Airship.channel.editTagGroups()\n        return tagGroupsEditor\n    }\n\n    @objc\n    public func editTagGroups(_ editorBlock: (UATagGroupsEditor) -> Void) {\n        let editor = editTagGroups()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    @objc\n    public func editSubscriptionLists() -> UASubscriptionListEditor {\n        let subscriptionListEditor = UASubscriptionListEditor()\n        subscriptionListEditor.editor = Airship.channel.editSubscriptionLists()\n        return subscriptionListEditor\n    }\n\n    @objc\n    public func editSubscriptionLists(_ editorBlock: (UASubscriptionListEditor) -> Void) {\n        let editor = editSubscriptionLists()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    @objc\n    public func fetchSubscriptionLists() async throws -> [String] {\n        try await Airship.channel.fetchSubscriptionLists()\n    }\n\n    /// Fetches current subscription lists.\n    /// - Parameter completionHandler: The completion handler with the subscription lists or an error.\n    @objc(fetchSubscriptionListsWithCompletion:)\n    public func fetchSubscriptionLists(completionHandler: @escaping @Sendable ([String]?, (any Error)?) -> Void) {\n        Task {\n            do {\n                let subscriptionLists = try await Airship.channel.fetchSubscriptionLists()\n                completionHandler(subscriptionLists, nil)\n            } catch {\n                completionHandler(nil, error)\n            }\n        }\n    }\n\n    @objc\n    public func editAttributes() -> UAAttributesEditor {\n        let attributesEditor =  UAAttributesEditor()\n        attributesEditor.editor = Airship.channel.editAttributes()\n        return attributesEditor\n    }\n\n    @objc\n    public func editAttributes(_ editorBlock: (UAAttributesEditor) -> Void) {\n        let editor = editAttributes()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    @objc(enableChannelCreation)\n    public func enableChannelCreation() {\n        Airship.channel.enableChannelCreation()\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Channel/UATagEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Tag editor.\n@objc\npublic class UATagEditor: NSObject {\n    \n    var editor: TagEditor?\n    \n    /**\n     * Adds tags.\n     * - Parameters:\n     *   - tags: The tags.\n     */\n    @objc(addTags:)\n    public func add(_ tags: [String]) {\n        self.editor?.add(tags)\n    }\n    \n    /**\n     * Adds a single tag.\n     * - Parameters:\n     *   - tag: The tag.\n     */\n    @objc(addTag:)\n    public func add(_ tag: String) {\n        self.editor?.add(tag)\n    }\n    \n    /**\n     * Removes tags from the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     */\n    @objc(removeTags:)\n    public func remove(_ tags: [String]) {\n        self.editor?.remove(tags)\n    }\n    \n    /**\n     * Removes a single tag.\n     * - Parameters:\n     *   - tag: The tag.\n     */\n    @objc(removeTag:)\n    public func remove(_ tag: String) {\n        self.editor?.remove(tag)\n    }\n    \n    /**\n     * Sets tags on the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     */\n    @objc(setTags:)\n    public func set(_ tags: [String]) {\n        self.editor?.set(tags)\n    }\n    \n    /**\n     * Clears tags.\n     */\n    @objc(clearTags)\n    public func clear() {\n        self.editor?.clear()\n    }\n    \n    /**\n     * Applies tag changes.\n     */\n    @objc\n    public func apply() {\n        self.editor?.apply()\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Channel/UATagGroupsEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Tag groups editor.\n@objc\npublic class UATagGroupsEditor: NSObject {\n    \n    var editor: TagGroupsEditor?\n    \n    /**\n     * Adds tags to the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - group: The tag group.\n     */\n    @objc(addTags:group:)\n    public func add(_ tags: [String], group: String) {\n        self.editor?.add(tags, group: group)\n    }\n\n    /**\n     * Removes tags from the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - group: The tag group.\n     */\n    @objc(removeTags:group:)\n    public func remove(_ tags: [String], group: String) {\n        self.editor?.remove(tags, group: group)\n    }\n\n    /**\n     * Sets tags on the given group.\n     * - Parameters:\n     *   - tags: The tags.\n     *   - group: The tag group.\n     */\n    @objc(setTags:group:)\n    public func set(_ tags: [String], group: String) {\n        self.editor?.set(tags, group: group)\n    }\n\n    /**\n     * Applies tag changes.\n     */\n    @objc\n    public func apply() {\n        self.editor?.apply()\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Contact/UAContact.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Airship contact. A contact is distinct from a channel and  represents a \"user\"\n/// within Airship. Contacts may be named and have channels associated with it.\n@objc\npublic final class UAContact: NSObject, Sendable {\n\n    override init() {\n        super.init()\n    }\n\n    /// Identifies the contact.\n    /// - Parameter namedUserID: The named user ID.\n    @objc\n    public func identify(_ namedUserID: String) {\n        Airship.contact.identify(namedUserID)\n    }\n\n    /// Resets the contact.\n    @objc\n    public func reset() {\n        Airship.contact.reset()\n    }\n\n    /// Can be called after the app performs a remote named user association for the channel instead\n    /// of using `identify` or `reset` through the SDK. When called, the SDK will refresh the contact\n    /// data. Applications should only call this method when the user login has changed.\n    @objc\n    public func notifyRemoteLogin() {\n        Airship.contact.notifyRemoteLogin()\n    }\n\n    /// Begins a tag groups editing session.\n    /// - Returns: A TagGroupsEditor\n    @objc\n    public func editTagGroups() -> UATagGroupsEditor {\n        let tagGroupsEditor = UATagGroupsEditor()\n        tagGroupsEditor.editor = Airship.contact.editTagGroups()\n        return tagGroupsEditor\n    }\n\n    @objc\n    public func editTagGroups(_ editorBlock: (UATagGroupsEditor) -> Void) {\n        let editor = editTagGroups()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    /// Begins an attribute editing session.\n    /// - Returns: An AttributesEditor\n    @objc\n    public func editAttributes() -> UAAttributesEditor {\n        let attributesEditor =  UAAttributesEditor()\n        attributesEditor.editor = Airship.contact.editAttributes()\n        return attributesEditor\n    }\n\n    @objc\n    public func editAttributes(_ editorBlock: (UAAttributesEditor) -> Void) {\n        let editor = editAttributes()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    /**\n     * Associates a channel to the contact.\n     * - Parameters:\n     *   - channelID: The channel ID.\n     *   - type: The channel type.\n     */\n    @objc\n    public func associateChannel(_ channelID: String, type: UAChannelType) {\n        Airship.contact.associateChannel(channelID, type: UAHelpers.toAirshipChannelType(type: type))\n    }\n\n\n    /// Begins a subscription list editing session\n    /// - Returns: A Scoped subscription list editor\n    @objc\n    public func editSubscriptionLists() -> UAScopedSubscriptionListEditor {\n        let subscriptionListEditor = UAScopedSubscriptionListEditor()\n        subscriptionListEditor.editor = Airship.contact.editSubscriptionLists()\n        return subscriptionListEditor\n    }\n\n\n    @objc\n    public func editSubscriptionLists(_ editorBlock: (UAScopedSubscriptionListEditor) -> Void) {\n        let editor = editSubscriptionLists()\n        editorBlock(editor)\n        editor.apply()\n    }\n\n    /// Fetches subscription lists.\n    /// - Parameter completionHandler: The completion handler with the subscription lists or an error.\n    @objc\n    public func fetchSubscriptionLists(completionHandler: @escaping @Sendable ([String: [String]]?, (any Error)?) -> Void) {\n        Task {\n            do {\n                let subscriptionLists = try await Airship.contact.fetchSubscriptionLists()\n                // Convert [String: [ChannelScope]] to [String: [String]] for Objective-C\n                var result: [String: [String]] = [:]\n                for (key, scopes) in subscriptionLists {\n                    result[key] = scopes.map { $0.rawValue }\n                }\n                completionHandler(result, nil)\n            } catch {\n                completionHandler(nil, error)\n            }\n        }\n    }\n\n    /// Gets the current named user ID.\n    /// - Parameter completionHandler: The completion handler with the named user ID or an error.\n    @objc\n    public func getNamedUserID(completionHandler: @escaping @Sendable (String?, (any Error)?) -> Void) {\n        Task {\n            let namedUserID = await Airship.contact.namedUserID\n            completionHandler(namedUserID, nil)\n        }\n    }\n}\n\n@objc\n/// Channel type\npublic enum UAChannelType: Int, Sendable, Equatable {\n\n    /**\n     * Email channel\n     */\n    case email\n\n    /**\n     * SMS channel\n     */\n    case sms\n\n    /**\n     * Open channel\n     */\n    case open\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Embedded/UAEmbeddedViewController.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\npublic import UIKit\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Embedded view controller factory\n@objc\npublic final class UAEmbeddedViewControllerFactory: NSObject, Sendable {\n\n    @MainActor\n    @objc\n    public class func makeViewController(embeddedID: String) -> UIViewController {\n        let vc = UIHostingController(rootView: AirshipEmbeddedView<EmptyView>(embeddedID: embeddedID))\n        // Let Auto Layout drive the height from the SwiftUI content's natural size\n        if #available(iOS 16.0, *) {\n            vc.sizingOptions = .intrinsicContentSize\n        }\n        return vc\n    }\n\n    @MainActor\n    @objc\n    public class func embed(embeddedID: String, in parentViewController: UIViewController) -> UIView {\n        let childVC = makeViewController(embeddedID: embeddedID)\n        parentViewController.addChild(childVC)\n        let containerView = UIView(frame: .zero)\n        containerView.addSubview(childVC.view)\n        childVC.view.translatesAutoresizingMaskIntoConstraints = false\n        NSLayoutConstraint.activate([\n            childVC.view.topAnchor.constraint(equalTo: containerView.topAnchor),\n            childVC.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),\n            childVC.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),\n            childVC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)\n        ])\n        childVC.didMove(toParent: parentViewController)\n        return containerView\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenter.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipMessageCenter\nimport AirshipCore\n#endif\n\n/// Delegate protocol for receiving callbacks related to message center.\n@objc\npublic protocol UAMessageCenterDisplayDelegate {\n\n    /// Called when a message is requested to be displayed.\n    ///\n    /// - Parameters:\n    ///   - messageID: The message ID.\n    @objc(displayMessageCenterForMessageID:)\n    @MainActor\n    func displayMessageCenter(messageID: String)\n\n    /// Called when the message center is requested to be displayed.\n    @objc\n    @MainActor\n    func displayMessageCenter()\n\n    /// Called when the message center is requested to be dismissed.\n    @objc\n    @MainActor\n    func dismissMessageCenter()\n}\n\n@objc\npublic protocol UAMessageCenterPredicate: Sendable {\n    /// Evaluate the message center message. Used to filter the message center list\n    /// - Parameters:\n    ///     - message: The message center message\n    /// - Returns: True if the message passed the evaluation, otherwise false.\n    func evaluate(message: UAMessageCenterMessage) -> Bool\n}\n\n@objc\npublic final class UAMessageCenter: NSObject, Sendable {\n\n    private let storage: Storage = Storage()\n\n    override init() {\n        super.init()\n    }\n\n\n    /// Message center display delegate.\n    @objc\n    @MainActor\n    public weak var displayDelegate: (any UAMessageCenterDisplayDelegate)? {\n        get {\n            guard let wrapped = Airship.messageCenter.displayDelegate as? UAMessageCenterDisplayDelegateWrapper else {\n                return nil\n            }\n            return wrapped.forwardDelegate\n        }\n\n        set {\n            if let newValue {\n                let wrapper = UAMessageCenterDisplayDelegateWrapper(newValue)\n                Airship.messageCenter.displayDelegate = wrapper\n                storage.displayDelegate = wrapper\n            } else {\n                Airship.messageCenter.displayDelegate = nil\n                storage.displayDelegate = nil\n            }\n        }\n    }\n    \n    /// Message center inbox.\n    @objc\n    public let inbox: UAMessageCenterInbox = UAMessageCenterInbox()\n\n    /// Loads a Message center theme from a plist file. If you are embedding the MessageCenterView directly\n    ///  you should pass the theme in through the view extension `.messageCenterTheme(_:)`.\n    /// - Parameters:\n    ///     - plist: The name of the plist in the bundle.\n    @objc\n    @MainActor\n    public func setThemeFromPlist(_ plist: String) throws {\n        try Airship.messageCenter.setThemeFromPlist(plist)\n    }\n\n    /// Default message center predicate. Only applies to the OOTB Message Center. If you are embedding the MessageCenterView directly\n    ///  you should pass the predicate in through the view extension `.messageCenterPredicate(_:)`.\n    @objc\n    @MainActor\n    public var predicate: (any UAMessageCenterPredicate)? {\n        didSet {\n            if let predicate {\n                Airship.messageCenter.predicate = UAMessageCenterPredicateWrapper(delegate: predicate)\n            } else {\n                Airship.messageCenter.predicate = nil\n            }\n        }\n    }\n\n    /// Display the message center.\n    @objc\n    @MainActor\n    public func display() {\n        Airship.messageCenter.display()\n    }\n\n    /// Display the given message with animation.\n    /// - Parameters:\n    ///     - messageID:  The messageID of the message.\n    @objc(displayWithMessageID:)\n    @MainActor\n    public func display(messageID: String) {\n        Airship.messageCenter.display(messageID: messageID)\n    }\n\n    /// Dismiss the message center.\n    @objc\n    @MainActor\n    public func dismiss() {\n        Airship.messageCenter.dismiss()\n    }\n\n\n    fileprivate final class Storage: Sendable  {\n        @MainActor\n        var displayDelegate: (any MessageCenterDisplayDelegate)?\n\n        init() {}\n    }\n\n}\n\nfileprivate final class UAMessageCenterDisplayDelegateWrapper: NSObject, MessageCenterDisplayDelegate {\n    weak var forwardDelegate: (any UAMessageCenterDisplayDelegate)?\n    init(_ forwardDelegate: any UAMessageCenterDisplayDelegate) {\n        self.forwardDelegate = forwardDelegate\n    }\n    \n    public func displayMessageCenter(messageID: String) {\n        self.forwardDelegate?.displayMessageCenter(messageID: messageID)\n    }\n    \n    public func displayMessageCenter() {\n        self.forwardDelegate?.displayMessageCenter()\n    }\n    \n    public func dismissMessageCenter() {\n        self.forwardDelegate?.dismissMessageCenter()\n    }\n}\n\nfinal class UAMessageCenterPredicateWrapper: NSObject, MessageCenterPredicate {\n    private let delegate: any UAMessageCenterPredicate\n    \n    init(delegate: any UAMessageCenterPredicate) {\n        self.delegate = delegate\n    }\n    \n    public func evaluate(message: MessageCenterMessage) -> Bool {\n        let mcMessage = UAMessageCenterMessage(message: message)\n        return self.delegate.evaluate(message: mcMessage)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenterList.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipMessageCenter\nimport AirshipCore\n#endif\n\n/// Message center inbox.\n@objc\npublic final class UAMessageCenterInbox: NSObject, Sendable {\n\n    /// The list of messages in the inbox.\n    @objc\n    public func getMessages() async -> [UAMessageCenterMessage] {\n        let messages = await Airship.messageCenter.inbox.messages\n        return messages.map { UAMessageCenterMessage(message: $0) }\n    }\n\n    /// The user associated to the Message Center\n    @objc\n    public func getUser() async -> UAMessageCenterUser? {\n        guard let user = await Airship.messageCenter.inbox.user else {\n            return nil\n        }\n        return UAMessageCenterUser(user: user)\n    }\n\n    /// The number of messages that are currently unread.\n    @objc\n    public func getUnreadCount() async -> Int {\n        await Airship.messageCenter.inbox.unreadCount\n    }\n\n    /// Returns the message associated with a particular URL.\n    /// - Parameters:\n    ///     - bodyURL: The URL of the message.\n    /// - Returns: The associated `MessageCenterMessage` object or nil if a message was unable to be found.\n    @objc\n    public func message(forBodyURL bodyURL: URL) async -> UAMessageCenterMessage? {\n        guard let message = await Airship.messageCenter.inbox.message(forBodyURL: bodyURL) else { return nil }\n        return UAMessageCenterMessage(message: message)\n    }\n\n    /// Returns the message associated with a particular ID.\n    /// - Parameters:\n    ///     - messageID: The message ID.\n    /// - Returns: The associated `MessageCenterMessage` object or nil if a message was unable to be found.\n    @objc\n    public func message(forID messageID: String) async -> UAMessageCenterMessage? {\n        guard let message = await Airship.messageCenter.inbox.message(forID: messageID) else {\n            return nil\n        }\n\n        return UAMessageCenterMessage(message: message)\n    }\n\n    /// Refreshes the list of messages in the inbox.\n    /// - Returns: `true` if the messages was refreshed, otherwise `false`.\n    @objc\n    @discardableResult\n    public func refreshMessages() async -> Bool {\n        await Airship.messageCenter.inbox.refreshMessages()\n    }\n    \n    /// Marks messages read.\n    /// - Parameters:\n    ///     - messages: The list of messages to be marked read.\n    @objc\n    public func markRead(messages: [UAMessageCenterMessage]) async {\n        await Airship.messageCenter.inbox.markRead(messages: messages.map{$0.mcMessage})\n    }\n\n    /// Marks messages read by message IDs.\n    /// - Parameters:\n    ///     - messageIDs: The list of message IDs for the messages to be marked read.\n    @objc\n    public func markRead(messageIDs: [String]) async {\n        await Airship.messageCenter.inbox.markRead(messageIDs: messageIDs)\n    }\n\n    /// Marks messages deleted.\n    /// - Parameters:\n    ///     - messages: The list of messages to be marked deleted.\n    @objc\n    public func delete(messages: [UAMessageCenterMessage]) async {\n        await Airship.messageCenter.inbox.delete(messages: messages.map{$0.mcMessage})\n    }\n\n    /// Marks messages deleted by message IDs.\n    /// - Parameters:\n    ///     - messageIDs: The list of message IDs for the messages to be marked deleted.\n    @objc\n    public func delete(messageIDs: [String]) async {\n        await Airship.messageCenter.inbox.delete(messageIDs: messageIDs)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenterMessage.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipMessageCenter\nimport AirshipCore\n#endif\n\n/// Message center message.\n@objc\npublic final class UAMessageCenterMessage: NSObject, Sendable {\n\n    let mcMessage: MessageCenterMessage\n\n    init(message: MessageCenterMessage) {\n        self.mcMessage = message\n    }\n\n    @objc\n    /// The Airship message ID.\n    /// This ID may be used to match an incoming push notification to a specific message.\n    public var id: String {\n        get {\n            return mcMessage.id\n        }\n    }\n\n    @objc\n    /// The message title.\n    public var title: String {\n        get {\n            return mcMessage.title\n        }\n    }\n\n    @objc\n    /// The message subtitle.\n    public var subtitle: String? {\n        get {\n            return mcMessage.subtitle\n        }\n    }\n\n    @objc\n    /// The message list icon.\n    public var listIcon: String? {\n        get {\n            return mcMessage.listIcon\n        }\n    }\n\n    @objc\n    /// The message expiry.\n    public var isExpired: Bool {\n        get {\n            return mcMessage.isExpired\n        }\n    }\n\n    @objc\n    /// The message's extra dictionary.\n    /// This dictionary can be populated with arbitrary key-value data at the time the message is composed.\n    public var extra: [String: String] {\n        get {\n            return mcMessage.extra\n        }\n    }\n\n    @objc\n    /// The URL for the message body itself.\n    /// This URL may only be accessed with Basic Auth credentials set to the user ID and password.\n    public var bodyURL: URL {\n        get {\n            return mcMessage.bodyURL\n        }\n    }\n\n    @objc\n    /// The date and time the message will expire.\n    /// A nil value indicates it will never expire.\n    public var expirationDate: Date? {\n        get {\n            return mcMessage.expirationDate\n        }\n    }\n\n\n    @objc\n    /// The date and time the message was sent (UTC).\n    public var sentDate: Date {\n        get {\n            return mcMessage.sentDate\n        }\n    }\n\n    @objc\n    /// The unread status of the message.\n    /// `true` if the message is unread, otherwise `false`.\n    public var unread: Bool {\n        get {\n            return mcMessage.unread\n        }\n    }\n\n    /// Parses the message ID from notification user info.\n    /// - Parameters:\n    ///     - userInfo: The notification user info.\n    /// - Returns: The message ID.\n    @objc(parseMessageIDFromUserInfo:)\n    public class func parseMessageID(userInfo: [AnyHashable: Any]) -> String? {\n        return MessageCenterMessage.parseMessageID(userInfo: userInfo)\n    }\n\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenterNativeBridge.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS)\n\npublic import Foundation\npublic import WebKit\n\n#if canImport(AirshipCore)\nimport AirshipCore\nimport AirshipMessageCenter\n#endif\n\n/// Delegate for native bridge events from Message Center web views.\n@objc\npublic protocol UAMessageCenterNativeBridgeDelegate: NSObjectProtocol {\n    /// Called when `UAirship.close()` is triggered from the JavaScript environment.\n    func close()\n}\n\nprivate final class NativeBridgeDelegateWrapper: NativeBridgeDelegate {\n    weak var delegate: (any UAMessageCenterNativeBridgeDelegate)?\n\n    init(_ delegate: any UAMessageCenterNativeBridgeDelegate) {\n        self.delegate = delegate\n    }\n\n    func close() {\n        delegate?.close()\n    }\n}\n\n// Wraps any WKNavigationDelegate as AirshipWKNavigationDelegate using ObjC\n// message forwarding, so the caller doesn't need to know about AirshipWKNavigationDelegate.\n// delegateObj is nonisolated(unsafe) so forwardingTarget/responds can be nonisolated\n// (WebKit always calls them on the main thread).\nprivate final class ForwardNavigationDelegateWrapper: NSObject, AirshipWKNavigationDelegate {\n    @MainActor weak var delegate: (any WKNavigationDelegate)?\n    nonisolated(unsafe) weak var delegateObj: AnyObject?\n\n    @MainActor init(_ delegate: any WKNavigationDelegate) {\n        self.delegate = delegate\n        self.delegateObj = delegate as AnyObject\n    }\n\n    nonisolated override func responds(to aSelector: Selector!) -> Bool {\n        return super.responds(to: aSelector) || (delegateObj?.responds(to: aSelector) ?? false)\n    }\n\n    nonisolated override func forwardingTarget(for aSelector: Selector!) -> Any? {\n        return delegateObj\n    }\n}\n\n/// Airship native bridge for Message Center web views.\n@objc\npublic final class UAMessageCenterNativeBridge: NSObject {\n\n    private let bridge: NativeBridge\n    private var forwardNavigationDelegateWrapper: ForwardNavigationDelegateWrapper?\n    private var nativeBridgeExtension: MessageCenterNativeBridgeExtension?\n    private var nativeBridgeDelegateWrapper: NativeBridgeDelegateWrapper?\n\n    /// The navigation delegate to set on the web view.\n    @objc\n    @MainActor\n    public var navigationDelegate: any WKNavigationDelegate {\n        return bridge\n    }\n\n    /// Optional delegate for native bridge events such as close.\n    @objc\n    @MainActor\n    public weak var nativeBridgeDelegate: (any UAMessageCenterNativeBridgeDelegate)? {\n        get { nativeBridgeDelegateWrapper?.delegate }\n        set {\n            nativeBridgeDelegateWrapper = newValue.map { NativeBridgeDelegateWrapper($0) }\n            bridge.nativeBridgeDelegate = nativeBridgeDelegateWrapper\n        }\n    }\n\n    /// Optional delegate to receive forwarded navigation callbacks.\n    @objc\n    @MainActor\n    public var forwardNavigationDelegate: (any WKNavigationDelegate)? {\n        get { forwardNavigationDelegateWrapper?.delegate }\n        set {\n            forwardNavigationDelegateWrapper = newValue.map { ForwardNavigationDelegateWrapper($0) }\n            bridge.forwardNavigationDelegate = forwardNavigationDelegateWrapper\n        }\n    }\n\n    @MainActor\n    @objc\n    public override init() {\n        bridge = NativeBridge()\n        super.init()\n    }\n\n    /// Sets the message and user on the native bridge.\n    /// - Parameters:\n    ///   - message: The message to display.\n    ///   - user: The Message Center user.\n    @objc\n    @MainActor\n    public func setMessage(_ message: UAMessageCenterMessage, user: UAMessageCenterUser) {\n        let ext = MessageCenterNativeBridgeExtension(\n            message: message.mcMessage,\n            user: user.mcUser\n        )\n        nativeBridgeExtension = ext\n        bridge.nativeBridgeExtensionDelegate = ext\n    }\n}\n\n#endif\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenterTheme.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\npublic import UIKit\n\n#if canImport(AirshipCore)\nimport AirshipMessageCenter\npublic import AirshipCore\n#endif\n\n/// Message Center theme\n@objc\npublic final class UAMessageCenterTheme: NSObject {\n    \n    @objc\n    /// The tint color of the \"pull to refresh\" control\n    public var refreshTintColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode tint color of the \"pull to refresh\" control\n    public var refreshTintColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// Whether icons are enabled. Defaults to `NO`.\n    public var iconsEnabled: Bool = false\n\n    @objc\n    /// An optional placeholder image to use when icons haven't fully loaded.\n    public var placeholderIcon: AirshipNativeImage? = nil\n\n    @objc\n    /// The font to use for message cell titles.\n    public var cellTitleFont: AirshipNativeFont? = AirshipNativeFont.preferredFont(forTextStyle: .headline)\n\n    @objc\n    /// The font to use for message cell dates.\n    public var cellDateFont: AirshipNativeFont? = AirshipNativeFont.preferredFont(forTextStyle: .subheadline)\n\n    @objc\n    /// The regular color for message cells\n    public var cellColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode color for message cells\n    public var cellColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The regular color for message cell titles.\n    public var cellTitleColor: AirshipNativeColor? = .label\n\n    @objc\n    /// The dark mode color for message cell titles.\n    public var cellTitleColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The regular color for message cell dates.\n    public var cellDateColor: AirshipNativeColor? = .secondaryLabel\n\n    @objc\n    /// The dark mode color for message cell dates.\n    public var cellDateColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The message cell tint color.\n    public var cellTintColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode message cell tint color.\n    public var cellTintColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The background color for the unread indicator.\n    public var unreadIndicatorColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode background color for the unread indicator.\n    public var unreadIndicatorColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The title color for the \"Select All\" button.\n    public var selectAllButtonTitleColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode title color for the \"Select All\" button.\n    public var selectAllButtonTitleColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The title color for the \"Delete\" button.\n    public var deleteButtonTitleColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode title color for the \"Delete\" button.\n    public var deleteButtonTitleColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The title color for the \"Mark Read\" button.\n    public var markAsReadButtonTitleColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode title color for the \"Mark Read\" button.\n    public var markAsReadButtonTitleColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// Whether the delete message button from the message view is enabled. Defaults to `NO`.\n    public var hideDeleteButton: Bool = false\n\n    @objc\n    /// The title color for the \"Edit\" button.\n    public var editButtonTitleColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode title color for the \"Edit\" button.\n    public var editButtonTitleColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The title color for the \"Cancel\" button.\n    public var cancelButtonTitleColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode title color for the \"Cancel\" button.\n    public var cancelButtonTitleColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The title color for the \"Done\" button.\n    public var backButtonColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode title color for the \"Done\" button.\n    public var backButtonColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The navigation bar title\n    public var navigationBarTitle: String? = nil\n\n    @objc\n    /// The background of the message list.\n    public var messageListBackgroundColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode background of the message list.\n    public var messageListBackgroundColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The background of the message list container.\n    public var messageListContainerBackgroundColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode background of the message list container.\n    public var messageListContainerBackgroundColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The background of the message view.\n    public var messageViewBackgroundColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode background of the message view.\n    public var messageViewBackgroundColorDark: AirshipNativeColor? = nil\n\n    @objc\n    /// The background of the message view container.\n    public var messageViewContainerBackgroundColor: AirshipNativeColor? = nil\n\n    @objc\n    /// The dark mode background of the message view container.\n    public var messageViewContainerBackgroundColorDark: AirshipNativeColor? = nil\n\n}\n    \n\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenterUser.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipMessageCenter\nimport AirshipCore\n#endif\n\n/// Message Center user.\n@objc\npublic final class UAMessageCenterUser: NSObject, Sendable {\n\n    internal let mcUser: MessageCenterUser\n\n    init(user: MessageCenterUser) {\n        self.mcUser = user\n    }\n\n    /// The password.\n    @objc\n    public var password: String {\n        get {\n            return mcUser.password\n        }\n    }\n\n    /// The username.\n    @objc\n    public var username: String {\n        get {\n            return mcUser.username\n        }\n    }\n\n    /// The basic auth string for the user.\n    /// - Returns: An HTTP Basic Auth header string value in the form of: `Basic [Base64 Encoded \"username:password\"]`\n    @objc\n    public var basicAuthString: String {\n        get {\n            return mcUser.basicAuthString\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/MessageCenter/UAMessageCenterViewController.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\npublic import UIKit\n\n#if canImport(AirshipCore)\nimport AirshipMessageCenter\nimport AirshipCore\n#endif\n\nimport SwiftUI\n\n/// Message Center view controller factory\n@objc\npublic final class UAMessageCenterViewControllerFactory: NSObject, Sendable {\n    \n    @objc\n    /// Makes a message view controller with the given theme.\n    /// - Parameters:\n    ///     - theme: The message center theme.\n    ///     - predicate: The message center predicate.\n    /// - Returns: A view controller.\n    @MainActor\n    public class func make(\n        theme: UAMessageCenterTheme? = nil,\n        predicate: (any UAMessageCenterPredicate)? = nil\n    ) -> UIViewController {\n        var airshipTheme: MessageCenterTheme?\n        var wrapper: UAMessageCenterPredicateWrapper?\n        \n        if let theme = theme {\n            airshipTheme = UAMessageCenterViewControllerFactory.toAirshipTheme(theme: theme)\n        }\n        if let predicate = predicate {\n            wrapper = UAMessageCenterPredicateWrapper(delegate: predicate)\n        }\n        return MessageCenterViewControllerFactory.make(theme: airshipTheme, predicate: wrapper, controller: MessageCenterController())\n    }\n    \n\n    @objc\n    /// Makes a message view controller with the given theme.\n    /// - Parameters:\n    ///     - themePlist: A path to a theme plist\n    /// - Returns: A view controller.\n    @MainActor\n    public class func make(\n        themePlist: String?\n    ) throws -> UIViewController {\n        return try MessageCenterViewControllerFactory.make(themePlist: themePlist, controller: MessageCenterController())\n    }\n    \n    @objc\n    /// Makes a message view controller with the given theme.\n    /// - Parameters:\n    ///     - themePlist: A path to a theme plist\n    ///     - predicate: The message center predicate\n    /// - Returns: A view controller.\n    @MainActor\n    public class func make(\n        themePlist: String?,\n        predicate: (any UAMessageCenterPredicate)?\n    ) throws -> UIViewController {\n        if let predicate = predicate {\n            let wrapper = UAMessageCenterPredicateWrapper(delegate: predicate)\n            return try MessageCenterViewControllerFactory.make(themePlist: themePlist, predicate: wrapper, controller: MessageCenterController())\n        }\n        return try MessageCenterViewControllerFactory.make(themePlist: themePlist, controller: MessageCenterController())\n    }\n\n    @objc\n    /// Embeds the message center view in another view.\n    /// - Parameters:\n    ///   - theme: The message center theme.\n    ///   - predicate: The message center predicate.\n    ///   - parentViewController: The parent view controller into which we'll embed the message center.\n    /// - Returns: A UIView to be added into another view.\n    @MainActor\n    public class func embed(\n        theme: UAMessageCenterTheme? = nil,\n        predicate: (any UAMessageCenterPredicate)? = nil,\n        in parentViewController: UIViewController\n    ) -> UIView {\n        let childVC = self.make(theme: theme, predicate: predicate)\n        parentViewController.addChild(childVC)\n\n        let containerView = UIView(frame: .zero)\n        containerView.addSubview(childVC.view)\n        childVC.view.translatesAutoresizingMaskIntoConstraints = false\n\n        NSLayoutConstraint.activate([\n            childVC.view.topAnchor.constraint(equalTo: containerView.topAnchor),\n            childVC.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),\n            childVC.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),\n            childVC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)\n        ])\n        childVC.didMove(toParent: parentViewController)\n        return containerView\n    }\n\n    private class func toAirshipTheme(theme: UAMessageCenterTheme) -> MessageCenterTheme {\n        return MessageCenterTheme(\n            refreshTintColor: UAMessageCenterViewControllerFactory.toColor(color: theme.refreshTintColor),\n            refreshTintColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.refreshTintColorDark),\n            iconsEnabled: theme.iconsEnabled,\n            placeholderIcon: UAMessageCenterViewControllerFactory.toImage(image: theme.placeholderIcon),\n            cellTitleFont: UAMessageCenterViewControllerFactory.toFont(font: theme.cellTitleFont),\n            cellDateFont: UAMessageCenterViewControllerFactory.toFont(font: theme.cellDateFont),\n            cellColor: UAMessageCenterViewControllerFactory.toColor(color: theme.cellColor),\n            cellColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.cellColorDark),\n            cellTitleColor: UAMessageCenterViewControllerFactory.toColor(color: theme.cellTitleColor),\n            cellTitleColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.cellTitleColorDark),\n            cellDateColor: UAMessageCenterViewControllerFactory.toColor(color: theme.cellDateColor),\n            cellDateColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.cellDateColorDark),\n            cellSeparatorStyle: nil,\n            cellSeparatorColor: nil,\n            cellSeparatorColorDark: nil,\n            cellTintColor: UAMessageCenterViewControllerFactory.toColor(color: theme.cellTintColor),\n            cellTintColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.cellTintColorDark),\n            unreadIndicatorColor: UAMessageCenterViewControllerFactory.toColor(color: theme.unreadIndicatorColor),\n            unreadIndicatorColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.unreadIndicatorColorDark),\n            selectAllButtonTitleColor: UAMessageCenterViewControllerFactory.toColor(color: theme.selectAllButtonTitleColor),\n            selectAllButtonTitleColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.selectAllButtonTitleColorDark),\n            deleteButtonTitleColor: UAMessageCenterViewControllerFactory.toColor(color: theme.deleteButtonTitleColor),\n            deleteButtonTitleColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.deleteButtonTitleColorDark),\n            markAsReadButtonTitleColor: UAMessageCenterViewControllerFactory.toColor(color: theme.markAsReadButtonTitleColor),\n            markAsReadButtonTitleColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.markAsReadButtonTitleColorDark),\n            hideDeleteButton: theme.hideDeleteButton,\n            editButtonTitleColor: UAMessageCenterViewControllerFactory.toColor(color: theme.editButtonTitleColor),\n            editButtonTitleColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.editButtonTitleColorDark),\n            cancelButtonTitleColor: UAMessageCenterViewControllerFactory.toColor(color: theme.cancelButtonTitleColor),\n            cancelButtonTitleColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.cancelButtonTitleColorDark),\n            backButtonColor: UAMessageCenterViewControllerFactory.toColor(color: theme.backButtonColor),\n            backButtonColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.backButtonColorDark),\n            navigationBarTitle: theme.navigationBarTitle,\n            messageListBackgroundColor: UAMessageCenterViewControllerFactory.toColor(color: theme.messageListBackgroundColor),\n            messageListBackgroundColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.messageListBackgroundColorDark),\n            messageListContainerBackgroundColor: UAMessageCenterViewControllerFactory.toColor(color: theme.messageListContainerBackgroundColor),\n            messageListContainerBackgroundColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.messageListContainerBackgroundColorDark),\n            messageViewBackgroundColor: UAMessageCenterViewControllerFactory.toColor(color: theme.messageViewBackgroundColor),\n            messageViewBackgroundColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.messageViewBackgroundColorDark),\n            messageViewContainerBackgroundColor: UAMessageCenterViewControllerFactory.toColor(color: theme.messageViewContainerBackgroundColor),\n            messageViewContainerBackgroundColorDark: UAMessageCenterViewControllerFactory.toColor(color: theme.messageViewContainerBackgroundColorDark))\n    }\n    \n    private class func toColor(color: AirshipNativeColor?) -> Color? {\n        if let color = color {\n            return Color(color)\n        }\n        return nil\n    }\n    \n    private class func toFont(font: AirshipNativeFont?) -> Font? {\n        if let font = font {\n            return Font(font)\n        }\n        return nil\n    }\n    \n    private class func toImage(image: AirshipNativeImage?) -> Image? {\n        if let image = image {\n            return Image(airshipNativeImage: image)\n        }\n        return nil\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/PreferenceCenter/UAPreferenceCenter.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\nimport AirshipPreferenceCenter\n#endif\n\n/// Open delegate.\n@objc\npublic protocol UAPreferenceCenterOpenDelegate {\n\n    /**\n     * Opens the Preference Center with the given ID.\n     * - Parameters:\n     *   - preferenceCenterID: The preference center ID.\n     * - Returns: `true` if the preference center was opened, otherwise `false` to fallback to OOTB UI.\n     */\n    @objc\n    func openPreferenceCenter(_ preferenceCenterID: String) -> Bool\n}\n\nfileprivate final class UAPreferenceCenterOpenDelegateWrapper: NSObject, PreferenceCenterOpenDelegate {\n    weak var forwardDelegate: (any UAPreferenceCenterOpenDelegate)?\n\n    init(_ forwardDelegate: any UAPreferenceCenterOpenDelegate) {\n        self.forwardDelegate = forwardDelegate\n    }\n    \n    public func openPreferenceCenter(_ preferenceCenterID: String) -> Bool {\n        return self.forwardDelegate?.openPreferenceCenter(preferenceCenterID) ?? false\n    }\n}\n\n/// Airship PreferenceCenter module.\n@objc\npublic final class UAPreferenceCenter: NSObject, Sendable {\n\n    private let storage: Storage = Storage()\n\n    override init() {\n        super.init()\n    }\n\n    /**\n     * Open delegate.\n     *\n     * If set, the delegate will be called instead of launching the OOTB preference center screen. Must be set\n     * on the main actor.\n     */\n    @objc\n    @MainActor\n    public weak var openDelegate: (any UAPreferenceCenterOpenDelegate)? {\n        get {\n            guard let wrapped = Airship.preferenceCenter.openDelegate as? UAPreferenceCenterOpenDelegateWrapper else {\n                return nil\n            }\n            return wrapped.forwardDelegate\n        }\n\n        set {\n            if let newValue {\n                let wrapper = UAPreferenceCenterOpenDelegateWrapper(newValue)\n                Airship.preferenceCenter.openDelegate = wrapper\n                storage.openDelegate = wrapper\n            } else {\n                Airship.preferenceCenter.openDelegate = nil\n                storage.openDelegate = nil\n            }\n        }\n    }\n    \n    @objc\n    @MainActor\n    public func setThemeFromPlist(_ plist: String) throws {\n        try  Airship.preferenceCenter.setThemeFromPlist(plist)\n    }\n    \n    /**\n     * Opens the Preference Center with the given ID.\n     * - Parameters:\n     *   - preferenceCenterID: The preference center ID.\n     */\n    @objc(openPreferenceCenter:)\n    @MainActor\n    public func open(_ preferenceCenterID: String) {\n        Airship.preferenceCenter.open(preferenceCenterID)\n    }\n    \n    /**\n     * Returns the configuration of the Preference Center as JSON data with the given ID.\n     * - Parameters:\n     *   - preferenceCenterID: The preference center ID.\n     */\n    @objc\n    public func jsonConfig(preferenceCenterID: String) async throws -> Data {\n        return try await Airship.preferenceCenter.jsonConfig(preferenceCenterID: preferenceCenterID)\n    }\n\n    fileprivate final class Storage: Sendable {\n        @MainActor\n        var openDelegate: (any PreferenceCenterOpenDelegate)?\n\n        init() {}\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/PreferenceCenter/UAPreferenceCenterViewController.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\npublic import UIKit\n\n#if canImport(AirshipCore)\nimport AirshipPreferenceCenter\nimport AirshipCore\n#endif\n\nimport SwiftUI\n\n/// Preference Center view controller factory\n@objc\npublic final class UAPreferenceCenterViewControllerFactory: NSObject, Sendable {\n    \n    @objc\n    /// Makes a view controller for the given Preference Center ID.\n    /// - Parameters:\n    ///     - preferenceCenterID: The preferenceCenterID.\n    /// - Returns: A view controller.\n    @MainActor\n    public class func makeViewController(\n        preferenceCenterID: String\n    )-> UIViewController {\n        return PreferenceCenterViewControllerFactory.makeViewController(preferenceCenterID: preferenceCenterID)\n    }\n\n    @objc\n    /// Makes a view controller for the given Preference Center ID and theme.\n    /// - Parameters:\n    ///     - preferenceCenterID: The preferenceCenterID.\n    ///     - preferenceCenterThemePlist: The theme plist.\n    /// - Returns: A view controller.\n    @MainActor\n    public class func makeViewController(\n        preferenceCenterID: String,\n        preferenceCenterThemePlist: String\n    ) throws -> UIViewController {\n        return try PreferenceCenterViewControllerFactory.makeViewController(preferenceCenterID: preferenceCenterID, preferenceCenterThemePlist: preferenceCenterThemePlist)\n    }\n\n    @objc\n    /// Embeds the preference center view in another view.\n    /// - Parameters:\n    ///   - preferenceCenterID: The preference center ID.\n    ///   - preferenceCenterThemePlist: Optional path to a theme plist.\n    ///   - parentViewController: The parent view controller into which we'll embed the preference center.\n    /// - Returns: A UIView to be added into another view.\n    @MainActor\n    public class func embed(\n        preferenceCenterID: String,\n        preferenceCenterThemePlist: String? = nil,\n        in parentViewController: UIViewController\n    ) throws -> UIView {\n        let childVC: UIViewController\n        if let themePlist = preferenceCenterThemePlist {\n            childVC = try makeViewController(preferenceCenterID: preferenceCenterID, preferenceCenterThemePlist: themePlist)\n        } else {\n            childVC = makeViewController(preferenceCenterID: preferenceCenterID)\n        }\n\n        parentViewController.addChild(childVC)\n\n        let containerView = UIView(frame: .zero)\n        containerView.addSubview(childVC.view)\n        childVC.view.translatesAutoresizingMaskIntoConstraints = false\n\n        NSLayoutConstraint.activate([\n            childVC.view.topAnchor.constraint(equalTo: containerView.topAnchor),\n            childVC.view.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),\n            childVC.view.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),\n            childVC.view.bottomAnchor.constraint(equalTo: containerView.bottomAnchor)\n        ])\n\n        childVC.didMove(toParent: parentViewController)\n\n        return containerView\n    }\n}\n    \n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/PrivacyManager/UAPrivacyManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The privacy manager allow enabling/disabling features in the SDK.\n/// The SDK will not make any network requests or collect data if all features are disabled, with\n/// a few exceptions when going from enabled -> disabled. To have the SDK opt-out of all features on startup,\n/// set the default enabled features in the Config to an empty option set, or in the\n/// airshipconfig.plist file with `enabledFeatures = none`.\n/// If any feature is enabled, the SDK will collect and send the following data:\n/// - Channel ID\n/// - Locale\n/// - TimeZone\n/// - Platform\n/// - Opt in state (push and notifications)\n/// - SDK version\n/// - Accengage Device ID (Accengage module for migration)\n@objc\npublic final class UAPrivacyManager: NSObject, Sendable {\n\n    override init() {\n        super.init()\n    }\n    \n    /// The current set of enabled features.\n    @objc(enabledFeatures)\n    public var enabledFeatures: UAFeature {\n        get {\n            return Airship.privacyManager.enabledFeatures.asUAFeature\n        }\n        set {\n            Airship.privacyManager.enabledFeatures = newValue.asAirshipFeature\n        }\n    }\n    \n    /// Enables features.\n    /// This will append any features to the `enabledFeatures` property.\n    /// - Parameter features: The features to enable.\n    @objc(enableFeatures:)\n    public func enableFeatures(_ features: UAFeature) {\n        Airship.privacyManager.enableFeatures(features.asAirshipFeature)\n    }\n    \n    /// Disables features.\n    /// This will remove any features to the `enabledFeatures` property.\n    /// - Parameter features: The features to disable.\n    @objc(disableFeatures:)\n    public func disableFeatures(_ features: UAFeature) {\n        Airship.privacyManager.disableFeatures(features.asAirshipFeature)\n    }\n    \n    /**\n     * Checks if a given feature is enabled.\n     *\n     * - Parameter features: The features to check.\n     * - Returns: True if the provided features are enabled, otherwise false.\n     */\n    @objc(isEnabled:)\n    public func isEnabled(_ features: UAFeature) -> Bool {\n        return Airship.privacyManager.isEnabled(features.asAirshipFeature)\n    }\n    \n    /// Checks if any feature is enabled.\n    /// - Returns: `true` if a feature is enabled, otherwise `false`.\n    @objc\n    public func isAnyFeatureEnabled() -> Bool {\n        return Airship.privacyManager.isAnyFeatureEnabled()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Push/UAAuthorizedNotificationSettings.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n\n// Authorized notification settings.\n@objc\npublic final class UAAuthorizedNotificationSettings: NSObject, OptionSet, Sendable {\n    public let rawValue: UInt\n\n    public init(rawValue: UInt) {\n        self.rawValue = rawValue\n    }\n\n    // Badge\n    @objc\n    public static func badge() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.badge.rawValue)\n    }\n\n    // Sound\n    @objc\n    public static func sound() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.sound.rawValue)\n    }\n\n    // Alert\n    @objc\n    public static func alert() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.alert.rawValue)\n    }\n\n    // Car Play\n    @objc\n    public static func carPlay() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.carPlay.rawValue)\n    }\n\n    // Lock Screen\n    @objc\n    public static func lockScreen() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.lockScreen.rawValue)\n    }\n\n    // Notification Center\n    @objc\n    public static func notificationCenter() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.notificationCenter.rawValue)\n    }\n\n    // Critical alert\n    @objc\n    public static func criticalAlert() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.criticalAlert.rawValue)\n    }\n\n    // Announcement\n    @objc\n    public static func announcement() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.announcement.rawValue)\n    }\n\n    // Scheduled delivery\n    @objc\n    public static func scheduledDelivery() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.scheduledDelivery.rawValue)\n    }\n\n    // Time sensitive\n    @objc\n    public static func timeSensitive() -> UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: AirshipAuthorizedNotificationSettings.timeSensitive.rawValue)\n    }\n    \n    public override var hash: Int {\n        return Int(rawValue)\n    }\n\n    public override func isEqual(_ object: Any?) -> Bool {\n        guard let that = object as? UAAuthorizedNotificationSettings else {\n            return false\n        }\n\n        return rawValue == that.rawValue\n    }\n}\n\nextension AirshipAuthorizedNotificationSettings {\n    var asUAAuthorizedNotificationSettings: UAAuthorizedNotificationSettings {\n        return UAAuthorizedNotificationSettings(rawValue: self.rawValue)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Push/UANotificationCategories.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\npublic import UserNotifications\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@objc\npublic final class UANotificationCategories: NSObject, Sendable {\n    \n    /**\n     * Factory method to create the default set of user notification categories.\n     * Background user notification actions will default to requiring authorization.\n     * - Returns: A set of user notification categories\n     */\n    @objc\n    public class func defaultCategories() -> Set<UNNotificationCategory> {\n        return NotificationCategories.defaultCategories()\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Push/UAPush.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\npublic import UserNotifications\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// This singleton provides an interface to the functionality provided by the Airship iOS Push API.\n@objc\npublic final class UAPush: NSObject, Sendable {\n\n    private let storage = Storage()\n\n    override init() {\n        super.init()\n    }\n\n    /// Enables/disables background remote notifications on this device through Airship.\n    /// Defaults to `true`.\n    @objc\n    @MainActor\n    public var backgroundPushNotificationsEnabled: Bool {\n        set {\n            Airship.push.backgroundPushNotificationsEnabled = newValue\n        }\n        get {\n            return Airship.push.backgroundPushNotificationsEnabled\n        }\n    }\n\n    /// Enables/disables user notifications on this device through Airship.\n    /// Defaults to `false`. Once set to `true`, the user will be prompted for remote notifications.\n    @objc\n    public var userPushNotificationsEnabled: Bool {\n        set {\n            Airship.push.userPushNotificationsEnabled = newValue\n        }\n\n        get {\n            return Airship.push.userPushNotificationsEnabled\n        }\n    }\n\n\n    /// When enabled, if the user has ephemeral notification authorization the SDK will prompt the user for\n    /// notifications.  Defaults to `false`.\n    @objc\n    public var requestExplicitPermissionWhenEphemeral: Bool {\n        set {\n            Airship.push.requestExplicitPermissionWhenEphemeral = newValue\n        }\n        get {\n            return Airship.push.requestExplicitPermissionWhenEphemeral\n        }\n    }\n\n    /// The device token for this device, as a hex string.\n    @objc\n    @MainActor\n    public var deviceToken: String? {\n        get {\n            return Airship.push.deviceToken\n        }\n    }\n\n    /// User Notification options this app will request from APNS. Changes to this value\n    /// will not take effect until the next time the app registers with\n    /// updateRegistration.\n    ///\n    /// Defaults to alert, sound and badge.\n    @objc\n    public var notificationOptions: UNAuthorizationOptions {\n        set {\n            Airship.push.notificationOptions = newValue\n        }\n\n        get {\n            return Airship.push.notificationOptions\n        }\n    }\n\n    #if !os(tvOS)\n    /// Custom notification categories. Airship default notification\n    /// categories will be unaffected by this field.\n    ///\n    /// Changes to this value will not take effect until the next time the app registers\n    /// with updateRegistration.\n    @objc\n    @MainActor\n    public var customCategories: Set<UNNotificationCategory> {\n        set {\n            Airship.push.customCategories = newValue\n        }\n\n        get {\n            return Airship.push.customCategories\n        }\n    }\n\n    /// The combined set of notification categories from `customCategories` set by the app\n    /// and the Airship provided categories.\n    @objc\n    @MainActor\n    public var combinedCategories: Set<UNNotificationCategory> {\n        get {\n            return Airship.push.combinedCategories\n        }\n    }\n\n    #endif\n\n    /// Sets authorization required for the default Airship categories. Only applies\n    /// to background user notification actions.\n    ///\n    /// Changes to this value will not take effect until the next time the app registers\n    /// with updateRegistration.\n    @objc\n    @MainActor\n    public var requireAuthorizationForDefaultCategories: Bool {\n        set {\n            Airship.push.requireAuthorizationForDefaultCategories = newValue\n        }\n\n        get {\n            return Airship.push.requireAuthorizationForDefaultCategories\n        }\n    }\n\n    @objc\n    @MainActor\n    public weak var pushNotificationDelegate: (any UAPushNotificationDelegate)? {\n        get {\n            guard let wrapped = Airship.push.pushNotificationDelegate as? UAPushNotificationDelegateWrapper else {\n                return nil\n            }\n            return wrapped.forwardDelegate\n        }\n\n        set {\n            if let newValue {\n                let wrapper = UAPushNotificationDelegateWrapper(newValue)\n                Airship.push.pushNotificationDelegate = wrapper\n                storage.pushNotificationDelegate = wrapper\n            } else {\n                Airship.push.pushNotificationDelegate = nil\n                storage.pushNotificationDelegate = nil\n            }\n        }\n    }\n\n    @objc\n    @MainActor\n    public weak var registrationDelegate: (any UARegistrationDelegate)? {\n        get {\n            guard let wrapped = Airship.push.registrationDelegate as? UARegistrationDelegateWrapper else {\n                return nil\n            }\n            return wrapped.forwardDelegate\n        }\n\n        set {\n            if let newValue {\n                let wrapper = UARegistrationDelegateWrapper(newValue)\n                Airship.push.registrationDelegate = wrapper\n                storage.registrationDelegate = wrapper\n            } else {\n                Airship.push.registrationDelegate = nil\n                storage.registrationDelegate = nil\n            }\n        }\n    }\n\n    #if !os(tvOS)\n    /// Notification response that launched the application.\n    @objc\n    public var launchNotificationResponse: UNNotificationResponse? {\n        get {\n            return Airship.push.launchNotificationResponse\n        }\n    }\n    #endif\n\n    @objc\n    @MainActor\n    public var authorizedNotificationSettings: UAAuthorizedNotificationSettings {\n        get {\n            return Airship.push.authorizedNotificationSettings.asUAAuthorizedNotificationSettings\n        }\n    }\n\n    @objc\n    public var authorizationStatus: UNAuthorizationStatus {\n        get {\n            return Airship.push.authorizationStatus\n        }\n    }\n\n    @objc\n    public var userPromptedForNotifications: Bool {\n        get {\n            return Airship.push.userPromptedForNotifications\n        }\n    }\n\n    @objc\n    public var defaultPresentationOptions: UNNotificationPresentationOptions {\n        set {\n            Airship.push.defaultPresentationOptions = newValue\n        }\n        \n        get {\n            return Airship.push.defaultPresentationOptions\n        }\n    }\n        \n    @objc\n    public func enableUserPushNotifications() async -> Bool {\n        return await Airship.push.enableUserPushNotifications()\n    }\n    \n    @objc\n    @MainActor\n    public var isPushNotificationsOptedIn: Bool {\n        get {\n            return Airship.push.isPushNotificationsOptedIn\n        }\n    }\n   \n    #if !os(watchOS)\n\n    /// Sets the badge number.\n    /// - Parameters:\n    ///     - badge: The badge to set\n    @objc\n    public func setBadgeNumber(_ badge: Int) async throws {\n        try await Airship.push.setBadgeNumber(badge)\n    }\n\n    /// Gets the badge number.\n    @objc\n    @MainActor\n    public var badgeNumber: Int {\n        get {\n            return Airship.push.badgeNumber\n        }\n    }\n\n    /// Enables/disables auto badge.\n    @objc\n    public var autobadgeEnabled: Bool {\n        set {\n            Airship.push.autobadgeEnabled = newValue\n        }\n\n        get {\n            return Airship.push.autobadgeEnabled\n        }\n    }\n\n    /// Resets the badge.\n    @objc\n    @MainActor\n    public func resetBadge() async throws {\n        try await Airship.push.resetBadge()\n    }\n\n    #endif\n\n    /// Time Zone for quiet time. If the time zone is not set, the current\n    /// local time zone is returned.\n    @objc\n    public var timeZone: NSTimeZone? {\n        set {\n            Airship.push.timeZone = newValue\n        }\n\n        get {\n            return Airship.push.timeZone\n        }\n    }\n\n    /// Enables/Disables quiet time\n    @objc\n    public var quietTimeEnabled: Bool {\n        set {\n            Airship.push.quietTimeEnabled = newValue\n        }\n\n        get {\n            return Airship.push.quietTimeEnabled\n        }\n    }\n\n    @objc\n    public func setQuietTimeStartHour(\n        _ startHour: Int,\n        startMinute: Int,\n        endHour: Int,\n        endMinute: Int\n    ) {\n        Airship.push.setQuietTimeStartHour(startHour, startMinute: startMinute, endHour: endHour, endMinute: endMinute)\n    }\n\n    /// Gets the current push notification status.\n    /// - Parameter completionHandler: The completion handler to call with the notification status.\n    @objc\n    @MainActor\n    public func getNotificationStatus(\n        completionHandler: @escaping (UAPushNotificationStatus) -> Void\n    ) {\n        Task { @MainActor in\n            let status = await Airship.push.notificationStatus\n            completionHandler(UAPushNotificationStatus(status))\n        }\n    }\n\n    fileprivate final class Storage: Sendable  {\n        @MainActor\n        var registrationDelegate: (any RegistrationDelegate)?\n\n        @MainActor\n        var pushNotificationDelegate: (any PushNotificationDelegate)?\n\n        init() {}\n    }\n}\n\npublic extension UAAirshipNotifications {\n\n    /// NSNotification info when enabled feature changed on PrivacyManager.\n    @objc(UAAirshipNotificationReceivedNotificationResponse)\n    final class ReceivedNotificationResponse: NSObject {\n\n        /// NSNotification name.\n        @objc\n        public static let name = NSNotification.Name(\n            \"com.urbanairship.push.received_notification_response\"\n        )\n    }\n\n\n    /// NSNotification info when enabled feature changed on PrivacyManager.\n    @objc(UAAirshipNotificationRecievedNotification)\n    final class RecievedNotification: NSObject {\n\n        /// NSNotification name.\n        @objc\n        public static let name = NSNotification.Name(\n            \"com.urbanairship.push.received_notification\"\n        )\n    }\n \n}\n\n    \n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Push/UAPushNotificationDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Protocol to be implemented by push notification clients. All methods are optional.\n@objc\npublic protocol UAPushNotificationDelegate: Sendable {\n    /// Called when a notification is received in the foreground.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    ///   - completionHandler: the completion handler to execute when notification processing is complete.\n    @objc\n    func receivedForegroundNotification(\n        _ userInfo: [AnyHashable: Any],\n        completionHandler: @escaping () -> Void\n    )\n\n#if !os(watchOS)\n    /// Called when a notification is received in the background.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    ///   - completionHandler: the completion handler to execute when notification processing is complete.\n    @objc\n    func receivedBackgroundNotification(\n        _ userInfo: [AnyHashable: Any],\n        completionHandler: @escaping (UIBackgroundFetchResult) -> Void\n    )\n#else\n    /// Called when a notification is received in the background.\n    ///\n    /// - Parameters:\n    ///   - userInfo: The notification info\n    ///   - completionHandler: the completion handler to execute when notification processing is complete.\n    @objc\n    func receivedBackgroundNotification(\n        _ userInfo: [AnyHashable: Any],\n        completionHandler: @escaping (WKBackgroundFetchResult) -> Void\n    )\n#endif\n\n#if !os(tvOS)\n    /// Called when a notification is received in the background or foreground and results in a user interaction.\n    /// User interactions can include launching the application from the push, or using an interactive control on the notification interface\n    /// such as a button or text field.\n    ///\n    /// - Parameters:\n    ///   - notificationResponse: UNNotificationResponse object representing the user's response\n    /// to the notification and the associated notification contents.\n    ///\n    ///   - completionHandler: the completion handler to execute when processing the user's response has completed.\n    @objc\n    func receivedNotificationResponse(\n        _ notificationResponse: UNNotificationResponse,\n        completionHandler: @escaping () -> Void\n    )\n#endif\n\n\n    /// Called when a notification has arrived in the foreground and is available for display.\n    ///\n    /// - Parameters:\n    ///   - options: The notification presentation options.\n    ///   - notification: The notification.\n    ///   - completionHandler: The completion handler.\n    @objc(extendPresentationOptions:notification:completionHandler:)\n    func extendPresentationOptions(\n        _ options: UNNotificationPresentationOptions,\n        notification: UNNotification,\n        completionHandler: @escaping (UNNotificationPresentationOptions) -> Void\n    )\n}\n\n@MainActor\nfinal class UAPushNotificationDelegateWrapper: NSObject, PushNotificationDelegate {\n\n    weak var forwardDelegate: (any UAPushNotificationDelegate)?\n\n    init(_ forwardDelegate: any UAPushNotificationDelegate) {\n        self.forwardDelegate = forwardDelegate\n    }\n\n    func receivedForegroundNotification(_ userInfo: [AnyHashable : Any]) async {\n        await withCheckedContinuation { [forwardDelegate] continuation in\n            forwardDelegate?.receivedForegroundNotification(userInfo) {\n                continuation.resume()\n            }\n        }\n    }\n\n#if !os(watchOS)\n    func receivedBackgroundNotification(_ userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {\n        await withCheckedContinuation { [forwardDelegate] continuation in\n            forwardDelegate?.receivedBackgroundNotification(userInfo) { result in\n                continuation.resume(returning: result)\n            }\n        }\n    }\n#else\n    func receivedBackgroundNotification(_ userInfo: [AnyHashable : Any]) async -> WKBackgroundFetchResult {\n        await withCheckedContinuation { [forwardDelegate] continuation in\n            forwardDelegate?.receivedBackgroundNotification(userInfo) { result in\n                continuation.resume(returning: result)\n            }\n        }\n    }\n#endif\n\n#if !os(tvOS)\n    func receivedNotificationResponse(_ notificationResponse: UNNotificationResponse) async {\n        await withCheckedContinuation { [forwardDelegate] continuation in\n            forwardDelegate?.receivedNotificationResponse(notificationResponse) {\n                continuation.resume()\n            }\n        }\n    }\n#endif\n\n    func extendPresentationOptions(_ options: UNNotificationPresentationOptions, notification: UNNotification) async -> UNNotificationPresentationOptions {\n        await withCheckedContinuation { [forwardDelegate] continuation in\n            forwardDelegate?.extendPresentationOptions(options, notification: notification) { result in\n                continuation.resume(returning: result)\n            }\n        }\n    }\n\n    @MainActor\n    public func receivedForegroundNotification(\n        _ userInfo: [AnyHashable: Any],\n        completionHandler: @escaping () -> Void\n    ) {\n        guard let forwardDelegate  else {\n            completionHandler()\n            return\n        }\n\n        forwardDelegate.receivedForegroundNotification(userInfo, completionHandler: completionHandler)\n    }\n    \n    #if !os(watchOS)\n    @MainActor\n    public func receivedBackgroundNotification(\n       _ userInfo: [AnyHashable: Any],\n       completionHandler: @escaping (UIBackgroundFetchResult) -> Void\n    ) {\n        guard let forwardDelegate  else {\n            completionHandler(.noData)\n            return\n        }\n\n        forwardDelegate.receivedBackgroundNotification(userInfo, completionHandler: completionHandler)\n    }\n    \n    #else\n    \n    public func receivedBackgroundNotification(\n        _ userInfo: [AnyHashable: Any],\n        completionHandler: @escaping (WKBackgroundFetchResult) -> Void\n    ) {\n        guard let forwardDelegate  else {\n            completionHandler(.noData)\n            return\n        }\n\n        forwardDelegate.receivedBackgroundNotification(userInfo, completionHandler: completionHandler)\n    }\n    \n    #endif\n    #if !os(tvOS)\n    @MainActor\n    public func receivedNotificationResponse(\n       _ notificationResponse: UNNotificationResponse,\n       completionHandler: @escaping () -> Void\n    ) {\n        guard let forwardDelegate  else {\n            completionHandler()\n            return\n        }\n        forwardDelegate.receivedNotificationResponse(notificationResponse, completionHandler: completionHandler)\n    }\n    \n    #endif\n    @MainActor\n    public func extendPresentationOptions(\n        _ options: UNNotificationPresentationOptions,\n        notification: UNNotification,\n        completionHandler: @escaping (UNNotificationPresentationOptions) -> Void\n    ) {\n        guard let forwardDelegate  else {\n            completionHandler(options)\n            return\n        }\n\n        forwardDelegate.extendPresentationOptions(options, notification: notification, completionHandler: completionHandler)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Push/UAPushNotificationStatus.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Push notification status\n@objc\npublic final class UAPushNotificationStatus: NSObject, Sendable {\n    /// If user notifications are enabled on AirshipPush.\n    @objc\n    public let isUserNotificationsEnabled: Bool\n\n    /// If notifications are either ephemeral or granted and has at least one authorized type.\n    @objc\n    public let areNotificationsAllowed: Bool\n\n    /// If the push feature is enabled on `AirshipPrivacyManager`.\n    @objc\n    public let isPushPrivacyFeatureEnabled: Bool\n\n    /// If a push token is generated.\n    @objc\n    public let isPushTokenRegistered: Bool\n\n    /// Display notification permission status\n    @objc\n    public let notificationPermissionStatus: UAPermissionStatus\n\n    /// If isUserNotificationsEnabled, isPushPrivacyFeatureEnabled, and areNotificationsAllowed are all true.\n    @objc\n    public let isUserOptedIn: Bool\n\n    /// If isUserOptedIn and isPushTokenRegistered are both true.\n    @objc\n    public let isOptedIn: Bool\n\n    init(_ status: AirshipNotificationStatus) {\n        self.isUserNotificationsEnabled = status.isUserNotificationsEnabled\n        self.areNotificationsAllowed = status.areNotificationsAllowed\n        self.isPushPrivacyFeatureEnabled = status.isPushPrivacyFeatureEnabled\n        self.isPushTokenRegistered = status.isPushTokenRegistered\n        self.notificationPermissionStatus = UAPermissionStatus(status.displayNotificationStatus)\n        self.isUserOptedIn = status.isUserOptedIn\n        self.isOptedIn = status.isOptedIn\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Push/UARegistrationDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\npublic import UserNotifications\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Implement this protocol and add as a Push.registrationDelegate to receive\n/// registration success and failure callbacks.\n///\n@objc\npublic protocol UARegistrationDelegate {\n    #if !os(tvOS)\n    /// Called when APNS registration completes.\n    ///\n    /// - Parameters:\n    ///   - authorizedSettings: The settings that were authorized at the time of registration.\n    ///   - categories: Set of the categories that were most recently registered.\n    ///   - status: The authorization status.\n    @objc\n    func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            UAAuthorizedNotificationSettings,\n        categories: Set<UNNotificationCategory>,\n        status: UNAuthorizationStatus\n    )\n    #endif\n\n    /// Called when APNS registration completes.\n    ///\n    /// - Parameters:\n    ///   - authorizedSettings: The settings that were authorized at the time of registration.\n    ///   - status: The authorization status.\n    @objc\n    func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            UAAuthorizedNotificationSettings,\n        status: UNAuthorizationStatus\n    )\n\n    /// Called when notification authentication changes with the new authorized settings.\n    ///\n    /// - Parameter authorizedSettings: AirshipAuthorizedNotificationSettings The newly changed authorized settings.\n    @objc\n    func notificationAuthorizedSettingsDidChange(\n        _ authorizedSettings: UAAuthorizedNotificationSettings\n    )\n\n    /// Called when the UIApplicationDelegate's application:didRegisterForRemoteNotificationsWithDeviceToken:\n    /// delegate method is called.\n    ///\n    /// - Parameter deviceToken: The APNS device token.\n    @objc\n    func apnsRegistrationSucceeded(\n        withDeviceToken deviceToken: Data\n    )\n\n    /// Called when the UIApplicationDelegate's application:didFailToRegisterForRemoteNotificationsWithError:\n    /// delegate method is called.\n    ///\n    /// - Parameter error: An NSError object that encapsulates information why registration did not succeed.\n    @objc\n    func apnsRegistrationFailedWithError(_ error: any Error)\n}\n\nfinal class UARegistrationDelegateWrapper: NSObject, RegistrationDelegate {\n\n    weak var forwardDelegate: (any UARegistrationDelegate)?\n\n    init(_ forwardDelegate: any UARegistrationDelegate) {\n        self.forwardDelegate = forwardDelegate\n    }\n    \n    public func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            AirshipAuthorizedNotificationSettings,\n        categories: Set<UNNotificationCategory>,\n        status: UNAuthorizationStatus\n    ) {\n        self.forwardDelegate?.notificationRegistrationFinished(\n            withAuthorizedSettings: authorizedSettings.asUAAuthorizedNotificationSettings,\n            categories: categories,\n            status: status\n        )\n    }\n    \n    public func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings:\n            AirshipAuthorizedNotificationSettings,\n        status: UNAuthorizationStatus\n    ) {\n        self.forwardDelegate?.notificationRegistrationFinished(\n            withAuthorizedSettings: authorizedSettings.asUAAuthorizedNotificationSettings,\n            status: status\n        )\n    }\n    \n    public func apnsRegistrationSucceeded(\n        withDeviceToken deviceToken: Data\n    ) {\n        self.forwardDelegate?.apnsRegistrationSucceeded(withDeviceToken: deviceToken)\n    }\n    \n    public func apnsRegistrationFailedWithError(_ error: any Error) {\n        self.forwardDelegate?.apnsRegistrationFailedWithError(error)\n    }\n    \n    public func notificationAuthorizedSettingsDidChange(_ authorizedSettings: AirshipAuthorizedNotificationSettings) {\n        self.forwardDelegate?.notificationAuthorizedSettingsDidChange(\n            authorizedSettings.asUAAuthorizedNotificationSettings\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Subscription Lists/UAScopedSubscriptionListEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Scoped subscription list editor.\n@objc\npublic class UAScopedSubscriptionListEditor: NSObject {\n    \n    var editor: ScopedSubscriptionListEditor?\n    \n    /**\n     * Subscribes to a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     *   - scope: Defines the channel types that the change applies to.\n     */\n    @objc(subscribe:scope:)\n    public func subscribe(_ subscriptionListID: String, scope: UAChannelScope) {\n        self.editor?.subscribe(subscriptionListID, scope: scope.toChannelScope)\n    }\n\n    /**\n     * Unsubscribes from a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     *   - scope: Defines the channel types that the change applies to.\n     */\n    @objc(unsubscribe:scope:)\n    public func unsubscribe(_ subscriptionListID: String, scope: UAChannelScope) {\n        self.editor?.unsubscribe(subscriptionListID, scope: scope.toChannelScope)\n    }\n\n    /**\n     * Applies subscription list changes.\n     */\n    @objc\n    public func apply() {\n        self.editor?.apply()\n    }\n}\n    \n@objc\n/// Channel scope.\npublic enum UAChannelScope: Int, Sendable, Equatable {\n    /**\n     * App channels - amazon, android, iOS\n     */\n    case app\n\n    /**\n     * Web channels\n     */\n    case web\n\n    /**\n     * Email channels\n     */\n    case email\n\n    /**\n     * SMS channels\n     */\n    case sms\n\n    var toChannelScope: ChannelScope {\n        return switch(self) {\n        case .app: .app\n        case .web: .web\n        case .email: .email\n        case .sms: .sms\n        }\n    }\n}\n\nextension ChannelScope {\n    var toUAChannelScope: UAChannelScope {\n        return switch(self) {\n        case .app: .app\n        case .web: .web\n        case .email: .email\n        case .sms: .sms\n#if canImport(AirshipCore)\n        @unknown default:\n                .app\n#endif\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/Subscription Lists/UASubscriptionListEditor.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Subscription list editor.\n@objc\npublic class UASubscriptionListEditor: NSObject {\n    \n    var editor: SubscriptionListEditor?\n    \n    /**\n     * Subscribes to a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     */\n    @objc(subscribe:)\n    public func subscribe(_ subscriptionListID: String) {\n        self.editor?.subscribe(subscriptionListID)\n    }\n    \n    /**\n     * Unsubscribes from a list.\n     * - Parameters:\n     *   - subscriptionListID: The subscription list identifier.\n     */\n    @objc(unsubscribe:)\n    public func unsubscribe(_ subscriptionListID: String) {\n        self.editor?.unsubscribe(subscriptionListID)\n    }\n    \n    /**\n     * Applies subscription list changes.\n     */\n    @objc\n    public func apply() {\n        self.editor?.apply()\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UAAppIntegration.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@preconcurrency import UserNotifications\n\n/// Application hooks required by Airship. If `automaticSetupEnabled` is enabled\n/// (enabled by default), Airship will automatically integrate these calls into\n/// the application by swizzling methods. If `automaticSetupEnabled` is disabled,\n/// the application must call through to every method provided by this class.\n@objc\n@MainActor\npublic final class UAAppIntegration: NSObject {\n\n#if !os(watchOS)\n    \n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:performFetchWithCompletionHandler:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - completionHandler: The completion handler.\n     */\n    @objc(application:performFetchWithCompletionHandler:)\n    public class func application(\n        _ application: UIApplication,\n        performFetchWithCompletionHandler completionHandler: @Sendable @escaping (\n            UIBackgroundFetchResult\n        ) -> Void\n    ) {\n        AppIntegration.application(application, performFetchWithCompletionHandler: completionHandler)\n    }\n    \n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:didRegisterForRemoteNotificationsWithDeviceToken:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - deviceToken: The device token.\n     */\n    @objc(application:didRegisterForRemoteNotificationsWithDeviceToken:)\n    public class func application(\n        _ application: UIApplication,\n        didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data\n    ) {\n        AppIntegration.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)\n    }\n    \n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:didFailToRegisterForRemoteNotificationsWithError:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - error: The error.\n     */\n    @objc\n    public class func application(\n        _ application: UIApplication,\n        didFailToRegisterForRemoteNotificationsWithError error: any Error\n    ) {\n        AppIntegration.application(application, didFailToRegisterForRemoteNotificationsWithError: error)\n    }\n    \n    /**\n     * Must be called by the UIApplicationDelegate's\n     * application:didReceiveRemoteNotification:fetchCompletionHandler:.\n     *\n     * - Parameters:\n     *   - application: The application\n     *   - userInfo: The remote notification.\n     */\n    @objc(application:didReceiveRemoteNotification:fetchCompletionHandler:)\n    public class func application(\n        _ application: UIApplication,\n        didReceiveRemoteNotification userInfo: [AnyHashable: Any]\n    ) async -> UIBackgroundFetchResult  {\n        return await AppIntegration.application(application, didReceiveRemoteNotification: userInfo)\n    }\n#else\n    /**\n     * Must be called by the WKExtensionDelegate's\n     * didRegisterForRemoteNotificationsWithDeviceToken:.\n     *\n     * - Parameters:\n     *   - deviceToken: The device token.\n     */\n    @objc(didRegisterForRemoteNotificationsWithDeviceToken:)\n    public class func didRegisterForRemoteNotificationsWithDeviceToken(\n        deviceToken: Data\n    ) {\n        AppIntegration.didRegisterForRemoteNotificationsWithDeviceToken(deviceToken)\n    }\n    \n    /**\n     * Must be called by the WKExtensionDelegate's\n     * didFailToRegisterForRemoteNotificationsWithError:.\n     *\n     * - Parameters:\n     *   - error: The error.\n     */\n    @objc\n    public class func didFailToRegisterForRemoteNotificationsWithError(\n        error: Error\n    ) {\n        AppIntegration.didFailToRegisterForRemoteNotificationsWithError(error)\n    }\n    \n    /**\n     * Must be called by the WKExtensionDelegate's\n     * didReceiveRemoteNotification:fetchCompletionHandler:.\n     *\n     * - Parameters:\n     *   - userInfo: The remote notification.\n     */\n    @objc(application:didReceiveRemoteNotification:fetchCompletionHandler:)\n    public class func didReceiveRemoteNotification(\n        userInfo: [AnyHashable: Any]\n    ) async -> WKBackgroundFetchResult {\n        return await AppIntegration.didReceiveRemoteNotification(userInfo: userInfo)\n    }\n#endif\n    \n    /**\n     * Must be called by the UNUserNotificationDelegate's\n     * userNotificationCenter:willPresentNotification:withCompletionHandler.\n     *\n     * - Parameters:\n     *   - center: The notification center.\n     *   - notification: The notification.\n     */\n    @objc(userNotificationCenter:willPresentNotification:withCompletionHandler:)\n    public class func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        willPresent notification: UNNotification\n    ) async -> UNNotificationPresentationOptions {\n        return await AppIntegration.userNotificationCenter(center, willPresent: notification)\n    }\n    \n#if !os(tvOS)\n    /**\n     * Must be called by the UNUserNotificationDelegate's\n     * userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler.\n     *\n     * - Parameters:\n     *   - center: The notification center.\n     *   - response: The notification response.\n     */\n    @objc(userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:)\n    public class func userNotificationCenter(\n        _ center: UNUserNotificationCenter,\n        didReceive response: UNNotificationResponse\n    ) async {\n        await AppIntegration.userNotificationCenter(center, didReceive: response)\n    }\n#endif\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UAConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The Config object provides an interface for passing common configurable values to `UAirship`.\n/// The simplest way to use this class is to add an AirshipConfig.plist file in your app's bundle and set\n/// the desired options.\n@objc\npublic class UAConfig: NSObject {\n\n    var config: AirshipConfig\n    \n    /// The development app key. This should match the application on go.urbanairship.com that is\n    /// configured with your development push certificate.\n    @objc\n    public var developmentAppKey: String? {\n        get {\n            return config.developmentAppKey\n        }\n        set {\n            config.developmentAppKey = newValue\n        }\n    }\n    \n    /// The development app secret. This should match the application on go.urbanairship.com that is\n    /// configured with your development push certificate.\n    @objc\n    public var developmentAppSecret: String? {\n        get {\n            return config.developmentAppSecret\n        }\n        set {\n            config.developmentAppSecret = newValue\n        }\n    }\n\n    /// The production app key. This should match the application on go.urbanairship.com that is\n    /// configured with your production push certificate. This is used for App Store, Ad-Hoc and Enterprise\n    /// app configurations.\n    @objc\n    public var productionAppKey: String? {\n        get {\n            return config.productionAppKey\n        }\n        set {\n            config.productionAppKey = newValue\n        }\n    }\n\n    /// The production app secret. This should match the application on go.urbanairship.com that is\n    /// configured with your production push certificate. This is used for App Store, Ad-Hoc and Enterprise\n    /// app configurations.\n    @objc\n    public var productionAppSecret: String? {\n        get {\n            return config.productionAppSecret\n        }\n        set {\n            config.productionAppSecret = newValue\n        }\n    }\n\n    /// The log level used for development apps. Defaults to `debug`.\n    @objc\n    public var developmentLogLevel: UAAirshipLogLevel {\n        get {\n            return UAHelpers.toLogLevel(level: config.developmentLogLevel)\n        }\n        set {\n            config.developmentLogLevel = UAHelpers.toAirshipLogLevel(level: newValue)\n        }\n    }\n\n    /// The log level used for production apps. Defaults to `error`.\n    @objc\n    public var productionLogLevel: UAAirshipLogLevel {\n        get {\n            return UAHelpers.toLogLevel(level: config.productionLogLevel)\n        }\n        set {\n            config.productionLogLevel = UAHelpers.toAirshipLogLevel(level: newValue)\n        }\n    }\n\n    /// Auto pause InAppAutomation on launch. Defaults to `false`\n    @objc\n    public var autoPauseInAppAutomationOnLaunch: Bool {\n        get {\n            return config.autoPauseInAppAutomationOnLaunch\n        }\n        set {\n            config.autoPauseInAppAutomationOnLaunch = newValue\n        }\n    }\n\n    /// The airship cloud site. Defaults to `us`.\n    @objc\n    public var site: UACloudSite {\n        get {\n            return UAHelpers.toSite(site: config.site)\n        }\n        set {\n            config.site = UAHelpers.toAirshipSite(site: newValue)\n        }\n    }\n\n    @objc(enabledFeatures)\n    public var enabledFeatures: UAFeature {\n        get {\n            return config.enabledFeatures.asUAFeature\n        }\n        set {\n            config.enabledFeatures = newValue.asAirshipFeature\n        }\n    }\n\n    /// The default app key. Depending on the `inProduction` status,\n    /// `developmentAppKey` or `productionAppKey` will take priority.\n    @objc\n    public var defaultAppKey: String? {\n        get {\n            return config.defaultAppKey\n        }\n        set {\n            config.defaultAppKey = newValue\n        }\n    }\n\n    /// The default app secret. Depending on the `inProduction` status,\n    /// `developmentAppSecret` or `productionAppSecret` will take priority.\n    @objc\n    public var defaultAppSecret: String? {\n        get {\n            return config.defaultAppSecret\n        }\n        set {\n            config.defaultAppSecret = newValue\n        }\n    }\n\n    /// The production status of this application. This may be set directly, or it may be determined\n    /// automatically if the `detectProvisioningMode` flag is set to `true`.\n    /// If neither `inProduction` nor `detectProvisioningMode` is set,\n    /// `detectProvisioningMode` will be enabled.\n    @objc\n    public var inProduction: NSNumber? {\n        get {\n            return if let inProduction = config.inProduction {\n                NSNumber(value: inProduction)\n            } else {\n                nil\n            }\n        }\n        set {\n            config.inProduction = newValue?.boolValue\n        }\n    }\n\n    /// If enabled, the Airship library automatically registers for remote notifications when push is enabled\n    /// and intercepts incoming notifications in both the foreground and upon launch.\n    ///\n    /// Defaults to `true`. If this is disabled, you will need to register for remote notifications\n    /// in application:didFinishLaunchingWithOptions: and forward all notification-related app delegate\n    /// calls to UAPush and UAInbox.\n    @objc\n    public var isAutomaticSetupEnabled: Bool  {\n        get {\n            return config.isAutomaticSetupEnabled\n        }\n        set {\n            config.isAutomaticSetupEnabled = newValue\n        }\n    }\n\n    /// An array of `UAURLAllowList` entry strings.\n    /// This url allow list is used for validating which URLs can be opened or load the JavaScript native bridge.\n    /// It affects landing pages, the open external URL and wallet actions,\n    /// deep link actions (if a delegate is not set), and HTML in-app messages.\n    ///\n    /// - NOTE: See `UAURLAllowList` for pattern entry syntax.\n    @objc(URLAllowList)\n    public var urlAllowList: [String]? {\n        get {\n            return config.urlAllowList\n        }\n        set {\n            config.urlAllowList = newValue\n        }\n    }\n\n    /// An array of` UAURLAllowList` entry strings.\n    /// This url allow list is used for validating which URLs can load the JavaScript native bridge,\n    /// It affects Landing Pages, Message Center and HTML In-App Messages.\n    ///\n    /// - NOTE: See `UAURLAllowList` for pattern entry syntax.\n    @objc(URLAllowListScopeJavaScriptInterface)\n    public var urlAllowListScopeJavaScriptInterface: [String]? {\n        get {\n            return config.urlAllowListScopeJavaScriptInterface\n        }\n        set {\n            config.urlAllowListScopeJavaScriptInterface = newValue\n        }\n    }\n\n    /// An array of UAURLAllowList entry strings.\n    /// This url allow list is used for validating which URLs can be opened.\n    /// It affects landing pages, the open external URL and wallet actions,\n    /// deep link actions (if a delegate is not set), and HTML in-app messages.\n    ///\n    /// - NOTE: See `UAURLAllowList` for pattern entry syntax.\n    @objc(URLAllowListScopeOpenURL)\n    public var urlAllowListScopeOpenURL: [String]? {\n        get {\n            return config.urlAllowListScopeOpenURL\n        }\n        set {\n            config.urlAllowListScopeOpenURL = newValue\n        }\n    }\n\n    /// The iTunes ID used for Rate App Actions.\n    @objc\n    public var itunesID: String? {\n        get {\n            return config.itunesID\n        }\n        set {\n            config.itunesID = newValue\n        }\n    }\n\n    /// Toggles Airship analytics. Defaults to `true`. If set to `false`, many Airship features will not be\n    /// available to this application.\n    @objc\n    public var isAnalyticsEnabled: Bool {\n        get {\n            return config.isAnalyticsEnabled\n        }\n        set {\n            config.isAnalyticsEnabled = newValue\n        }\n    }\n\n    /// The Airship default message center style configuration file.\n    @objc\n    public var messageCenterStyleConfig: String? {\n        get {\n            return config.messageCenterStyleConfig\n        }\n        set {\n            config.messageCenterStyleConfig = newValue\n        }\n    }\n\n    /// If set to `true`, the Airship user will be cleared if the application is\n    /// restored on a different device from an encrypted backup.\n    ///\n    /// Defaults to `false`.\n    @objc\n    public var clearUserOnAppRestore: Bool {\n        get {\n            return config.clearUserOnAppRestore\n        }\n        set {\n            config.clearUserOnAppRestore = newValue\n        }\n    }\n\n    /// If set to `true`, the application will clear the previous named user ID on a\n    /// re-install. Defaults to `false`.\n    @objc\n    public var clearNamedUserOnAppRestore: Bool {\n        get {\n            return config.clearNamedUserOnAppRestore\n        }\n        set {\n            config.clearNamedUserOnAppRestore = newValue\n        }\n    }\n\n    /// Flag indicating whether channel capture feature is enabled or not.\n    ///\n    /// Defaults to `true`.\n    @objc\n    public var isChannelCaptureEnabled: Bool {\n        get {\n            return config.isChannelCaptureEnabled\n        }\n        set {\n            config.isChannelCaptureEnabled = newValue\n        }\n    }\n\n    /// Flag indicating whether delayed channel creation is enabled. If set to `true` channel\n    /// creation will not occur until channel creation is manually enabled.\n    ///\n    /// Defaults to `false`.\n    @objc\n    public var isChannelCreationDelayEnabled: Bool {\n        get {\n            return config.isChannelCreationDelayEnabled\n        }\n        set {\n            config.isChannelCreationDelayEnabled = newValue\n        }\n    }\n\n    /// Flag indicating whether extended broadcasts are enabled. If set to `true` the AirshipReady NSNotification\n    /// will contain additional data: the channel identifier and the app key.\n    ///\n    /// Defaults to `false`.\n    @objc\n    public var isExtendedBroadcastsEnabled: Bool {\n        get {\n            return config.isExtendedBroadcastsEnabled\n        }\n        set {\n            config.isExtendedBroadcastsEnabled = newValue\n        }\n    }\n\n    /// If set to 'YES', the Airship SDK will request authorization to use\n    /// notifications from the user. Apps that set this flag to `false` are\n    /// required to request authorization themselves.\n    ///\n    /// Defaults to `true`.\n    @objc\n    public var requestAuthorizationToUseNotifications: Bool {\n        get {\n            return config.requestAuthorizationToUseNotifications\n        }\n        set {\n            config.requestAuthorizationToUseNotifications = newValue\n        }\n    }\n\n    /// If set to `true`, the SDK will wait for an initial remote config instead of falling back on default API URLs.\n    ///\n    /// Defaults to `true`.\n    @objc\n    public var requireInitialRemoteConfigEnabled: Bool {\n        get {\n            return config.requireInitialRemoteConfigEnabled\n        }\n        set {\n            config.requireInitialRemoteConfigEnabled = newValue\n        }\n    }\n\n    /// The Airship URL used to pull the initial config. This should only be set\n    /// if you are using custom domains that forward to Airship.\n    ///\n    @objc\n    public var initialConfigURL: String? {\n        get {\n            return config.initialConfigURL\n        }\n        set {\n            config.initialConfigURL = newValue\n        }\n    }\n\n    /// If set to `true`, the SDK will use the preferred locale. Otherwise it will use the current locale.\n    ///\n    /// Defaults to `false`.\n    @objc\n    public var useUserPreferredLocale: Bool {\n        get {\n            return config.useUserPreferredLocale\n        }\n        set {\n            config.useUserPreferredLocale = newValue\n        }\n    }\n\n    /// Creates an instance using the values set in the `AirshipConfig.plist` file.\n    /// - Returns: A config with values from `AirshipConfig.plist` file.\n    @objc(defaultConfigWithError:)\n    public class func `default`() throws -> UAConfig {\n        return UAConfig(\n            config: try AirshipConfig.default()\n        )\n    }\n\n    /**\n     * Creates an instance using the values found in the specified `.plist` file.\n     * - Parameter path: The path of the specified file.\n     * - Returns: A config with values from the specified file.\n     */\n    @objc\n    public class func fromPlist(contentsOfFile path: String) throws -> UAConfig {\n        return UAConfig(\n            config: try AirshipConfig(fromPlist: path)\n        )\n    }\n\n    /// Creates an instance with empty values.\n    /// - Returns: A config with empty values.\n    @objc\n    public class func config() -> UAConfig {\n        return UAConfig(config: AirshipConfig())\n    }\n\n    /// Creates an instance using the values set in the `AirshipConfig.plist` file.\n    /// - Returns: A config with values from `AirshipConfig.plist` file.\n    @objc\n    public static func defaultConfig() throws -> UAConfig {\n        return UAConfig(config: try AirshipConfig.default())\n    }\n\n    private init(config: AirshipConfig) {\n        self.config = config\n    }\n\n    /// Validates credentails\n    /// - Parameters:\n    ///     - inProduction: To validate production or development credentials\n    @objc\n    public func validateCredentials(inProduction: Bool) throws {\n        try self.config.validateCredentials(inProduction: inProduction)\n    }\n}\n\n\n@objc\n/// Represents the possible log levels.\npublic enum UAAirshipLogLevel: Int, Sendable {\n    /**\n     * Undefined log level.\n     */\n    case undefined = -1\n\n    /**\n     * No log messages.\n     */\n    case none = 0\n\n    /**\n     * Log error messages.\n     *\n     * Used for critical errors, parse exceptions and other situations that cannot be gracefully handled.\n     */\n    case error = 1\n\n    /**\n     * Log warning messages.\n     *\n     * Used for API deprecations, invalid setup and other potentially problematic situations.\n     */\n    case warn = 2\n\n    /**\n     * Log informative messages.\n     *\n     * Used for reporting general SDK status.\n     */\n    case info = 3\n\n    /**\n     * Log debugging messages.\n     *\n     * Used for reporting general SDK status with more detailed information.\n     */\n    case debug = 4\n\n    /**\n     * Log detailed verbose messages.\n     *\n     * Used for reporting highly detailed SDK status that can be useful when debugging and troubleshooting.\n     */\n    case verbose = 5\n}\n\n\n@objc\n/// Represents the possible sites.\npublic enum UACloudSite: Int, Sendable {\n    /// Represents the US cloud site. This is the default value.\n    /// Projects available at go.airship.com must use this value.\n    @objc(UACloudSiteUS)\n    case us = 0\n    /// Represents the EU cloud site.\n    /// Projects available at go.airship.eu must use this value.\n    @objc(UACloudSiteEU)\n    case eu = 1\n}\n\nclass UAHelpers: NSObject {\n    \n    public static func toLogLevel(level: AirshipLogLevel) -> UAAirshipLogLevel {\n        switch(level){\n        case AirshipLogLevel.undefined:\n            return UAAirshipLogLevel.undefined\n        case AirshipLogLevel.none:\n            return UAAirshipLogLevel.none\n        case AirshipLogLevel.error:\n            return UAAirshipLogLevel.error\n        case AirshipLogLevel.warn:\n            return UAAirshipLogLevel.warn\n        case AirshipLogLevel.info:\n            return UAAirshipLogLevel.info\n        case AirshipLogLevel.debug:\n            return UAAirshipLogLevel.debug\n        case AirshipLogLevel.verbose:\n            return UAAirshipLogLevel.verbose\n        @unknown default:\n            return UAAirshipLogLevel.undefined\n        }\n    }\n    \n    public static func toAirshipLogLevel(level: UAAirshipLogLevel) -> AirshipLogLevel {\n        switch(level){\n        case UAAirshipLogLevel.undefined:\n            return AirshipLogLevel.undefined\n        case UAAirshipLogLevel.none:\n            return AirshipLogLevel.none\n        case UAAirshipLogLevel.error:\n            return AirshipLogLevel.error\n        case UAAirshipLogLevel.warn:\n            return AirshipLogLevel.warn\n        case UAAirshipLogLevel.info:\n            return AirshipLogLevel.info\n        case UAAirshipLogLevel.debug:\n            return AirshipLogLevel.debug\n        case UAAirshipLogLevel.verbose:\n            return AirshipLogLevel.verbose\n        default:\n            return AirshipLogLevel.undefined\n        }\n    }\n    \n    public static func toSite(site: CloudSite) -> UACloudSite {\n        switch(site){\n        case CloudSite.us:\n            return UACloudSite.us\n        case CloudSite.eu:\n            return UACloudSite.eu\n        @unknown default:\n            return UACloudSite.us\n        }\n    }\n    \n    public static func toAirshipSite(site: UACloudSite) -> CloudSite {\n        switch(site){\n        case UACloudSite.us:\n            return CloudSite.us\n        case UACloudSite.eu:\n            return CloudSite.eu\n        default:\n            return CloudSite.us\n        }\n    }\n    \n    public static func toChannelType(type: ChannelType) -> UAChannelType {\n        switch(type){\n        case ChannelType.email:\n            return UAChannelType.email\n        case ChannelType.open:\n            return UAChannelType.open\n        case ChannelType.sms:\n            return UAChannelType.sms\n        @unknown default:\n            return UAChannelType.email\n        }\n    }\n    \n    public static func toAirshipChannelType(type: UAChannelType) -> ChannelType {\n        switch(type){\n        case UAChannelType.email:\n            return ChannelType.email\n        case UAChannelType.open:\n            return ChannelType.open\n        case UAChannelType.sms:\n            return ChannelType.sms\n        default:\n            return ChannelType.email\n        }\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UADeepLinkDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Protocol to be implemented by deep link handlers.\n@objc\npublic protocol UADeepLinkDelegate: Sendable {\n    \n    /// Called when a deep link has been triggered from Airship. If implemented, the delegate is responsible for processing the provided url.\n    /// - Parameters:\n    ///     - deepLink: The deep link.\n    @MainActor\n    func receivedDeepLink(_ deepLink: URL) async\n}\n\n@MainActor\nfinal class UADeepLinkDelegateWrapper: NSObject, DeepLinkDelegate {\n    \n    var forwardDelegate: (any UADeepLinkDelegate)?\n\n    init(delegate: any UADeepLinkDelegate) {\n        self.forwardDelegate = delegate\n    }\n    \n    public func receivedDeepLink(_ deepLink: URL) async {\n        await self.forwardDelegate?.receivedDeepLink(deepLink)\n    }\n    \n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UAFeature.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Bindings for `AirshipFeature`\n@objc\npublic final class UAFeature: NSObject, OptionSet, Sendable {\n    public let rawValue: UInt\n\n\n\n    /// Bindings for `AirshipFeature.inAppAutomation`\n    @objc\n    public static func inAppAutomation() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.inAppAutomation.rawValue)\n    }\n\n    /// Bindings for `AirshipFeature.messageCenter`\n    @objc\n    public static func messageCenter() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.messageCenter.rawValue)\n    }\n\n    /// Bindings for `AirshipFeature.push`\n    @objc\n    public static func push() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.push.rawValue)\n    }\n\n    /// Bindings for `AirshipFeature.analytics`\n    @objc\n    public static func analytics() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.analytics.rawValue)\n    }\n\n    /// Bindings for `AirshipFeature.tagsAndAttributes`\n    @objc\n    public static func tagsAndAttributes() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.tagsAndAttributes.rawValue)\n    }\n\n    /// Bindings for `AirshipFeature.contacts`\n    @objc\n    public static func contacts() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.contacts.rawValue)\n    }\n\n    /// Bindings for `AirshipFeature.featureFlags`\n    @objc\n    public static func featureFlags() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.featureFlags.rawValue)\n    }\n\n    /// All features\n    @objc\n    public static func all() -> UAFeature {\n        return UAFeature(rawValue: AirshipFeature.all.rawValue)\n    }\n\n    @objc\n    public static func none() -> UAFeature {\n        return UAFeature(rawValue: 0)\n    }\n\n    @objc\n    public convenience init(from: [UAFeature]) {\n        self.init(from)\n    }\n\n    @objc(contains:)\n    public func _contains(_ feature: UAFeature) -> Bool {\n        return self.contains(feature)\n    }\n\n    public init(rawValue: UInt) {\n        self.rawValue = rawValue\n    }\n\n    public override var hash: Int {\n          return Int(rawValue)\n      }\n\n      public override func isEqual(_ object: Any?) -> Bool {\n          guard let that = object as? UAFeature else {\n              return false\n          }\n\n          return rawValue == that.rawValue\n      }\n}\n\nextension UAFeature {\n    var asAirshipFeature: AirshipFeature {\n        return AirshipFeature(rawValue: self.rawValue)\n    }\n}\n\nextension AirshipFeature {\n    var asUAFeature: UAFeature {\n        return UAFeature(rawValue: self.rawValue)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UAPermission.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Airship permissions. Used with `UAPermissionsManager`\n@objc\npublic enum UAPermission: Int, Sendable {\n    /// Post notifications\n    case displayNotifications = 0\n\n    /// Location\n    case location = 1\n\n    var airshipPermission: AirshipPermission {\n        switch self {\n        case .displayNotifications: return .displayNotifications\n        case .location: return .location\n        }\n    }\n\n    init(_ permission: AirshipPermission) {\n        switch permission {\n        case .displayNotifications: self = .displayNotifications\n        case .location: self = .location\n        @unknown default: self = .displayNotifications\n        }\n    }\n}\n\n/// Permission status\n@objc\npublic enum UAPermissionStatus: Int, Sendable {\n    /// Could not determine the permission status.\n    case notDetermined = 0\n\n    /// Permission is granted.\n    case granted = 1\n\n    /// Permission is denied.\n    case denied = 2\n\n    var airshipStatus: AirshipPermissionStatus {\n        switch self {\n        case .notDetermined: return .notDetermined\n        case .granted: return .granted\n        case .denied: return .denied\n        }\n    }\n\n    init(_ status: AirshipPermissionStatus) {\n        switch status {\n        case .notDetermined: self = .notDetermined\n        case .granted: self = .granted\n        case .denied: self = .denied\n        @unknown default: self = .notDetermined\n        }\n    }\n}\n\n/// Protocol to be implemented by permission delegates.\n@objc\npublic protocol UAAirshipPermissionDelegate: Sendable {\n\n    /// Called when a permission needs to be checked.\n    /// - Parameter completionHandler: The completion handler to call with the permission status.\n    @MainActor\n    func checkPermissionStatus(completionHandler: @escaping (UAPermissionStatus) -> Void)\n\n    /// Called when a permission should be requested.\n    /// - Note: A permission might be already granted when this method is called.\n    /// - Parameter completionHandler: The completion handler to call with the permission status.\n    @MainActor\n    func requestPermission(completionHandler: @escaping (UAPermissionStatus) -> Void)\n}\n\n/// Internal wrapper to convert UAAirshipPermissionDelegate to AirshipPermissionDelegate\nfinal class UAPermissionDelegateWrapper: AirshipPermissionDelegate, @unchecked Sendable {\n\n    private let forwardDelegate: any UAAirshipPermissionDelegate\n\n    init(delegate: any UAAirshipPermissionDelegate) {\n        self.forwardDelegate = delegate\n    }\n\n    @MainActor\n    func checkPermissionStatus() async -> AirshipPermissionStatus {\n        await withCheckedContinuation { continuation in\n            self.forwardDelegate.checkPermissionStatus { status in\n                continuation.resume(returning: status.airshipStatus)\n            }\n        }\n    }\n\n    @MainActor\n    func requestPermission() async -> AirshipPermissionStatus {\n        await withCheckedContinuation { continuation in\n            self.forwardDelegate.requestPermission { status in\n                continuation.resume(returning: status.airshipStatus)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UAPermissionsManager.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Airship permissions manager.\n///\n/// Airship will provide the default handling for `UAPermission.displayNotifications`. All other permissions will need\n/// to be configured by the app by providing a `UAAirshipPermissionDelegate` for the given permissions.\n@objc\npublic final class UAPermissionsManager: NSObject, Sendable {\n\n    /// Sets a permission delegate.\n    ///\n    /// - Note: The delegate will be strongly retained.\n    ///\n    /// - Parameters:\n    ///     - delegate: The delegate.\n    ///     - permission: The permission.\n    @objc\n    public func setDelegate(\n        _ delegate: (any UAAirshipPermissionDelegate)?,\n        permission: UAPermission\n    ) {\n        if let delegate = delegate {\n            let wrapper = UAPermissionDelegateWrapper(delegate: delegate)\n            Airship.permissionsManager.setDelegate(wrapper, permission: permission.airshipPermission)\n        } else {\n            Airship.permissionsManager.setDelegate(nil, permission: permission.airshipPermission)\n        }\n    }\n\n    /// Checks a permission status.\n    ///\n    /// - Note: If no delegate is set for the given permission this will always return `.notDetermined`.\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    ///     - completionHandler: The completion handler to call with the permission status.\n    @objc\n    @MainActor\n    public func checkPermissionStatus(\n        _ permission: UAPermission,\n        completionHandler: @escaping (UAPermissionStatus) -> Void\n    ) {\n        Task { @MainActor in\n            let status = await Airship.permissionsManager.checkPermissionStatus(permission.airshipPermission)\n            completionHandler(UAPermissionStatus(status))\n        }\n    }\n\n    /// Requests a permission.\n    ///\n    /// - Note: If no permission delegate is set for the given permission this will always return `.notDetermined`\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    ///     - completionHandler: The completion handler to call with the permission status.\n    @objc\n    @MainActor\n    public func requestPermission(\n        _ permission: UAPermission,\n        completionHandler: @escaping (UAPermissionStatus) -> Void\n    ) {\n        Task { @MainActor in\n            let status = await Airship.permissionsManager.requestPermission(permission.airshipPermission)\n            completionHandler(UAPermissionStatus(status))\n        }\n    }\n\n    /// Requests a permission with option to enable Airship usage on grant.\n    ///\n    /// - Note: If no permission delegate is set for the given permission this will always return `.notDetermined`\n    ///\n    /// - Parameters:\n    ///     - permission: The permission.\n    ///     - enableAirshipUsageOnGrant: `true` to allow any Airship features that need the permission to be enabled as well.\n    ///     - completionHandler: The completion handler to call with the permission status.\n    @objc\n    @MainActor\n    public func requestPermission(\n        _ permission: UAPermission,\n        enableAirshipUsageOnGrant: Bool,\n        completionHandler: @escaping (UAPermissionStatus) -> Void\n    ) {\n        Task { @MainActor in\n            let status = await Airship.permissionsManager.requestPermission(\n                permission.airshipPermission,\n                enableAirshipUsageOnGrant: enableAirshipUsageOnGrant\n            )\n            completionHandler(UAPermissionStatus(status))\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipObjectiveC/Source/UAirship.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Main entry point for Airship. The application must call `takeOff` during `application:didFinishLaunchingWithOptions:`\n/// before accessing any instances on Airship or Airship modules.\n@objc\npublic final class UAirship: NSObject, Sendable {\n\n    private static let storage = Storage()\n    private static let _push: UAPush = UAPush()\n    private static let _channel: UAChannel = UAChannel()\n    private static let _contact: UAContact = UAContact()\n    private static let _privacyManager: UAPrivacyManager = UAPrivacyManager()\n    private static let _messageCenter: UAMessageCenter = UAMessageCenter()\n    private static let _preferenceCenter: UAPreferenceCenter = UAPreferenceCenter()\n    private static let _analytics: UAAnalytics = UAAnalytics()\n    private static let _inAppAutomation: UAInAppAutomation = UAInAppAutomation()\n    private static let _permissionsManager: UAPermissionsManager = UAPermissionsManager()\n\n    /// Asserts that Airship is flying (initalized)\n    public static func assertAirshipIsFlying() {\n        if !Airship.isFlying {\n            assertionFailure(\"TakeOff must be called before accessing Airship.\")\n        }\n    }\n\n    /// Push instance\n    @objc\n    public static var push: UAPush {\n        assertAirshipIsFlying()\n        return _push\n    }\n\n    /// Channel instance\n    @objc\n    public static var channel: UAChannel {\n        assertAirshipIsFlying()\n        return _channel\n    }\n\n    /// Contact instance\n    @objc\n    public static var contact: UAContact {\n        assertAirshipIsFlying()\n        return _contact\n    }\n\n    /// Analytics instance\n    @objc\n    public static var analytics: UAAnalytics {\n        assertAirshipIsFlying()\n        return _analytics\n    }\n\n    /// Message Center  instance\n    @objc\n    public static var messageCenter: UAMessageCenter {\n        assertAirshipIsFlying()\n        return _messageCenter\n    }\n\n    /// Preference Center instance\n    @objc\n    public static var preferenceCenter: UAPreferenceCenter {\n        assertAirshipIsFlying()\n        return _preferenceCenter\n    }\n\n    /// Privacy manager\n    @objc\n    public static var privacyManager: UAPrivacyManager {\n        assertAirshipIsFlying()\n        return _privacyManager\n    }\n    \n    /// In App Automation\n    @objc\n    public static var inAppAutomation: UAInAppAutomation {\n        assertAirshipIsFlying()\n        return _inAppAutomation\n    }\n\n    /// Permissions manager\n    @objc\n    public static var permissionsManager: UAPermissionsManager {\n        assertAirshipIsFlying()\n        return _permissionsManager\n    }\n\n    /// A user configurable deep link delegate\n    @MainActor\n    @objc\n    public static var deepLinkDelegate: (any UADeepLinkDelegate)? {\n        get {\n            assertAirshipIsFlying()\n            guard let wrapped = Airship.deepLinkDelegate as? UADeepLinkDelegateWrapper else {\n                return nil\n            }\n            return wrapped.forwardDelegate\n        }\n        set {\n            assertAirshipIsFlying()\n            if let newValue {\n                let wrapper = UADeepLinkDelegateWrapper(delegate: newValue)\n                Airship.deepLinkDelegate = wrapper\n                storage.deepLinkDelegate = wrapper\n            } else {\n                Airship.deepLinkDelegate = nil\n                storage.deepLinkDelegate = nil\n            }\n        }\n    }\n\n#if !os(watchOS)\n    \n    /// Initializes Airship. Config will be read from `AirshipConfig.plist`.\n    /// - Parameters:\n    ///     - launchOptions: The launch options passed into `application:didFinishLaunchingWithOptions:`.\n    @objc\n    @MainActor\n    @available(*, deprecated, message: \"Use Airship.takeOff() instead\")\n    public class func takeOff(\n        launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n    ) throws {\n        try Airship.takeOff(launchOptions: launchOptions)\n    }\n\n    /// Initializes Airship.\n    /// - Parameters:\n    ///     - config: The Airship config.\n    ///     - launchOptions: The launch options passed into `application:didFinishLaunchingWithOptions:`.\n    @objc\n    @MainActor\n    @available(*, deprecated, message: \"Use Airship.takeOff(_:) instead\")\n    public class func takeOff(\n        _ config: UAConfig?,\n        launchOptions: [UIApplication.LaunchOptionsKey: Any]?\n    ) throws {\n        try Airship.takeOff(config?.config, launchOptions: launchOptions)\n    }\n\n#endif\n\n    /// Initializes Airship. Config will be read from `AirshipConfig.plist`.\n    @objc\n    @MainActor\n    public class func takeOff() throws {\n        try Airship.takeOff()\n    }\n\n    /// Initializes Airship.\n    /// - Parameters:\n    ///     - config: The Airship config.\n    @objc\n    @MainActor\n    public class func takeOff(_ config: UAConfig?) throws {\n        try Airship.takeOff(config?.config)\n    }\n\n    @MainActor\n    fileprivate final class Storage  {\n        var deepLinkDelegate: (any DeepLinkDelegate)?\n    }\n\n    /// Processes a deep link.\n    /// For `uairship://` scheme URLs, Airship will handle the deep link internally.\n    /// For other URLs, Airship will forward the deep link to the deep link listener if set.\n    /// - Parameters:\n    ///     - url: The deep link.\n    ///     - completionHandler: The result. `true` if the link was able to be processed, otherwise `false`.\n    @objc\n    @MainActor\n    public class func processDeepLink(\n        _ url: URL,\n        completionHandler: @escaping (Bool) -> Void\n    ) {\n        Task { @MainActor in\n            let handled = await Airship.processDeepLink(url)\n            completionHandler(handled)\n        }\n    }\n}\n\n/// NSNotificationCenter keys event names\n@objc\npublic final class UAAirshipNotifications: NSObject {\n\n    /// Notification when Airship is ready.\n    @objc(UAAirshipNotificationsAirshipReady)\n    public final class UAAirshipReady: NSObject {\n        /// Notification name\n        @objc\n        public static let name = AirshipNotifications.AirshipReady.name\n\n        /// Airship ready channel ID key. Only available if `extendedBroadcastEnabled` is true in config.\n        @objc\n        public static let channelIDKey = AirshipNotifications.AirshipReady.channelIDKey\n\n        /// Airship ready app key. Only available if `extendedBroadcastEnabled` is true in config.\n        @objc\n        public static let appKey = AirshipNotifications.AirshipReady.appKey\n\n        /// Airship ready payload version. Only available if `extendedBroadcastEnabled` is true in config.\n        @objc\n        public static let payloadVersionKey = AirshipNotifications.AirshipReady.payloadVersionKey\n    }\n\n    /// Notification when channel is created.\n    @objc(UAirshipNotificationChannelCreated)\n    public final class UAirshipNotificationChannelCreated: NSObject {\n        /// Notification name\n        @objc\n        public static let name = AirshipNotifications.ChannelCreated.name\n\n        /// NSNotification userInfo key to get the channel ID.\n        @objc\n        public static let channelIDKey = AirshipNotifications.ChannelCreated.channelIDKey\n\n        /// NSNotification userInfo key to get a boolean if the channel is existing or not.\n        @objc\n        public static let isExistingChannelKey = AirshipNotifications.ChannelCreated.isExistingChannelKey\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/AirshipPreferenceCenterResources.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Resources for AirshipPreferenceCenter.\npublic final class AirshipPreferenceCenterResources {\n\n    public static func localizedString(key: String) -> String? {\n        return AirshipLocalizationUtils.localizedString(\n            key,\n            withTable: \"UrbanAirship\",\n            moduleBundle: AirshipCoreResources.bundle\n        )\n    }\n}\n\nextension String {\n    var preferenceCenterLocalizedString: String {\n        return AirshipPreferenceCenterResources.localizedString(key: self) ?? self\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/PreferenceCenter.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Open delegate.\n@MainActor\npublic protocol PreferenceCenterOpenDelegate {\n\n    /// Opens the Preference Center with the given ID.\n    /// - Parameters:\n    ///   - preferenceCenterID: The preference center ID.\n    /// - Returns: `true` if the preference center was opened, otherwise `false` to fallback to OOTB UI.\n    func openPreferenceCenter(_ preferenceCenterID: String) -> Bool\n}\n\n/// An interface for interacting with Airship's Preference Center.\n@MainActor\npublic protocol PreferenceCenter: AnyObject, Sendable {\n\n    /// Called when the Preference Center is requested to be displayed.\n    /// Return `true` if the display was handled, `false` to fall back to default SDK behavior.\n    var onDisplay: (@MainActor @Sendable (_ preferenceCenterID: String) -> Bool)? { get set }\n\n    /// Open delegate for the Preference Center.\n    var openDelegate: (any PreferenceCenterOpenDelegate)? { get set }\n\n    /// The theme for the Preference Center.\n    var theme: PreferenceCenterTheme? { get set }\n\n    /// Loads a Preference Center theme from a plist file.\n    /// - Parameter plist: The name of the plist in the bundle.\n    func setThemeFromPlist(_ plist: String) throws\n\n    /// Displays the Preference Center with the given ID.\n    /// - Parameter preferenceCenterID: The preference center ID.\n    func display(_ preferenceCenterID: String)\n\n    /// Opens the Preference Center with the given ID. (Deprecated)\n    @available(*, deprecated, renamed: \"display(identifier:)\")\n    func open(_ preferenceCenterID: String)\n\n    /// Returns the configuration of the Preference Center with the given ID.\n    /// - Parameter preferenceCenterID: The preference center ID.\n    func config(preferenceCenterID: String) async throws -> PreferenceCenterConfig\n\n    /// Returns the configuration of the Preference Center as JSON data with the given ID.\n    /// - Parameter preferenceCenterID: The preference center ID.\n    func jsonConfig(preferenceCenterID: String) async throws -> Data\n}\n\n@MainActor\nfinal class DefaultPreferenceCenter: PreferenceCenter {\n\n    let inputValidator: any AirshipInputValidation.Validator\n\n    private static let payloadType = \"preference_forms\"\n    private static let preferenceFormsKey = \"preference_forms\"\n\n    private let delegates: Delegates = Delegates()\n\n    public var onDisplay: (@MainActor @Sendable (_ preferenceCenterID: String) -> Bool)?\n\n    public var openDelegate: (any PreferenceCenterOpenDelegate)? {\n        get {\n            self.delegates.openDelegate\n        }\n        set {\n            self.delegates.openDelegate = newValue\n        }\n    }\n\n    private let dataStore: PreferenceDataStore\n    private let privacyManager: any AirshipPrivacyManager\n    private let remoteData: any RemoteDataProtocol\n\n    private let currentDisplay: AirshipMainActorValue<(any AirshipMainActorCancellable)?> = AirshipMainActorValue(nil)\n\n    private let _theme: AirshipMainActorValue<PreferenceCenterTheme?> = AirshipMainActorValue(nil)\n\n    public var theme: PreferenceCenterTheme? {\n        get {\n            self._theme.value\n        }\n        set {\n            self._theme.set(newValue)\n        }\n    }\n\n    public func setThemeFromPlist(_ plist: String) throws {\n        self.theme = try PreferenceCenterThemeLoader.fromPlist(plist)\n    }\n\n    init(\n        dataStore: PreferenceDataStore,\n        privacyManager: any AirshipPrivacyManager,\n        remoteData: any RemoteDataProtocol,\n        inputValidator: any AirshipInputValidation.Validator\n    ) {\n        self.dataStore = dataStore\n        self.privacyManager = privacyManager\n        self.remoteData = remoteData\n        self.inputValidator = inputValidator\n        self._theme.set(PreferenceCenterThemeLoader.defaultPlist())\n        AirshipLogger.info(\"PreferenceCenter initialized\")\n    }\n\n    public func display(_ preferenceCenterID: String) {\n        let handled: Bool = if let onDisplay {\n            onDisplay(preferenceCenterID)\n        } else if let openDelegate {\n            openDelegate.openPreferenceCenter(preferenceCenterID)\n        } else {\n            false\n        }\n\n        guard !handled else {\n            AirshipLogger.trace(\n                \"Preference center \\(preferenceCenterID) display request handled by the app.\"\n            )\n            return\n        }\n\n        Task {\n            await displayDefaultPreferenceCenter(preferenceCenterID)\n        }\n    }\n\n    @available(*, deprecated, renamed: \"display(_:)\")\n    public func open(_ preferenceCenterID: String) {\n        self.display(preferenceCenterID)\n    }\n\n    private func displayDefaultPreferenceCenter(_ preferenceCenterID: String) async {\n        currentDisplay.value?.cancel()\n\n        AirshipLogger.debug(\"Opening default preference center UI\")\n\n        do {\n            let display = try displayPreferenceCenter(\n                preferenceCenterID,\n                theme: theme\n            )\n            self.currentDisplay.set(display)\n        } catch {\n            AirshipLogger.error(\"Unable to display preference center \\(error)\")\n        }\n\n    }\n\n    public func config(preferenceCenterID: String) async throws -> PreferenceCenterConfig {\n        let data = try await jsonConfig(preferenceCenterID: preferenceCenterID)\n        return try PreferenceCenterDecoder.decodeConfig(data: data)\n    }\n\n    public func jsonConfig(preferenceCenterID: String) async throws -> Data {\n        let payloads = await self.remoteData.payloads(types: [\"preference_forms\"])\n\n        for payload in payloads {\n            let config = payload.data(key: \"preference_forms\") as? [[AnyHashable: Any]]\n\n            let form = config?\n                .compactMap { $0[\"form\"] as? [AnyHashable: Any] }\n                .first(where: { $0[\"id\"] as? String == preferenceCenterID })\n\n            if let form = form {\n                return try JSONSerialization.data(\n                    withJSONObject: form,\n                    options: []\n                )\n            }\n        }\n\n        throw AirshipErrors.error(\"Preference center not found \\(preferenceCenterID)\")\n    }\n}\n\n/// Delegates holder so I can keep the executor sendable\n@MainActor\nprivate final class Delegates {\n    var openDelegate: (any PreferenceCenterOpenDelegate)?\n}\n\nextension DefaultPreferenceCenter {\n\n    @MainActor\n    fileprivate func displayPreferenceCenter(\n        _ preferenceCenterID: String,\n        theme: PreferenceCenterTheme?\n    ) throws -> any AirshipMainActorCancellable {\n        let displayable = AirshipDisplayTarget().prepareDisplay(for: .modal)\n\n        try displayable.display { _ in\n            return PreferenceCenterViewControllerFactory.makeViewController(\n                view: PreferenceCenterView(\n                    preferenceCenterID: preferenceCenterID\n                ),\n                preferenceCenterTheme: theme,\n                dismissAction: {\n                    displayable.dismiss()\n                }\n            )\n        }\n\n        return AirshipMainActorCancellableBlock {\n            displayable.dismiss()\n        }\n    }\n}\n\nextension DefaultPreferenceCenter {\n    @MainActor\n    func deepLink(_ deepLink: URL) -> Bool {\n        guard deepLink.scheme == Airship.deepLinkScheme,\n              deepLink.host == \"preferences\",\n              deepLink.pathComponents.count == 2\n        else {\n            return false\n        }\n\n        let preferenceCenterID = deepLink.pathComponents[1]\n        self.display(preferenceCenterID)\n        return true\n    }\n}\n\npublic extension Airship {\n    /// The shared `PreferenceCenter` instance. `Airship.takeOff` must be called before accessing this instance.\n    static var preferenceCenter: any PreferenceCenter  {\n        Airship.requireComponent(\n           ofType: PreferenceCenterComponent.self\n       ).preferenceCenter\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/PreferenceCenterComponent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Actual airship component for PreferenceCenter. Used to hide AirshipComponent methods.\nfinal class PreferenceCenterComponent: AirshipComponent {\n    final let preferenceCenter: DefaultPreferenceCenter\n\n    init(preferenceCenter: DefaultPreferenceCenter) {\n        self.preferenceCenter = preferenceCenter\n    }\n\n    @MainActor\n    public func deepLink(_ deepLink: URL) -> Bool {\n        return self.preferenceCenter.deepLink(deepLink)\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/PreferenceCenterSDKModule.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\npublic import Foundation\n\n/// AirshipPreferenceCenter module loader.\n/// @note For internal use only. :nodoc:\n@objc(UAPreferenceCenterSDKModule)\npublic class PreferenceCenterSDKModule: NSObject, AirshipSDKModule {\n    public let actionsManifest: (any ActionsManifest)? = nil\n    public let components: [any AirshipComponent]\n\n    public static func load(_ args: AirshiopModuleLoaderArgs) -> (any AirshipSDKModule)? {\n        let preferenceCenter = DefaultPreferenceCenter(\n            dataStore: args.dataStore,\n            privacyManager: args.privacyManager,\n            remoteData: args.remoteData,\n            inputValidator: args.inputValidator\n        )\n        return PreferenceCenterSDKModule(preferenceCenter)\n    }\n\n    private init(_ preferenceCenter: DefaultPreferenceCenter) {\n        self.components = [\n            PreferenceCenterComponent(preferenceCenter: preferenceCenter)\n        ]\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/data/PreferenceCenterConfig+ContactManagement.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\npublic extension PreferenceCenterConfig {\n\n    /// Contact management item - base container object for contact management in the preference center\n    struct ContactManagementItem: Decodable, Equatable, PreferenceCenterConfigItem, Sendable {\n        /// The contact management item's type.\n        public let type = PreferenceCenterConfigItemType.contactManagement\n\n        /// The contact management item's identifier.\n        public var id: String\n\n        /// The contact management item's channel platform - for example: email or sms.\n        public var platform: Platform\n\n        // The common title and optional description\n        public var display: CommonDisplay\n\n        // The add prompt\n        public var addChannel: AddChannel?\n\n        /// The remove prompt\n        public var removeChannel: RemoveChannel?\n\n        /// The empty message label that's visible when no channels of this type have been added\n        public var emptyMessage: String?\n\n        /// The section's display conditions.\n        public var conditions: [Condition]?\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case platform = \"platform\"\n            case display = \"display\"\n            case emptyMessage = \"empty_message\"\n            case addChannel = \"add\"\n            case removeChannel = \"remove\"\n            case registrationOptions = \"registration_options\"\n            case conditions = \"conditions\"\n        }\n\n        public init(\n            id: String,\n            platform: Platform,\n            display: CommonDisplay,\n            emptyMessage: String? = nil,\n            addChannel: AddChannel? = nil,\n            removeChannel: RemoveChannel? = nil,\n            conditions: [Condition]? = nil\n        ) {\n            self.id = id\n            self.platform = platform\n            self.display = display\n            self.emptyMessage = emptyMessage\n            self.addChannel = addChannel\n            self.removeChannel = removeChannel\n            self.conditions = conditions\n        }\n\n        enum PlatformType: String, Decodable {\n            case email\n            case sms\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n\n            self.id = try container.decode(String.self, forKey: .id)\n\n            let platformType = try container.decode(PlatformType.self, forKey: CodingKeys.platform)\n            switch platformType {\n            case .email:\n                self.platform = .email(\n                    try container.decode(Email.self, forKey: .registrationOptions)\n                )\n            case .sms:\n                self.platform = .sms(\n                    try container.decode(SMS.self, forKey: .registrationOptions)\n                )\n            }\n\n            self.display = try container.decode(CommonDisplay.self, forKey: .display)\n            self.addChannel = try container.decodeIfPresent(AddChannel.self, forKey: .addChannel)\n            self.removeChannel = try container.decodeIfPresent(RemoveChannel.self, forKey: .removeChannel)\n            self.emptyMessage = try container.decodeIfPresent(String.self, forKey: .emptyMessage)\n            self.conditions = try container.decodeIfPresent([Condition].self, forKey: .conditions)\n        }\n\n\n        /// Platform\n        public enum Platform: Equatable, Sendable {\n            case sms(SMS)\n            case email(Email)\n\n            var errorMessages: ErrorMessages? {\n                switch self {\n                case .sms(let sms):\n                    return sms.errorMessages\n                case .email(let email):\n                    return email.errorMessages\n                }\n            }\n        }\n\n        /// Pending label that appears after channel list item is added. Resend button appears after interval.\n        public struct PendingLabel: Decodable, Equatable, Sendable {\n\n            /// The interval in seconds to wait before resend button appears\n            public let intervalInSeconds: Int\n\n            /// The message that displays when a channel is pending\n            public let message: String\n\n            /// Resend button that appears after the given interval\n            public let button: LabeledButton\n\n            /// Resend prompt after successfully resending\n            public let resendSuccessPrompt: ActionableMessage?\n\n            enum CodingKeys: String, CodingKey {\n                case intervalInSeconds = \"interval\"\n                case message = \"message\"\n                case button = \"button\"\n                case resendSuccessPrompt = \"on_success\"\n            }\n\n            public init(\n                intervalInSeconds: Int,\n                message: String,\n                button: LabeledButton,\n                resendSuccessPrompt: ActionableMessage? = nil\n            ) {\n                self.intervalInSeconds = intervalInSeconds\n                self.message = message\n                self.button = button\n                self.resendSuccessPrompt = resendSuccessPrompt\n            }\n        }\n\n        /// Email registration options\n        public struct Email: Decodable, Equatable, Sendable {\n\n            /// Text placeholder for email address\n            public var placeholder: String?\n\n            /// The label for the email address\n            public var addressLabel: String\n\n            /// Additional JSON payload\n            public var properties: AirshipJSON?\n\n            /// Label with resend button\n            public var pendingLabel: PendingLabel\n\n            /// Error messages that can result of attempting to add an email address\n            public var errorMessages: ErrorMessages\n\n            enum CodingKeys: String, CodingKey {\n                case placeholder = \"placeholder_text\"\n                case properties = \"properties\"\n                case addressLabel = \"address_label\"\n                case pendingLabel = \"resend\"\n                case errorMessages = \"error_messages\"\n            }\n\n            public init(\n                placeholder: String?,\n                addressLabel: String,\n                pendingLabel: PendingLabel,\n                properties: AirshipJSON? = nil,\n                errorMessages: ErrorMessages\n            ) {\n                self.placeholder = placeholder\n                self.addressLabel = addressLabel\n                self.pendingLabel = pendingLabel\n                self.properties = properties\n                self.errorMessages = errorMessages\n            }\n        }\n\n        /// SMS registration options\n        public struct SMS: Decodable, Equatable, Sendable {\n\n            /// List of sender ids - the identifiers for the senders of the SMS verification message\n            public var senders: [SMSSenderInfo]\n\n            /// Country code label\n            public var countryLabel: String\n\n            /// MSISDN Label\n            public var msisdnLabel: String\n\n            /// Label with resend button\n            public var pendingLabel: PendingLabel\n\n            /// Error messages that can result of attempting to add a MSISDN\n            public var errorMessages: ErrorMessages\n\n            enum CodingKeys: String, CodingKey {\n                case senders = \"senders\"\n                case countryLabel = \"country_label\"\n                case msisdnLabel = \"msisdn_label\"\n                case pendingLabel = \"resend\"\n                case errorMessages = \"error_messages\"\n            }\n\n            public init(\n                senders: [SMSSenderInfo],\n                countryLabel: String,\n                msisdnLabel: String,\n                pendingLabel: PendingLabel,\n                errorMessages: ErrorMessages\n            ) {\n                self.senders = senders\n                self.countryLabel = countryLabel\n                self.msisdnLabel = msisdnLabel\n                self.pendingLabel = pendingLabel\n                self.errorMessages = errorMessages\n            }\n        }\n\n        /// Reusable container for holding a title and optional description.\n        public struct CommonDisplay: Decodable, Equatable, Sendable {\n\n            /// Title text.\n            public let title: String\n\n            /// Subtitle text.\n            public let subtitle: String?\n\n            enum CodingKeys: String, CodingKey {\n                case title = \"name\"\n                case subtitle = \"description\"\n            }\n\n            public init(title: String, subtitle: String? = nil) {\n                self.title = title\n                self.subtitle = subtitle\n            }\n        }\n\n        /// The label message that appears when a channel listing is empty.\n        public struct EmptyMessage: Decodable, Equatable {\n\n            /// The empty message's text.\n            public let text: String\n\n            /// The empty message's content description.\n            public let contentDescription: String?\n\n            enum CodingKeys: String, CodingKey {\n                case text = \"text\"\n                case contentDescription = \"content_description\"\n            }\n\n            public init(\n                text: String,\n                contentDescription: String? = nil\n            ) {\n\n                self.text = text\n                self.contentDescription = contentDescription\n            }\n        }\n\n        /// The container for the add prompt button and resulting add prompt.\n        public struct AddChannel: Decodable, Equatable, Sendable {\n\n            /// The add channel prompt view that appears when the add channel button is tapped.\n            public let view: AddChannelPrompt\n\n            /// The labeled button that surfaces the add channel prompt.\n            public let button: LabeledButton\n\n            enum CodingKeys: String, CodingKey {\n                case view = \"view\"\n                case button = \"button\"\n            }\n\n            public init(\n                view: AddChannelPrompt,\n                button: LabeledButton\n            ) {\n                self.view = view\n                self.button = button\n            }\n        }\n\n        /// The container for the remove channel button and resulting remove prompt for adding a channel to a channel list.\n        public struct RemoveChannel: Decodable, Equatable, Sendable {\n\n            /// The remove channel prompt view that appears when the remove channel button is tapped.\n            public let view: RemoveChannelPrompt\n\n            /// The icon button that surfaces the remove channel prompt.\n            public let button: IconButton\n\n            enum CodingKeys: String, CodingKey {\n                case view = \"view\"\n                case button = \"button\"\n            }\n\n            public init(\n                view: RemoveChannelPrompt,\n                button: IconButton\n            ) {\n\n                self.view = view\n                self.button = button\n            }\n        }\n\n        public struct RemoveChannelPrompt: Decodable, Equatable, Sendable {\n\n            /// Optional additional prompt display info.\n            public let display: PromptDisplay\n\n            /// The prompt display that appears when a channel is removed.\n            public let onSuccess: ActionableMessage?\n\n            /// Close button info primarily for passing content descriptions.\n            public let closeButton: IconButton?\n\n            /// Cancel button.\n            public let cancelButton: LabeledButton?\n\n            /// The labeled button that initiates the removal of a channel on tap.\n            public let submitButton: LabeledButton?\n\n            enum CodingKeys: String, CodingKey {\n                case display = \"display\"\n                case onSuccess = \"on_success\"\n                case submitButton = \"submit_button\"\n                case closeButton = \"close_button\"\n                case cancelButton = \"cancel_button\"\n            }\n\n            public init(\n                display: PromptDisplay,\n                onSuccess: ActionableMessage? = nil,\n                submitButton: LabeledButton? = nil,\n                closeButton: IconButton? = nil,\n                cancelButton: LabeledButton? = nil\n            ) {\n                self.display = display\n                self.onSuccess = onSuccess\n                self.submitButton = submitButton\n                self.closeButton = closeButton\n                self.cancelButton = cancelButton\n            }\n        }\n\n        /// A more dynamic version of common display that includes a footer and error message.\n        public struct PromptDisplay: Decodable, Equatable, Sendable {\n\n            /// Title text.\n            public let title: String\n\n            /// Body text.\n            public let body: String?\n\n            /// Footer text that can contain markdown.\n            public let footer: String?\n\n            enum CodingKeys: String, CodingKey {\n                case title = \"title\"\n                case body = \"description\"\n                case footer = \"footer\"\n            }\n\n            public init(\n                title: String,\n                body: String? = nil,\n                footer: String? = nil\n            ) {\n\n                self.title = title\n                self.body = body\n                self.footer = footer\n            }\n        }\n\n        public struct AddChannelPrompt: Decodable, Equatable, Sendable {\n\n            /// The item text display.\n            public let display: PromptDisplay\n\n            /// The submission message.\n            public let onSubmit: ActionableMessage?\n\n            /// The close button.\n            public let closeButton: IconButton?\n\n            /// The cancel prompt button.\n            public let cancelButton: LabeledButton?\n\n            /// The submit prompt button.\n            public let submitButton: LabeledButton\n\n            enum CodingKeys: String, CodingKey {\n                case display = \"display\"\n                case onSubmit = \"on_submit\"\n                case cancelButton = \"cancel_button\"\n                case submitButton = \"submit_button\"\n                case closeButton = \"close_button\"\n            }\n\n            public init(\n                display: PromptDisplay,\n                onSubmit: ActionableMessage? = nil,\n                cancelButton: LabeledButton? = nil,\n                submitButton: LabeledButton,\n                closeButton: IconButton? = nil\n            ) {\n                self.display = display\n                self.onSubmit = onSubmit\n                self.cancelButton = cancelButton\n                self.submitButton = submitButton\n                self.closeButton = closeButton\n            }\n        }\n\n        public struct IconButton: Codable, Equatable, Sendable {\n            /// The button's content description.\n            public let contentDescription: String?\n\n            enum CodingKeys: String, CodingKey {\n                case contentDescription = \"content_description\"\n            }\n\n            public init(\n                contentDescription: String? = nil\n            ) {\n                self.contentDescription = contentDescription\n            }\n        }\n\n        /// Alert button info.\n        public struct LabeledButton: Decodable, Equatable, Sendable {\n\n            /// The button's text.\n            public let text: String\n\n            /// The button's content description.\n            public let contentDescription: String?\n\n            enum CodingKeys: String, CodingKey {\n                case text = \"text\"\n                case contentDescription = \"content_description\"\n            }\n\n            public init(\n                text: String,\n                contentDescription: String? = nil\n            ) {\n\n                self.text = text\n                self.contentDescription = contentDescription\n            }\n        }\n\n        /// Alert display info\n        public struct ActionableMessage: Decodable, Equatable, Sendable {\n\n            /// Title text.\n            public let title: String\n\n            /// Body text.\n            public let body: String?\n\n            /// Labeled button for submitting the action or closing the prompt.\n            public let button: LabeledButton\n\n            enum CodingKeys: String, CodingKey {\n                case title = \"name\"\n                case body = \"description\"\n                case button = \"button\"\n            }\n\n            public init(\n                title: String,\n                body: String?,\n                button: LabeledButton\n            ) {\n                self.title = title\n                self.body = body\n                self.button = button\n            }\n        }\n\n        /// Error message container for showing error messages on the add channel prompt\n        public struct ErrorMessages: Codable, Equatable, Sendable {\n            var invalidMessage: String\n            var defaultMessage: String\n\n            enum CodingKeys: String, CodingKey {\n                case invalidMessage = \"invalid\"\n                case defaultMessage = \"default\"\n            }\n        }\n\n        /// The info used to populate the add channel prompt sender input for SMS.\n        public struct SMSSenderInfo: Decodable, Identifiable, Equatable, Hashable, Sendable {\n            public var id: String {\n                return senderId\n            }\n\n            /// The senderId is the number from which the SMS is sent.\n            public var senderId: String\n\n            /// Placeholder text.\n            public var placeholderText: String\n\n            /// Country calling code. Examples: (1, 33, 44)\n            public var countryCode: String\n\n            /// Country display name.\n            public var displayName: String\n\n            enum CodingKeys: String, CodingKey {\n                case senderId = \"sender_id\"\n                case placeholderText = \"placeholder_text\"\n                case countryCode = \"country_calling_code\"\n                case displayName = \"display_name\"\n            }\n\n            public init(\n                senderId: String,\n                placeholderText: String,\n                countryCode: String,\n                displayName: String\n            ) {\n                self.senderId = senderId\n                self.placeholderText = placeholderText\n                self.countryCode = countryCode\n                self.displayName = displayName\n            }\n\n            static let none = SMSSenderInfo(\n                senderId: \"none\",\n                placeholderText: \"none\",\n                countryCode: \"none\",\n                displayName: \"none\"\n            )\n        }\n    }\n}\n\nextension PreferenceCenterConfig.ContactManagementItem.Platform: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.PendingLabel: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.Email: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.SMS: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.CommonDisplay: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.AddChannel: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.RemoveChannel: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.RemoveChannelPrompt: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.PromptDisplay: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.AddChannelPrompt: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.LabeledButton: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.ActionableMessage: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem.SMSSenderInfo: Encodable {}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/data/PreferenceCenterConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Preference center config.\npublic struct PreferenceCenterConfig: Decodable, Sendable, Equatable {\n\n    /// The config's identifier.\n    public var identifier: String\n\n    /// The config's sections.\n    public var sections: [Section]\n\n    /// The config's display info.\n    public var display: CommonDisplay?\n\n    /**\n     * The config's options.\n     */\n    public var options: Options?\n\n    public init(\n        identifier: String,\n        sections: [Section],\n        display: CommonDisplay? = nil,\n        options: Options? = nil\n    ) {\n        self.identifier = identifier\n        self.sections = sections\n        self.display = display\n        self.options = options\n    }\n\n    enum CodingKeys: String, CodingKey {\n        case identifier = \"id\"\n        case sections = \"sections\"\n        case display = \"display\"\n        case options = \"options\"\n    }\n\n    /// Config options.\n    public struct Options: Decodable, Sendable, Equatable {\n\n        /**\n         * The config identifier.\n         */\n        public var mergeChannelDataToContact: Bool?\n\n        enum CodingKeys: String, CodingKey {\n            case mergeChannelDataToContact = \"merge_channel_data_to_contact\"\n        }\n\n        public init(mergeChannelDataToContact: Bool?) {\n            self.mergeChannelDataToContact = mergeChannelDataToContact\n        }\n    }\n\n    /// Common display info\n    public struct CommonDisplay: Decodable, Sendable, Equatable {\n\n        /// Title\n        public var title: String?\n\n        // Subtitle\n        public var subtitle: String?\n\n        public init(title: String? = nil, subtitle: String? = nil) {\n            self.title = title\n            self.subtitle = subtitle\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case title = \"name\"\n            case subtitle = \"description\"\n        }\n    }\n\n    public struct NotificationOptInCondition: Decodable, PreferenceConfigCondition, Sendable\n    {\n\n        public enum OptInStatus: String, Equatable, Sendable, Codable {\n            case optedIn = \"opt_in\"\n            case optedOut = \"opt_out\"\n        }\n\n        public let type = PreferenceCenterConfigConditionType.notificationOptIn\n\n        public var optInStatus: OptInStatus\n\n        enum CodingKeys: String, CodingKey {\n            case optInStatus = \"when_status\"\n        }\n\n        public init(optInStatus: OptInStatus) {\n            self.optInStatus = optInStatus\n        }\n    }\n\n    /**\n     * Typed conditions.\n     */\n    public enum Condition: Decodable, Equatable, Sendable {\n        case notificationOptIn(NotificationOptInCondition)\n\n        enum CodingKeys: String, CodingKey {\n            case type = \"type\"\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let type = try container.decode(PreferenceCenterConfigConditionType.self, forKey: .type)\n            let singleValueContainer = try decoder.singleValueContainer()\n\n            switch type {\n            case .notificationOptIn:\n                self = .notificationOptIn(\n                    try singleValueContainer.decode(\n                        NotificationOptInCondition.self\n                    )\n                )\n            }\n        }\n    }\n\n    /// Common section.\n    public struct CommonSection: Decodable, PreferenceCenterConfigSection {\n\n        /// The section's type.\n        public let type = PreferenceCenterConfigSectionType.common\n\n        /// The section's identifier.\n        public var id: String\n\n        /// The section's items.\n        public var items: [Item]\n\n        /// The section's display info.\n        public var display: CommonDisplay?\n\n        /// The section's display conditions.\n        public var conditions: [Condition]?\n\n        public init(\n            id: String,\n            items: [Item],\n            display: CommonDisplay? = nil,\n            conditions: [Condition]? = nil\n        ) {\n            self.id = id\n            self.items = items\n            self.display = display\n            self.conditions = conditions\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case items = \"items\"\n            case conditions = \"conditions\"\n        }\n    }\n\n    /// Labeled section break info.\n    public struct LabeledSectionBreak: Decodable, PreferenceCenterConfigSection {\n\n\n        /// The section's type.\n        public let type = PreferenceCenterConfigSectionType.labeledSectionBreak\n\n        /// The section's identifier.\n        public var id: String\n\n        /// The section's display info.\n        public var display: CommonDisplay?\n\n        /// The section's display conditions.\n        public var conditions: [Condition]?\n\n        public init(\n            id: String,\n            display: CommonDisplay? = nil,\n            conditions: [Condition]? = nil\n        ) {\n\n            self.id = id\n            self.display = display\n            self.conditions = conditions\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case conditions = \"conditions\"\n        }\n    }\n\n    /// Contact Management section.\n    public struct ContactManagementSection: Decodable, PreferenceCenterConfigSection {\n        /// The section's type.\n        public let type = PreferenceCenterConfigSectionType.common\n\n        /// The section's identifier.\n        public var id: String\n\n        /// The section's items.\n        public var items: [Item]\n\n        /// The section's display info.\n        public var display: CommonDisplay?\n\n        /// The section's display conditions.\n        public var conditions: [Condition]?\n\n        public init(\n            id: String,\n            items: [Item],\n            display: CommonDisplay? = nil,\n            conditions: [Condition]? = nil\n        ) {\n            self.id = id\n            self.items = items\n            self.display = display\n            self.conditions = conditions\n        }\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case items = \"items\"\n            case conditions = \"conditions\"\n        }\n    }\n\n    /// Preference config section.\n    public enum Section: Decodable, Equatable, Sendable {\n\n        /// Common section\n        case common(CommonSection)\n\n        /// Labeled section break\n        case labeledSectionBreak(LabeledSectionBreak)\n\n        enum CodingKeys: String, CodingKey {\n            case type = \"type\"\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let type = try container.decode(PreferenceCenterConfigSectionType.self, forKey: .type)\n            let singleValueContainer = try decoder.singleValueContainer()\n\n            switch type {\n            case .common:\n                self = .common(\n                    (try singleValueContainer.decode(CommonSection.self))\n                )\n            case .labeledSectionBreak:\n                self = .labeledSectionBreak(\n                    (try singleValueContainer.decode(LabeledSectionBreak.self))\n                )\n            }\n        }\n    }\n    \n    /// Channel subscription item info.\n    public struct ChannelSubscription: Decodable, Equatable, PreferenceCenterConfigItem {\n\n        /// The item's type.\n        public let type = PreferenceCenterConfigItemType.channelSubscription\n\n        /// The item's identifier.\n        public var id: String\n\n        /// The item's subscription ID.\n        public var subscriptionID: String\n\n        /// The item's display info.\n        public var display: CommonDisplay?\n\n        /// The item's display conditions.\n        public var conditions: [Condition]?\n\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case subscriptionID = \"subscription_id\"\n            case conditions = \"conditions\"\n        }\n\n        public init(\n            id: String,\n            subscriptionID: String,\n            display: CommonDisplay? = nil,\n            conditions: [Condition]? = nil\n        ) {\n\n            self.id = id\n            self.subscriptionID = subscriptionID\n            self.display = display\n            self.conditions = conditions\n        }\n    }\n\n    /// Group contact subscription item info.\n    public struct ContactSubscriptionGroup: Decodable, Equatable, PreferenceCenterConfigItem {\n\n        /// The item's type.\n        public let type = PreferenceCenterConfigItemType\n            .contactSubscriptionGroup\n\n        /// The item's identifier.\n        public var id: String\n\n        /// The item's subscription ID.\n        public var subscriptionID: String\n\n        /// Components\n        public var components: [Component]\n\n        /// The item's display info.\n        public var display: CommonDisplay?\n\n        /// The item's display conditions.\n        public var conditions: [Condition]?\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case subscriptionID = \"subscription_id\"\n            case conditions = \"conditions\"\n            case components = \"components\"\n        }\n\n        public init(\n            id: String,\n            subscriptionID: String,\n            components: [Component],\n            display: CommonDisplay? = nil,\n            conditions: [Condition]? = nil\n        ) {\n\n            self.id = id\n            self.subscriptionID = subscriptionID\n            self.components = components\n            self.display = display\n            self.conditions = conditions\n        }\n\n        /// Contact subscription group component.\n        public struct Component: Decodable, Sendable, Equatable {\n\n            /// The component's scopes.\n            public var scopes: [ChannelScope]\n\n            /// The component's display info.\n            public var display: CommonDisplay?\n\n            enum CodingKeys: String, CodingKey {\n                case scopes = \"scopes\"\n                case display = \"display\"\n            }\n\n            public init(\n                scopes: [ChannelScope],\n                display: CommonDisplay? = nil\n            ) {\n                self.scopes = scopes\n                self.display = display\n            }\n        }\n    }\n\n    /// Contact subscription item info.\n    public struct ContactSubscription: Decodable, PreferenceCenterConfigItem, Equatable {\n\n        /// The item's type.\n        public let type = PreferenceCenterConfigItemType.contactSubscription\n\n        /// The item's identifier.\n        public var id: String\n\n        /// The item's display info.\n        public var display: CommonDisplay?\n\n        /// The item's display conditions.\n        public let conditions: [Condition]?\n\n        /// The item's subscription ID.\n        public var subscriptionID: String\n\n        /// The item's scopes.\n        public var scopes: [ChannelScope]\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case subscriptionID = \"subscription_id\"\n            case conditions = \"conditions\"\n            case scopes = \"scopes\"\n        }\n\n        public init(\n            id: String,\n            subscriptionID: String,\n            scopes: [ChannelScope],\n            display: CommonDisplay? = nil,\n            conditions: [Condition]? = nil\n        ) {\n\n            self.id = id\n            self.subscriptionID = subscriptionID\n            self.scopes = scopes\n            self.display = display\n            self.conditions = conditions\n        }\n\n    }\n\n    /// Alert item info.\n    public struct Alert: Decodable, PreferenceCenterConfigItem, Equatable {\n\n        public let type = PreferenceCenterConfigItemType.alert\n\n        /// The item's identifier.\n        public let id: String\n\n        /// The item's display info.\n        public var display: Display?\n\n        /// The item's display conditions.\n        public var conditions: [Condition]?\n\n        /// The alert's button.\n        public var button: Button?\n\n        enum CodingKeys: String, CodingKey {\n            case id = \"id\"\n            case display = \"display\"\n            case conditions = \"conditions\"\n            case button = \"button\"\n        }\n\n        public init(\n            id: String,\n            display: Display? = nil,\n            conditions: [Condition]? = nil,\n            button: Button? = nil\n        ) {\n\n            self.id = id\n            self.display = display\n            self.conditions = conditions\n            self.button = button\n        }\n\n        /// Alert button info.\n        public struct Button: Decodable, Sendable, Equatable {\n\n            /// The button's text.\n            public var text: String\n\n            /// The button's content description.\n            public var contentDescription: String?\n\n            /// Actions payload to run on tap\n            public var actionJSON: AirshipJSON\n\n            enum CodingKeys: String, CodingKey {\n                case text = \"text\"\n                case contentDescription = \"content_description\"\n                case actionJSON = \"actions\"\n            }\n\n            public init(\n                text: String,\n                contentDescription: String? = nil,\n                actionJSON: AirshipJSON = .null\n            ) {\n\n                self.text = text\n                self.contentDescription = contentDescription\n                self.actionJSON = actionJSON\n            }\n        }\n\n        /// Alert display info\n        public struct Display: Decodable, Sendable, Equatable {\n\n            /// Title\n            public var title: String?\n\n            /// Subtitle\n            public var subtitle: String?\n\n            /// Icon URL\n            public var iconURL: String?\n\n            enum CodingKeys: String, CodingKey {\n                case title = \"name\"\n                case subtitle = \"description\"\n                case iconURL = \"icon\"\n            }\n\n            public init(\n                title: String? = nil,\n                subtitle: String? = nil,\n                iconURL: String? = nil\n            ) {\n                self.title = title\n                self.subtitle = subtitle\n                self.iconURL = iconURL\n            }\n        }\n    }\n    \n    /// Contact management item\n\n    \n    /// Config item.\n    public enum Item: Decodable, Equatable, Sendable {\n        case channelSubscription(ChannelSubscription)\n        case contactSubscription(ContactSubscription)\n        case contactSubscriptionGroup(ContactSubscriptionGroup)\n        case alert(Alert)\n        case contactManagement(ContactManagementItem)\n\n        enum CodingKeys: String, CodingKey {\n            case type = \"type\"\n        }\n\n        public init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let type = try container.decode(PreferenceCenterConfigItemType.self, forKey: .type)\n            let singleValueContainer = try decoder.singleValueContainer()\n\n            switch type {\n            case .channelSubscription:\n                self = .channelSubscription(\n                    (try singleValueContainer.decode(ChannelSubscription.self))\n                )\n            case .contactSubscription:\n                self = .contactSubscription(\n                    (try singleValueContainer.decode(ContactSubscription.self))\n                )\n            case .contactSubscriptionGroup:\n                self = .contactSubscriptionGroup(\n                    (try singleValueContainer.decode(\n                        ContactSubscriptionGroup.self\n                    ))\n                )\n            case .alert:\n                self = .alert((try singleValueContainer.decode(Alert.self)))\n            case .contactManagement:\n                self = .contactManagement((try singleValueContainer.decode(ContactManagementItem.self)))\n            }\n        }\n    }\n\n}\n\n/// Condition types\npublic enum PreferenceCenterConfigConditionType: String, Equatable, Sendable, Codable {\n    /// Notification opt-in condition.\n    case notificationOptIn = \"notification_opt_in\"\n}\n\n/// Condition\npublic protocol PreferenceConfigCondition: Sendable, Equatable {\n\n    /**\n     * Condition type.\n     */\n    var type: PreferenceCenterConfigConditionType { get }\n}\n\n/// Item types.\npublic enum PreferenceCenterConfigItemType: String, Equatable, Sendable, Codable {\n    /// Channel subscription type.\n    case channelSubscription = \"channel_subscription\"\n\n    /// Contact subscription type.\n    case contactSubscription = \"contact_subscription\"\n\n    /// Channel group subscription type.\n    case contactSubscriptionGroup = \"contact_subscription_group\"\n\n    /// Alert type.\n    case alert\n\n    /// Contact management\n    case contactManagement = \"contact_management\"\n}\n\n/// Preference section item info.\npublic protocol PreferenceCenterConfigItem: Sendable, Identifiable {\n    /// The type.\n    var type: PreferenceCenterConfigItemType { get }\n\n    /// The identifier.\n    var id: String { get }\n}\n    \n/// Preference config section type.\npublic enum PreferenceCenterConfigSectionType: String, Equatable, Sendable, Codable {\n    /// Common section type.\n    case common = \"section\"\n\n    /// Labeled section break type.\n    case labeledSectionBreak = \"labeled_section_break\"\n}\n\n/// Preference config section.\npublic protocol PreferenceCenterConfigSection: Sendable, Equatable, Identifiable {\n\n    /**\n     * The section's type.\n     */\n    var type: PreferenceCenterConfigSectionType { get }\n\n    /**\n     * The section's identifier.\n     */\n    var id: String { get }\n}\n\nextension PreferenceCenterConfig.Item {\n    var info: any PreferenceCenterConfigItem {\n        switch self {\n        case .channelSubscription(let info): return info\n        case .contactSubscription(let info): return info\n        case .contactSubscriptionGroup(let info): return info\n        case .alert(let info): return info\n        case .contactManagement(let info): return info\n        }\n    }\n}\n\nextension PreferenceCenterConfig.Section {\n    var info: any PreferenceCenterConfigSection {\n        switch self {\n        case .common(let info): return info\n        case .labeledSectionBreak(let info): return info\n        }\n    }\n}\n\nextension PreferenceCenterConfig.Condition {\n    var info: any PreferenceConfigCondition {\n        switch self {\n        case .notificationOptIn(let info): return info\n        }\n    }\n}\n\nextension PreferenceCenterConfig {\n    public func containsChannelSubscriptions() -> Bool {\n        return self.sections.contains(where: { section in\n            guard case .common(let info) = section else { return false }\n            return info.items.contains(where: { item in\n                return (item.info.type == .channelSubscription)\n            })\n        })\n    }\n\n    public func containsContactSubscriptions() -> Bool {\n        return self.sections.contains(where: { section in\n            guard case .common(let info) = section else { return false }\n            return info.items.contains(where: { item in\n                return\n                    (item.info.type == .contactSubscription\n                    || item.info.type == .contactSubscriptionGroup)\n            })\n        })\n    }\n    \n    public func containsContactManagement() -> Bool {\n        return self.sections.contains(where: { section in\n            guard case .common(let info) = section else { return false }\n            return info.items.contains(where: { item in\n                return item.info.type == .contactManagement\n            })\n        })\n    }\n}\n\n// MARK: Encodable support for testing\n\nextension PreferenceCenterConfig {\n    func prettyPrintedJSON() throws -> String {\n        let encoder = JSONEncoder()\n        encoder.dateEncodingStrategy = .iso8601\n        encoder.outputFormatting = .prettyPrinted\n        let jsonData = try encoder.encode(self)\n\n        guard let jsonString = String(data: jsonData, encoding: .utf8) else {\n            throw NSError(domain: \"JSONEncoding\", code: 0, userInfo: [NSLocalizedDescriptionKey: \"Failed to convert JSON data to string.\"])\n        }\n\n        return jsonString\n    }\n}\n\nextension PreferenceCenterConfig: Encodable {\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(identifier, forKey: .identifier)\n        try container.encode(sections, forKey: .sections)\n        try container.encodeIfPresent(display, forKey: .display)\n        try container.encodeIfPresent(options, forKey: .options)\n    }\n}\n\nextension PreferenceCenterConfig.Options: Encodable {}\n\nextension PreferenceCenterConfig.CommonDisplay: Encodable {}\n\nextension PreferenceCenterConfig.NotificationOptInCondition: Encodable {\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        try container.encode(optInStatus.rawValue, forKey: .optInStatus)\n    }\n}\n\nextension PreferenceCenterConfig.Condition: Encodable {\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch self {\n        case .notificationOptIn(let condition):\n            try container.encode(condition.type, forKey: .type)\n            try condition.encode(to: encoder)\n        }\n    }\n}\n\nextension PreferenceCenterConfig.CommonSection: Encodable {}\n\nextension PreferenceCenterConfig.LabeledSectionBreak: Encodable {}\n\nextension PreferenceCenterConfig.Section: Encodable {\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch self {\n        case .common(let section):\n            try container.encode(section.type, forKey: .type)\n            try section.encode(to: encoder)\n        case .labeledSectionBreak(let section):\n            try container.encode(section.type, forKey: .type)\n            try section.encode(to: encoder)\n        }\n    }\n}\n\nextension PreferenceCenterConfig.ChannelSubscription: Encodable {}\n\nextension PreferenceCenterConfig.ContactSubscriptionGroup: Encodable {}\n\nextension PreferenceCenterConfig.ContactSubscriptionGroup.Component: Encodable {}\n\nextension PreferenceCenterConfig.ContactSubscription: Encodable {}\n\nextension PreferenceCenterConfig.Alert: Encodable {}\n\nextension PreferenceCenterConfig.Alert.Button: Encodable {}\n\nextension PreferenceCenterConfig.Alert.Display: Encodable {}\n\nextension PreferenceCenterConfig.ContactManagementItem: Encodable {\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n\n        try container.encode(id, forKey: .id)\n        try container.encode(platform, forKey: .platform)\n        try container.encode(display, forKey: .display)\n        try container.encodeIfPresent(emptyMessage, forKey: .emptyMessage)\n        try container.encodeIfPresent(addChannel, forKey: .addChannel)\n        try container.encodeIfPresent(removeChannel, forKey: .removeChannel)\n        try container.encodeIfPresent(conditions, forKey: .conditions)\n    }\n}\n\nextension PreferenceCenterConfig.Item: Encodable {\n    public func encode(to encoder: any Encoder) throws {\n        var container = encoder.container(keyedBy: CodingKeys.self)\n        switch self {\n        case .channelSubscription(let item):\n            try container.encode(item.type, forKey: .type)\n            try item.encode(to: encoder)\n        case .contactSubscription(let item):\n            try container.encode(item.type, forKey: .type)\n            try item.encode(to: encoder)\n        case .contactSubscriptionGroup(let item):\n            try container.encode(item.type, forKey: .type)\n            try item.encode(to: encoder)\n        case .alert(let item):\n            try container.encode(item.type, forKey: .type)\n            try item.encode(to: encoder)\n        case .contactManagement(let item):\n            try container.encode(item.type, forKey: .type)\n            try item.encode(to: encoder)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/data/PreferenceCenterDecoder.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\nclass PreferenceCenterDecoder {\n    private static let decoder: JSONDecoder = {\n        var decoder = JSONDecoder()\n        decoder.dateDecodingStrategy = .iso8601\n        return decoder\n    }()\n\n    class func decodeConfig(data: Data) throws -> PreferenceCenterConfig {\n        return try self.decoder.decode(PreferenceCenterConfig.self, from: data)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/data/PreferenceCenterResponse.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct PrefrenceCenterResponse: Decodable {\n    let config: PreferenceCenterConfig\n\n    enum CodingKeys: String, CodingKey {\n        case config = \"form\"\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/theme/PreferenceCenterTheme.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n#if canImport(AppKit)\nimport AppKit\n#endif\n\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Preference Center theme\npublic struct PreferenceCenterTheme: Equatable, Sendable {\n\n    /// View controller theme\n    public var viewController: PreferenceCenterTheme.ViewController? = nil\n\n    /// Preference center\n    public var preferenceCenter: PreferenceCenterTheme.PreferenceCenter? = nil\n\n    /// Common section theme\n    public var commonSection: CommonSection? = nil\n\n    /// Labeled section break theme\n    public var labeledSectionBreak: LabeledSectionBreak? = nil\n\n    /// Alert theme\n    public var alert: Alert? = nil\n\n    /// Contact management theme\n    public var contactManagement: ContactManagement? = nil\n\n    /// Channel subscription item theme\n    public var channelSubscription: ChannelSubscription? = nil\n\n    /// Contact subscription item theme\n    public var contactSubscription: ContactSubscription? = nil\n\n    /// Contact subscription group theme\n    public var contactSubscriptionGroup: ContactSubscriptionGroup? = nil\n\n    /// Navigation bar theme\n    public struct NavigationBar: Equatable, Sendable {\n        /// The default title\n        public var title: String? = nil\n\n        /// Override the preference center config title. If `false`, preference center will display the config title if exists otherwise the default title\n        /// Defaults to `true`\n        public var overrideConfigTitle: Bool?  = true\n\n        /// Navigation bar background color\n        public var backgroundColor: AirshipNativeColor? = nil\n\n        /// Navigation bar background color for dark mode\n        public var backgroundColorDark: AirshipNativeColor? = nil\n\n        /// Navigation bar back button color\n        public var backButtonColor: AirshipNativeColor? = nil\n\n        /// Navigation bar back button color for dark mode\n        public var backButtonColorDark: AirshipNativeColor? = nil\n\n        public init(\n            title: String? = nil,\n            overrideConfigTitle: Bool? = true,\n            backgroundColor: AirshipNativeColor? = nil,\n            backgroundColorDark: AirshipNativeColor? = nil,\n            backButtonColor: AirshipNativeColor? = nil,\n            backButtonColorDark: AirshipNativeColor? = nil\n        ) {\n            self.title = title\n            self.overrideConfigTitle = overrideConfigTitle\n            self.backgroundColor = backgroundColor\n            self.backgroundColorDark = backgroundColorDark\n            self.backButtonColor = backButtonColor\n            self.backButtonColorDark = backButtonColorDark\n        }\n    }\n    /// View controller theme\n    public struct ViewController: Equatable, Sendable {\n        /// Navigation bar theme\n        public var navigationBar: NavigationBar? = nil\n\n        /// Window background color\n        public var backgroundColor: AirshipNativeColor? = nil\n\n        /// Window background color for dark mode\n        public var backgroundColorDark: AirshipNativeColor? = nil\n\n        public init(\n            navigationBar: NavigationBar? = nil,\n            backgroundColor: AirshipNativeColor? = nil,\n            backgroundColorDark: AirshipNativeColor? = nil\n        ) {\n            self.navigationBar = navigationBar\n            self.backgroundColor = backgroundColor\n            self.backgroundColorDark = backgroundColorDark\n        }\n    }\n\n    /// Preference center\n    public struct PreferenceCenter: Equatable, Sendable {\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        /// The retry button background color\n        public var retryButtonBackgroundColor: Color? = nil\n\n        /// The retry button background color for dark mode\n        public var retryButtonBackgroundColorDark: Color? = nil\n\n        /// The retry button label appearance\n        public var retryButtonLabelAppearance: TextAppearance? = nil\n\n        /// The retry button label\n        public var retryButtonLabel: String? = nil\n\n        /// The retry message\n        public var retryMessage: String? = nil\n\n        /// The retry message appearance\n        public var retryMessageAppearance: TextAppearance? = nil\n\n        public init(\n            subtitleAppearance: TextAppearance? = nil,\n            retryButtonBackgroundColor: Color? = nil,\n            retryButtonBackgroundColorDark: Color? = nil,\n            retryButtonLabelAppearance: TextAppearance? = nil,\n            retryButtonLabel: String? = nil,\n            retryMessage: String? = nil,\n            retryMessageAppearance: TextAppearance? = nil\n        ) {\n            self.subtitleAppearance = subtitleAppearance\n            self.retryButtonBackgroundColor = retryButtonBackgroundColor\n            self.retryButtonBackgroundColorDark = retryButtonBackgroundColorDark\n            self.retryButtonLabelAppearance = retryButtonLabelAppearance\n            self.retryButtonLabel = retryButtonLabel\n            self.retryMessage = retryMessage\n            self.retryMessageAppearance = retryMessageAppearance\n        }\n    }\n\n    /// Text appearance\n    public struct TextAppearance: Equatable, Sendable {\n        /// The text font\n        public var font: Font? = nil\n\n        /// The text color\n        public var color: Color? = nil\n\n        /// The text color for dark mode\n        public var colorDark: Color? = nil\n\n        public init(\n            font: Font? = nil,\n            color: Color? = nil,\n            colorDark: Color? = nil\n        ) {\n            self.font = font\n            self.color = color\n            self.colorDark = colorDark\n        }\n    }\n\n    /// Chip theme for contact subscription groups\n    public struct Chip: Equatable, Sendable {\n        /// The check color\n        public var checkColor: Color? = nil\n\n        /// The check color for dark mode\n        public var checkColorDark: Color? = nil\n\n        /// Border color around the full chip and check area\n        public var borderColor: Color? = nil\n\n        /// Border color around the full chip and check area for dark mode\n        public var borderColorDark: Color? = nil\n\n        /// Chip label appearance\n        public var labelAppearance: TextAppearance? = nil\n\n        public init(\n            checkColor: Color? = nil,\n            checkColorDark: Color? = nil,\n            borderColor: Color? = nil,\n            borderColorDark: Color? = nil,\n            labelAppearance: TextAppearance? = nil\n        ) {\n            self.checkColor = checkColor\n            self.checkColorDark = checkColorDark\n            self.borderColor = borderColor\n            self.borderColorDark = borderColorDark\n            self.labelAppearance = labelAppearance\n        }\n    }\n\n\n    /// Common section theme\n    public struct CommonSection: Equatable, Sendable {\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        public init(\n            titleAppearance: TextAppearance? = nil,\n            subtitleAppearance: TextAppearance? = nil\n        ) {\n            self.titleAppearance = titleAppearance\n            self.subtitleAppearance = subtitleAppearance\n        }\n    }\n\n    /// Labeled section break theme\n    public struct LabeledSectionBreak: Equatable, Sendable {\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Background color\n        public var backgroundColor: Color? = nil\n\n        /// Background color for dark mode\n        public var backgroundColorDark: Color? = nil\n\n        public init(\n            titleAppearance: TextAppearance? = nil,\n            backgroundColor: Color? = nil,\n            backgroundColorDark: Color? = nil\n        ) {\n            self.titleAppearance = titleAppearance\n            self.backgroundColor = backgroundColor\n            self.backgroundColorDark = backgroundColorDark\n        }\n    }\n\n    /// Alert item theme\n    public struct Alert: Equatable, Sendable {\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        /// Button label appearance\n        public var buttonLabelAppearance: TextAppearance? = nil\n\n        /// Button background color\n        public var buttonBackgroundColor: Color? = nil\n\n        /// Button background color for dark mode\n        public var buttonBackgroundColorDark: Color? = nil\n\n        public init(\n            titleAppearance: TextAppearance? = nil,\n            subtitleAppearance: TextAppearance? = nil,\n            buttonLabelAppearance: TextAppearance? = nil,\n            buttonBackgroundColor: Color? = nil,\n            buttonBackgroundColorDark: Color? = nil\n        ) {\n            self.titleAppearance = titleAppearance\n            self.subtitleAppearance = subtitleAppearance\n            self.buttonLabelAppearance = buttonLabelAppearance\n            self.buttonBackgroundColor = buttonBackgroundColor\n            self.buttonBackgroundColorDark = buttonBackgroundColorDark\n        }\n    }\n\n    /// Contact management item theme\n    public struct ContactManagement: Equatable, Sendable {\n        /// Background color\n        public var backgroundColor: Color? = nil\n\n        /// Background color for dark mode\n        public var backgroundColorDark: Color? = nil\n\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        /// List title appearance\n        public var listTitleAppearance: TextAppearance? = nil\n\n        /// List subtitle appearance\n        public var listSubtitleAppearance: TextAppearance? = nil\n\n        /// Error appearance\n        public var errorAppearance: TextAppearance? = nil\n\n        /// Text field placeholder appearance\n        public var textFieldTextAppearance: TextAppearance? = nil\n\n        /// Text field placeholder appearance\n        public var textFieldPlaceholderAppearance: TextAppearance? = nil\n\n        /// Button label appearance\n        public var buttonLabelAppearance: TextAppearance? = nil\n\n        /// Button background color\n        public var buttonBackgroundColor: Color? = nil\n\n        /// Button background color for dark mode\n        public var buttonBackgroundColorDark: Color? = nil\n\n        /// Destructive button background color - used submit button background color when removing channels\n        public var buttonDestructiveBackgroundColor: Color? = nil\n\n        /// Destructive button background color for dark mode\n        public var buttonDestructiveBackgroundColorDark: Color? = nil\n\n        public init(\n            backgroundColor: Color? = nil,\n            backgroundColorDark: Color? = nil,\n            titleAppearance: TextAppearance? = nil,\n            subtitleAppearance: TextAppearance? = nil,\n            listTitleAppearance: TextAppearance? = nil,\n            listSubtitleAppearance: TextAppearance? = nil,\n            errorAppearance: TextAppearance? = nil,\n            textFieldTextAppearance: TextAppearance? = nil,\n            textFieldPlaceholderAppearance: TextAppearance? = nil,\n            buttonLabelAppearance: TextAppearance? = nil,\n            buttonBackgroundColor: Color? = nil,\n            buttonBackgroundColorDark: Color? = nil,\n            buttonDestructiveBackgroundColor: Color? = nil,\n            buttonDestructiveBackgroundColorDark: Color? = nil\n        ) {\n            self.backgroundColor = backgroundColor\n            self.backgroundColorDark = backgroundColorDark\n            self.titleAppearance = titleAppearance\n            self.subtitleAppearance = subtitleAppearance\n            self.listTitleAppearance = listTitleAppearance\n            self.listSubtitleAppearance = listSubtitleAppearance\n            self.errorAppearance = errorAppearance\n            self.textFieldTextAppearance = textFieldTextAppearance\n            self.textFieldPlaceholderAppearance = textFieldPlaceholderAppearance\n            self.buttonLabelAppearance = buttonLabelAppearance\n            self.buttonBackgroundColor = buttonBackgroundColor\n            self.buttonBackgroundColorDark = buttonBackgroundColorDark\n            self.buttonDestructiveBackgroundColor = buttonDestructiveBackgroundColor\n            self.buttonDestructiveBackgroundColorDark = buttonDestructiveBackgroundColorDark\n        }\n    }\n\n    /// Channel subscription item theme\n    public struct ChannelSubscription: Equatable, Sendable {\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        /// Empty appearance - for when a section has an empty message set\n        public var emptyTextAppearance: TextAppearance? = nil\n\n        /// Toggle tint color\n        public var toggleTintColor: Color? = nil\n\n        /// Toggle tint color for dark mode\n        public var toggleTintColorDark: Color? = nil\n\n        public init(\n            titleAppearance: TextAppearance? = nil,\n            subtitleAppearance: TextAppearance? = nil,\n            emptyTextAppearance: TextAppearance? = nil,\n            toggleTintColor: Color? = nil,\n            toggleTintColorDark: Color? = nil\n        ) {\n            self.titleAppearance = titleAppearance\n            self.subtitleAppearance = subtitleAppearance\n            self.emptyTextAppearance = emptyTextAppearance\n            self.toggleTintColor = toggleTintColor\n            self.toggleTintColorDark = toggleTintColorDark\n        }\n    }\n\n    /// Contact subscription item theme\n    public struct ContactSubscription: Equatable, Sendable {\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        /// Toggle tint color\n        public var toggleTintColor: Color? = nil\n\n        /// Toggle tint color for dark mode\n        public var toggleTintColorDark: Color? = nil\n\n        public init(\n            titleAppearance: TextAppearance? = nil,\n            subtitleAppearance: TextAppearance? = nil,\n            toggleTintColor: Color? = nil,\n            toggleTintColorDark: Color? = nil\n        ) {\n            self.titleAppearance = titleAppearance\n            self.subtitleAppearance = subtitleAppearance\n            self.toggleTintColor = toggleTintColor\n            self.toggleTintColorDark = toggleTintColorDark\n        }\n    }\n\n    /// Contact subscription group item theme\n    public struct ContactSubscriptionGroup: Equatable, Sendable {\n        /// Title appearance\n        public var titleAppearance: TextAppearance? = nil\n\n        /// Subtitle appearance\n        public var subtitleAppearance: TextAppearance? = nil\n\n        /// Chip theme\n        public var chip: Chip? = nil\n\n        public init(\n            titleAppearance: TextAppearance? = nil,\n            subtitleAppearance: TextAppearance? = nil,\n            chip: Chip? = nil\n        ) {\n            self.titleAppearance = titleAppearance\n            self.subtitleAppearance = subtitleAppearance\n            self.chip = chip\n        }\n    }\n\n    public init(\n        viewController: PreferenceCenterTheme.ViewController? = nil,\n        preferenceCenter: PreferenceCenterTheme.PreferenceCenter? = nil,\n        commonSection: CommonSection? = nil,\n        labeledSectionBreak: LabeledSectionBreak? = nil,\n        alert: Alert? = nil,\n        contactManagement: ContactManagement? = nil,\n        channelSubscription: ChannelSubscription? = nil,\n        contactSubscription: ContactSubscription? = nil,\n        contactSubscriptionGroup: ContactSubscriptionGroup? = nil\n    ) {\n        self.viewController = viewController\n        self.preferenceCenter = preferenceCenter\n        self.commonSection = commonSection\n        self.labeledSectionBreak = labeledSectionBreak\n        self.alert = alert\n        self.contactManagement = contactManagement\n        self.channelSubscription = channelSubscription\n        self.contactSubscription = contactSubscription\n        self.contactSubscriptionGroup = contactSubscriptionGroup\n    }\n}\n\nstruct PreferenceCenterThemeKey: EnvironmentKey {\n    static let defaultValue = PreferenceCenterTheme()\n}\n\nextension EnvironmentValues {\n    /// Airship preference theme environment value\n    public var airshipPreferenceCenterTheme: PreferenceCenterTheme {\n        get { self[PreferenceCenterThemeKey.self] }\n        set { self[PreferenceCenterThemeKey.self] = newValue }\n    }\n}\n\nextension View {\n    /// Overrides the preference center theme\n    /// - Parameters:\n    ///     - theme: The preference center theme\n    public func preferenceCenterTheme(\n        _ theme: PreferenceCenterTheme\n    )-> some View {\n        environment(\\.airshipPreferenceCenterTheme, theme)\n    }\n}\n\nextension PreferenceCenterTheme {\n    /// Loads a preference center theme from a plist file\n    /// - Parameters:\n    ///     - plist: The name of the plist in the bundle\n    public static func fromPlist(\n        _ plist: String\n    ) throws -> PreferenceCenterTheme {\n        return try PreferenceCenterThemeLoader.fromPlist(plist)\n    }\n}\n\n\nextension Color {\n    \n\n    /**\n     ** Derives secondary variant for a particular color by shifting a given color's RGBA values\n     ** by the difference between the current primary and secondary colors\n     **/\n    func secondaryVariant(for colorScheme: ColorScheme) -> Color {\n        /// Convert target, primary and secondary colors to AirshipNativeColor\n        #if os(macOS)\n        guard\n            let targetUIColor = AirshipNativeColor(self).usingColorSpace(.sRGB),\n            let primaryUIColor = AirshipNativeColor(.primary).usingColorSpace(.sRGB),\n            let secondaryUIColor = AirshipNativeColor(.secondary).usingColorSpace(.sRGB)\n        else {\n            return self\n        }\n        #else\n        let targetUIColor = AirshipNativeColor(self)\n        let primaryUIColor = AirshipNativeColor(.primary)\n        let secondaryUIColor = AirshipNativeColor(.secondary)\n        #endif\n\n        /// Calculate RGBA differences between primary and secondary\n        var primaryRed: CGFloat = 0, primaryGreen: CGFloat = 0, primaryBlue: CGFloat = 0, primaryAlpha: CGFloat = 0\n        primaryUIColor.getRed(&primaryRed, green: &primaryGreen, blue: &primaryBlue, alpha: &primaryAlpha)\n\n        var secondaryRed: CGFloat = 0, secondaryGreen: CGFloat = 0, secondaryBlue: CGFloat = 0, secondaryAlpha: CGFloat = 0\n        secondaryUIColor.getRed(&secondaryRed, green: &secondaryGreen, blue: &secondaryBlue, alpha: &secondaryAlpha)\n\n        let redDiff = secondaryRed - primaryRed\n        let greenDiff = secondaryGreen - primaryGreen\n        let blueDiff = secondaryBlue - primaryBlue\n        let alphaDiff = secondaryAlpha - primaryAlpha\n\n        /// Apply the differences to the target color\n        var targetRed: CGFloat = 0, targetGreen: CGFloat = 0, targetBlue: CGFloat = 0, targetAlpha: CGFloat = 0\n\n#if os(macOS)\n        targetUIColor.getRed(&targetRed, green: &targetGreen, blue: &targetBlue, alpha: &targetAlpha)\n#else\n        if !targetUIColor.getRed(&targetRed, green: &targetGreen, blue: &targetBlue, alpha: &targetAlpha) {\n            return self\n        }\n#endif\n        let newRed = colorScheme == .light ? max(targetRed - redDiff, 0) : min(targetRed + redDiff, 1)\n        let newGreen = colorScheme == .light ? max(targetGreen - greenDiff, 0) : min(targetGreen + greenDiff, 1)\n        let newBlue = colorScheme == .light ? max(targetBlue - blueDiff, 0) : min(targetBlue + blueDiff, 1)\n        let newAlpha = targetAlpha + alphaDiff\n\n        return Color(AirshipNativeColor(red: newRed, green: newGreen, blue: newBlue, alpha: newAlpha))\n    }\n}\n\nstruct PreferenceCenterDefaults {\n\n\n#if os(tvOS)\n    static let promptMaxWidth: Double = 800.0\n    static let promptMinWidth: Double = 270.0\n    static let chipSpacing: Double = 36.0\n    static let smallPadding: Double = 5\n#elseif os(visionOS)\n    static let promptMaxWidth: Double = 420.0\n    static let promptMinWidth: Double = 270.0\n    static let chipSpacing: Double = 30.0\n    static let smallPadding: Double = 10.0\n#else\n    static let promptMaxWidth: Double = 420.0\n    static let promptMinWidth: Double = 270.0\n    static let chipSpacing: Double = 8.0\n    static let smallPadding: Double = 5\n#endif\n\n\n    static let labeledSectionBreakTitleBackgroundColor: Color = .gray\n    static let buttonDestructiveBackgroundColor = Color.red\n    static let buttonBackgroundColor = AirshipSystemColors.label\n\n    static let promptBackgroundColor: Color = {\n#if os(macOS)\n        return Color(NSColor(name: nil) { appearance in\n            return (appearance.isDark ? AirshipColor.resolveNativeColor(\"#272727\") : .controlBackgroundColor) ?? .controlBackgroundColor\n        })\n#elseif os(tvOS)\n        return Color(UIColor { trait in\n            return (trait.userInterfaceStyle == .dark ? AirshipColor.resolveNativeColor(\"#272727\") : .black) ?? .white\n        })\n#else\n        return Color(UIColor { trait in\n            return (trait.userInterfaceStyle == .dark ? AirshipColor.resolveNativeColor(\"#272727\") : .secondarySystemBackground) ?? .secondarySystemBackground\n        })\n#endif\n    }()\n\n    static let labeledSectionBreakTitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .headline,\n        color: Color.black,\n        colorDark: Color.white\n    )\n\n    static let sectionTitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .title2,\n        color: AirshipSystemColors.label\n    )\n\n    static let sectionSubtitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .subheadline,\n        color: AirshipSystemColors.label\n    )\n\n    static let titleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .title3,\n        color: AirshipSystemColors.label\n    )\n\n    static let subtitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .subheadline,\n        color: AirshipSystemColors.label\n    )\n\n    static let channelListItemTitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .headline,\n        color: AirshipSystemColors.label\n    )\n\n    static let channelListItemSubtitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .subheadline,\n        color: AirshipSystemColors.secondaryLabel\n    )\n\n    static let emptyTextAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .body,\n        color: AirshipSystemColors.label\n    )\n\n    static let errorAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .footnote.weight(.medium),\n        color: .red\n    )\n\n    static let textFieldTextAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .body,\n    )\n\n    static let textFieldPlaceholderAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .body,\n        color: AirshipSystemColors.placeholder\n    )\n\n    static let chipLabelAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .headline.weight(.bold),\n    )\n\n    static let resendButtonTitleAppearance = PreferenceCenterTheme.TextAppearance(\n        font: .caption.weight(.bold),\n        color: AirshipSystemColors.link\n    )\n}\n\n\ninternal struct AirshipSystemColors {\n\n    static let label: Color = .primary\n    static let secondaryLabel: Color = .secondary\n\n#if os(macOS)\n    static let placeholder: Color = Color(.placeholderTextColor)\n    static let tertiaryLabel: Color = Color(.tertiaryLabelColor)\n    static let link = Color(.linkColor)\n    static let background = Color(.windowBackgroundColor)\n    static let secondaryBackground = Color(.controlBackgroundColor)\n    static let tertiaryBackground = Color(.underPageBackgroundColor)\n#elseif os(tvOS)\n    static let placeholder: Color = Color(.placeholderText)\n    static let tertiaryLabel: Color = Color(.tertiaryLabel)\n    static let link = Color(.link)\n    static let background: Color = AirshipColor.systemBackground\n    static let secondaryBackground: Color = AirshipColor.systemBackground\n    static let tertiaryBackground: Color = AirshipColor.systemBackground\n#else\n    static let placeholder: Color = Color(.placeholderText)\n    static let tertiaryLabel: Color = Color(.tertiaryLabel)\n    static let link: Color = Color(.link)\n    static let background: Color = Color(.systemBackground)\n    static let secondaryBackground: Color = Color(.secondarySystemBackground)\n    static let tertiaryBackground: Color = Color(.tertiarySystemBackground)\n#endif\n\n}\n\n\n#if os(macOS)\nextension NSAppearance {\n    var isDark: Bool {\n        return bestMatch(from: [.darkAqua, .aqua]) == .darkAqua\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/theme/PreferenceCenterThemeLoader.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct PreferenceCenterThemeLoader {\n    // Existing code remains unchanged\n    static func defaultPlist() -> PreferenceCenterTheme? {\n        if let _ = try? plistPath(\n            file: \"AirshipPreferenceCenterTheme\",\n            bundle: Bundle.main\n        ) {\n            do {\n                return try fromPlist(\"AirshipPreferenceCenterTheme\")\n            } catch {\n                AirshipLogger.error(\n                    \"Unable to load preference center theme \\(error)\"\n                )\n            }\n        } else if let _ = try? plistPath(\n            file: \"AirshipPreferenceCenterStyle\",\n            bundle: Bundle.main\n        ) {\n            do {\n                return try fromPlist(\"AirshipPreferenceCenterStyle\")\n            } catch {\n                AirshipLogger.error(\n                    \"Unable to load preference center theme \\(error)\"\n                )\n            }\n        }\n\n        return nil\n    }\n\n    static func fromPlist(\n        _ file: String,\n        bundle: Bundle = Bundle.main\n    ) throws -> PreferenceCenterTheme {\n        let path = try plistPath(file: file, bundle: bundle)\n\n        guard let data = FileManager.default.contents(atPath: path) else {\n            throw AirshipErrors.error(\"Failed to load contents of theme.\")\n        }\n\n        let decoder = PropertyListDecoder()\n\n        let config = try decoder.decode(Config.self, from: data)\n        guard config.isEmpty else {\n            return try config.toPreferenceCenterTheme()\n        }\n        let legacy = try decoder.decode(LegacyConfig.self, from: data)\n        return try legacy.toPreferenceCenterTheme()\n    }\n\n    static func plistPath(file: String, bundle: Bundle) throws -> String {\n        guard let path = bundle.path(forResource: file, ofType: \"plist\"),\n              FileManager.default.fileExists(atPath: path)\n        else {\n            throw AirshipErrors.error(\"File not found \\(file).\")\n        }\n\n        return path\n    }\n\n    fileprivate struct LegacyConfig: Decodable {\n        // Existing properties remain unchanged\n        let title: String?\n        let titleFont: FontConfig?\n        let titleColor: String?\n        let navigationBarColor: String?\n        let backgroundColor: String?\n        let tintColor: String?\n        let subtitleFont: FontConfig?\n        let subtitleColor: String?\n        let sectionTextColor: String?\n        let sectionTextFont: FontConfig?\n        let sectionTitleTextColor: String?\n        let sectionTitleTextFont: FontConfig?\n        let sectionSubtitleTextColor: String?\n        let sectionSubtitleTextFont: FontConfig?\n        let sectionBreakTextColor: String?\n        let sectionBreakTextFont: FontConfig?\n        let sectionBreakBackgroundColor: String?\n        let preferenceTextColor: String?\n        let preferenceTextFont: FontConfig?\n        let preferenceTitleTextColor: String?\n        let preferenceTitleTextFont: FontConfig?\n        let preferenceSubtitleTextColor: String?\n        let preferenceSubtitleTextFont: FontConfig?\n\n        let switchTintColor: String?\n\n        let preferenceChipTextColor: String?\n        let preferenceChipTextFont: FontConfig?\n        let preferenceChipCheckmarkCheckedBackgroundColor: String?\n        let preferenceChipBorderColor: String?\n\n        let alertTitleColor: String?\n        let alertTitleFont: FontConfig?\n        let alertSubtitleColor: String?\n        let alertSubtitleFont: FontConfig?\n        let alertButtonBackgroundColor: String?\n        let alertButtonLabelColor: String?\n        let alertButtonLabelFont: FontConfig?\n\n        // New properties for dark mode\n        let titleColorDark: String?\n        let navigationBarColorDark: String?\n        let backgroundColorDark: String?\n        let tintColorDark: String?\n        let subtitleColorDark: String?\n        let sectionTextColorDark: String?\n        let sectionTitleTextColorDark: String?\n        let sectionSubtitleTextColorDark: String?\n        let sectionBreakTextColorDark: String?\n        let sectionBreakBackgroundColorDark: String?\n        let preferenceTextColorDark: String?\n        let preferenceTitleTextColorDark: String?\n        let preferenceSubtitleTextColorDark: String?\n        let switchTintColorDark: String?\n        let preferenceChipTextColorDark: String?\n        let preferenceChipCheckmarkCheckedBackgroundColorDark: String?\n        let preferenceChipBorderColorDark: String?\n        let alertTitleColorDark: String?\n        let alertSubtitleColorDark: String?\n        let alertButtonBackgroundColorDark: String?\n        let alertButtonLabelColorDark: String?\n    }\n\n    fileprivate struct Config: Decodable {\n        let viewController: ViewController?\n        let preferenceCenter: PreferenceCenter?\n        let commonSection: CommonSection?\n        let labeledSectionBreak: LabeledSectionBreak?\n        let alert: Alert?\n        let channelSubscription: ChannelSubscription?\n        let contactSubscription: ContactSubscription?\n        let contactSubscriptionGroup: ContactSubscriptionGroup?\n\n        struct NavigationBar: Decodable {\n            let title: String?\n            let titleFont: FontConfig?\n            let titleColor: String?\n            let titleColorDark: String?\n            let tintColor: String?\n            let tintColorDark: String?\n            let backgroundColor: String?\n            let backgroundColorDark: String?\n            let backButtonColor: String?\n            let backButtonColorDark: String?\n        }\n\n        struct ViewController: Decodable {\n            let navigationBar: NavigationBar?\n            let backgroundColor: String?\n            let backgroundColorDark: String?\n        }\n\n        struct PreferenceCenter: Decodable {\n            let subtitleAppearance: TextAppearance?\n            let retryButtonBackgroundColor: String?\n            let retryButtonBackgroundColorDark: String?\n            let retryButtonLabelAppearance: TextAppearance?\n            let retryButtonLabel: String?\n            let retryMessage: String?\n            let retryMessageAppearance: TextAppearance?\n        }\n\n        struct TextAppearance: Decodable {\n            let font: FontConfig?\n            let color: String?\n            let colorDark: String?\n        }\n\n        struct Chip: Decodable {\n            let checkColor: String?\n            let checkColorDark: String?\n            let borderColor: String?\n            let borderColorDark: String?\n            let labelAppearance: TextAppearance?\n        }\n\n        struct CommonSection: Decodable {\n            let titleAppearance: TextAppearance?\n            let subtitleAppearance: TextAppearance?\n        }\n\n        struct LabeledSectionBreak: Decodable {\n            let titleAppearance: TextAppearance?\n            let backgroundColor: String?\n            let backgroundColorDark: String?\n        }\n\n        struct Alert: Decodable {\n            let titleAppearance: TextAppearance?\n            let subtitleAppearance: TextAppearance?\n            let buttonLabelAppearance: TextAppearance?\n            let buttonBackgroundColor: String?\n            let buttonBackgroundColorDark: String?\n        }\n\n        struct ChannelSubscription: Decodable {\n            let titleAppearance: TextAppearance?\n            let subtitleAppearance: TextAppearance?\n            let toggleTintColor: String?\n            let toggleTintColorDark: String?\n            let buttonBackgroundColor: String?\n            let buttonBackgroundColorDark: String?\n        }\n\n        struct ContactSubscription: Decodable {\n            let titleAppearance: TextAppearance?\n            let subtitleAppearance: TextAppearance?\n            let toggleTintColor: String?\n            let toggleTintColorDark: String?\n        }\n\n        struct ContactSubscriptionGroup: Decodable {\n            let titleAppearance: TextAppearance?\n            let subtitleAppearance: TextAppearance?\n            let chip: Chip?\n        }\n    }\n\n    fileprivate struct FontConfig: Decodable {\n        let fontName: String\n        let fontSize: String\n    }\n}\n\n\nextension PreferenceCenterThemeLoader.FontConfig {\n    fileprivate func toFont() throws -> Font {\n        guard\n            let fontSize = Double(\n                fontSize.trimmingCharacters(in: .whitespaces)\n            ),\n            fontSize > 0.0\n        else {\n            throw AirshipErrors.error(\n                \"Font size must represent a double greater than 0\"\n            )\n        }\n\n        return Font.custom(\n            fontName.trimmingCharacters(in: .whitespaces),\n            size: fontSize\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.TextAppearance {\n    func toTextAppearance() throws -> PreferenceCenterTheme.TextAppearance {\n        return PreferenceCenterTheme.TextAppearance(\n            font: try self.font?.toFont(),\n            color: self.color?.airshipToColor(),\n            colorDark: self.colorDark?.airshipToColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.Chip {\n    func toChip() throws -> PreferenceCenterTheme.Chip {\n        return PreferenceCenterTheme.Chip(\n            checkColor: self.checkColor?.airshipToColor(),\n            checkColorDark: self.checkColorDark?.airshipToColor(),\n            borderColor: self.borderColor?.airshipToColor(),\n            borderColorDark: self.borderColorDark?.airshipToColor(),\n            labelAppearance: try self.labelAppearance?.toTextAppearance()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.NavigationBar {\n    func toNavigationBar() throws -> PreferenceCenterTheme.NavigationBar {\n        return PreferenceCenterTheme.NavigationBar(\n            title: self.title,\n            backgroundColor: self.backgroundColor?.airshipHexToNativeColor(),\n            backgroundColorDark: self.backgroundColorDark?.airshipHexToNativeColor(),\n            backButtonColor: self.backButtonColor?.airshipHexToNativeColor(),\n            backButtonColorDark: self.backButtonColorDark?.airshipHexToNativeColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.CommonSection {\n    func toCommonSection() throws -> PreferenceCenterTheme.CommonSection {\n        return PreferenceCenterTheme.CommonSection(\n            titleAppearance: try self.titleAppearance?.toTextAppearance(),\n            subtitleAppearance: try self.subtitleAppearance?.toTextAppearance()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.LabeledSectionBreak {\n    func toLabeledSectionBreak() throws\n    -> PreferenceCenterTheme.LabeledSectionBreak\n    {\n        return PreferenceCenterTheme.LabeledSectionBreak(\n            titleAppearance: try self.titleAppearance?.toTextAppearance(),\n            backgroundColor: self.backgroundColor?.airshipToColor(),\n            backgroundColorDark: self.backgroundColorDark?.airshipToColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.ChannelSubscription {\n    func toChannelSubscription() throws\n    -> PreferenceCenterTheme.ChannelSubscription\n    {\n        return PreferenceCenterTheme.ChannelSubscription(\n            titleAppearance: try self.titleAppearance?.toTextAppearance(),\n            subtitleAppearance: try self.subtitleAppearance?.toTextAppearance(),\n            toggleTintColor: self.toggleTintColor?.airshipToColor(),\n            toggleTintColorDark: self.toggleTintColorDark?.airshipToColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.ContactSubscription {\n    func toContactSubscription() throws\n    -> PreferenceCenterTheme.ContactSubscription\n    {\n        return PreferenceCenterTheme.ContactSubscription(\n            titleAppearance: try self.titleAppearance?.toTextAppearance(),\n            subtitleAppearance: try self.subtitleAppearance?.toTextAppearance(),\n            toggleTintColor: self.toggleTintColor?.airshipToColor(),\n            toggleTintColorDark: self.toggleTintColorDark?.airshipToColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.ContactSubscriptionGroup {\n    func toContactSubscriptionGroup() throws\n    -> PreferenceCenterTheme.ContactSubscriptionGroup\n    {\n        return PreferenceCenterTheme.ContactSubscriptionGroup(\n            titleAppearance: try self.titleAppearance?.toTextAppearance(),\n            subtitleAppearance: try self.subtitleAppearance?.toTextAppearance(),\n            chip: try self.chip?.toChip()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.Alert {\n    func toAlert() throws -> PreferenceCenterTheme.Alert {\n        return PreferenceCenterTheme.Alert(\n            titleAppearance: try self.titleAppearance?.toTextAppearance(),\n            subtitleAppearance: try self.subtitleAppearance?.toTextAppearance(),\n            buttonLabelAppearance: try self.buttonLabelAppearance?.toTextAppearance(),\n            buttonBackgroundColor: self.buttonBackgroundColor?.airshipToColor(),\n            buttonBackgroundColorDark: self.buttonBackgroundColorDark?.airshipToColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.PreferenceCenter {\n    func toPreferenceCenter() throws -> PreferenceCenterTheme.PreferenceCenter {\n        return PreferenceCenterTheme.PreferenceCenter(\n            subtitleAppearance: try self.subtitleAppearance?.toTextAppearance(),\n            retryButtonBackgroundColor: self.retryButtonBackgroundColor?\n                .airshipToColor(),\n            retryButtonBackgroundColorDark: self.retryButtonBackgroundColorDark?\n                .airshipToColor(),\n            retryButtonLabelAppearance: try self.retryButtonLabelAppearance?\n                .toTextAppearance(),\n            retryButtonLabel: self.retryButtonLabel,\n            retryMessage: self.retryMessage,\n            retryMessageAppearance: try self.retryMessageAppearance?\n                .toTextAppearance()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config.ViewController {\n    func toViewController() throws -> PreferenceCenterTheme.ViewController {\n        return PreferenceCenterTheme.ViewController(\n            navigationBar: try self.navigationBar?.toNavigationBar(),\n            backgroundColor: self.backgroundColor?.airshipHexToNativeColor(),\n            backgroundColorDark: self.backgroundColorDark?.airshipHexToNativeColor()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.Config {\n    fileprivate var isEmpty: Bool {\n        guard self.viewController == nil else { return false }\n        guard self.preferenceCenter == nil else { return false }\n        guard self.commonSection == nil else { return false }\n        guard self.labeledSectionBreak == nil else { return false }\n        guard self.alert == nil else { return false }\n        guard self.channelSubscription == nil else { return false }\n        guard self.contactSubscription == nil else { return false }\n        guard self.contactSubscriptionGroup == nil else { return false }\n        return true\n    }\n\n    fileprivate func toPreferenceCenterTheme() throws -> PreferenceCenterTheme {\n        return PreferenceCenterTheme(\n            viewController: try self.viewController?.toViewController(),\n            preferenceCenter: try self.preferenceCenter?.toPreferenceCenter(),\n            commonSection: try self.commonSection?.toCommonSection(),\n            labeledSectionBreak: try self.labeledSectionBreak?\n                .toLabeledSectionBreak(),\n            alert: try self.alert?.toAlert(),\n            channelSubscription: try self.channelSubscription?\n                .toChannelSubscription(),\n            contactSubscription: try self.contactSubscription?\n                .toContactSubscription(),\n            contactSubscriptionGroup: try self.contactSubscriptionGroup?\n                .toContactSubscriptionGroup()\n        )\n    }\n}\n\nextension PreferenceCenterThemeLoader.LegacyConfig {\n    fileprivate func toPreferenceCenterTheme() throws -> PreferenceCenterTheme {\n        let preferenceTitle = PreferenceCenterTheme.TextAppearance(\n            font: try (self.preferenceTitleTextFont ?? self.preferenceTextFont)?\n                .toFont(),\n            color: (self.preferenceTitleTextColor ?? self.preferenceTextColor)?\n                .airshipToColor(),\n            colorDark: (self.preferenceTitleTextColorDark ?? self.preferenceTextColorDark)?\n                .airshipToColor()\n        )\n\n        let preferenceSubtitle = PreferenceCenterTheme.TextAppearance(\n            font: try\n            (self.preferenceSubtitleTextFont ?? self.preferenceTextFont)?\n                .toFont(),\n            color: (self.preferenceSubtitleTextColor ?? self.preferenceTextColor)?\n                .airshipToColor(),\n            colorDark: (self.preferenceSubtitleTextColorDark ?? self.preferenceTextColorDark)?\n                .airshipToColor()\n        )\n\n        return PreferenceCenterTheme(\n            viewController: PreferenceCenterTheme.ViewController(\n                navigationBar: PreferenceCenterTheme.NavigationBar(\n                    title: self.title,\n                    backgroundColor: self.navigationBarColor?.airshipHexToNativeColor(),\n                    backgroundColorDark: self.navigationBarColorDark?.airshipHexToNativeColor()\n                ),\n                backgroundColor: self.backgroundColor?.airshipHexToNativeColor(),\n                backgroundColorDark: self.backgroundColorDark?.airshipHexToNativeColor()\n            ),\n            preferenceCenter: PreferenceCenterTheme.PreferenceCenter(\n                subtitleAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try self.subtitleFont?.toFont(),\n                    color: self.subtitleColor?.airshipToColor(),\n                    colorDark: self.subtitleColorDark?.airshipToColor()\n                )\n            ),\n            commonSection: PreferenceCenterTheme.CommonSection(\n                titleAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try\n                    (self.sectionTitleTextFont ?? self.sectionTextFont)?\n                        .toFont(),\n                    color: (self.sectionTitleTextColor ?? self.sectionTextColor)?\n                        .airshipToColor(),\n                    colorDark: (self.sectionTitleTextColorDark ?? self.sectionTextColorDark)?\n                        .airshipToColor()\n                ),\n                subtitleAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try\n                    (self.sectionSubtitleTextFont ?? self.sectionTextFont)?\n                        .toFont(),\n                    color: (self.sectionSubtitleTextColor\n                            ?? self.sectionTextColor)?\n                        .airshipToColor(),\n                    colorDark: (self.sectionSubtitleTextColorDark\n                                ?? self.sectionTextColorDark)?\n                        .airshipToColor()\n                )\n            ),\n            labeledSectionBreak: PreferenceCenterTheme.LabeledSectionBreak(\n                titleAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try\n                    (self.sectionBreakTextFont ?? self.sectionTextFont)?\n                        .toFont(),\n                    color: (self.sectionBreakTextColor ?? self.sectionTextColor)?\n                        .airshipToColor(),\n                    colorDark: (self.sectionBreakTextColorDark ?? self.sectionTextColorDark)?\n                        .airshipToColor()\n                ),\n                backgroundColor: self.sectionBreakBackgroundColor?.airshipToColor(),\n                backgroundColorDark: self.sectionBreakBackgroundColorDark?.airshipToColor()\n            ),\n            alert: PreferenceCenterTheme.Alert(\n                titleAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try self.alertTitleFont?.toFont(),\n                    color: self.alertTitleColor?.airshipToColor(),\n                    colorDark: self.alertTitleColorDark?.airshipToColor()\n                ),\n                subtitleAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try self.alertSubtitleFont?.toFont(),\n                    color: self.alertSubtitleColor?.airshipToColor(),\n                    colorDark: self.alertSubtitleColorDark?.airshipToColor()\n                ),\n                buttonLabelAppearance: PreferenceCenterTheme.TextAppearance(\n                    font: try self.alertButtonLabelFont?.toFont(),\n                    color: self.alertButtonLabelColor?.airshipToColor(),\n                    colorDark: self.alertButtonLabelColorDark?.airshipToColor()\n                ),\n                buttonBackgroundColor: self.alertButtonBackgroundColor?\n                    .airshipToColor(),\n                buttonBackgroundColorDark: self.alertButtonBackgroundColorDark?\n                    .airshipToColor()\n            ),\n            channelSubscription: PreferenceCenterTheme.ChannelSubscription(\n                titleAppearance: preferenceTitle,\n                subtitleAppearance: preferenceSubtitle,\n                toggleTintColor: self.switchTintColor?.airshipToColor(),\n                toggleTintColorDark: self.switchTintColorDark?.airshipToColor()\n            ),\n            contactSubscription: PreferenceCenterTheme.ContactSubscription(\n                titleAppearance: preferenceTitle,\n                subtitleAppearance: preferenceSubtitle,\n                toggleTintColor: self.switchTintColor?.airshipToColor(),\n                toggleTintColorDark: self.switchTintColorDark?.airshipToColor()\n            ),\n            contactSubscriptionGroup:\n                PreferenceCenterTheme.ContactSubscriptionGroup(\n                    titleAppearance: preferenceTitle,\n                    subtitleAppearance: preferenceSubtitle,\n                    chip: PreferenceCenterTheme.Chip(\n                        checkColor: self\n                            .preferenceChipCheckmarkCheckedBackgroundColor?\n                            .airshipToColor(),\n                        checkColorDark: self\n                            .preferenceChipCheckmarkCheckedBackgroundColorDark?\n                            .airshipToColor(),\n                        borderColor: self.preferenceChipBorderColor?.airshipToColor(),\n                        borderColorDark: self.preferenceChipBorderColorDark?.airshipToColor(),\n                        labelAppearance: PreferenceCenterTheme.TextAppearance(\n                            font: try self.preferenceChipTextFont?.toFont(),\n                            color: self.preferenceChipTextColor?.airshipToColor(),\n                            colorDark: self.preferenceChipTextColorDark?.airshipToColor()\n                        )\n                    )\n                )\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/ChannelSubscriptionView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The channel subscription item view\npublic struct ChannelSubscriptionView: View {\n\n    /// The item's config\n    public let item: PreferenceCenterConfig.ChannelSubscription\n\n    // The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipChannelSubscriptionViewStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @State\n    private var displayConditionsMet: Bool = true\n\n    public init(item: PreferenceCenterConfig.ChannelSubscription, state: PreferenceCenterState) {\n        self.item = item\n        self.state = state\n    }\n    \n    @ViewBuilder\n    public var body: some View {\n        let isSubscribed = state.makeBinding(channelListID: item.subscriptionID)\n\n        let configuration = ChannelSubscriptionViewStyleConfiguration(\n            item: self.item,\n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            isSubscribed: isSubscribed,\n            colorScheme: self.colorScheme\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.item.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\nextension View {\n    /// Sets the channel subscription style\n    /// - Parameters:\n    ///     - style: The style\n    public func channelSubscriptionStyle<S>(_ style: S) -> some View\n    where S: ChannelSubscriptionViewStyle {\n        self.environment(\n            \\.airshipChannelSubscriptionViewStyle,\n            AnyChannelSubscriptionViewStyle(style: style)\n        )\n    }\n}\n\n/// Channel subscription item view style configuration\npublic struct ChannelSubscriptionViewStyleConfiguration {\n    /// The item's config\n    public let item: PreferenceCenterConfig.ChannelSubscription\n\n    /// The preference state\n    public let state: PreferenceCenterState\n\n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// The item's subscription binding\n    public let isSubscribed: Binding<Bool>\n\n    /// Color scheme\n    public let colorScheme: ColorScheme\n}\n\npublic protocol ChannelSubscriptionViewStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = ChannelSubscriptionViewStyleConfiguration\n    \n    @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension ChannelSubscriptionViewStyle\nwhere Self == DefaultChannelSubscriptionViewStyle {\n\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default channel subscription view style\npublic struct DefaultChannelSubscriptionViewStyle: ChannelSubscriptionViewStyle {\n\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let item = configuration.item\n        let itemTheme = configuration.preferenceCenterTheme.channelSubscription\n        let colorScheme = configuration.colorScheme\n        let resolvedToggleTintColor: Color? = colorScheme.airshipResolveColor(light:  itemTheme?.toggleTintColor, dark:  itemTheme?.toggleTintColorDark)\n\n        if configuration.displayConditionsMet {\n            Toggle(isOn: configuration.isSubscribed) {\n                VStack(alignment: .leading) {\n                    if let title = item.display?.title {\n                        Text(title)\n                            .textAppearance(\n                                itemTheme?.titleAppearance,\n                                base: PreferenceCenterDefaults.titleAppearance,\n                                colorScheme: colorScheme\n                            )\n                            .accessibilityAddTraits(.isHeader)\n                    }\n\n                    if let subtitle = item.display?.subtitle {\n                        Text(subtitle)\n                            .textAppearance(\n                                itemTheme?.subtitleAppearance,\n                                base: PreferenceCenterDefaults.subtitleAppearance,\n                                colorScheme: colorScheme\n                            )\n\n                    }\n                }\n            }\n            .toggleStyle(tint: resolvedToggleTintColor)\n            .padding(.trailing, 2)\n        }\n    }\n}\n\nstruct AnyChannelSubscriptionViewStyle: ChannelSubscriptionViewStyle {\n    @ViewBuilder\n    private let _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: ChannelSubscriptionViewStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct ChannelSubscriptionViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyChannelSubscriptionViewStyle(\n        style: .defaultStyle\n    )\n}\n\nextension EnvironmentValues {\n    var airshipChannelSubscriptionViewStyle: AnyChannelSubscriptionViewStyle {\n        get { self[ChannelSubscriptionViewStyleKey.self] }\n        set { self[ChannelSubscriptionViewStyleKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/CommonSectionView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Common section item view\npublic struct CommonSectionView: View {\n\n    /// The section's config\n    public let section: PreferenceCenterConfig.CommonSection\n\n    /// The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipCommonSectionViewStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @State\n    private var displayConditionsMet: Bool = true\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    private var isLast: Bool\n\n    init(\n        section: PreferenceCenterConfig.CommonSection,\n        state: PreferenceCenterState,\n        isLast: Bool\n    ) {\n        self.section = section\n        self.state = state\n        self.isLast = isLast\n    }\n    \n    @ViewBuilder\n    public var body: some View {\n        let configuration = CommonSectionViewStyleConfiguration(\n            section: self.section, \n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            colorScheme: self.colorScheme,\n            isLast: self.isLast\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.section.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\nextension View {\n    /// Sets the common section style\n    /// - Parameters:\n    ///     - style: The style\n    public func commonSectionViewStyle<S>(_ style: S) -> some View\n    where S: CommonSectionViewStyle {\n        self.environment(\n            \\.airshipCommonSectionViewStyle,\n            AnyCommonSectionViewStyle(style: style)\n        )\n    }\n}\n\n/// Common section style configuration\npublic struct CommonSectionViewStyleConfiguration {\n    /// The section config\n    public let section: PreferenceCenterConfig.CommonSection\n\n    /// The preference state\n    public let state: PreferenceCenterState\n\n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// The color scheme\n    public let colorScheme: ColorScheme\n\n    /// Is last section or not\n    public let isLast: Bool\n}\n\n/// Common section view style\npublic protocol CommonSectionViewStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = CommonSectionViewStyleConfiguration\n    \n    @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension CommonSectionViewStyle where Self == DefaultCommonSectionViewStyle {\n\n    /// The default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default common section view style\npublic struct DefaultCommonSectionViewStyle: CommonSectionViewStyle {\n\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let section = configuration.section\n        let sectionTheme = configuration.preferenceCenterTheme.commonSection\n        let colorScheme = configuration.colorScheme\n\n        if configuration.displayConditionsMet {\n            VStack(alignment: .leading) {\n                Spacer()\n                if section.display?.title?.isEmpty == false || section.display?.subtitle?.isEmpty == false {\n                    VStack(alignment: .leading) {\n                        if let title = section.display?.title {\n                            Text(title)\n                                .textAppearance(\n                                    sectionTheme?.titleAppearance,\n                                    base: PreferenceCenterDefaults.sectionTitleAppearance,\n                                    colorScheme: colorScheme\n                                )\n                                .accessibilityAddTraits(.isHeader)\n                        }\n\n                        if let subtitle = section.display?.subtitle {\n                            Text(subtitle)\n                                .textAppearance(\n                                    sectionTheme?.subtitleAppearance,\n                                    base: PreferenceCenterDefaults.sectionSubtitleAppearance,\n                                    colorScheme: colorScheme\n                                )\n                        }\n                    }\n                    .padding(.bottom, PreferenceCenterDefaults.smallPadding)\n                }\n\n                ForEach(0..<section.items.count, id: \\.self) { index in\n                    makeItem(\n                        section.items[index],\n                        state: configuration.state\n                    ).airshipApplyIf(index != section.items.count - 1) {\n                        $0.padding(.bottom, PreferenceCenterDefaults.smallPadding)\n                    }\n\n                }\n            }\n            .airshipApplyIf(configuration.isLast) {\n                $0.padding(.bottom, PreferenceCenterDefaults.smallPadding)\n            }\n#if os(tvOS)\n            .focusSection()\n#endif\n            if !configuration.isLast {\n                Divider().padding(.vertical)\n            }\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    func makeItem(\n        _ item: PreferenceCenterConfig.Item,\n        state: PreferenceCenterState\n    ) -> some View {\n        switch item {\n        case .alert(let item):\n            PreferenceCenterAlertView(item: item, state: state).transition(.opacity)\n        case .channelSubscription(let item):\n            ChannelSubscriptionView(item: item, state: state)\n        case .contactSubscription(let item):\n            ContactSubscriptionView(item: item, state: state)\n        case .contactSubscriptionGroup(let item):\n            ContactSubscriptionGroupView(item: item, state: state)\n        case .contactManagement(let item):\n            PreferenceCenterContactManagementView(\n                item: item,\n                state: state\n            )\n        }\n    }\n}\n\nstruct AnyCommonSectionViewStyle: CommonSectionViewStyle {\n    @ViewBuilder\n    private var _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: CommonSectionViewStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct CommonSectionViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyCommonSectionViewStyle(\n        style: DefaultCommonSectionViewStyle()\n    )\n}\n\nextension EnvironmentValues {\n    var airshipCommonSectionViewStyle: AnyCommonSectionViewStyle {\n        get { self[CommonSectionViewStyleKey.self] }\n        set { self[CommonSectionViewStyleKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/ConditionsMonitor.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nclass ConditionsMonitor: ObservableObject {\n    @Published\n    public private(set) var isMet: Bool = true\n\n    private let conditions: [PreferenceCenterConfig.Condition]\n    private var cancellable: AnyCancellable?\n\n    init(conditions: [PreferenceCenterConfig.Condition]) {\n        self.conditions = conditions\n\n        Task { @MainActor [weak self] in\n            self?.updateConditions()\n        }\n\n        let conditionUpdates = conditions.map { self.conditionUpdates($0) }\n        self.cancellable = Publishers.MergeMany(conditionUpdates)\n            .receive(on: RunLoop.main)\n            .sink { [weak self] _ in\n                Task { @MainActor [weak self] in\n                    self?.updateConditions()\n                }\n            }\n    }\n\n    @MainActor\n    private func updateConditions() {\n        self.isMet = self.checkConditions()\n    }\n\n    private func conditionUpdates(_ condition: PreferenceCenterConfig.Condition)\n        -> AnyPublisher<Bool, Never>\n    {\n        guard Airship.isFlying else {\n            return Just(true).eraseToAnyPublisher()\n        }\n\n        switch condition {\n        case .notificationOptIn(_):\n            return Airship.push.notificationStatusPublisher\n                .receive(on: RunLoop.main)\n                .map { status in\n                    status.isUserOptedIn\n                }\n                .eraseToAnyPublisher()\n        }\n    }\n\n    @MainActor\n    private func checkConditions() -> Bool {\n        let conditionResults = self.conditions.map { self.checkCondition($0) }\n        return !conditionResults.contains(false)\n    }\n\n    @MainActor\n    private func checkCondition(_ condition: PreferenceCenterConfig.Condition)\n        -> Bool\n    {\n        guard Airship.isFlying else {\n            return true\n        }\n\n        switch condition {\n        case .notificationOptIn(let condition):\n            switch condition.optInStatus {\n            case .optedIn:\n                return Airship.push.isPushNotificationsOptedIn\n            case .optedOut:\n                return !Airship.push.isPushNotificationsOptedIn\n            }\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/ConditionsViewModifier.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct ConditionsViewModifier: ViewModifier {\n    @StateObject\n    var conditionsMonitor: ConditionsMonitor\n\n    @Binding\n    var binding: Bool\n\n    @ViewBuilder\n    func body(content: Content) -> some View {\n        content\n            .onReceive(conditionsMonitor.$isMet) { incoming in\n                binding = incoming\n            }\n    }\n}\n\nextension View {\n\n    @ViewBuilder\n    func preferenceConditions(\n        _ conditions: [PreferenceCenterConfig.Condition]?\n    ) -> some View {\n        self\n    }\n\n    @MainActor\n    @ViewBuilder\n    func preferenceConditions(\n        _ conditions: [PreferenceCenterConfig.Condition]?,\n        binding: Binding<Bool>\n    ) -> some View {\n        if let conditions = conditions {\n            self.modifier(\n                ConditionsViewModifier(\n                    conditionsMonitor: ConditionsMonitor(\n                        conditions: conditions\n                    ),\n                    binding: binding\n                )\n            )\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/AddChannelPromptView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic enum AddChannelState {\n    case failedInvalid\n    case failedDefault\n    case succeeded\n    case ready\n    case loading\n}\n\nstruct AddChannelPromptView: View, Sendable {\n    // MARK: - Constants\n    private enum Layout {\n        static let standardSpacing: CGFloat = 20\n        static let buttonTopPadding: CGFloat = 10\n        static let maxWidth: CGFloat = 500  // Consistent max width for sheets\n    }\n\n    // MARK: - Environment\n    @Environment(\\.colorScheme) private var colorScheme\n    @Environment(\\.dismiss) private var dismiss\n\n    // MARK: - State\n    @StateObject private var viewModel: AddChannelPromptViewModel\n    @State private var showSuccessAlert = false\n\n    // MARK: - Initialization\n    init(viewModel: AddChannelPromptViewModel) {\n        _viewModel = StateObject(wrappedValue: viewModel)\n    }\n\n    // MARK: - Computed Properties\n    private var errorMessage: String? {\n        switch viewModel.state {\n        case .failedInvalid:\n            return viewModel.platform?.errorMessages?.invalidMessage\n        case .failedDefault:\n            return viewModel.platform?.errorMessages?.defaultMessage\n        default:\n            return nil\n        }\n    }\n\n    private var isLoading: Bool {\n        viewModel.state == .loading\n    }\n\n    private var isInputValid: Bool {\n        !viewModel.inputText.isEmpty\n    }\n\n    private var hasError: Bool {\n        viewModel.state == .failedInvalid || viewModel.state == .failedDefault\n    }\n\n    private var successAlertTitle: String {\n        viewModel.item.onSubmit?.title ?? \"Success\"\n    }\n\n    // MARK: - Body\n    var body: some View {\n#if os(tvOS)\n        // On tvOS, use a simpler structure without NavigationStack\n        promptContentView\n            .interactiveDismissDisabled(isLoading)\n            .airshipOnChangeOf(viewModel.state) { newState in\n                handleStateChange(newState)\n            }\n            .alert(\n                successAlertTitle,\n                isPresented: $showSuccessAlert,\n                presenting: viewModel.item.onSubmit\n            ) { successPrompt in\n                successAlertButton(for: successPrompt)\n            } message: { successPrompt in\n                successAlertMessage(for: successPrompt)\n            }\n#else\n        NavigationStack {\n            promptContentView\n                .frame(maxWidth: Layout.maxWidth)\n        }\n        .interactiveDismissDisabled(isLoading)\n        .airshipOnChangeOf(viewModel.state) { newState in\n            handleStateChange(newState)\n        }\n        .alert(\n            successAlertTitle,\n            isPresented: $showSuccessAlert,\n            presenting: viewModel.item.onSubmit\n        ) { successPrompt in\n            successAlertButton(for: successPrompt)\n        } message: { successPrompt in\n            successAlertMessage(for: successPrompt)\n        }\n#endif\n    }\n\n    // MARK: - View Components\n\n    @ViewBuilder\n    private var promptContentView: some View {\n#if os(tvOS)\n        // tvOS: Custom header with title and cancel button\n        VStack(spacing: 0) {\n            // Custom header bar\n            HStack {\n                Text(viewModel.item.display.title)\n                    .font(.title2)\n                    .fontWeight(.semibold)\n                Spacer()\n                Button(\"ua_cancel_edit_messages\".preferenceCenterLocalizedString) {\n                    handleCancellation()\n                }\n                .buttonStyle(.bordered)\n            }\n            .padding()\n\n            ScrollView {\n                VStack(alignment: .leading, spacing: Layout.standardSpacing) {\n                    descriptionSection\n                    inputSection\n                    errorSection\n                    submitButtonSection\n                    footerSection\n\n                    Spacer(minLength: Layout.standardSpacing)\n                }\n                .padding()\n            }\n        }\n        .airshipOnChangeOf(viewModel.inputText) { _ in\n            resetErrorStateIfNeeded()\n        }\n#else\n        // iOS and other platforms: Use navigation bar\n        ScrollView {\n            VStack(alignment: .leading, spacing: Layout.standardSpacing) {\n                descriptionSection\n                inputSection\n                errorSection\n                submitButtonSection\n                footerSection\n\n                Spacer(minLength: Layout.standardSpacing)\n            }\n            .frame(maxWidth: .infinity)\n            .padding()\n        }\n        .navigationTitle(viewModel.item.display.title)\n#if !os(macOS)\n        .navigationBarTitleDisplayMode(.inline)\n#endif\n        .toolbar {\n            ToolbarItem(placement: .cancellationAction) {\n                cancelButton\n            }\n        }\n        .airshipOnChangeOf(viewModel.inputText) { _ in\n            resetErrorStateIfNeeded()\n        }\n#endif\n    }\n\n    @ViewBuilder\n    private var descriptionSection: some View {\n        if let bodyText = viewModel.item.display.body {\n            Text(bodyText)\n                .textAppearance(\n                    viewModel.theme?.subtitleAppearance,\n                    base: PreferenceCenterDefaults.sectionSubtitleAppearance,\n                    colorScheme: colorScheme\n                )\n        }\n    }\n\n    @ViewBuilder\n    private var inputSection: some View {\n        ChannelTextField(\n            platform: viewModel.platform,\n            selectedSender: $viewModel.selectedSender,\n            inputText: $viewModel.inputText,\n            theme: viewModel.theme\n        )\n    }\n\n    @ViewBuilder\n    private var errorSection: some View {\n        if let errorMessage = errorMessage {\n            ErrorLabel(\n                message: errorMessage,\n                theme: viewModel.theme\n            )\n        }\n    }\n\n    @ViewBuilder\n    private var submitButtonSection: some View {\n        submitButton\n            .padding(.top, Layout.buttonTopPadding)\n    }\n\n    @ViewBuilder\n    private var footerSection: some View {\n        if let footer = viewModel.item.display.footer {\n            FooterView(\n                text: footer,\n                textAppearance: viewModel.theme?.subtitleAppearance ?? PreferenceCenterDefaults.subtitleAppearance\n            )\n            .padding(.top, Layout.standardSpacing)\n        }\n    }\n\n    @ViewBuilder\n    private var submitButton: some View {\n        Button(action: handleSubmission) {\n            HStack {\n                Spacer()\n                \n                if isLoading {\n                    ProgressView()\n                        .progressViewStyle(CircularProgressViewStyle())\n                } else {\n                    Text(viewModel.item.submitButton.text)\n                }\n                \n                Spacer()\n            }\n        }\n        .buttonStyle(.borderedProminent)\n#if !os(tvOS)\n        .controlSize(.large)\n#endif\n        .disabled(isLoading || !isInputValid)\n        .optAccessibilityLabel(\n            string: viewModel.item.submitButton.contentDescription\n        )\n    }\n\n    @ViewBuilder\n    private var cancelButton: some View {\n        Button(\n            \"ua_cancel_edit_messages\".preferenceCenterLocalizedString,\n            systemImage: \"xmark\"\n        ) {\n            handleCancellation()\n        }\n        .disabled(isLoading)\n    }\n\n    // MARK: - Alert Components\n    @ViewBuilder\n    private func successAlertButton(\n        for successPrompt: PreferenceCenterConfig.ContactManagementItem.ActionableMessage\n    ) -> some View {\n        Button {\n            handleSuccessCompletion()\n        } label: {\n            Text(successPrompt.button.text)\n        }\n    }\n\n    @ViewBuilder\n    private func successAlertMessage(\n        for successPrompt: PreferenceCenterConfig.ContactManagementItem.ActionableMessage\n    ) -> some View {\n        if let body = successPrompt.body {\n            Text(body)\n        }\n    }\n\n    // MARK: - Actions\n    private func handleStateChange(_ newState: AddChannelState) {\n        guard newState == .succeeded else { return }\n\n        if viewModel.item.onSubmit != nil {\n            showSuccessAlert = true\n        } else {\n            handleSuccessCompletion()\n        }\n    }\n\n    private func handleSubmission() {\n        viewModel.attemptSubmission()\n    }\n\n    private func handleCancellation() {\n        viewModel.onCancel()\n        dismiss()\n    }\n\n    private func handleSuccessCompletion() {\n        viewModel.onSubmit()\n        dismiss()\n    }\n\n    private func resetErrorStateIfNeeded() {\n        guard hasError else { return }\n\n        withAnimation {\n            viewModel.state = .ready\n        }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/AddChannelPromptViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\nimport Combine\n\n/// Imported for Logger and Contact calls\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\ninternal class AddChannelPromptViewModel: ObservableObject {\n    let inputValidator: (any AirshipInputValidation.Validator)?\n\n    @Published var state: AddChannelState = .ready\n    @Published var selectedSender: PreferenceCenterConfig.ContactManagementItem.SMSSenderInfo\n    @Published var inputText = \"\"\n    @Published var isInputFormatValid = false\n\n    var theme: PreferenceCenterTheme.ContactManagement?\n\n    internal let item: PreferenceCenterConfig.ContactManagementItem.AddChannelPrompt\n    internal let platform: PreferenceCenterConfig.ContactManagementItem.Platform?\n    internal let onCancel: () -> Void\n    internal let onRegisterSMS: (_ msisdn: String, _ senderID: String) -> Void\n    internal let onRegisterEmail: (_ email: String) -> Void\n\n    private var validatedAddress: String?\n\n    internal init(\n        item: PreferenceCenterConfig.ContactManagementItem.AddChannelPrompt,\n        theme: PreferenceCenterTheme.ContactManagement?,\n        registrationOptions: PreferenceCenterConfig.ContactManagementItem.Platform?,\n        onCancel: @escaping () -> Void,\n        onRegisterSMS: @escaping (_ msisdn: String, _ senderID: String) -> Void,\n        onRegisterEmail: @escaping (_ email: String) -> Void,\n        validator: (any AirshipInputValidation.Validator)? = nil\n    ) {\n        self.item = item\n        self.theme = theme\n        self.platform = registrationOptions\n        self.onCancel = onCancel\n        self.onRegisterSMS = onRegisterSMS\n        self.onRegisterEmail = onRegisterEmail\n        self.selectedSender = .none\n        self.inputValidator = if Airship.isFlying {\n            validator ?? Airship.requireComponent(\n                ofType: PreferenceCenterComponent.self\n            ).preferenceCenter.inputValidator\n        } else {\n            validator\n        }\n    }\n\n    /// Attempts submission and updates state based on results of attempt\n    @MainActor\n    internal func attemptSubmission() {\n        Task {\n            if platform?.channelType == .sms {\n                await attemptSMSSubmission()\n            } else {\n                await attemptEmailSubmission()\n            }\n        }\n    }\n\n    @MainActor\n    private func attemptSMSSubmission() async {\n        do {\n            /// Only start to load when we are sure it's not a duplicate failed request\n            onStartLoading()\n\n            let smsRequest: AirshipInputValidation.Request = .sms(\n                AirshipInputValidation.Request.SMS(\n                    rawInput: self.inputText,\n                    validationOptions: .sender(senderID: selectedSender.senderId, prefix: selectedSender.countryCode),\n                    validationHints: .init(minDigits: 4)\n                )\n            )\n\n            /// Attempt validation call\n            let passedValidation = try await inputValidator?.validateRequest(smsRequest) ?? .invalid\n\n            if case let .valid(address) = passedValidation {\n                validatedAddress = address\n                onValidationSucceeded()\n            } else {\n                onValidationFailed()\n            }\n\n            return\n        } catch {\n            AirshipLogger.error(error.localizedDescription)\n        }\n\n        /// Even if an error is thrown, if this ever is hit something went wrong, show it as a generic error\n        onValidationError()\n    }\n\n    @MainActor\n    private func attemptEmailSubmission() async {\n        onStartLoading()\n\n        let emailRequest: AirshipInputValidation.Request = .email(\n            AirshipInputValidation.Request.Email(\n                rawInput: self.inputText\n            )\n        )\n\n        do {\n            let passedValidation = try await inputValidator?.validateRequest(emailRequest) ?? .invalid\n\n            if case let .valid(address) = passedValidation {\n                validatedAddress = address\n                onValidationSucceeded()\n            } else {\n                onValidationFailed()\n            }\n        } catch {\n            AirshipLogger.error(error.localizedDescription)\n            onValidationError()\n        }\n    }\n\n    internal func onSubmit() {\n        if let platform = platform, let validatedAddress = validatedAddress {\n            switch platform {\n            case .sms(_):\n                onRegisterSMS(validatedAddress, selectedSender.senderId)\n            case .email(_):\n                onRegisterEmail(validatedAddress)\n            }\n        }\n\n        validatedAddress = nil\n    }\n\n    @MainActor\n    internal func onStartLoading() {\n        withAnimation {\n            self.state = .loading\n        }\n    }\n\n    @MainActor\n    internal func onValidationSucceeded() {\n        withAnimation {\n            self.state = .succeeded\n        }\n    }\n\n    @MainActor\n    private func onValidationFailed() {\n        withAnimation {\n            self.state = .failedInvalid\n        }\n    }\n\n    @MainActor\n    private func onValidationError() {\n        withAnimation {\n            self.state = .failedDefault\n        }\n    }\n\n    /// Validates input format for UI feedback (enabling/disabling submit button)\n    @MainActor\n    internal func validateInputFormat() {\n        if let platform = self.platform {\n            // Basic validation to enable/disable submit button\n            // Full validation happens in attemptSubmission\n            switch platform {\n            case .email(_):\n                let emailRegex = \"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\\\.[A-Za-z]{2,64}\"\n                let emailPredicate = NSPredicate(format: \"SELF MATCHES %@\", emailRegex)\n                isInputFormatValid = emailPredicate.evaluate(with: inputText)\n            case .sms(_):\n                let formattedPhone = inputText.replacingOccurrences(of: \"[^0-9]\", with: \"\", options: .regularExpression)\n                isInputFormatValid = formattedPhone.count >= 7\n            }\n        } else {\n            isInputFormatValid = false\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/ChannelListView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct ChannelListView: View {\n    // MARK: - Constants\n    private enum Layout {\n        static let presentationDetentHeight: CGFloat = 0.5 // medium detent as fraction\n    }\n\n    // MARK: - Environment\n    @Environment(\\.colorScheme) private var colorScheme\n    @Environment(\\.airshipPreferenceCenterTheme) private var theme: PreferenceCenterTheme\n\n    // MARK: - Properties\n    let item: PreferenceCenterConfig.ContactManagementItem\n    @ObservedObject var state: PreferenceCenterState\n\n    // MARK: - State\n    @State private var selectedChannel: ContactChannel?\n    @State private var showAddChannelSheet = false\n    @State private var showRemoveChannelAlert = false\n    @State private var showResendSuccessAlert = false\n\n    // MARK: - Computed Properties\n    private var channels: [ContactChannel] {\n        state.channelsList.filter(with: item.platform.channelType)\n    }\n\n    private var isChannelListEmpty: Bool {\n        channels.isEmpty\n    }\n\n    private var removeAlertTitle: String {\n        item.removeChannel?.view.display.title ?? \"Remove Channel\"\n    }\n\n    private var resendAlertTitle: String {\n        pendingLabelModel?.resendSuccessPrompt?.title ?? \"Verification Sent\"\n    }\n\n    private var pendingLabelModel: PreferenceCenterConfig.ContactManagementItem.PendingLabel? {\n        switch item.platform {\n        case .sms(let options):\n            return options.pendingLabel\n        case .email(let options):\n            return options.pendingLabel\n        }\n    }\n\n    // MARK: - Body\n    var body: some View {\n        VStack(alignment: .leading) {\n            Section {\n                VStack(alignment: .leading) {\n                    if isChannelListEmpty {\n                        EmptySectionLabel(label: item.emptyMessage)\n                    } else {\n                        channelListView\n                    }\n\n                    if let buttonModel = item.addChannel?.button {\n                        addChannelButton(model: buttonModel)\n                    }\n                }\n            } header: {\n                headerView\n            }\n        }\n        .sheet(isPresented: $showAddChannelSheet) {\n            addChannelPromptView.presentationDetents([.medium])\n        }\n        .alert(\n            removeAlertTitle,\n            isPresented: $showRemoveChannelAlert,\n            presenting: selectedChannel\n        ) { channel in\n            removeAlertButtons(for: channel)\n        } message: { _ in\n            removeAlertMessage\n        }\n        .alert(\n            resendAlertTitle,\n            isPresented: $showResendSuccessAlert,\n            presenting: selectedChannel\n        ) { _ in\n            resendAlertButton\n        } message: { _ in\n            resendAlertMessage\n        }\n    }\n\n    // MARK: - View Components\n    @ViewBuilder\n    private var headerView: some View {\n        HStack {\n            VStack(alignment: .leading) {\n                Text(item.display.title)\n                    .textAppearance(\n                        theme.contactManagement?.titleAppearance,\n                        base: PreferenceCenterDefaults.sectionTitleAppearance,\n                        colorScheme: colorScheme\n                    )\n                    .accessibilityAddTraits(.isHeader)\n\n                if let subtitle = item.display.subtitle {\n                    Text(subtitle)\n                        .textAppearance(\n                            theme.contactManagement?.subtitleAppearance,\n                            base: PreferenceCenterDefaults.sectionSubtitleAppearance,\n                            colorScheme: colorScheme\n                        )\n                }\n            }\n            Spacer()\n        }\n        .accessibilityAddTraits(.isHeader)\n    }\n\n    @ViewBuilder\n    private var channelListView: some View {\n        ForEach(channels, id: \\.self) { channel in\n            ChannelListViewCell(\n                viewModel: ChannelListCellViewModel(\n                    channel: channel,\n                    pendingLabelModel: pendingLabelModel,\n                    onResend: { resend(channel) },\n                    onRemove: { remove(channel) }\n                )\n            )\n#if os(tvOS)\n            .focusSection()\n#endif\n        }\n    }\n\n    @ViewBuilder\n    private func addChannelButton(model: PreferenceCenterConfig.ContactManagementItem.LabeledButton) -> some View {\n        HStack {\n            Button {\n                if item.addChannel?.view != nil {\n                    showAddChannelSheet = true\n                }\n            } label: {\n                Text(model.text)\n                    .textAppearance(\n                        theme.contactManagement?.buttonLabelAppearance,\n                        colorScheme: colorScheme\n                    )\n            }\n#if !os(tvOS)\n            .controlSize(.regular)\n#endif\n\n\n            Spacer()\n        }\n#if os(tvOS)\n        .focusSection()\n#endif\n    }\n\n    @ViewBuilder\n    private var addChannelPromptView: some View {\n        if let view = item.addChannel?.view {\n            AddChannelPromptView(\n                viewModel: AddChannelPromptViewModel(\n                    item: view,\n                    theme: theme.contactManagement,\n                    registrationOptions: item.platform,\n                    onCancel: { showAddChannelSheet = false },\n                    onRegisterSMS: registerSMS,\n                    onRegisterEmail: registerEmail\n                )\n            )\n        }\n    }\n\n    // MARK: - Alert Components\n    @ViewBuilder\n    private func removeAlertButtons(for channel: ContactChannel) -> some View {\n        Button(role: .destructive) {\n            AirshipLogger.info(\"Removing channel: \\(channel.channelType)\")\n            Airship.contact.disassociateChannel(channel)\n            selectedChannel = nil\n        } label: {\n            Text(item.removeChannel?.view.submitButton?.text ?? \"Remove\")\n        }\n\n        Button(role: .cancel) {\n            selectedChannel = nil\n        } label: {\n            Text(item.removeChannel?.view.cancelButton?.text ?? \"Cancel\")\n        }\n    }\n\n    @ViewBuilder\n    private var removeAlertMessage: some View {\n        if let body = item.removeChannel?.view.display.body {\n            Text(body)\n        }\n    }\n\n    @ViewBuilder\n    private var resendAlertButton: some View {\n        Button {\n            selectedChannel = nil\n        } label: {\n            Text(pendingLabelModel?.resendSuccessPrompt?.button.text ?? \"OK\")\n        }\n    }\n\n    @ViewBuilder\n    private var resendAlertMessage: some View {\n        if let body = pendingLabelModel?.resendSuccessPrompt?.body {\n            Text(body)\n        }\n    }\n\n    // MARK: - Actions\n    private func resend(_ channel: ContactChannel) {\n        selectedChannel = channel\n        Airship.contact.resend(channel)\n        AirshipLogger.info(\"Resending verification for channel: \\(channel.channelType)\")\n        showResendSuccessAlert = true\n    }\n\n    private func remove(_ channel: ContactChannel) {\n        selectedChannel = channel\n        AirshipLogger.info(\"Showing remove confirmation for channel: \\(channel.channelType)\")\n        showRemoveChannelAlert = true\n    }\n\n    private func registerSMS(msisdn: String, sender: String) {\n        let options = SMSRegistrationOptions.optIn(senderID: sender)\n        Airship.contact.registerSMS(msisdn, options: options)\n        AirshipLogger.info(\"Registered SMS channel: \\(msisdn)\")\n    }\n\n    private func registerEmail(email: String) {\n        let options = EmailRegistrationOptions.options(properties: nil, doubleOptIn: true)\n        Airship.contact.registerEmail(email, options: options)\n        AirshipLogger.info(\"Registered email channel: \\(email)\")\n    }\n}\n\n// MARK: - Extensions\nextension PreferenceCenterConfig.ContactManagementItem.Platform {\n    var channelType: ChannelType {\n        switch self {\n        case .sms: return .sms\n        case .email: return .email\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/ChannelListViewCell.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nimport Combine\n\n@MainActor\nclass ChannelListCellViewModel: ObservableObject {\n    let channel: ContactChannel\n    let pendingLabelModel: PreferenceCenterConfig.ContactManagementItem.PendingLabel?\n\n    @Published\n    internal var isPendingLabelShowing: Bool = false\n\n    @Published\n    internal var isResendShowing: Bool = false\n\n    let onResend: () -> Void\n    let onRemove: () -> Void\n\n    private var resendLabelHideDelaySeconds: Double { Double(pendingLabelModel?.intervalInSeconds ?? 15) }\n\n    private var hidePendingLabelTask:Task<Void, Never>?\n    private var hideResendButtonTask:Task<Void, Never>?\n\n    init(channel: ContactChannel,\n         pendingLabelModel: PreferenceCenterConfig.ContactManagementItem.PendingLabel?,\n         onResend: @escaping () -> (),\n         onRemove: @escaping () -> Void\n    ) {\n        self.channel = channel\n        self.pendingLabelModel = pendingLabelModel\n        self.onResend = onResend\n        self.onRemove = onRemove\n        initializePendingLabel()\n    }\n\n    private func initializePendingLabel() {\n        let isOptedIn = channel.isOptedIn\n\n        if isOptedIn {\n            withAnimation {\n                isPendingLabelShowing = false\n                isResendShowing = false\n            }\n        } else {\n            withAnimation {\n                isPendingLabelShowing = true\n            }\n            temporarilyHideResend()\n        }\n    }\n\n    /// Used to temporarily hide the resend button for the interval provided by the model, also used to hide the button after each tap\n    func temporarilyHideResend() {\n        withAnimation {\n            isResendShowing = false\n        }\n\n        hideResendButtonTask?.cancel()\n        hideResendButtonTask = Task { @MainActor [weak self] in\n            guard let self = self, !Task.isCancelled else { return}\n\n            try? await Task.sleep(nanoseconds: UInt64(resendLabelHideDelaySeconds * 1_000_000_000))\n\n            guard !Task.isCancelled, !channel.isOptedIn else { return}\n\n            withAnimation {\n                self.isResendShowing = true\n            }\n        }\n    }\n}\n\nstruct ChannelListViewCell: View {\n    @StateObject private var viewModel: ChannelListCellViewModel\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var theme: PreferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    init(viewModel: ChannelListCellViewModel) {\n        _viewModel = StateObject(wrappedValue: viewModel)\n    }\n\n    @ViewBuilder\n    private var pendingLabelView: some View {\n        HStack(spacing: PreferenceCenterDefaults.smallPadding) {\n            if let pendingText = viewModel.pendingLabelModel?.message {\n                Text(pendingText).textAppearance(\n                    theme.contactManagement?.listSubtitleAppearance,\n                    base: PreferenceCenterDefaults.channelListItemSubtitleAppearance,\n                    colorScheme: colorScheme\n                )\n            }\n\n            if viewModel.isResendShowing {\n                resendButton\n            }\n\n            Spacer()\n        }\n    }\n\n    @ViewBuilder\n    private var resendButton: some View {\n        if let resendTitle = viewModel.pendingLabelModel?.button.text {\n            Button {\n                viewModel.temporarilyHideResend()\n                viewModel.onResend()\n            } label: {\n                Text(resendTitle).textAppearance(\n                    theme.contactManagement?.listSubtitleAppearance,\n                    base: PreferenceCenterDefaults.resendButtonTitleAppearance,\n                    colorScheme: colorScheme\n                )\n            }\n        }\n    }\n\n    private var trashButton: some View {\n        Button(action: {\n            viewModel.onRemove()\n        }) {\n            Image(systemName: \"trash\")\n                .foregroundColor(\n                    theme.contactManagement?.titleAppearance?.color ?? .primary\n                )\n        }\n    }\n\n    @ViewBuilder\n    private func makeCellLabel(iconSystemName: String, labelText: String) -> some View {\n        SwiftUI.Label {\n            Text(labelText).textAppearance(\n                theme.contactManagement?.listSubtitleAppearance,\n                base: PreferenceCenterDefaults.channelListItemTitleAppearance,\n                colorScheme: colorScheme\n            )\n            .lineLimit(1)\n            .truncationMode(.middle)\n        } icon: {\n            Image(systemName: iconSystemName)\n                .textAppearance(\n                    theme.contactManagement?.listSubtitleAppearance,\n                    base: PreferenceCenterDefaults.channelListItemTitleAppearance,\n                    colorScheme: colorScheme\n                )\n        }\n    }\n\n    @ViewBuilder\n    private func makePendingLabel(channel: ContactChannel) -> some View {\n        if (channel.isRegistered) {\n            let isOptedIn = channel.isOptedIn\n            if viewModel.isPendingLabelShowing, !isOptedIn {\n                pendingLabelView\n            }\n        } else {\n            if viewModel.isPendingLabelShowing {\n                pendingLabelView\n            }\n        }\n    }\n\n    @ViewBuilder\n    private func makeCell(channel: ContactChannel) -> some View {\n        VStack(alignment: .leading) {\n            let cellText = channel.maskedAddress\n            if channel.channelType == .email {\n                makeCellLabel(iconSystemName: \"envelope\", labelText: cellText)\n            } else {\n                makeCellLabel(iconSystemName: \"phone\", labelText: cellText)\n            }\n\n            makePendingLabel(channel: channel)\n        }\n    }\n\n    private var cellBody: some View {\n        VStack(alignment: .leading) {\n            HStack(alignment: .top) {\n                makeCell(channel: viewModel.channel)\n                Spacer()\n                trashButton\n            }\n        }\n        .padding(PreferenceCenterDefaults.smallPadding)\n    }\n\n    var body: some View {\n        cellBody\n    }\n}\n\n\nextension ContactChannel {\n    var isOptedIn: Bool {\n        switch (self) {\n        case .email(let email):\n            switch(email) {\n            case .pending(_): return false\n            case .registered(let info):\n                guard let optedOut = info.commercialOptedOut else {\n                    return info.commercialOptedIn != nil\n                }\n                return info.commercialOptedIn?.compare(optedOut) == .orderedDescending /// Make sure optedIn date is after opt out date if both exist\n#if canImport(AirshipCore)\n            @unknown default:\n                return false\n#endif\n            }\n        case .sms(let sms):\n            switch(sms) {\n            case .pending(_): return false\n            case .registered(let info): return info.isOptIn\n#if canImport(AirshipCore)\n            @unknown default:\n                return false\n#endif\n            }\n#if canImport(AirshipCore)\n        @unknown default:\n            return false\n#endif\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/Component Views/BackgroundShape.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n// MARK: Background\nstruct BackgroundShape: View {\n    var color: Color\n    var body: some View {\n        Rectangle()\n            .fill(color)\n            .cornerRadius(10)\n            .shadow(radius: 5)\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/Component Views/ChannelTextField.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n// MARK: Channel text field\npublic struct ChannelTextField: View {\n    // MARK: - Constants\n    private enum Layout {\n#if os(tvOS)\n        static let fieldHeight: CGFloat = 66  // tvOS needs taller fields for focus\n        static let fieldPadding: CGFloat = 12\n#else\n        static let fieldHeight: CGFloat = 52\n        static let fieldPadding: CGFloat = 10\n#endif\n        static let fieldCornerRadius: CGFloat = 4\n        static let stackSpacing: CGFloat = 2\n        static let standardSpacing: CGFloat = 12\n        static let placeHolderPadding = EdgeInsets(top: 4, leading: 15, bottom: 4, trailing: 4)\n    }\n\n    // MARK: - Environment\n    @Environment(\\.colorScheme) var colorScheme\n\n    // MARK: - Properties\n    private var senders: [PreferenceCenterConfig.ContactManagementItem.SMSSenderInfo]?\n    private var platform: PreferenceCenterConfig.ContactManagementItem.Platform?\n    private var smsOptions: PreferenceCenterConfig.ContactManagementItem.SMS?\n    private var emailOptions: PreferenceCenterConfig.ContactManagementItem.Email?\n\n    // MARK: - State\n    @Binding var selectedSender: PreferenceCenterConfig.ContactManagementItem.SMSSenderInfo\n    @State var selectedSenderID: String = \"\"\n    @Binding var inputText: String\n    @State private var placeholder: String = \"\"\n\n    /// The preference center theme\n    var theme: PreferenceCenterTheme.ContactManagement?\n\n    public init(\n        platform: PreferenceCenterConfig.ContactManagementItem.Platform?,\n        selectedSender: Binding<PreferenceCenterConfig.ContactManagementItem.SMSSenderInfo>,\n        inputText: Binding<String>,\n        theme: PreferenceCenterTheme.ContactManagement?\n    ) {\n        self.platform = platform\n        _selectedSender = selectedSender\n        _inputText = inputText\n\n        self.theme = theme\n\n        if let platform = self.platform {\n            switch platform {\n            case .sms(let options):\n                self.senders = options.senders\n                smsOptions = options\n            case .email(let options):\n                emailOptions = options\n            }\n        }\n\n        self.placeholder = makePlaceholder()\n    }\n\n    public var body: some View {\n        VStack(spacing: Layout.standardSpacing) {\n            countryPicker\n\n            VStack {\n                HStack(spacing: Layout.stackSpacing) {\n                    textFieldLabel\n                    textField\n                }\n                .padding(Layout.fieldPadding)\n                .background(backgroundView)\n            }\n            .frame(height: Layout.fieldHeight)\n        }\n        .frame(maxWidth: .infinity)\n    }\n\n    @ViewBuilder\n    private var countryPicker: some View {\n        /// Use text field color for picker accent. May want to expose this separately at some point.\n        let pickerAccent = colorScheme.airshipResolveColor(light: theme?.textFieldTextAppearance?.color, dark: theme?.textFieldTextAppearance?.colorDark)\n        if let senders = self.senders, (senders.count >= 1) {\n            HStack {\n                if let smsOptions = smsOptions {\n                    Text(smsOptions.countryLabel).textAppearance(\n                        theme?.textFieldPlaceholderAppearance,\n                        base: PreferenceCenterDefaults.textFieldTextAppearance,\n                        colorScheme: colorScheme\n                    )\n                }\n                Spacer()\n                Picker(\"senders\", selection: $selectedSenderID) {\n                    ForEach(senders, id: \\.self) {\n                        Text($0.displayName).textAppearance(\n                            theme?.textFieldPlaceholderAppearance,\n                            base: PreferenceCenterDefaults.textFieldTextAppearance,\n                            colorScheme: colorScheme\n                        ).tag($0.senderId)\n                    }\n                }\n                .pickerStyle(.menu)\n                .accentColor(pickerAccent ?? AirshipSystemColors.label)\n                .airshipOnChangeOf(self.selectedSenderID, { newVal in\n                    if let sender = senders.first(where: { $0.senderId == newVal }) {\n                        selectedSender = sender\n                        /// Update placeholder with selection\n                        placeholder = makePlaceholder()\n                    }\n                })\n                .onAppear {\n                    /// Ensure initial value is set\n                    if let sender = self.senders?.first {\n                        self.selectedSenderID = sender.senderId\n                    }\n                }\n            }\n            .padding(.horizontal)\n            .frame(height: Layout.fieldHeight)\n            .background(backgroundView)\n        }\n    }\n\n    @ViewBuilder\n    private var textField: some View {\n        let textColor = colorScheme.airshipResolveColor(\n            light: theme?.textFieldTextAppearance?.color,\n            dark: theme?.textFieldTextAppearance?.colorDark\n        )\n\n        TextField(makePlaceholder(), text: $inputText)\n            .foregroundColor(textColor)\n            .padding(Layout.placeHolderPadding)\n#if !os(macOS)\n            .keyboardType(keyboardType)\n#endif\n    }\n\n    @ViewBuilder\n    private var textFieldLabel: some View {\n        if let smsOptions = smsOptions {\n            Text(smsOptions.msisdnLabel)\n                .textAppearance(\n                    theme?.textFieldPlaceholderAppearance,\n                    base: PreferenceCenterDefaults.textFieldTextAppearance,\n                    colorScheme: colorScheme\n                )\n        } else if let emailOptions = emailOptions {\n            Text(emailOptions.addressLabel)\n                .textAppearance(\n                    theme?.textFieldPlaceholderAppearance,\n                    base: PreferenceCenterDefaults.textFieldTextAppearance,\n                    colorScheme: colorScheme\n                )\n        }\n    }\n\n    @ViewBuilder\n    private var backgroundView: some View {\n        let backgroundColor = colorScheme.airshipResolveColor(\n            light: theme?.backgroundColor,\n            dark: theme?.backgroundColorDark\n        ) ?? AirshipSystemColors.background\n\n        RoundedRectangle(cornerRadius: Layout.fieldCornerRadius)\n            .foregroundColor(backgroundColor.secondaryVariant(for: colorScheme).opacity(0.2))\n    }\n\n    // MARK: Keyboard type\n\n#if !os(macOS)\n    private var keyboardType: UIKeyboardType {\n        if let platform = self.platform {\n            switch platform {\n            case .sms(_):\n                return .decimalPad\n            case .email(_):\n                return .emailAddress\n            }\n        } else {\n            return .default\n        }\n    }\n#endif\n\n    // MARK: Placeholder\n    private func makePlaceholder() -> String {\n        let defaultPlaceholder = \"\"\n        if let platform = self.platform {\n            switch platform {\n            case .sms(_):\n                return self.selectedSender.placeholderText\n            case .email(let emailRegistrationOption):\n                return emailRegistrationOption.placeholder ?? defaultPlaceholder\n            }\n        } else {\n            return defaultPlaceholder\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/Component Views/EmptySectionLabel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nstruct EmptySectionLabel: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    // The empty message\n    var label: String?\n\n    /// The preference center theme\n    var theme: PreferenceCenterTheme.ChannelSubscription?\n\n    public var body: some View {\n        if let label = label {\n            SwiftUI.Label {\n                Text(label)\n                    .textAppearance(\n                        theme?.emptyTextAppearance,\n                        base: PreferenceCenterDefaults.subtitleAppearance,\n                        colorScheme: colorScheme\n                    )\n            } icon: {\n                Image(systemName: \"info.circle\")\n                    .foregroundColor(.primary.opacity(0.5))\n            }\n            .padding(PreferenceCenterDefaults.smallPadding)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/Component Views/ErrorLabel.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\n\n/// Error text view that appears under the add channel fields when an error occurs\npublic struct ErrorLabel: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    public var message: String?\n    var theme: PreferenceCenterTheme.ContactManagement?\n\n    public init(\n        message: String?,\n        theme: PreferenceCenterTheme.ContactManagement?\n    ) {\n        self.message = message\n        self.theme = theme\n    }\n\n    public var body: some View {\n        if let errorMessage = self.message {\n            HStack (alignment: .top){\n                Text(errorMessage)\n                    .textAppearance(\n                        theme?.errorAppearance,\n                        base: PreferenceCenterDefaults.errorAppearance,\n                        colorScheme: colorScheme\n                    )\n                    .lineLimit(2)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/Component Views/PreferenceCloseButton.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct PreferenceCloseButton: View {\n    internal init(dismissIconColor: Color, dismissIconResource:String, onTap: @escaping () -> ()) {\n        self.dismissIconColor = dismissIconColor\n        self.dismissIconResource = dismissIconResource\n        self.onTap = onTap\n    }\n\n    let dismissIconColor: Color\n    let dismissIconResource: String\n\n    let onTap: () -> Void\n\n    private let opacity: CGFloat = 0.64\n\n    private let height: CGFloat = 24\n    private let width: CGFloat = 24\n\n    private let tappableHeight: CGFloat = 44\n    private let tappableWidth: CGFloat = 44\n\n    private func imageExistsInBundle(name: String) -> Bool {\n        return AirshipNativeImage(named: name) != nil\n    }\n\n    /// Check bundle and system for resource name\n    /// If system image assume it's an icon and add a circular background\n    @ViewBuilder\n    private var dismissButtonImage: some View {\n        imageExistsInBundle(name: dismissIconResource) ?\n        AnyView(Image(dismissIconResource)\n            .resizable()\n            .frame(width: width/2, height: height/2)) :\n        AnyView(Image(systemName: dismissIconResource)\n            .resizable()\n            .frame(width: width/2, height: height/2)\n            .foregroundColor(dismissIconColor)\n            .padding()\n            .clipShape(Circle()))\n    }\n\n    var body: some View {\n        Button(action: onTap) {\n            VStack(alignment:.center, spacing:0) {\n                Spacer()\n                dismissButtonImage\n                    .opacity(opacity)\n                Spacer()\n            }\n            .frame(width: tappableWidth, height: tappableHeight)\n        }\n        .accessibilityLabel(\"Dismiss\")\n    }\n}\n\n#Preview {\n    PreferenceCloseButton(dismissIconColor: .primary,\n                dismissIconResource: \"xmark\",\n                onTap: {})\n    .background(Color.green)\n}\n\nextension View {\n    @ViewBuilder\n    func addPromptBackground(theme: PreferenceCenterTheme.ContactManagement?, colorScheme: ColorScheme) -> some View {\n        let color = colorScheme.airshipResolveColor(\n            light: theme?.backgroundColor,\n            dark: theme?.backgroundColorDark\n        )\n\n        self.background(\n            BackgroundShape(\n                color: color ?? PreferenceCenterDefaults.promptBackgroundColor\n            )\n        )\n    }\n\n    @ViewBuilder\n    func addPreferenceCloseButton(\n        dismissButtonColor: Color,\n        dismissIconResource: String,\n        contentDescription: String?,\n        onUserDismissed: @escaping () -> Void\n    ) -> some View {\n        ZStack(alignment: .topTrailing) { // Align close button to the top trailing corner\n            self.zIndex(0)\n            PreferenceCloseButton(\n                dismissIconColor: dismissButtonColor,\n                dismissIconResource: dismissIconResource,\n                onTap: onUserDismissed\n            )\n            .airshipApplyIf(contentDescription != nil) { view in\n                view.accessibilityLabel(contentDescription!)\n            }\n            .zIndex(1)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/ContactManagementView.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\npublic struct PreferenceCenterContactManagementView: View {\n\n    /// The item's config\n    public let item: PreferenceCenterConfig.ContactManagementItem\n\n    /// The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipContactManagementSectionStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var theme: PreferenceCenterTheme\n    \n    @State\n    private var displayConditionsMet: Bool = true\n    \n    public init(\n        item: PreferenceCenterConfig.ContactManagementItem,\n        state: PreferenceCenterState\n    ) {\n        self.item = item\n        self.state = state\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        let configuration = ContactManagementSectionStyleConfiguration(\n            section: self.item,\n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.theme\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.item.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\n/// The labeled section break style configuration\npublic struct ContactManagementSectionStyleConfiguration: Sendable {\n\n    /// The section config\n    public let section: PreferenceCenterConfig.ContactManagementItem\n\n    /// The preference state\n    public let state: PreferenceCenterState\n    \n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n    \n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n}\n\n\nextension View {\n    /// Sets the contact management section style\n    /// - Parameters:\n    ///     - style: The style\n    public func ContactManagementSectionStyle<S>(_ style: S) -> some View\n    where S: ContactManagementSectionStyle {\n        self.environment(\n            \\.airshipContactManagementSectionStyle,\n             AnyContactManagementSectionStyle(style: style)\n        )\n    }\n}\n\n/// Contact management section style\npublic protocol ContactManagementSectionStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = ContactManagementSectionStyleConfiguration\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension ContactManagementSectionStyle\nwhere Self == DefaultContactManagementSectionStyle {\n\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n\n// MARK: - DEFAULT Contact Management View\n/// Default contact management section style. Also styles alert views.\npublic struct DefaultContactManagementSectionStyle: ContactManagementSectionStyle {\n\n    @ViewBuilder\n    public func makeBody(configuration: Configuration) -> some View {\n        if configuration.displayConditionsMet {\n            DefaultContactManagementView(configuration: configuration)\n        }\n    }\n}\n\nprivate struct DefaultContactManagementView: View {\n\n    /// The item's config\n    public let configuration: ContactManagementSectionStyleConfiguration\n\n    var body: some View {\n        ChannelListView(\n            item: configuration.section,\n            state: configuration.state\n        )\n        .transition(.opacity)\n    }\n}\n\nstruct AnyContactManagementSectionStyle: ContactManagementSectionStyle {\n    @ViewBuilder\n    private let _makeBody: @Sendable (Configuration) -> AnyView\n\n    init<S: ContactManagementSectionStyle>(style: S) {\n        _makeBody = { configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct ContactManagementSectionStyleKey: EnvironmentKey {\n    static let defaultValue = AnyContactManagementSectionStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipContactManagementSectionStyle: AnyContactManagementSectionStyle {\n        get { self[ContactManagementSectionStyleKey.self] }\n        set { self[ContactManagementSectionStyleKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/Contact management/FooterView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nstruct FooterView: View {\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    let text: String\n    let textAppearance: PreferenceCenterTheme.TextAppearance\n\n    var body: some View {\n        /// Footer\n        Text(LocalizedStringKey(text))\n            .textAppearance(\n                textAppearance,\n                colorScheme: colorScheme\n            )\n            .fixedSize(horizontal: false, vertical: true)\n            .lineLimit(2)\n            .airshipApplyIf(containsMarkdownLink(text: text)) { view in\n                view.accessibilityAddTraits(.isLink)\n            }\n    }\n\n    private func containsMarkdownLink(text: String) -> Bool {\n        let text = try? AttributedString(markdown: text)\n        return text?.runs.contains(where: { $0.link != nil }) ?? false\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/ContactSubscriptionGroupView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Contact subscription group item view\npublic struct ContactSubscriptionGroupView: View {\n    /// The item's config\n    public let item: PreferenceCenterConfig.ContactSubscriptionGroup\n\n    /// The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipContactSubscriptionGroupStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @State\n    private var displayConditionsMet: Bool = true\n    \n\n    public init(item: PreferenceCenterConfig.ContactSubscriptionGroup, state: PreferenceCenterState) {\n        self.item = item\n        self.state = state\n    }\n\n    @ViewBuilder\n    public var body: some View {\n\n        let componentStates = self.item.components\n            .map {\n                ContactSubscriptionGroupStyleConfiguration.ComponentState(\n                    component: $0,\n                    isSubscribed: self.state.makeBinding(\n                        contactListID: item.subscriptionID,\n                        scopes: $0.scopes\n                    )\n                )\n            }\n\n        let configuration = ContactSubscriptionGroupStyleConfiguration(\n            item: self.item,\n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            componentStates: componentStates,\n            colorScheme: self.colorScheme\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.item.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\nextension View {\n    /// Sets the contact subscription group style\n    /// - Parameters:\n    ///     - style: The style\n    public func contactSubscriptionGroupStyle<S>(_ style: S) -> some View\n    where S: ContactSubscriptionGroupStyle {\n        self.environment(\n            \\.airshipContactSubscriptionGroupStyle,\n            AnyContactSubscriptionGroupStyle(style: style)\n        )\n    }\n}\n\n/// The contact subscription group item style config\npublic struct ContactSubscriptionGroupStyleConfiguration {\n    public let item: PreferenceCenterConfig.ContactSubscriptionGroup\n    /// The preference state\n    public let state: PreferenceCenterState\n\n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// Component enabled states\n    public let componentStates: [ComponentState]\n\n    /// Color scheme\n    public let colorScheme: ColorScheme\n\n    /// Component state\n    public struct ComponentState {\n        /// The component\n        public let component:\n            PreferenceCenterConfig.ContactSubscriptionGroup.Component\n\n        /// The component's subscription binding\n        public let isSubscribed: Binding<Bool>\n    }\n}\n\npublic protocol ContactSubscriptionGroupStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = ContactSubscriptionGroupStyleConfiguration\n    \n    @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension ContactSubscriptionGroupStyle\nwhere Self == DefaultContactSubscriptionGroupStyle {\n\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default subscription group item style\npublic struct DefaultContactSubscriptionGroupStyle: ContactSubscriptionGroupStyle {\n\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let colorScheme = configuration.colorScheme\n        let item = configuration.item\n        let itemTheme = configuration.preferenceCenterTheme\n            .contactSubscriptionGroup\n\n        if configuration.displayConditionsMet {\n            VStack(alignment: .leading) {\n                if let title = item.display?.title {\n                    Text(title)\n                        .textAppearance(\n                            itemTheme?.titleAppearance,\n                            base: PreferenceCenterDefaults.titleAppearance,\n                            colorScheme: colorScheme\n                        )\n                        .accessibilityAddTraits(.isHeader)\n                }\n\n                if let subtitle = item.display?.subtitle {\n                    Text(subtitle)\n                        .textAppearance(\n                            itemTheme?.subtitleAppearance,\n                            base: PreferenceCenterDefaults.subtitleAppearance,\n                            colorScheme: colorScheme\n                        )\n                }\n\n                ComponentsView(\n                    componentStates: configuration.componentStates,\n                    chipTheme: itemTheme?.chip\n                )\n            }\n        }\n    }\n\n    private struct ComponentsView: View {\n        @Environment(\\.colorScheme)\n        private var colorScheme\n\n        let componentStates: [Configuration.ComponentState]\n        let chipTheme: PreferenceCenterTheme.Chip?\n\n        @State\n        private var componentHeight: CGFloat?\n\n        @ViewBuilder\n        var body: some View {\n            let dx = AirshipAtomicValue(CGFloat.zero)\n            let dy = AirshipAtomicValue(CGFloat.zero)\n\n            let chipSpacing = PreferenceCenterDefaults.chipSpacing\n\n            GeometryReader { geometry in\n                ZStack(alignment: .topLeading) {\n                    ForEach(0..<self.componentStates.count, id: \\.self) {\n                        index in\n                        let state = self.componentStates[index]\n                        let size = geometry.size\n                        \n                        makeComponent(state.component, isOn: state.isSubscribed)\n                            .alignmentGuide(HorizontalAlignment.leading) {\n                                viewDimensions in\n                                \n                                if index == 0 {\n                                    dx.value = 0\n                                    dy.value = 0\n                                }\n\n                                var offSet = dx.value\n                                if abs(offSet - viewDimensions.width)\n                                    > size.width\n                                {\n                                    offSet = 0\n                                    dx.value = 0\n                                    dy.value -= viewDimensions.height + chipSpacing\n                                }\n\n                                dx.value -= (viewDimensions.width + chipSpacing)\n                                return offSet\n                            }\n                            .alignmentGuide(VerticalAlignment.top) {\n                                viewDimensions in\n                                return dy.value\n                            }\n                    }\n                }\n                .background(\n                    GeometryReader(content: { contentMetrics -> Color in\n                        let size = contentMetrics.size\n                        DispatchQueue.main.async {\n                            self.componentHeight = size.height\n                        }\n                        return Color.clear\n                    })\n                )\n            }\n            .frame(minHeight: componentHeight)\n        }\n\n        @ViewBuilder\n        func makeComponent(\n            _ component: PreferenceCenterConfig.ContactSubscriptionGroup\n                .Component,\n            isOn: Binding<Bool>\n        ) -> some View {\n            let onColor: Color = colorScheme.airshipResolveColor(light: chipTheme?.checkColor, dark: chipTheme?.checkColorDark) ?? .green\n            let offColor: Color = colorScheme.airshipResolveColor(light: chipTheme?.borderColor, dark: chipTheme?.borderColorDark) ?? .secondary\n            let borderColor: Color = colorScheme.airshipResolveColor(light: chipTheme?.borderColor, dark: chipTheme?.borderColorDark) ?? .secondary\n\n            Toggle(isOn: isOn) {\n                Text(component.display?.title ?? \"\")\n            }.toggleStyle(\n                ChipToggleStyle(onColor: onColor, offColor: offColor, borderColor: borderColor)\n            )\n#if !os(tvOS)\n            .controlSize(.regular)\n#endif\n            .textAppearance(\n                chipTheme?.labelAppearance,\n                base: PreferenceCenterDefaults.chipLabelAppearance,\n                colorScheme: colorScheme\n            )\n        }\n    }\n}\n\nstruct AnyContactSubscriptionGroupStyle: ContactSubscriptionGroupStyle {\n    @ViewBuilder\n    private let _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: ContactSubscriptionGroupStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct ContactSubscriptionGroupStyleKey: EnvironmentKey {\n    static let defaultValue = AnyContactSubscriptionGroupStyle(\n        style: .defaultStyle\n    )\n}\n\nextension EnvironmentValues {\n    var airshipContactSubscriptionGroupStyle: AnyContactSubscriptionGroupStyle {\n        get { self[ContactSubscriptionGroupStyleKey.self] }\n        set { self[ContactSubscriptionGroupStyleKey.self] = newValue }\n    }\n}\n\nstruct ChipToggleStyle: ToggleStyle {\n    // Custom colors can be passed in during initialization.\n    var onColor: Color\n    var offColor: Color\n    var borderColor: Color\n\n    func makeBody(configuration: Configuration) -> some View {\n#if os(tvOS)\n        Button(action: { configuration.isOn.toggle() }) {\n            HStack(spacing: 10) {\n                Image(systemName: configuration.isOn ? \"checkmark.circle.fill\" : \"circle\")\n                    .foregroundColor(configuration.isOn ? onColor : offColor)\n\n                configuration.label\n                    .foregroundColor(.primary)\n            }\n        }\n        .buttonStyle(.bordered)\n        .tint(configuration.isOn ? onColor.opacity(0.2) : nil)\n        .animation(.easeInOut(duration: 0.2), value: configuration.isOn)\n#elseif os(visionOS)\n        Button(action: { configuration.isOn.toggle() }) {\n            HStack {\n                Image(systemName: configuration.isOn ? \"checkmark.circle.fill\" : \"circle\")\n                    .font(.title2)\n                    .foregroundColor(configuration.isOn ? onColor : offColor)\n\n                configuration.label\n                    .foregroundColor(.primary)\n            }\n            .frame(minHeight: 44)\n        }\n        .buttonStyle(.bordered)\n        .tint(configuration.isOn ? onColor.opacity(0.2) : nil)\n        .animation(.easeInOut(duration: 0.2), value: configuration.isOn)\n#else\n        Button(action: { configuration.isOn.toggle() }) {\n            HStack {\n                Image(systemName: configuration.isOn ? \"checkmark.circle.fill\" : \"circle\")\n                    .font(.title2)\n                    .foregroundColor(configuration.isOn ? onColor : offColor)\n\n                configuration.label\n                    .foregroundColor(.primary)\n            }\n        }\n        .buttonStyle(.bordered)\n        .tint(configuration.isOn ? onColor.opacity(0.4) : offColor.opacity(0.4))\n        .animation(.easeInOut(duration: 0.2), value: configuration.isOn)\n#endif\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/ContactSubscriptionView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The contact subscription item view\npublic struct ContactSubscriptionView: View {\n\n    /// The item's config\n    public let item: PreferenceCenterConfig.ContactSubscription\n\n    /// The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipContactSubscriptionViewStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @State\n    private var displayConditionsMet: Bool = true\n\n    public init(item: PreferenceCenterConfig.ContactSubscription, state: PreferenceCenterState) {\n        self.item = item\n        self.state = state\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        let isSubscribed = state.makeBinding(\n            contactListID: item.subscriptionID,\n            scopes: item.scopes\n        )\n\n        let configuration = ContactSubscriptionViewStyleConfiguration(\n            item: self.item,\n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            isSubscribed: isSubscribed,\n            colorScheme: self.colorScheme\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.item.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\nextension View {\n    /// Sets the contact subscription style\n    /// - Parameters:\n    ///     - style: The style\n    public func contactSubscriptionStyle<S>(_ style: S) -> some View\n    where S: ContactSubscriptionViewStyle {\n        self.environment(\n            \\.airshipContactSubscriptionViewStyle,\n            AnyContactSubscriptionViewStyle(style: style)\n        )\n    }\n}\n\n/// Contact subscription item view style configuration\npublic struct ContactSubscriptionViewStyleConfiguration {\n    /// The item's config\n    public let item: PreferenceCenterConfig.ContactSubscription\n\n    /// The preference state\n    public let state: PreferenceCenterState\n\n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// The item's subscription binding\n    public let isSubscribed: Binding<Bool>\n\n    /// Color scheme\n    public let colorScheme: ColorScheme\n}\n\n/// Contact subscription view style\npublic protocol ContactSubscriptionViewStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = ContactSubscriptionViewStyleConfiguration\n    \n    @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension ContactSubscriptionViewStyle\nwhere Self == DefaultContactSubscriptionViewStyle {\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// Default contact subscription view style\npublic struct DefaultContactSubscriptionViewStyle: ContactSubscriptionViewStyle {\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let colorScheme = configuration.colorScheme\n        let item = configuration.item\n        let itemTheme = configuration.preferenceCenterTheme.contactSubscription\n\n        if configuration.displayConditionsMet {\n            Toggle(isOn: configuration.isSubscribed) {\n                VStack(alignment: .leading) {\n                    if let title = item.display?.title {\n                        Text(title)\n                            .textAppearance(\n                                itemTheme?.titleAppearance,\n                                base: PreferenceCenterDefaults.titleAppearance,\n                                colorScheme: colorScheme\n                            ).accessibilityAddTraits(.isHeader)\n                    }\n\n                    if let subtitle = item.display?.subtitle {\n                        Text(subtitle)\n                            .textAppearance(\n                                itemTheme?.subtitleAppearance,\n                                base: PreferenceCenterDefaults.subtitleAppearance,\n                                colorScheme: colorScheme\n                            )\n                    }\n                }\n            }\n            .toggleStyle(\n                tint: colorScheme.airshipResolveColor(\n                    light: itemTheme?.toggleTintColor,\n                    dark: itemTheme?.toggleTintColorDark\n                )\n            )\n            .padding(.trailing, 2)\n        }\n    }\n}\n\nstruct AnyContactSubscriptionViewStyle: ContactSubscriptionViewStyle {\n    @ViewBuilder\n    private let _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: ContactSubscriptionViewStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct ContactSubscriptionViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyContactSubscriptionViewStyle(\n        style: .defaultStyle\n    )\n}\n\nextension EnvironmentValues {\n    var airshipContactSubscriptionViewStyle: AnyContactSubscriptionViewStyle {\n        get { self[ContactSubscriptionViewStyleKey.self] }\n        set { self[ContactSubscriptionViewStyleKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/LabeledSectionBreakView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Labeled section break view\npublic struct LabeledSectionBreakView: View {\n\n    /// The section's config\n    public let section: PreferenceCenterConfig.LabeledSectionBreak\n\n    /// The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipLabeledSectionBreakStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @State\n    private var displayConditionsMet: Bool = true\n\n    public init(section: PreferenceCenterConfig.LabeledSectionBreak, state: PreferenceCenterState) {\n        self.section = section\n        self.state = state\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        let configuration = LabeledSectionBreakStyleConfiguration(\n            section: self.section,\n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            colorScheme: self.colorScheme\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.section.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\nextension View {\n    /// Sets the labeled section break style\n    /// - Parameters:\n    ///     - style: The style\n    public func labeledSectionBreakStyle<S>(_ style: S) -> some View\n    where S: LabeledSectionBreakStyle {\n        self.environment(\n            \\.airshipLabeledSectionBreakStyle,\n            AnyLabeledSectionBreakStyle(style: style)\n        )\n    }\n}\n\n/// The labeled section break style configuration\n@MainActor\npublic struct LabeledSectionBreakStyleConfiguration {\n    /// The section config\n    public let section: PreferenceCenterConfig.LabeledSectionBreak\n\n    /// The preference state\n    public let state: PreferenceCenterState\n\n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// The color scheme\n    public let colorScheme: ColorScheme\n}\n\n/// Labeled section break style\npublic protocol LabeledSectionBreakStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = LabeledSectionBreakStyleConfiguration\n    @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension LabeledSectionBreakStyle\nwhere Self == DefaultLabeledSectionBreakStyle {\n\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// Default labeled section break style\npublic struct DefaultLabeledSectionBreakStyle: LabeledSectionBreakStyle {\n\n    @ViewBuilder\n    @preconcurrency @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let colorScheme = configuration.colorScheme\n        let section = configuration.section\n        let sectionTheme = configuration.preferenceCenterTheme.labeledSectionBreak\n        let backgroundColor = colorScheme.airshipResolveColor(\n            light: sectionTheme?.backgroundColor,\n            dark: sectionTheme?.backgroundColorDark\n        ) ?? PreferenceCenterDefaults.labeledSectionBreakTitleBackgroundColor\n\n        if configuration.displayConditionsMet {\n            Text(section.display?.title ?? \"\")\n                .textAppearance(\n                    sectionTheme?.titleAppearance,\n                    base: PreferenceCenterDefaults.labeledSectionBreakTitleAppearance,\n                    colorScheme: colorScheme\n                )\n                .padding(.vertical, PreferenceCenterDefaults.smallPadding/2)\n                .padding(.horizontal, PreferenceCenterDefaults.smallPadding)\n                .background(backgroundColor)\n                .accessibilityAddTraits(.isHeader)\n        }\n    }\n}\n\nstruct AnyLabeledSectionBreakStyle: LabeledSectionBreakStyle {\n    @ViewBuilder\n    private let _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: LabeledSectionBreakStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct LabeledSectionBreakStyleKey: EnvironmentKey {\n    static let defaultValue = AnyLabeledSectionBreakStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipLabeledSectionBreakStyle: AnyLabeledSectionBreakStyle {\n        get { self[LabeledSectionBreakStyleKey.self] }\n        set { self[LabeledSectionBreakStyleKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterAlertView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The Preference Center alert item view\npublic struct PreferenceCenterAlertView: View {\n\n    /// The item's config\n    public let item: PreferenceCenterConfig.Alert\n\n    /// The preference state\n    @ObservedObject\n    public var state: PreferenceCenterState\n\n    @Environment(\\.airshipPreferenceCenterAlertStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    @State\n    private var displayConditionsMet: Bool = true\n\n    public init(item: PreferenceCenterConfig.Alert, state: PreferenceCenterState) {\n        self.item = item\n        self.state = state\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        let configuration = PreferenceCenterAlertStyleConfiguration(\n            item: self.item,\n            state: self.state,\n            displayConditionsMet: self.displayConditionsMet,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            colorScheme: self.colorScheme\n        )\n\n        style.makeBody(configuration: configuration)\n            .preferenceConditions(\n                self.item.conditions,\n                binding: self.$displayConditionsMet\n            )\n    }\n}\n\nextension View {\n    /// Sets the alert style\n    /// - Parameters:\n    ///     - style: The style\n    public func PreferenceCenterAlertStyle<S>(_ style: S) -> some View\n    where S: PreferenceCenterAlertStyle {\n        self.environment(\n            \\.airshipPreferenceCenterAlertStyle,\n             AnyPreferenceCenterAlertStyle(style: style)\n        )\n    }\n}\n\n/// Preference Center alert style configuration\npublic struct PreferenceCenterAlertStyleConfiguration {\n    /// The item config\n    public let item: PreferenceCenterConfig.Alert\n\n    /// The preference state\n    public let state: PreferenceCenterState\n\n    /// If the display conditions are met for this item\n    public let displayConditionsMet: Bool\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// The color scheme\n    public let colorScheme: ColorScheme\n}\n\n/// Preference Center alert style\npublic protocol PreferenceCenterAlertStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = PreferenceCenterAlertStyleConfiguration\n    \n    @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension PreferenceCenterAlertStyle\nwhere Self == DefaultPreferenceCenterAlertStyle {\n\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default Preference Center alert style\npublic struct DefaultPreferenceCenterAlertStyle: PreferenceCenterAlertStyle {\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let colorScheme = configuration.colorScheme\n        let item = configuration.item\n        let itemTheme = configuration.preferenceCenterTheme.alert\n\n        if configuration.displayConditionsMet {\n            VStack(alignment: .center) {\n                HStack(alignment: .top) {\n                    if let url = item.display?.iconURL, !url.isEmpty {\n                        AirshipAsyncImage(\n                            url: url,\n                            image: { image, _ in\n                                image\n                                    .resizable()\n                                    .scaledToFit()\n                            },\n                            placeholder: {\n                                return ProgressView()\n                            }\n                        )\n                        .frame(width: 60, height: 60)\n                        .padding()\n                    }\n\n                    VStack(alignment: .leading) {\n                        if let title = item.display?.title {\n                            Text(title)\n                                .textAppearance(\n                                    itemTheme?.titleAppearance,\n                                    base: PreferenceCenterDefaults.titleAppearance,\n                                    colorScheme: colorScheme\n                                )\n                        }\n\n                        if let subtitle = item.display?.subtitle {\n                            Text(subtitle)\n                                .textAppearance(\n                                    itemTheme?.subtitleAppearance,\n                                    base: PreferenceCenterDefaults.subtitleAppearance,\n                                    colorScheme: colorScheme\n                                )\n                        }\n\n                        if let button = item.button {\n                            Button(\n                                action: {\n                                    let actions = button.actionJSON\n                                    Task {\n                                        await ActionRunner.run(\n                                            actionsPayload: actions,\n                                            situation: .manualInvocation,\n                                            metadata: [:]\n                                        )\n                                    }\n                                },\n                                label: {\n                                    Text(button.text)\n                                        .textAppearance(\n                                            itemTheme?.buttonLabelAppearance,\n                                            colorScheme: colorScheme\n                                        )\n                                }\n                            )\n                            .buttonStyle(.borderedProminent)\n                            #if !os(tvOS)\n                            .controlSize(.large)\n                            #endif\n                            .buttonBorderShape(.capsule)\n                            .optAccessibilityLabel(\n                                string: button.contentDescription\n                            )\n                        }\n                    }\n                }\n            }\n            .frame(maxWidth: .infinity)\n        }\n    }\n}\n\nstruct AnyPreferenceCenterAlertStyle: PreferenceCenterAlertStyle {\n    @ViewBuilder\n    private var _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: PreferenceCenterAlertStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct PreferenceCenterAlertStyleKey: EnvironmentKey {\n    static let defaultValue = AnyPreferenceCenterAlertStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipPreferenceCenterAlertStyle: AnyPreferenceCenterAlertStyle {\n        get { self[PreferenceCenterAlertStyleKey.self] }\n        set { self[PreferenceCenterAlertStyleKey.self] = newValue }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterContent.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Preference center content phase\npublic enum PreferenceCenterContentPhase: Sendable {\n    /// The view is loading\n    case loading\n    /// The view failed to load the config\n    case error(any Error)\n    /// The view is loaded with the state\n    case loaded(PreferenceCenterState)\n}\n\n/// The core view for the Airship Preference Center.\n/// This view is responsible for loading and displaying the preference center content. For a navigation controller, see `PreferenceCenterView`.\n@MainActor\npublic struct PreferenceCenterContent: View {\n\n    @StateObject\n    private var loader: PreferenceCenterContentLoader = PreferenceCenterContentLoader()\n\n    @State\n    private var initialLoadCalled = false\n\n    @State\n    private var namedUser: String?\n\n    @Environment(\\.airshipPreferenceCenterStyle)\n    private var style\n\n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var preferenceCenterTheme\n\n    @Environment(\\.colorScheme)\n    private var colorScheme\n\n    private let preferenceCenterID: String\n    private let onLoad: (@Sendable (String) async -> PreferenceCenterContentPhase)?\n    private let onPhaseChange: ((PreferenceCenterContentPhase) -> Void)?\n\n    /// The default constructor\n    /// - Parameters:\n    ///   - preferenceCenterID: The preference center ID\n    ///   - onLoad: An optional load block to load the view phase\n    ///   - onPhaseChange: A callback when the phase changed\n    public init(\n        preferenceCenterID: String,\n        onLoad: (@Sendable (String) async -> PreferenceCenterContentPhase)? = nil,\n        onPhaseChange: ((PreferenceCenterContentPhase) -> Void)? = nil\n    ) {\n        self.preferenceCenterID = preferenceCenterID\n        self.onLoad = onLoad\n        self.onPhaseChange = onPhaseChange\n    }\n\n    @ViewBuilder\n    public var body: some View {\n        let phase = self.loader.phase\n\n        let refresh: @MainActor @Sendable () -> Void = { @MainActor in\n            self.loader.load(\n                preferenceCenterID: preferenceCenterID,\n                onLoad: onLoad\n            )\n        }\n\n        let configuration = PreferenceCenterContentStyleConfiguration(\n            phase: phase,\n            preferenceCenterTheme: self.preferenceCenterTheme,\n            colorScheme: self.colorScheme,\n            refresh: refresh\n        )\n\n        style.makeBody(configuration: configuration)\n            .onReceive(makeNamedUserIDPublisher()) { identifier in\n                if (self.namedUser != identifier) {\n                    self.namedUser = identifier\n                    refresh()\n                }\n            }\n            .onReceive(self.loader.$phase) {\n                self.onPhaseChange?($0)\n\n                if !self.initialLoadCalled {\n                    refresh()\n                    self.initialLoadCalled = true\n                }\n            }\n    }\n\n    private func makeNamedUserIDPublisher() -> AnyPublisher<String?, Never> {\n        guard Airship.isFlying else {\n            return Just(nil).eraseToAnyPublisher()\n        }\n\n        return Airship.contact.namedUserIDPublisher\n            .receive(on: RunLoop.main)\n            .dropFirst()\n            .eraseToAnyPublisher()\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterContentLoader.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n@MainActor\nfinal class PreferenceCenterContentLoader: ObservableObject {\n\n    @Published\n    public private(set) var phase: PreferenceCenterContentPhase = .loading\n\n    private var task: Task<Void, Never>?\n\n    public func load(\n        preferenceCenterID: String,\n        onLoad: (@Sendable (String) async -> PreferenceCenterContentPhase)? = nil\n    ) {\n        self.task?.cancel()\n        self.task = Task { @MainActor in\n            await loadAsync(\n                preferenceCenterID: preferenceCenterID,\n                onLoad: onLoad\n            )\n        }\n    }\n\n    @MainActor\n    private func loadAsync(\n        preferenceCenterID: String,\n        onLoad: (@Sendable @MainActor (String) async -> PreferenceCenterContentPhase)? = nil\n    ) async {\n        self.phase = .loading\n\n        if let onLoad = onLoad {\n            self.phase = await onLoad(preferenceCenterID)\n            return\n        }\n\n        do {\n            let state = try await self.fetchState(\n                preferenceCenterID: preferenceCenterID\n            )\n            self.phase = .loaded(state)\n        } catch {\n            self.phase = .error(error)\n        }\n    }\n\n    @MainActor\n    private func fetchState(preferenceCenterID: String) async throws\n        -> PreferenceCenterState\n    {\n        AirshipLogger.info(\"Fetching config: \\(preferenceCenterID)\")\n\n        guard Airship.isFlying else {\n            throw AirshipErrors.error(\"TakeOff not called\")\n        }\n\n        let config = try await Airship.preferenceCenter.config(\n            preferenceCenterID: preferenceCenterID\n        )\n\n        var channelSubscriptions: [String] = []\n        var contactSubscriptions: [String: Set<ChannelScope>] = [:]\n\n        if config.containsChannelSubscriptions() {\n            channelSubscriptions = try await Airship.channel\n                .fetchSubscriptionLists()\n        }\n\n        if config.containsContactSubscriptions() {\n            contactSubscriptions = try await Airship.contact\n                .fetchSubscriptionLists()\n                .mapValues { Set($0) }\n        }\n        \n        var channelUpdates: AsyncStream<ContactChannelsResult>? = nil\n\n        if config.containsContactManagement() {\n            channelUpdates = Airship.contact.contactChannelUpdates\n        }\n\n        return PreferenceCenterState(\n            config: config,\n            contactSubscriptions: contactSubscriptions,\n            channelSubscriptions: Set(channelSubscriptions),\n            channelUpdates: channelUpdates\n        )\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterContentStyle.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// Preference Center view style configuration\npublic struct PreferenceCenterContentStyleConfiguration: Sendable {\n    /// The view's phase\n    public let phase: PreferenceCenterContentPhase\n\n    /// The preference center theme\n    public let preferenceCenterTheme: PreferenceCenterTheme\n\n    /// The colorScheme\n    public let colorScheme: ColorScheme\n\n    /// A block that can be called to refresh the view\n    public let refresh: @MainActor @Sendable () -> Void\n}\n\n/// Preference Center view style\npublic protocol PreferenceCenterContentStyle: Sendable {\n    associatedtype Body: View\n    typealias Configuration = PreferenceCenterContentStyleConfiguration\n    \n    @preconcurrency @MainActor\n    func makeBody(configuration: Self.Configuration) -> Self.Body\n}\n\nextension PreferenceCenterContentStyle\nwhere Self == DefaultPreferenceCenterViewStyle {\n    /// Default style\n    public static var defaultStyle: Self {\n        return .init()\n    }\n}\n\n/// The default Preference Center view style\n@MainActor @preconcurrency \npublic struct DefaultPreferenceCenterViewStyle: PreferenceCenterContentStyle {\n\n    @ViewBuilder\n    private func makeProgressView(configuration: Configuration) -> some View {\n        ProgressView()\n            .frame(alignment: .center)\n    }\n\n    @ViewBuilder\n    public func makeErrorView(configuration: Configuration) -> some View {\n        let colorScheme = configuration.colorScheme\n        let theme = configuration.preferenceCenterTheme.preferenceCenter\n        let retry = theme?.retryButtonLabel ?? \"ua_retry_button\".preferenceCenterLocalizedString\n        let errorMessage =\n        theme?.retryMessage ?? \"ua_preference_center_empty\".preferenceCenterLocalizedString\n\n        VStack {\n            Text(errorMessage)\n                .textAppearance(\n                    theme?.retryMessageAppearance,\n                    colorScheme: colorScheme\n                )\n                .padding()\n\n            Button(\n                action: {\n                    configuration.refresh()\n                },\n                label: {\n                    Text(retry)\n                        .textAppearance(\n                            theme?.retryButtonLabelAppearance,\n                            base: PreferenceCenterTheme.TextAppearance(\n                                color: colorScheme.airshipResolveColor(\n                                    light: Color.white,\n                                    dark: Color.black\n                                )\n                            ),\n                            colorScheme: colorScheme\n                        )\n                        .padding(.horizontal, 8)\n                        .padding(.vertical, 4)\n                        .background(\n                            Capsule()\n                                .fill(\n                                    colorScheme.airshipResolveColor(\n                                        light: theme?.retryButtonBackgroundColor,\n                                        dark: theme?.retryButtonBackgroundColorDark\n                                    ) ?? Color.blue\n                                )\n                        )\n                        .cornerRadius(8)\n                        .frame(minWidth: 44)\n                }\n            )\n            #if os(macOS)\n            .buttonStyle(.plain)\n            #endif\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    public func makePreferenceCenterView(\n        configuration: Configuration,\n        state: PreferenceCenterState\n    ) -> some View {\n        let colorScheme = configuration.colorScheme\n        let theme = configuration.preferenceCenterTheme\n        ScrollView {\n            LazyVStack(alignment: .leading) {\n                if let subtitle = state.config.display?.subtitle {\n                    Text(subtitle)\n                        .textAppearance(\n                            theme.preferenceCenter?.subtitleAppearance,\n                            base: PreferenceCenterDefaults.subtitleAppearance,\n                            colorScheme: colorScheme\n                        )\n                        .padding(.bottom)\n                }\n\n                ForEach(0..<state.config.sections.count, id: \\.self) { index in\n                    self.section(\n                        state.config.sections[index],\n                        state: state,\n                        isLast: index == state.config.sections.count - 1\n                    )\n                }\n            }\n            .padding()\n            Spacer()\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    public func makeBody(configuration: Configuration) -> some View {\n        let resolvedBackgroundColor: Color? = configuration.colorScheme.airshipResolveColor(\n            light: configuration.preferenceCenterTheme.viewController?.backgroundColor,\n            dark: configuration.preferenceCenterTheme.viewController?.backgroundColorDark\n        )\n\n        ZStack {\n            if let resolvedBackgroundColor {\n                resolvedBackgroundColor.ignoresSafeArea()\n            }\n\n            switch configuration.phase {\n            case .loading:\n                makeProgressView(configuration: configuration)\n            case .error(_):\n                makeErrorView(configuration: configuration)\n            case .loaded(let state):\n                makePreferenceCenterView(configuration: configuration, state: state)\n            }\n        }\n    }\n\n    @ViewBuilder\n    @MainActor\n    func section(\n        _ section: PreferenceCenterConfig.Section,\n        state: PreferenceCenterState,\n        isLast: Bool\n    ) -> some View {\n        switch section {\n        case .common(let section):\n            CommonSectionView(\n                section: section,\n                state: state,\n                isLast: isLast\n            )\n        case .labeledSectionBreak(let section):\n            LabeledSectionBreakView(\n                section: section,\n                state: state\n            )\n        }\n    }\n}\n\nstruct AnyPreferenceCenterViewStyle: PreferenceCenterContentStyle {\n    @ViewBuilder\n    private var _makeBody: @MainActor @Sendable (Configuration) -> AnyView\n\n    init<S: PreferenceCenterContentStyle>(style: S) {\n        _makeBody = { @MainActor configuration in\n            AnyView(style.makeBody(configuration: configuration))\n        }\n    }\n\n    @ViewBuilder\n    func makeBody(configuration: Configuration) -> some View {\n        _makeBody(configuration)\n    }\n}\n\nstruct PreferenceCenterViewStyleKey: EnvironmentKey {\n    static let defaultValue = AnyPreferenceCenterViewStyle(style: .defaultStyle)\n}\n\nextension EnvironmentValues {\n    var airshipPreferenceCenterStyle: AnyPreferenceCenterViewStyle {\n        get { self[PreferenceCenterViewStyleKey.self] }\n        set { self[PreferenceCenterViewStyleKey.self] = newValue }\n    }\n}\n\nextension String {\n    fileprivate func nullIfEmpty() -> String? {\n        return self.isEmpty ? nil : self\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterState.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Combine\nimport Foundation\npublic import SwiftUI\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n/// Preference Center State\n@MainActor\npublic class PreferenceCenterState: ObservableObject {\n\n    /// The config\n    public let config: PreferenceCenterConfig\n\n    private var contactSubscriptions: [String: Set<ChannelScope>]\n    private var channelSubscriptions: Set<String>\n\n    @Published\n    var channelsList: [ContactChannel] = []\n\n    private var subscriptions: Set<AnyCancellable> = []\n    private let subscriber: any PreferenceSubscriber\n\n    /// Default constructor.\n    /// - Parameters:\n    ///     - config: The preference config\n    ///     - contactSubscriptions: The relevant contact subscriptions\n    ///     - channelSubscriptions: The relevant channel subscriptions.\n    ///     - channelsLists: The relevant channels list.\n    public convenience init(\n        config: PreferenceCenterConfig,\n        contactSubscriptions: [String: Set<ChannelScope>] = [:],\n        channelSubscriptions: Set<String> = Set(),\n        channelsList: [ContactChannel] = [],\n        channelUpdates: AsyncStream<ContactChannelsResult>? = nil\n    ) {\n\n        self.init(\n            config: config,\n            contactSubscriptions: contactSubscriptions,\n            channelSubscriptions: channelSubscriptions,\n            channelUpdates: channelUpdates,\n            subscriber: PreferenceCenterState.makeSubscriber()\n        )\n    }\n\n    init(\n        config: PreferenceCenterConfig,\n        contactSubscriptions: [String: Set<ChannelScope>] = [:],\n        channelSubscriptions: Set<String> = Set(),\n        channelUpdates: AsyncStream<ContactChannelsResult>? = nil,\n        subscriber: any PreferenceSubscriber\n    ) {\n        self.config = config\n        self.contactSubscriptions = contactSubscriptions\n        self.channelSubscriptions = channelSubscriptions\n        self.subscriber = subscriber\n\n        self.subscribeToUpdates()\n\n        if let channelUpdates {\n            Task { @MainActor [weak self] in\n                for await update in channelUpdates {\n                    if case .success(let channels) = update {\n                        self?.channelsList = channels\n                        AirshipLogger.info(\"Preference center channel updated\")\n                    }\n\n                }\n            }\n        }\n    }\n\n    /// Subscribes to updates from the Airship instance\n    private func subscribeToUpdates() {\n        self.subscriber.channelSubscriptionListEdits\n            .sink { edit in\n                self.processChannelEdit(edit)\n            }\n            .store(in: &subscriptions)\n\n        self.subscriber.contactSubscriptionListEdits\n            .sink { edit in\n                self.processContactEdit(edit)\n            }\n            .store(in: &subscriptions)\n    }\n\n    /// Checks if the channel is subscribed to the preference state\n    /// - Parameters:\n    ///     - listID: The preference list ID\n    /// - Returns: true if any of the channel is subscribed, otherwise false.\n    public func isChannelSubscribed(_ listID: String) -> Bool {\n        return self.channelSubscriptions.contains(listID)\n    }\n\n    /// Checks if the contact is subscribed to the preference state\n    /// - Parameters:\n    ///     - listID: The preference list ID\n    ///     - scope: The channel scope\n    /// - Returns: true if any the contact is subscribed for that scope, otherwise false.\n    public func isContactSubscribed(_ listID: String, scope: ChannelScope)\n    -> Bool\n    {\n        let containsSubscription = self.contactSubscriptions[listID]?\n            .contains {\n                $0 == scope\n            }\n\n        if containsSubscription == true {\n            return true\n        }\n\n        if config.options?.mergeChannelDataToContact == true && scope == .app {\n            return isChannelSubscribed(listID)\n        }\n\n        return false\n    }\n\n    /// Checks if the contact is subscribed to the preference state\n    /// - Parameters:\n    ///     - listID: The preference list ID\n    ///     - scopes: The channel scopes\n    /// - Returns: true if the contact is subscribed to any of the scopes, otherwise false.\n    public func isContactSubscribed(_ listID: String, scopes: [ChannelScope])\n    -> Bool\n    {\n        return scopes.contains { scope in\n            isContactSubscribed(listID, scope: scope)\n        }\n    }\n\n    /// Creates a channel subscription binding for the list ID.\n    /// - Parameters:\n    ///     - channelListID: The subscription list ID\n    /// - Returns: A subscription binding\n    public func makeBinding(channelListID: String) -> Binding<Bool> {\n        return Binding<Bool>(\n            get: { self.isChannelSubscribed(channelListID) },\n            set: { subscribe in\n                self.subscriber.updateChannelSubscription(\n                    channelListID,\n                    subscribe: subscribe\n                )\n            }\n        )\n    }\n\n    /// Creates a contact subscription binding for the list ID and scopes.\n    /// - Parameters:\n    ///     - contactListID: The subscription list ID\n    ///     - scopes: The subscription list scopes\n    /// - Returns: A subscription binding\n    public func makeBinding(\n        contactListID: String,\n        scopes: [ChannelScope]\n    ) -> Binding<Bool> {\n\n        return Binding<Bool>(\n            get: {\n                self.isContactSubscribed(contactListID, scopes: scopes)\n            },\n            set: { subscribe in\n                self.subscriber.updateContactSubscription(\n                    contactListID,\n                    scopes: scopes,\n                    subscribe: subscribe\n                )\n            }\n        )\n    }\n\n    private func processContactEdit(_ edit: ScopedSubscriptionListEdit) {\n        self.objectWillChange.send()\n\n        switch edit {\n        case .subscribe(let listID, let scope):\n            var scopes =\n            self.contactSubscriptions[listID] ?? Set<ChannelScope>()\n            scopes.insert(scope)\n            self.contactSubscriptions[listID] = scopes\n        case .unsubscribe(let listID, let scope):\n            if var scopes = self.contactSubscriptions[listID] {\n                scopes.remove(scope)\n                if scopes.isEmpty {\n                    self.contactSubscriptions[listID] = nil\n                } else {\n                    self.contactSubscriptions[listID] = scopes\n                }\n            }\n#if canImport(AirshipCore)\n        @unknown default:\n            AirshipLogger.error(\"Unknown scooped subscription list edit \\(edit)\")\n#endif\n        }\n    }\n\n    private func processChannelEdit(_ edit: SubscriptionListEdit) {\n        self.objectWillChange.send()\n\n        switch edit {\n        case .subscribe(let listID):\n            self.channelSubscriptions.insert(listID)\n        case .unsubscribe(let listID):\n            self.channelSubscriptions.remove(listID)\n#if canImport(AirshipCore)\n        @unknown default:\n            AirshipLogger.error(\"Unknown subscription list edit \\(edit)\")\n#endif\n        }\n    }\n\n    static func makeSubscriber() -> any PreferenceSubscriber {\n        guard Airship.isFlying else {\n            return PreviewPreferenceSubscriber()\n        }\n        return AirshipPreferenceSubscriber()\n    }\n\n}\n\nprotocol PreferenceSubscriber {\n\n    var channelSubscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never> { get }\n    var contactSubscriptionListEdits: AnyPublisher<ScopedSubscriptionListEdit, Never> { get }\n\n    func updateChannelSubscription(\n        _ listID: String,\n        subscribe: Bool\n    )\n\n    func updateContactSubscription(\n        _ listID: String,\n        scopes: [ChannelScope],\n        subscribe: Bool\n    )\n}\n\nclass PreviewPreferenceSubscriber: PreferenceSubscriber {\n\n    private let channelEditsSubject = PassthroughSubject<\n        SubscriptionListEdit, Never\n    >()\n\n    var channelSubscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never>\n    {\n        return channelEditsSubject.eraseToAnyPublisher()\n    }\n\n    private let contactEditsSubject = PassthroughSubject<\n        ScopedSubscriptionListEdit, Never\n    >()\n    var contactSubscriptionListEdits:\n    AnyPublisher<ScopedSubscriptionListEdit, Never>\n    {\n        return contactEditsSubject.eraseToAnyPublisher()\n    }\n\n    func updateChannelSubscription(_ listID: String, subscribe: Bool) {\n        if subscribe {\n            channelEditsSubject.send(.subscribe(listID))\n        } else {\n            channelEditsSubject.send(.unsubscribe(listID))\n        }\n    }\n\n    func updateContactSubscription(\n        _ listID: String,\n        scopes: [ChannelScope],\n        subscribe: Bool\n    ) {\n        scopes.forEach { scope in\n            if subscribe {\n                contactEditsSubject.send(.subscribe(listID, scope))\n            } else {\n                contactEditsSubject.send(.unsubscribe(listID, scope))\n            }\n        }\n    }\n}\n\nclass AirshipPreferenceSubscriber: PreferenceSubscriber {\n    var channelSubscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never>\n    {\n        return Airship.channel.subscriptionListEdits\n    }\n\n    var contactSubscriptionListEdits:\n    AnyPublisher<ScopedSubscriptionListEdit, Never>\n    {\n        return Airship.contact.subscriptionListEdits\n    }\n\n    func updateChannelSubscription(_ listID: String, subscribe: Bool) {\n        Airship.channel.editSubscriptionLists { editor in\n            if subscribe {\n                editor.subscribe(listID)\n            } else {\n                editor.unsubscribe(listID)\n            }\n        }\n    }\n\n    func updateContactSubscription(\n        _ listID: String,\n        scopes: [ChannelScope],\n        subscribe: Bool\n    ) {\n        Airship.contact.editSubscriptionLists { editor in\n            editor.mutate(\n                listID,\n                scopes: scopes,\n                subscribe: subscribe\n            )\n        }\n    }\n}\n\nextension [ContactChannel] {\n    func filter(with type: ChannelType) -> [ContactChannel] {\n        return self.filter { channel in\n            channel.channelType == type\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterUtils.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\ninternal extension View {\n    @ViewBuilder\n    func backgroundWithCloseAction(onClose: (()->())?) -> some View {\n        ZStack {\n            Rectangle()\n                .foregroundColor(Color.clear)\n                .background(Color.airshipTappableClear.ignoresSafeArea(.all))\n                .onTapGesture {\n                    if let onClose = onClose {\n                        onClose()\n                    }\n                }\n                .zIndex(0)\n            self.zIndex(1)\n        }\n    }\n}\n\n#if !os(macOS)\ninternal extension UIWindow {\n    static func makeModalReadyWindow(scene: UIWindowScene) -> UIWindow {\n        let window = AirshipWindowFactory.shared.makeWindow(windowScene: scene)\n        window.accessibilityViewIsModal = false\n        window.alpha = 0\n        window.makeKeyAndVisible()\n        window.isUserInteractionEnabled = false\n        return window\n    }\n\n    func animateIn() {\n        self.windowLevel = .alert\n        self.makeKeyAndVisible()\n        self.isUserInteractionEnabled = true\n\n        UIView.animate(withDuration: 0.3) {\n            self.alpha = 1\n        }\n    }\n\n    func animateOut() {\n        UIView.animate(withDuration: 0.3, animations: {\n            self.alpha = 0\n        }, completion: { _ in\n            self.isHidden = true\n            self.isUserInteractionEnabled = false\n            self.removeFromSuperview()\n        })\n    }\n}\n#else\ninternal extension NSWindow {\n    static func makeModalReadyWindow() -> NSWindow {\n        let window = AirshipWindowFactory.shared.makeWindow()\n\n        // On macOS, modal accessibility is usually handled at the view level\n        window.contentView?.setAccessibilityModal(false)\n        window.alphaValue = 0\n        window.makeKeyAndOrderFront(nil)\n        window.ignoresMouseEvents = true\n\n        return window\n    }\n\n    func animateIn() {\n        self.level = .modalPanel // Equivalent to .alert level on iOS\n        self.makeKeyAndOrderFront(nil)\n        self.ignoresMouseEvents = false\n\n        NSAnimationContext.runAnimationGroup { context in\n            context.duration = 0.3\n            self.animator().alphaValue = 1\n        }\n    }\n\n    func animateOut() {\n        NSAnimationContext.runAnimationGroup({ context in\n            context.duration = 0.3\n            self.animator().alphaValue = 0\n        }, completionHandler: {\n            // Ensure we are on MainActor for UI changes\n            Task { @MainActor in\n                self.orderOut(nil)\n                self.ignoresMouseEvents = true\n            }\n        })\n    }\n}\n#endif\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\npublic import SwiftUI\nimport Combine\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\n/// The main view for the Airship Preference Center. This view provides a navigation stack.\n/// If you wish to provide your own navigation, see `PreferenceCenterContent`.\npublic struct PreferenceCenterView: View {\n    \n    @Environment(\\.preferenceCenterDismissAction)\n    private var dismissAction: (@MainActor @Sendable () -> Void)?\n    \n    @Environment(\\.airshipPreferenceCenterTheme)\n    private var theme\n    \n    @Environment(\\.colorScheme)\n    private var colorScheme\n    \n    private let preferenceCenterID: String\n    \n    @State private var title: String? = nil\n    \n    /// Default constructor\n    /// - Parameters:\n    ///     - preferenceCenterID: The preference center ID\n    public init(preferenceCenterID: String) {\n        self.preferenceCenterID = preferenceCenterID\n    }\n    \n    @ViewBuilder\n    private func makeBackButton() -> some View {\n        let theme = theme.viewController?.navigationBar\n        let resolvedBackButtonColor = colorScheme.airshipResolveColor(\n            light: theme?.backButtonColor,\n            dark: theme?.backButtonColorDark\n        )\n        \n        Button(action: {\n            self.dismissAction?()\n        }) {\n            Image(systemName: \"chevron.backward\")\n                .scaleEffect(0.68)\n                .font(Font.title.weight(.medium))\n                .foregroundColor(resolvedBackButtonColor)\n        }\n    }\n    \n    private var navigationBarTitle: String? {\n        var title: String? = self.title\n        \n        if theme.viewController?.navigationBar?.overrideConfigTitle == true {\n            title = theme.viewController?.navigationBar?.title ?? title\n        }\n        \n        return title\n    }\n    \n    \n    @ViewBuilder\n    public var body: some View {\n        let resolvedNavigationBarColor = colorScheme.airshipResolveColor(\n            light: theme.viewController?.navigationBar?.backgroundColor,\n            dark: theme.viewController?.navigationBar?.backgroundColorDark\n        )\n        \n        NavigationStack {\n            PreferenceCenterContent(\n                preferenceCenterID: preferenceCenterID,\n                onPhaseChange: { phase in\n                    guard case .loaded(let state) = phase else { return }\n                    \n                    let title = state.config.display?.title\n                    if let title, title.isEmpty == false {\n                        self.title = title\n                    } else {\n                        self.title = \"ua_preference_center_title\".preferenceCenterLocalizedString\n                    }\n                }\n            )\n            .frame(maxWidth: .infinity, maxHeight: .infinity)\n            .airshipApplyIf(resolvedNavigationBarColor != nil) { view in\n                let visibility: Visibility = if #available(iOS 26.0, *) {\n                    .automatic\n                } else {\n                    .visible\n                }\n#if !os(macOS)\n                view.toolbarBackground(resolvedNavigationBarColor!, for: .navigationBar)\n                    .toolbarBackground(visibility, for: .navigationBar)\n#endif\n            }\n#if !os(macOS)\n            .toolbar {\n                if self.dismissAction != nil {\n                    ToolbarItem(placement: .navigationBarLeading) {\n                        makeBackButton()\n                    }\n                }\n            }\n#else\n            .toolbar {\n                if self.dismissAction != nil {\n                    ToolbarItem(placement: .automatic) {\n                        makeBackButton()\n                    }\n                }\n            }\n            \n#endif\n            \n            .navigationTitle(navigationBarTitle ?? \"\")\n        }\n    }\n}\n\n\nprivate struct PreferenceCenterDismissActionKey: EnvironmentKey {\n    static let defaultValue: (@MainActor @Sendable () -> Void)? = nil\n}\n\nextension EnvironmentValues {\n    var preferenceCenterDismissAction: (@MainActor @Sendable () -> Void)? {\n        get { self[PreferenceCenterDismissActionKey.self] }\n        set { self[PreferenceCenterDismissActionKey.self] = newValue }\n    }\n}\n\npublic extension View {\n    \n    /// Sets a dismiss action on the preference center.\n    /// - Parameters:\n    ///     - action: The dismiss action.\n    func addPreferenceCenterDismissAction(action: (@MainActor @Sendable () -> Void)?) -> some View {\n        environment(\\.preferenceCenterDismissAction, action)\n    }\n}\n\nstruct PreferenceCenterView_Previews: PreviewProvider {\n    static var previews: some View {\n        let config = PreferenceCenterConfig(\n            identifier: \"PREVIEW\",\n            sections: [\n                .labeledSectionBreak(\n                    PreferenceCenterConfig.LabeledSectionBreak(\n                        id: \"LabeledSectionBreak\",\n                        display: PreferenceCenterConfig.CommonDisplay(\n                            title: \"Labeled Section Break\"\n                        )\n                    )\n                ),\n                .common(\n                    PreferenceCenterConfig.CommonSection(\n                        id: \"common\",\n                        items: [\n                            .channelSubscription(\n                                PreferenceCenterConfig.ChannelSubscription(\n                                    id: \"ChannelSubscription\",\n                                    subscriptionID: \"ChannelSubscription\",\n                                    display:\n                                        PreferenceCenterConfig.CommonDisplay(\n                                            title: \"Channel Subscription Title\",\n                                            subtitle:\n                                                \"Channel Subscription Subtitle\"\n                                        )\n                                )\n                            ),\n                            .contactSubscription(\n                                PreferenceCenterConfig.ContactSubscription(\n                                    id: \"ContactSubscription\",\n                                    subscriptionID: \"ContactSubscription\",\n                                    scopes: [.app, .web],\n                                    display:\n                                        PreferenceCenterConfig.CommonDisplay(\n                                            title: \"Contact Subscription Title\",\n                                            subtitle:\n                                                \"Contact Subscription Subtitle\"\n                                        )\n                                )\n                            ),\n                            .contactSubscriptionGroup(\n                                PreferenceCenterConfig.ContactSubscriptionGroup(\n                                    id: \"ContactSubscriptionGroup\",\n                                    subscriptionID: \"ContactSubscriptionGroup\",\n                                    components: [\n                                        PreferenceCenterConfig\n                                            .ContactSubscriptionGroup.Component(\n                                                scopes: [.web, .app],\n                                                display:\n                                                    PreferenceCenterConfig\n                                                    .CommonDisplay(\n                                                        title:\n                                                            \"Web and App Component\"\n                                                    )\n                                            )\n                                    ],\n                                    display:\n                                        PreferenceCenterConfig.CommonDisplay(\n                                            title:\n                                                \"Contact Subscription Group Title\",\n                                            subtitle:\n                                                \"Contact Subscription Group Subtitle\"\n                                        )\n                                )\n                            ),\n                        ],\n                        display: PreferenceCenterConfig.CommonDisplay(\n                            title: \"Section Title\",\n                            subtitle: \"Section Subtitle\"\n                        )\n                    )\n                ),\n            ]\n        )\n\n        PreferenceCenterContent(preferenceCenterID: \"PREVIEW\") {\n            preferenceCenterID in\n            return await .loaded(PreferenceCenterState(config: config))\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterViewControllerFactory.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\npublic import AirshipCore\n#endif\n\n#if canImport(UIKit)\npublic import UIKit\n#endif\n\n#if canImport(AppKit)\npublic import AppKit\n#endif\n\n/// View factories for Preference Center view controllers.\n///\n/// This factory provides a unified way to create native view controllers (`UIViewController` on iOS/tvOS\n/// or `NSViewController` on macOS) that host a Preference Center SwiftUI view.\npublic class PreferenceCenterViewControllerFactory: NSObject {\n\n    /// Makes a view controller for the given Preference Center ID.\n    /// - Parameters:\n    ///   - preferenceCenterID: The preference center identifier.\n    ///   - dismissAction: Optional action to be executed when the Preference Center is dismissed.\n    /// - Returns: A native view controller hosting the Preference Center.\n    @MainActor\n    public class func makeViewController(\n        preferenceCenterID: String,\n        dismissAction: (@Sendable () -> Void)? = nil\n    ) -> AirshipNativeViewController {\n        makeViewController(\n            view: PreferenceCenterView(preferenceCenterID: preferenceCenterID),\n            preferenceCenterTheme: nil,\n            dismissAction: dismissAction\n        )\n    }\n\n    /// Makes a view controller for the given Preference Center ID and theme plist.\n    /// - Parameters:\n    ///   - preferenceCenterID: The preference center identifier.\n    ///   - preferenceCenterThemePlist: The name of the plist file containing the theme configuration.\n    /// - Returns: A native view controller hosting the Preference Center.\n    /// - Throws: An error if the theme plist could not be loaded.\n    @MainActor\n    public class func makeViewController(\n        preferenceCenterID: String,\n        preferenceCenterThemePlist: String\n    ) throws -> AirshipNativeViewController {\n        let theme = try PreferenceCenterThemeLoader.fromPlist(preferenceCenterThemePlist)\n        return makeViewController(\n            preferenceCenterID: preferenceCenterID,\n            preferenceCenterTheme: theme\n        )\n    }\n\n    /// Makes a view controller for the given Preference Center ID and optional theme.\n    /// - Parameters:\n    ///   - preferenceCenterID: The preference center identifier.\n    ///   - preferenceCenterTheme: An optional `PreferenceCenterTheme` to style the view.\n    ///   - dismissAction: Optional action to be executed when the Preference Center is dismissed.\n    /// - Returns: A native view controller hosting the Preference Center.\n    @MainActor\n    public class func makeViewController(\n        preferenceCenterID: String,\n        preferenceCenterTheme: PreferenceCenterTheme? = nil,\n        dismissAction: (@Sendable () -> Void)? = nil\n    ) -> AirshipNativeViewController {\n        makeViewController(\n            view: PreferenceCenterView(preferenceCenterID: preferenceCenterID),\n            preferenceCenterTheme: preferenceCenterTheme,\n            dismissAction: dismissAction\n        )\n    }\n\n    /// Makes a view controller for a specific `PreferenceCenterView` instance and theme.\n    /// - Parameters:\n    ///   - view: The `PreferenceCenterView` to host.\n    ///   - preferenceCenterTheme: The theme configuration.\n    ///   - dismissAction: Optional action to be executed when the Preference Center is dismissed.\n    /// - Returns: A native view controller hosting the Preference Center.\n    @MainActor\n    public class func makeViewController(\n        view: PreferenceCenterView,\n        preferenceCenterTheme: PreferenceCenterTheme?,\n        dismissAction: (@MainActor @Sendable () -> Void)? = nil\n    ) -> AirshipNativeViewController {\n        let theme = preferenceCenterTheme ?? PreferenceCenterTheme()\n\n        #if os(macOS)\n        let isDark = NSApp.effectiveAppearance.isDark\n        #else\n        let isDark = UITraitCollection.current.userInterfaceStyle == .dark\n        #endif\n\n        let resolvedBackgroundColor = isDark ? theme.viewController?.backgroundColorDark : theme.viewController?.backgroundColor\n\n        return PreferenceCenterViewController(\n            rootView: view\n                .preferenceCenterTheme(theme)\n                .addPreferenceCenterDismissAction(action: dismissAction),\n            backgroundColor: resolvedBackgroundColor\n        )\n    }\n}\n\n/// A platform-specific hosting controller that manages the lifecycle and styling of the Preference Center view.\nprivate class PreferenceCenterViewController<Content>: AirshipNativeHostingController<Content> where Content: View {\n    \n    /// Initializes the controller.\n    /// - Parameters:\n    ///   - rootView: The SwiftUI content.\n    ///   - backgroundColor: The background color to apply to the underlying native view.\n    init(rootView: Content, backgroundColor: AirshipNativeColor? = nil) {\n        super.init(rootView: rootView)\n        \n#if os(macOS)\n        // Ensure the view is layer-backed on macOS to support background colors\n        self.view.wantsLayer = true\n        self.view.layer?.backgroundColor = backgroundColor?.cgColor\n#else\n        if let backgroundColor = backgroundColor {\n            self.view.backgroundColor = backgroundColor\n        }\n#endif\n    }\n    \n    @MainActor required dynamic init?(coder aDecoder: NSCoder) {\n        fatalError(\"init(coder:) has not been implemented\")\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Source/view/PreferenceCenterViewExtensions.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\n#if canImport(AirshipCore)\nimport AirshipCore\n#endif\n\nextension View {\n    @ViewBuilder\n    func textAppearance(\n        _ overrides: PreferenceCenterTheme.TextAppearance?,\n        base: PreferenceCenterTheme.TextAppearance? = nil,\n        colorScheme: ColorScheme\n    ) -> some View {\n        let overridesColor = colorScheme.airshipResolveColor(light: overrides?.color, dark: overrides?.colorDark)\n        self.font(overrides?.font ?? base?.font)\n            .foregroundColor(overridesColor ?? base?.color)\n    }\n\n    @ViewBuilder\n    func toggleStyle(tint: Color?) -> some View {\n        if let tint = tint {\n            #if os(tvOS)\n            self.tint(tint)\n            #else\n            self.toggleStyle(SwitchToggleStyle(tint: tint))\n            #endif\n        } else {\n            self\n        }\n    }\n\n    @ViewBuilder\n    func optAccessibilityLabel(string: String?) -> some View {\n        if let string = string {\n            self.accessibilityLabel(string)\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/PreferenceCenterTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport AirshipCore\nimport Foundation\n\n@testable\nimport AirshipPreferenceCenter\n\n@Suite(\"Preference Center\")\nstruct PreferenceCenterTest {\n    \n    private let dataStore: PreferenceDataStore = PreferenceDataStore(appKey: UUID().uuidString)\n    private var privacyManager: TestPrivacyManager!\n    private var preferenceCenter: DefaultPreferenceCenter!\n    private let remoteDataProvider: TestRemoteData = TestRemoteData()\n    \n    init() async {\n        self.privacyManager = TestPrivacyManager(\n            dataStore: self.dataStore,\n            config: .testConfig(),\n            defaultEnabledFeatures: .all\n        )\n        \n        self.preferenceCenter = await DefaultPreferenceCenter(\n            dataStore: self.dataStore,\n            privacyManager: self.privacyManager,\n            remoteData: self.remoteDataProvider,\n            inputValidator: TestInputValidator()\n        )\n    }\n    \n    @Test(\"Json config\", arguments: [\"form-1\", \"form-2\"])\n    func config(id: String) async throws {\n        let payloadData = \"\"\"\n            {\n               \"preference_forms\":[\n                  {\n                     \"created\":\"2017-10-10T12:13:14.023\",\n                     \"last_updated\":\"2017-10-10T12:13:14.023\",\n                     \"form_id\":\"031de218-9fff-44d4-b348-de4b724bb924\",\n                     \"form\":{\n                        \"id\":\"form-1\",\n                        \"sections\":[]\n                     }\n                  },\n                  {\n                     \"created\":\"2018-10-10T12:13:14.023\",\n                     \"last_updated\":\"2018-10-10T12:13:14.023\",\n                     \"form_id\":\"031de218-9fff-44d4-b348-de4b724bb931\",\n                     \"form\":{\n                        \"id\":\"form-2\",\n                        \"sections\":[]\n                     }\n                  }\n               ]\n            }\n            \"\"\"\n        \n        let remoteData = createPayload(payloadData)\n        self.remoteDataProvider.payloads = [remoteData]\n        \n        var config = try! await self.preferenceCenter.config(preferenceCenterID: id)\n        #expect(id == config.identifier)\n    }\n    \n    @Test(\"Json config\", arguments: [\"form-1\", \"form-2\"])\n    func jsonConfig(id: String) async throws {\n        let payloadData = \"\"\"\n            {\n               \"preference_forms\":[\n                  {\n                     \"created\":\"2017-10-10T12:13:14.023\",\n                     \"last_updated\":\"2017-10-10T12:13:14.023\",\n                     \"form_id\":\"031de218-9fff-44d4-b348-de4b724bb924\",\n                     \"form\":{\n                        \"id\":\"form-1\"\n                     }\n                  },\n                  {\n                     \"created\":\"2018-10-10T12:13:14.023\",\n                     \"last_updated\":\"2018-10-10T12:13:14.023\",\n                     \"form_id\":\"031de218-9fff-44d4-b348-de4b724bb931\",\n                     \"form\":{\n                        \"id\":\"form-2\"\n                     }\n                  }\n               ]\n            }\n            \"\"\"\n        \n        let remoteData = createPayload(payloadData)\n        self.remoteDataProvider.payloads = [remoteData]\n        \n        let config = try! await self.preferenceCenter.jsonConfig(preferenceCenterID: id)\n        \n        let jsonConfig = try! AirshipJSON.from(data: config)\n        let jsonform = try! AirshipJSON.wrap([\"id\": id])\n        #expect(jsonConfig == jsonform)\n    }\n    \n    @MainActor\n    @Test(\"Ensure preference center displays the correct form\")\n    func onDisplay() async throws {\n        let delegate = MockPreferenceCenterOpenDelegate()\n        self.preferenceCenter.openDelegate = delegate\n        \n        await confirmation(\"onDisplay called\", expectedCount: 1) { confirm in\n            \n            self.preferenceCenter.onDisplay = { identifier in\n                #expect(identifier == \"some-form\")\n                confirm()\n                return true\n            }\n            \n            self.preferenceCenter.display(\"some-form\")\n        }\n        #expect(!delegate.openCalled)\n    }\n    \n    @MainActor\n    @Test\n    func onDisplayNilFallback() async throws {\n        let delegate = MockPreferenceCenterOpenDelegate()\n        self.preferenceCenter.openDelegate = delegate\n        self.preferenceCenter.onDisplay = nil\n        \n        self.preferenceCenter.display(\"some-form\")\n        #expect(\"some-form\" == delegate.lastOpenID)\n    }\n    \n    @MainActor\n    @Test\n    func deepLink() async throws {\n        let delegate = MockPreferenceCenterOpenDelegate()\n        self.preferenceCenter.openDelegate = delegate\n        \n        let valid = URL(string: \"uairship://preferences/some-id\")!\n        #expect(self.preferenceCenter.deepLink(valid))\n        \n        #expect(\"some-id\" == delegate.lastOpenID)\n        \n        let trailingSlash = URL(\n            string: \"uairship://preferences/some-other-id/\"\n        )!\n        #expect(self.preferenceCenter.deepLink(trailingSlash))\n        \n        #expect(\"some-other-id\" == delegate.lastOpenID)\n    }\n    \n    @MainActor\n    @Test\n    func deepLinkInvalid() {\n        let delegate = MockPreferenceCenterOpenDelegate()\n        self.preferenceCenter.openDelegate = delegate\n        \n        let wrongScheme = URL(string: \"whatever://preferences/some-id\")!\n        #expect(!self.preferenceCenter.deepLink(wrongScheme))\n        \n        let wrongHost = URL(string: \"uairship://message_center/some-id\")!\n        #expect(!self.preferenceCenter.deepLink(wrongHost))\n        \n        let tooManyArgs = URL(\n            string: \"uairship://preferences/some-other-id/what\"\n        )!\n        #expect(!self.preferenceCenter.deepLink(tooManyArgs))\n    }\n    \n    private func createPayload(_ json: String) -> RemoteDataPayload {\n        return RemoteDataPayload(\n            type: \"preference_forms\",\n            timestamp: Date(),\n            data: try! AirshipJSON.from(json: json),\n            remoteDataInfo: nil\n        )\n    }\n}\n\nfileprivate final class TestInputValidator: AirshipInputValidation.Validator {\n    func validateRequest(_ request: AirshipCore.AirshipInputValidation.Request) async throws -> AirshipCore.AirshipInputValidation.Result {\n        return .invalid\n    }\n}\n\n\n@MainActor\nfileprivate class MockPreferenceCenterOpenDelegate: PreferenceCenterOpenDelegate {\n    var lastOpenID: String?\n    var openCalled: Bool = false\n    \n    func openPreferenceCenter(_ preferenceCenterID: String) -> Bool {\n        self.lastOpenID = preferenceCenterID\n        self.openCalled = true\n        return true\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/data/PreferenceCenterConfigTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport AirshipCore\nimport Foundation\n\n@testable\nimport AirshipPreferenceCenter\n\n@Suite(\"Preference Center Decoder\")\nstruct PreferenceCenterDecoderTest {\n    \n    @Test\n    func form() throws {\n        let testPayload: String =\n\"\"\"\n{\n          \"id\": \"cool-prefs\",\n          \"display\": {\n            \"name\": \"Cool Prefs\",\n            \"description\": \"Preferences but they're cool\"\n          },\n          \"sections\": [\n            {\n              \"id\": \"a2db6801-c766-44d7-b5d6-070ca64421b2\",\n              \"type\": \"section\",\n              \"items\": [\n                {\n                  \"id\": \"a2db6801-c766-44d7-b5d6-070ca64421b3\",\n                  \"type\": \"contact_management\",\n                  \"platform\": \"email\",\n                  \"display\": {\n                    \"name\": \"Email Addresses\",\n                    \"description\": \"Addresses associated with your account.\"\n                  },\n                  \"registration_options\": {\n                    \"address_label\": \"Email address\",\n                    \"resend\": {\n                      \"interval\": 10,\n                      \"message\": \"Pending verification\",\n                      \"button\": {\n                        \"text\": \"Resend\",\n                        \"content_description\": \"Resend a verification message to this email address\"\n                      },\n                      \"on_success\": {\n                        \"name\": \"Verification resent\",\n                        \"description\": \"Check your inbox for a new confirmation email.\",\n                        \"button\": {\n                          \"text\": \"Ok\",\n                          \"content_description\": \"Close prompt\"\n                        }\n                      }\n                    },\n                    \"error_messages\": {\n                      \"invalid\": \"Please enter a valid email address.\",\n                      \"default\": \"Uh oh, something went wrong.\"\n                    }\n                  },\n                  \"add\": {\n                    \"button\": {\n                      \"text\": \"Add email\",\n                      \"content_description\": \"Add a new email address\"\n                    },\n                    \"view\": {\n                      \"type\": \"prompt\",\n                      \"display\": {\n                        \"title\": \"Add an email address\",\n                        \"description\": \"You will receive a confirmation email to verify your address.\",\n                        \"footer\": \"Does anyone read our [Terms and Conditions](https://example.com) and [Privacy Policy](https://example.com)?\"\n                      },\n                      \"submit_button\": {\n                        \"text\": \"Send\",\n                        \"content_description\": \"Send a message to this email address\"\n                      },\n                      \"cancel_button\": {\n                        \"text\": \"Cancel\"\n                      },\n                      \"close_button\": {\n                        \"content_description\": \"Close\"\n                      },\n                      \"on_submit\": {\n                        \"name\": \"Oh no, it worked.\",\n                        \"description\": \"Hope you like emails.\",\n                        \"button\": {\n                          \"text\": \"Ok, dang\"\n                        }\n                      }\n                    }\n                  },\n                  \"remove\": {\n                    \"button\": {\n                      \"content_description\": \"Opt out and remove this email address\"\n                    },\n                    \"view\": {\n                      \"type\": \"prompt\",\n                      \"display\": {\n                        \"title\": \"Remove email address?\",\n                        \"description\": \"I thought you liked emails.\"\n                      },\n                      \"submit_button\": {\n                        \"text\": \"Yes\",\n                        \"content_description\": \"Confirm opt out\"\n                      },\n                      \"cancel_button\": {\n                        \"text\": \"No\",\n                        \"content_description\": \"Cancel opt out\"\n                      },\n                      \"close_button\": {\n                        \"content_description\": \"Close\"\n                      },\n                      \"on_submit\": {\n                        \"name\": \"Success\",\n                        \"description\": \"Bye!\",\n                        \"button\": {\n                          \"text\": \"Ok\",\n                          \"content_description\": \"Close prompt\"\n                        }\n                      }\n                    }\n                  }\n                },\n                {\n                  \"id\": \"a2db6801-c766-44d7-b5d6-070ca64421b4\",\n                  \"type\": \"contact_management\",\n                  \"platform\": \"sms\",\n                  \"display\": {\n                    \"name\": \"Mobile Numbers\"\n                  },\n                  \"registration_options\": {\n                    \"country_label\": \"Country\",\n                    \"msisdn_label\": \"Phone number\",\n                    \"resend\": {\n                      \"interval\": 10,\n                      \"message\": \"Pending verification\",\n                      \"button\": {\n                        \"text\": \"Resend\",\n                        \"content_description\": \"Resend a verification message to this phone number\"\n                      }\n                    },\n                    \"senders\": [\n                      {\n                        \"country_calling_code\": \"+1\",\n                        \"country_code\": \"US\",\n                        \"display_name\": \"\\u{1F1FA}\\u{1F1F8} +1\",\n                        \"placeholder_text\": \"7010 111222\",\n                        \"sender_id\": \"23450\"\n                      }\n                    ],\n                    \"error_messages\": {\n                      \"invalid\": \"Please enter a valid phone number.\",\n                      \"default\": \"Uh oh, something went wrong.\"\n                    }\n                  },\n                  \"add\": {\n                    \"view\": {\n                      \"type\": \"prompt\",\n                      \"display\": {\n                        \"title\": \"Add a phone number\",\n                        \"description\": \"You will receive a text message with further details.\",\n                        \"footer\": \"By opting in you give us the OK to hound you forever.\"\n                      },\n                      \"submit_button\": {\n                        \"text\": \"Send\",\n                        \"content_description\": \"Send a message to this phone number\"\n                      },\n                      \"cancel_button\": {\n                        \"text\": \"Cancel\"\n                      },\n                      \"close_button\": {\n                        \"content_description\": \"Close\"\n                      },\n                      \"on_submit\": {\n                        \"name\": \"Oh no, it worked.\",\n                        \"description\": \"Hope you like text messages.\",\n                        \"button\": {\n                          \"text\": \"Ok, dang\"\n                        }\n                      }\n                    },\n                    \"button\": {\n                      \"text\": \"Add SMS\",\n                      \"content_description\": \"Add a new phone number\"\n                    }\n                  },\n                  \"remove\": {\n                    \"button\": {\n                      \"content_description\": \"Opt out and remove this phone number\"\n                    },\n                    \"view\": {\n                      \"type\": \"prompt\",\n                      \"display\": {\n                        \"title\": \"Remove phone number?\",\n                        \"description\": \"Your phone will buzz less.\"\n                      },\n                      \"submit_button\": {\n                        \"text\": \"Yes\",\n                        \"content_description\": \"Confirm opt out\"\n                      },\n                      \"cancel_button\": {\n                        \"text\": \"No\",\n                        \"content_description\": \"Cancel opt out\"\n                      },\n                      \"close_button\": {\n                        \"content_description\": \"Close\"\n                      },\n                      \"on_submit\": {\n                        \"name\": \"Success\",\n                        \"description\": \"Bye!\",\n                        \"button\": {\n                          \"text\": \"Ok\",\n                          \"content_description\": \"Close prompt\"\n                        }\n                      }\n                    }\n                  }\n                }\n              ]\n            }\n          ]\n        }\n\"\"\"\n        let _ = try PreferenceCenterDecoder.decodeConfig(\n            data: testPayload.data(using: .utf8)!\n        )\n    }\n    \n    private func parseAndSortJSON(jsonString: String) -> String? {\n        guard let data = jsonString.data(using: .utf8),\n              let jsonObject = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any]\n        else { return nil }\n        \n        let sortedData = try? JSONSerialization.data(withJSONObject: jsonObject, options: [.sortedKeys, .prettyPrinted])\n        return sortedData.flatMap { String(data: $0, encoding: .utf8) }\n    }\n}\n\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/test data/TestLegacyTheme.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>title</key>\n\t<string>Preference Center</string>\n\t<key>titleColor</key>\n\t<string>#0000FF</string>\n\t<key>titleFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>15</string>\n\t</dict>\n\t<key>sectionTitleTextColor</key>\n\t<string>#de0000</string>\n\t<key>sectionTitleTextFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>32</string>\n\t</dict>\n\t<key>sectionSubtitleTextColor</key>\n\t<string>#da833b</string>\n\t<key>sectionSubtitleTextFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>25</string>\n\t</dict>\n\t<key>preferenceTitleTextColor</key>\n\t<string>#034710</string>\n\t<key>preferenceTitleTextFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>20</string>\n\t</dict>\n\t<key>preferenceSubtitleTextColor</key>\n\t<string>#8fe388</string>\n\t<key>preferenceSubtitleTextFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>15</string>\n\t</dict>\n\t<key>preferenceChipTextColor</key>\n\t<string>#7c6bea</string>\n\t<key>preferenceChipTextFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>15</string>\n\t</dict>\n\t<key>preferenceChipCheckmarkColor</key>\n\t<string>#0a0fc9</string>\n\t<key>preferenceChipCheckmarkCheckedBackgroundColor</key>\n\t<string>#3bd2d6</string>\n\t<key>preferenceChipBorderColor</key>\n\t<string>#0a0fc9</string>\n\t<key>alertTitleColor</key>\n\t<string>#0a0fc9</string>\n\t<key>alertTitleFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>15</string>\n\t</dict>\n\t<key>alertSubtitleColor</key>\n\t<string>#d1b4d4</string>\n\t<key>alertButtonBackgroundColor</key>\n\t<string>#da833b</string>\n\t<key>alertButtonLabelColor</key>\n\t<string>#78c8c0</string>\n\t<key>alertButtonLabelFont</key>\n\t<dict>\n\t\t<key>fontName</key>\n\t\t<string>Helvetica</string>\n\t\t<key>fontSize</key>\n\t\t<string>25</string>\n\t</dict>\n</dict>\n</plist>"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/test data/TestTheme.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>viewController</key>\n        <dict>\n            <key>navigationBar</key>\n            <dict>       \n                <key>title</key>\n                <string>Preference Center</string>\n                <key>titleFont</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>15</string>\n                </dict>\n                <key>titleColor</key>\n                <string>#0000FF</string>\n            </dict>\n        </dict>\n        <key>preferenceCenter</key>\n        <dict>\n            <key>subtitleAppearance</key>\n            <dict>\n            </dict>\n        </dict>\n        <key>commonSection</key>\n        <dict>\n            <key>titleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#de0000</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>32</string>\n                </dict>\n            </dict>\n            <key>subtitleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#da833b</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>25</string>\n                </dict>\n            </dict>\n        </dict>\n        <key>labeledSectionBreak</key>\n        <dict>\n            <key>titleAppearance</key>\n            <dict>\n            </dict>\n        </dict>\n        <key>channelSubscription</key>\n        <dict>\n            <key>titleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#034710</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>20</string>\n                </dict>\n            </dict>\n            <key>subtitleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#8fe388</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>15</string>\n                </dict>\n            </dict>\n        </dict>\n        \n        <key>contactSubscription</key>\n        <dict>\n            <key>titleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#034710</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>20</string>\n                </dict>\n            </dict>\n            <key>subtitleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#8fe388</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>15</string>\n                </dict>\n            </dict>\n        </dict>\n        <key>contactSubscriptionGroup</key>\n        <dict>\n            <key>titleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#034710</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>20</string>\n                </dict>\n            </dict>\n            <key>subtitleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#8fe388</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>15</string>\n                </dict>\n            </dict>\n            <key>chip</key>\n            <dict>\n                <key>checkColor</key>\n                <string>#3bd2d6</string>\n                <key>borderColor</key>\n                <string>#0a0fc9</string>\n                <key>labelAppearance</key>\n                <dict>\n                    <key>color</key>\n                    <string>#7c6bea</string>\n                    <key>font</key>\n                    <dict>\n                        <key>fontName</key>\n                        <string>Helvetica</string>\n                        <key>fontSize</key>\n                        <string>15</string>\n                    </dict>\n                </dict>\n            </dict>\n        </dict>\n        <key>alert</key>\n        <dict>\n            <key>titleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#0a0fc9</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>15</string>\n                </dict>\n            </dict>\n            <key>subtitleAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#d1b4d4</string>\n            </dict>\n            <key>buttonLabelAppearance</key>\n            <dict>\n                <key>color</key>\n                <string>#78c8c0</string>\n                <key>font</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>25</string>\n                </dict>\n            </dict>\n            <key>buttonBackgroundColor</key>\n            <string>#da833b</string>\n        </dict>\n    </dict>\n</plist>"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/test data/TestThemeEmpty.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n</dict>\n</plist>"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/test data/TestThemeInvalid.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n    <dict>\n        <key>viewController</key>\n        <dict>\n            <key>navigationBar</key>\n            <dict>       \n                <key>title</key>\n                <string>Preference Center</string>\n                <key>titleFont</key>\n                <dict>\n                    <key>fontName</key>\n                    <string>Helvetica</string>\n                    <key>fontSize</key>\n                    <string>-1</string>\n                </dict>\n            </dict>\n        </dict>\n    </dict>\n</plist>"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/theme/PreferenceThemeLoaderTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport SwiftUI\n\n@testable\nimport AirshipPreferenceCenter\n\n@Suite(\"Preference Theme Loader\")\nstruct PreferenceThemeLoaderTest {\n    \n    private class BundleFinder {}\n    let bundle: Bundle\n    \n    init() {\n        bundle = Bundle(for: BundleFinder.self)\n    }\n    \n    @Test\n    func fromPlist() throws {\n        \n        let legacyTheme = try PreferenceCenterThemeLoader.fromPlist(\n            \"TestLegacyTheme\",\n            bundle: bundle\n        )\n        let theme = try PreferenceCenterThemeLoader.fromPlist(\n            \"TestTheme\",\n            bundle: bundle\n        )\n        \n        #expect(legacyTheme == theme)\n        #expect(PreferenceCenterTheme() != theme)\n    }\n    \n    @Test\n    func loadEmptyPlist() throws {\n        _ = try PreferenceCenterThemeLoader.fromPlist(\n            \"TestThemeEmpty\",\n            bundle: bundle\n        )\n    }\n    \n    @Test\n    func invalidFile() throws {\n        #expect(throws: (any Error).self) {\n            try PreferenceCenterThemeLoader.fromPlist(\"Not a file\", bundle: bundle)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship/AirshipPreferenceCenter/Tests/view/PreferenceCenterStateTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\nimport AirshipCore\nimport Combine\nimport SwiftUI\n\n@testable\nimport AirshipPreferenceCenter\n\n@Suite(\"Preference Center State\")\nstruct PreferenceCenterStateTest {\n    let subscriber = TestPreferenceSubscriber()\n    let state: PreferenceCenterState!\n\n    @MainActor\n    init() {\n        state = PreferenceCenterState(\n            config: PreferenceCenterConfig(\n                identifier: \"empty\",\n                sections: []\n            ),\n            contactSubscriptions: [\n                \"baz\": [.app]\n            ],\n            channelSubscriptions: [\"foo\", \"bar\"],\n            subscriber: subscriber\n        )\n    }\n\n    @MainActor\n    @Test\n    func channelBinding() {\n        let channelFoo = self.state.makeBinding(channelListID: \"foo\")\n        #expect(channelFoo.wrappedValue)\n\n        channelFoo.wrappedValue.toggle()\n        #expect(!channelFoo.wrappedValue)\n        #expect([.unsubscribe(\"foo\")] == self.subscriber.channelEdits)\n\n        channelFoo.wrappedValue.toggle()\n        #expect([.unsubscribe(\"foo\"), .subscribe(\"foo\")] == self.subscriber.channelEdits)\n    }\n\n    @MainActor\n    @Test\n    func channelBindingNotSubscribed() {\n        let channelNotFoo = self.state.makeBinding(channelListID: \"not foo\")\n        #expect(!channelNotFoo.wrappedValue)\n\n        channelNotFoo.wrappedValue.toggle()\n        #expect(channelNotFoo.wrappedValue)\n        #expect([.subscribe(\"not foo\")] == self.subscriber.channelEdits)\n    }\n\n    @MainActor\n    @Test\n    func contactBinding() {\n        let contactBaz = self.state.makeBinding(\n            contactListID: \"baz\",\n            scopes: [.app]\n        )\n        #expect(contactBaz.wrappedValue)\n\n        contactBaz.wrappedValue.toggle()\n        #expect(!contactBaz.wrappedValue)\n        #expect([.unsubscribe(\"baz\", .app)] == self.subscriber.contactEdits)\n\n        contactBaz.wrappedValue.toggle()\n        #expect([.unsubscribe(\"baz\", .app), .subscribe(\"baz\", .app)] == self.subscriber.contactEdits)\n    }\n\n    @MainActor\n    @Test\n    func contactlBindingNotSubscribed() {\n        let contactNotBaz = self.state.makeBinding(\n            contactListID: \"not baz\",\n            scopes: [.app]\n        )\n        #expect(!contactNotBaz.wrappedValue)\n\n        contactNotBaz.wrappedValue.toggle()\n        #expect(contactNotBaz.wrappedValue)\n        #expect([.subscribe(\"not baz\", .app)] == self.subscriber.contactEdits)\n    }\n\n    @MainActor\n    @Test\n    func contactlBindingPartialScope() {\n        let contactBaz = self.state.makeBinding(\n            contactListID: \"baz\",\n            scopes: [.app, .web]\n        )\n        #expect(contactBaz.wrappedValue)\n\n        contactBaz.wrappedValue.toggle()\n        #expect(!contactBaz.wrappedValue)\n        #expect([.unsubscribe(\"baz\", .app), .unsubscribe(\"baz\", .web)] == self.subscriber.contactEdits)\n\n        contactBaz.wrappedValue.toggle()\n        #expect(\n            [\n                .unsubscribe(\"baz\", .app),\n                .unsubscribe(\"baz\", .web),\n                .subscribe(\"baz\", .app),\n                .subscribe(\"baz\", .web)\n            ] == self.subscriber.contactEdits)\n    }\n\n    @MainActor\n    @Test\n    func contactDifferentScope() {\n        let contactBaz = self.state.makeBinding(\n            contactListID: \"baz\",\n            scopes: [.web]\n        )\n        #expect(!contactBaz.wrappedValue)\n    }\n\n    @MainActor\n    @Test\n    func channelMergeData() {\n        let state = PreferenceCenterState(\n            config: PreferenceCenterConfig(\n                identifier: \"empty\",\n                sections: [],\n                options: PreferenceCenterConfig.Options(\n                    mergeChannelDataToContact: true\n                )\n            ),\n            contactSubscriptions: [\n                \"baz\": [.web]\n            ],\n            channelSubscriptions: [\"foo\", \"baz\"],\n            subscriber: subscriber\n        )\n\n        let contactAppBaz = state.makeBinding(\n            contactListID: \"baz\",\n            scopes: [.app]\n        )\n        #expect(contactAppBaz.wrappedValue)\n\n        contactAppBaz.wrappedValue.toggle()\n        #expect(contactAppBaz.wrappedValue)\n        #expect([.unsubscribe(\"baz\", .app)] == self.subscriber.contactEdits)\n        #expect(self.subscriber.channelEdits.isEmpty)\n    }\n\n    @MainActor\n    @Test\n    func channelExternalUpdates() {\n        let channelFoo = self.state.makeBinding(channelListID: \"foo\")\n        #expect(channelFoo.wrappedValue)\n\n        self.subscriber.channelEditsSubject.send(.subscribe(\"foo\"))\n        #expect(channelFoo.wrappedValue)\n\n        self.subscriber.channelEditsSubject.send(.unsubscribe(\"foo\"))\n        #expect(!channelFoo.wrappedValue)\n\n        self.subscriber.channelEditsSubject.send(.unsubscribe(\"foo\"))\n        #expect(!channelFoo.wrappedValue)\n\n        self.subscriber.channelEditsSubject.send(.subscribe(\"foo\"))\n        #expect(channelFoo.wrappedValue)\n    }\n\n    @MainActor\n    @Test\n    func contactExternalUpdates() {\n        let contactBaz = self.state.makeBinding(\n            contactListID: \"baz\",\n            scopes: [.app]\n        )\n        #expect(contactBaz.wrappedValue)\n\n        self.subscriber.contactEditsSubject.send(.subscribe(\"baz\", .app))\n        #expect(contactBaz.wrappedValue)\n\n        self.subscriber.contactEditsSubject.send(.unsubscribe(\"baz\", .app))\n        #expect(!contactBaz.wrappedValue)\n\n        self.subscriber.contactEditsSubject.send(.unsubscribe(\"baz\", .app))\n        #expect(!contactBaz.wrappedValue)\n\n        self.subscriber.contactEditsSubject.send(.subscribe(\"baz\", .app))\n        #expect(contactBaz.wrappedValue)\n    }\n}\n\nclass TestPreferenceSubscriber: PreferenceSubscriber {\n    let channelEditsSubject = PassthroughSubject<SubscriptionListEdit, Never>()\n    var channelSubscriptionListEdits: AnyPublisher<SubscriptionListEdit, Never>\n    {\n        return channelEditsSubject.eraseToAnyPublisher()\n    }\n\n    let contactEditsSubject = PassthroughSubject<\n        ScopedSubscriptionListEdit, Never\n    >()\n    var contactSubscriptionListEdits:\n        AnyPublisher<ScopedSubscriptionListEdit, Never>\n    {\n        return contactEditsSubject.eraseToAnyPublisher()\n    }\n\n    var channelEdits = [SubscriptionListEdit]()\n    var contactEdits = [ScopedSubscriptionListEdit]()\n\n    func updateChannelSubscription(_ listID: String, subscribe: Bool) {\n        var edit: SubscriptionListEdit!\n        if subscribe {\n            edit = .subscribe(listID)\n        } else {\n            edit = .unsubscribe(listID)\n        }\n        self.channelEdits.append(edit)\n        self.channelEditsSubject.send(edit)\n    }\n\n    func updateContactSubscription(\n        _ listID: String,\n        scopes: [ChannelScope],\n        subscribe: Bool\n    ) {\n        scopes.forEach { scope in\n            var edit: ScopedSubscriptionListEdit!\n            if subscribe {\n                edit = .subscribe(listID, scope)\n            } else {\n                edit = .unsubscribe(listID, scope)\n            }\n            self.contactEdits.append(edit)\n            self.contactEditsSubject.send(edit)\n        }\n    }\n}\n"
  },
  {
    "path": "Airship.podspec",
    "content": "AIRSHIP_VERSION=\"20.6.2\"\n\nPod::Spec.new do |s|\n   s.version                    = AIRSHIP_VERSION\n   s.name                       = \"Airship\"\n   s.summary                    = \"Airship iOS SDK\"\n   s.documentation_url          = \"https://docs.airship.com/platform/ios\"\n   s.homepage                   = \"https://www.airship.com\"\n   s.author                     = { \"Airship\" => \"support@airship.com\" }\n   s.license                    = { :type => \"Apache License, Version 2.0\", :file => \"LICENSE\" }\n   s.source                     = { :git => \"https://github.com/urbanairship/ios-library.git\", :tag => s.version.to_s }\n   s.module_name                = \"AirshipKit\"\n   s.header_dir                 = \"AirshipKit\"\n   s.ios.deployment_target      = \"16.0\"\n   s.tvos.deployment_target     = \"18.0\"\n   s.watchos.deployment_target  = \"11.0\"\n   s.swift_versions             = \"6.0\"\n   s.requires_arc               = true\n   s.default_subspecs           = [\"Basement\", \"Core\", \"Automation\", \"MessageCenter\", \"PreferenceCenter\", \"FeatureFlags\"]\n\n   s.subspec \"Basement\" do |basement|\n      basement.source_files               = \"Airship/AirshipBasement/Source/**/*.{swift}\"\n   end\n\n   s.subspec \"Core\" do |core|\n      core.source_files               = \"Airship/AirshipCore/Source/**/*.{swift}\"\n      core.resource_bundle            = { 'AirshipCoreResources' => \"Airship/AirshipCore/Resources/**/*\" }\n      core.exclude_files              = \"Airship/AirshipCore/Resources/Info.plist\"\n      core.dependency                 \"Airship/Basement\"\n   end\n\n   s.subspec \"Automation\" do |automation|\n      automation.ios.source_files          = \"Airship/AirshipAutomation/Source/**/*.{swift}\"\n      automation.ios.resource_bundle       = { 'AirshipAutomationResources' => \"Airship/AirshipAutomation/Resources/**/*\" }\n      automation.dependency                \"Airship/Core\"\n   end\n\n   s.subspec \"MessageCenter\" do |messageCenter|\n      messageCenter.ios.source_files          = \"Airship/AirshipMessageCenter/Source/**/*.{swift}\"\n      messageCenter.ios.resource_bundle       = { 'AirshipMessageCenterResources' => \"Airship/AirshipMessageCenter/Resources/**/*\" }\n      messageCenter.dependency                \"Airship/Core\"\n   end\n\n   s.subspec \"PreferenceCenter\" do |preferenceCenter|\n      preferenceCenter.ios.source_files              = \"Airship/AirshipPreferenceCenter/Source/**/*.{swift}\"\n      preferenceCenter.dependency                      \"Airship/Core\"\n   end\n\n   s.subspec \"FeatureFlags\" do |airshipFeatureFlags|\n      airshipFeatureFlags.ios.source_files              = \"Airship/AirshipFeatureFlags/Source/**/*.{swift}\"\n      airshipFeatureFlags.dependency                      \"Airship/Core\"\n   end\n\n   s.subspec \"ObjectiveC\" do |objectiveC|\n      objectiveC.ios.source_files              = \"Airship/AirshipObjectiveC/Source/**/*.{swift}\"\n      objectiveC.dependency                      \"Airship/Core\"\n      objectiveC.dependency                      \"Airship/Automation\"\n      objectiveC.dependency                      \"Airship/MessageCenter\"\n      objectiveC.dependency                      \"Airship/PreferenceCenter\"\n      objectiveC.dependency                      \"Airship/FeatureFlags\"\n   end\nend\n"
  },
  {
    "path": "Airship.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"group:Airship/Airship.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:AirshipExtensions/AirshipExtensions.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:DevApp/DevApp.xcodeproj\">\n   </FileRef>\n   <FileRef\n      location = \"group:DevApp watchOS/DevApp watchOS.xcodeproj\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Airship.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "Airship.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict/>\n</plist>\n"
  },
  {
    "path": "Airship.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"00fc82b0de716b0b6fae8646fae1c289e5b4679f93a4b0a9aca7e47cd71f9315\",\n  \"pins\" : [\n    {\n      \"identity\" : \"yams\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/jpsim/Yams.git\",\n      \"state\" : {\n        \"revision\" : \"deaf82e867fa2cbd3cd865978b079bfcf384ac28\",\n        \"version\" : \"6.2.1\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "AirshipExtensions/AirshipExtensions.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 53;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t6014AD692C1CB6360072DCF0 /* ChallengeResolver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6014AD682C1CB6360072DCF0 /* ChallengeResolver.swift */; };\n\t\t60F8E7522B88AF8D00460EDF /* MediaAttachmentPayload.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E7512B88AF8D00460EDF /* MediaAttachmentPayload.swift */; };\n\t\t60F8E7542B89272300460EDF /* UANotificationServiceExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E7532B89272300460EDF /* UANotificationServiceExtension.swift */; };\n\t\t60F8E7562B8D11B300460EDF /* MediaAttachmentPayloadTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E7552B8D11B300460EDF /* MediaAttachmentPayloadTest.swift */; };\n\t\t60F8E7582B8D259B00460EDF /* UANotificationServiceExtensionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 60F8E7572B8D259B00460EDF /* UANotificationServiceExtensionTests.swift */; };\n\t\t6E4AEE3A2B6B3E94008AEAC1 /* airship.jpg in Resources */ = {isa = PBXBuildFile; fileRef = 6E4AEE382B6B3E2D008AEAC1 /* airship.jpg */; };\n\t\t6E66BB1A2D14F6B60083A9FD /* AirshipNotificationMutationProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E66BB192D14F6B20083A9FD /* AirshipNotificationMutationProvider.swift */; };\n\t\t6EE62B122D668D1B00C2F53B /* AirshipExtensionLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE62B112D668D1600C2F53B /* AirshipExtensionLogger.swift */; };\n\t\t6EE62B162D66AD0800C2F53B /* AirshipExtensionConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EE62B152D66AD0400C2F53B /* AirshipExtensionConfig.swift */; };\n\t\tDF0C1B19244E483C0011ACCA /* AirshipNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 49AA00FC1D65158C0081989A /* AirshipNotificationServiceExtension.framework */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\tDF0C1B1A244E483C0011ACCA /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 49AA00F31D65158C0081989A /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 49AA00FB1D65158C0081989A;\n\t\t\tremoteInfo = AirshipNotificationServiceExtension;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXFileReference section */\n\t\t1BFF1862238543FF00013FB9 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };\n\t\t1BFF1864238543FF00013FB9 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; };\n\t\t49AA00FC1D65158C0081989A /* AirshipNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AirshipNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t49AA01001D65158C0081989A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t49AA010C1D65158C0081989A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6014AD682C1CB6360072DCF0 /* ChallengeResolver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ChallengeResolver.swift; sourceTree = \"<group>\"; };\n\t\t60F8E7512B88AF8D00460EDF /* MediaAttachmentPayload.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaAttachmentPayload.swift; sourceTree = \"<group>\"; };\n\t\t60F8E7532B89272300460EDF /* UANotificationServiceExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UANotificationServiceExtension.swift; sourceTree = \"<group>\"; };\n\t\t60F8E7552B8D11B300460EDF /* MediaAttachmentPayloadTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MediaAttachmentPayloadTest.swift; sourceTree = \"<group>\"; };\n\t\t60F8E7572B8D259B00460EDF /* UANotificationServiceExtensionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UANotificationServiceExtensionTests.swift; sourceTree = \"<group>\"; };\n\t\t6E4AEE382B6B3E2D008AEAC1 /* airship.jpg */ = {isa = PBXFileReference; lastKnownFileType = image.jpeg; name = airship.jpg; path = AirshipNotificationServiceExtension/airship.jpg; sourceTree = SOURCE_ROOT; };\n\t\t6E66BB192D14F6B20083A9FD /* AirshipNotificationMutationProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipNotificationMutationProvider.swift; sourceTree = \"<group>\"; };\n\t\t6EE62B112D668D1600C2F53B /* AirshipExtensionLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipExtensionLogger.swift; sourceTree = \"<group>\"; };\n\t\t6EE62B152D66AD0400C2F53B /* AirshipExtensionConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipExtensionConfig.swift; sourceTree = \"<group>\"; };\n\t\tDF0C1B14244E483C0011ACCA /* AirshipNotificationServiceExtensionTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = AirshipNotificationServiceExtensionTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t49AA00F81D65158C0081989A /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tDF0C1B11244E483C0011ACCA /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\tDF0C1B19244E483C0011ACCA /* AirshipNotificationServiceExtension.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t1BFF1861238543FF00013FB9 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BFF1862238543FF00013FB9 /* UserNotifications.framework */,\n\t\t\t\t1BFF1864238543FF00013FB9 /* UserNotificationsUI.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t49AA00F21D65158C0081989A = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t49AA00FE1D65158C0081989A /* AirshipNotificationServiceExtension */,\n\t\t\t\t1BFF1861238543FF00013FB9 /* Frameworks */,\n\t\t\t\t49AA00FD1D65158C0081989A /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t49AA00FD1D65158C0081989A /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t49AA00FC1D65158C0081989A /* AirshipNotificationServiceExtension.framework */,\n\t\t\t\tDF0C1B14244E483C0011ACCA /* AirshipNotificationServiceExtensionTests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t49AA00FE1D65158C0081989A /* AirshipNotificationServiceExtension */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t49AA01001D65158C0081989A /* Info.plist */,\n\t\t\t\t6E21736A237CCEDA0084933A /* Source */,\n\t\t\t\t49AA01091D65158C0081989A /* Tests */,\n\t\t\t);\n\t\t\tpath = AirshipNotificationServiceExtension;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t49AA01091D65158C0081989A /* Tests */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E4AEE382B6B3E2D008AEAC1 /* airship.jpg */,\n\t\t\t\t49AA010C1D65158C0081989A /* Info.plist */,\n\t\t\t\t60F8E7552B8D11B300460EDF /* MediaAttachmentPayloadTest.swift */,\n\t\t\t\t60F8E7572B8D259B00460EDF /* UANotificationServiceExtensionTests.swift */,\n\t\t\t);\n\t\t\tpath = Tests;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E21736A237CCEDA0084933A /* Source */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EE62B152D66AD0400C2F53B /* AirshipExtensionConfig.swift */,\n\t\t\t\t6EE62B112D668D1600C2F53B /* AirshipExtensionLogger.swift */,\n\t\t\t\t6014AD682C1CB6360072DCF0 /* ChallengeResolver.swift */,\n\t\t\t\t6E66BB192D14F6B20083A9FD /* AirshipNotificationMutationProvider.swift */,\n\t\t\t\t60F8E7512B88AF8D00460EDF /* MediaAttachmentPayload.swift */,\n\t\t\t\t60F8E7532B89272300460EDF /* UANotificationServiceExtension.swift */,\n\t\t\t);\n\t\t\tpath = Source;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXHeadersBuildPhase section */\n\t\t49AA00F91D65158C0081989A /* Headers */ = {\n\t\t\tisa = PBXHeadersBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXHeadersBuildPhase section */\n\n/* Begin PBXNativeTarget section */\n\t\t49AA00FB1D65158C0081989A /* AirshipNotificationServiceExtension */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 49AA01101D65158C0081989A /* Build configuration list for PBXNativeTarget \"AirshipNotificationServiceExtension\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t49AA00F71D65158C0081989A /* Sources */,\n\t\t\t\t49AA00F81D65158C0081989A /* Frameworks */,\n\t\t\t\t49AA00F91D65158C0081989A /* Headers */,\n\t\t\t\t49AA00FA1D65158C0081989A /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = AirshipNotificationServiceExtension;\n\t\t\tproductName = AirshipAppExtensions;\n\t\t\tproductReference = 49AA00FC1D65158C0081989A /* AirshipNotificationServiceExtension.framework */;\n\t\t\tproductType = \"com.apple.product-type.framework\";\n\t\t};\n\t\tDF0C1B13244E483C0011ACCA /* AirshipNotificationServiceExtensionTests */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = DF0C1B1C244E483C0011ACCA /* Build configuration list for PBXNativeTarget \"AirshipNotificationServiceExtensionTests\" */;\n\t\t\tbuildPhases = (\n\t\t\t\tDF0C1B10244E483C0011ACCA /* Sources */,\n\t\t\t\tDF0C1B11244E483C0011ACCA /* Frameworks */,\n\t\t\t\tDF0C1B12244E483C0011ACCA /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\tDF0C1B1B244E483C0011ACCA /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = AirshipNotificationServiceExtensionTests;\n\t\t\tproductName = AirshipNotificationServiceExtensionTests;\n\t\t\tproductReference = DF0C1B14244E483C0011ACCA /* AirshipNotificationServiceExtensionTests.xctest */;\n\t\t\tproductType = \"com.apple.product-type.bundle.unit-test\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t49AA00F31D65158C0081989A /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tLastSwiftUpdateCheck = 1140;\n\t\t\t\tLastUpgradeCheck = 1250;\n\t\t\t\tORGANIZATIONNAME = \"Urban Airship\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t49AA00FB1D65158C0081989A = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 8.0;\n\t\t\t\t\t\tDevelopmentTeam = PGJV57GD94;\n\t\t\t\t\t\tLastSwiftMigration = 1520;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t\tDF0C1B13244E483C0011ACCA = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 11.4;\n\t\t\t\t\t\tLastSwiftMigration = 1140;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 49AA00F61D65158C0081989A /* Build configuration list for PBXProject \"AirshipExtensions\" */;\n\t\t\tcompatibilityVersion = \"Xcode 11.4\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 49AA00F21D65158C0081989A;\n\t\t\tproductRefGroup = 49AA00FD1D65158C0081989A /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t49AA00FB1D65158C0081989A /* AirshipNotificationServiceExtension */,\n\t\t\t\tDF0C1B13244E483C0011ACCA /* AirshipNotificationServiceExtensionTests */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t49AA00FA1D65158C0081989A /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tDF0C1B12244E483C0011ACCA /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6E4AEE3A2B6B3E94008AEAC1 /* airship.jpg in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t49AA00F71D65158C0081989A /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t60F8E7522B88AF8D00460EDF /* MediaAttachmentPayload.swift in Sources */,\n\t\t\t\t60F8E7542B89272300460EDF /* UANotificationServiceExtension.swift in Sources */,\n\t\t\t\t6EE62B162D66AD0800C2F53B /* AirshipExtensionConfig.swift in Sources */,\n\t\t\t\t6E66BB1A2D14F6B60083A9FD /* AirshipNotificationMutationProvider.swift in Sources */,\n\t\t\t\t6EE62B122D668D1B00C2F53B /* AirshipExtensionLogger.swift in Sources */,\n\t\t\t\t6014AD692C1CB6360072DCF0 /* ChallengeResolver.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\tDF0C1B10244E483C0011ACCA /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t60F8E7582B8D259B00460EDF /* UANotificationServiceExtensionTests.swift in Sources */,\n\t\t\t\t60F8E7562B8D11B300460EDF /* MediaAttachmentPayloadTest.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\tDF0C1B1B244E483C0011ACCA /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 49AA00FB1D65158C0081989A /* AirshipNotificationServiceExtension */;\n\t\t\ttargetProxy = DF0C1B1A244E483C0011ACCA /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\t49AA010E1D65158C0081989A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tBUILD_LIBRARY_FOR_DISTRIBUTION = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t49AA010F1D65158C0081989A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tBUILD_LIBRARY_FOR_DISTRIBUTION = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tONLY_ACTIVE_ARCH = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t\tVERSIONING_SYSTEM = \"apple-generic\";\n\t\t\t\tVERSION_INFO_PREFIX = \"\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t49AA01111D65158C0081989A /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tAPPLICATION_EXTENSION_API_ONLY = YES;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipNotificationServiceExtension/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipNotificationServiceExtension;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = complete;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t49AA01121D65158C0081989A /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tAPPLICATION_EXTENSION_API_ONLY = YES;\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_IDENTITY = \"\";\n\t\t\t\tDEFINES_MODULE = YES;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tDYLIB_COMPATIBILITY_VERSION = 1;\n\t\t\t\tDYLIB_CURRENT_VERSION = 1;\n\t\t\t\tDYLIB_INSTALL_NAME_BASE = \"@rpath\";\n\t\t\t\tINFOPLIST_FILE = \"$(SRCROOT)/AirshipNotificationServiceExtension/Info.plist\";\n\t\t\t\tINSTALL_PATH = \"$(LOCAL_LIBRARY_DIR)/Frameworks\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipNotificationServiceExtension;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = YES;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_STRICT_CONCURRENCY = complete;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_CONCISE_MAGIC_FILE = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_DEPRECATE_APPLICATION_MAIN = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_DISABLE_OUTWARD_ACTOR_ISOLATION = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_EXISTENTIAL_ANY = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_FORWARD_TRAILING_CLOSURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_GLOBAL_CONCURRENCY = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_IMPLICIT_OPEN_EXISTENTIALS = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INFER_SENDABLE_FROM_CAPTURES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_INTERNAL_IMPORTS_BY_DEFAULT = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_ISOLATED_DEFAULT_VALUES = YES;\n\t\t\t\tSWIFT_UPCOMING_FEATURE_REGION_BASED_ISOLATION = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tDF0C1B1D244E483C0011ACCA /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = JZP4756KMT;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tINFOPLIST_FILE = AirshipNotificationServiceExtension/Tests/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipNotificationServiceExtensionTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tDF0C1B1E244E483C0011ACCA /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++14\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tDEVELOPMENT_TEAM = JZP4756KMT;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tINFOPLIST_FILE = AirshipNotificationServiceExtension/Tests/Info.plist;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.0;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@loader_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.AirshipNotificationServiceExtensionTests;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t49AA00F61D65158C0081989A /* Build configuration list for PBXProject \"AirshipExtensions\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t49AA010E1D65158C0081989A /* Debug */,\n\t\t\t\t49AA010F1D65158C0081989A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t49AA01101D65158C0081989A /* Build configuration list for PBXNativeTarget \"AirshipNotificationServiceExtension\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t49AA01111D65158C0081989A /* Debug */,\n\t\t\t\t49AA01121D65158C0081989A /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tDF0C1B1C244E483C0011ACCA /* Build configuration list for PBXNativeTarget \"AirshipNotificationServiceExtensionTests\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tDF0C1B1D244E483C0011ACCA /* Debug */,\n\t\t\t\tDF0C1B1E244E483C0011ACCA /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 49AA00F31D65158C0081989A /* Project object */;\n}\n"
  },
  {
    "path": "AirshipExtensions/AirshipExtensions.xcodeproj/xcshareddata/xcschemes/AirshipNotificationServiceExtension.xcscheme",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Scheme\n   LastUpgradeVersion = \"1600\"\n   version = \"1.3\">\n   <BuildAction\n      parallelizeBuildables = \"YES\"\n      buildImplicitDependencies = \"YES\">\n      <BuildActionEntries>\n         <BuildActionEntry\n            buildForTesting = \"YES\"\n            buildForRunning = \"YES\"\n            buildForProfiling = \"YES\"\n            buildForArchiving = \"YES\"\n            buildForAnalyzing = \"YES\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"49AA00FB1D65158C0081989A\"\n               BuildableName = \"AirshipNotificationServiceExtension.framework\"\n               BlueprintName = \"AirshipNotificationServiceExtension\"\n               ReferencedContainer = \"container:AirshipExtensions.xcodeproj\">\n            </BuildableReference>\n         </BuildActionEntry>\n      </BuildActionEntries>\n   </BuildAction>\n   <TestAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\">\n      <Testables>\n         <TestableReference\n            skipped = \"NO\">\n            <BuildableReference\n               BuildableIdentifier = \"primary\"\n               BlueprintIdentifier = \"DF0C1B13244E483C0011ACCA\"\n               BuildableName = \"AirshipNotificationServiceExtensionTests.xctest\"\n               BlueprintName = \"AirshipNotificationServiceExtensionTests\"\n               ReferencedContainer = \"container:AirshipExtensions.xcodeproj\">\n            </BuildableReference>\n         </TestableReference>\n      </Testables>\n   </TestAction>\n   <LaunchAction\n      buildConfiguration = \"Debug\"\n      selectedDebuggerIdentifier = \"Xcode.DebuggerFoundation.Debugger.LLDB\"\n      selectedLauncherIdentifier = \"Xcode.DebuggerFoundation.Launcher.LLDB\"\n      launchStyle = \"0\"\n      useCustomWorkingDirectory = \"NO\"\n      ignoresPersistentStateOnLaunch = \"NO\"\n      debugDocumentVersioning = \"YES\"\n      debugServiceExtension = \"internal\"\n      allowLocationSimulation = \"YES\">\n   </LaunchAction>\n   <ProfileAction\n      buildConfiguration = \"Release\"\n      shouldUseLaunchSchemeArgsEnv = \"YES\"\n      savedToolIdentifier = \"\"\n      useCustomWorkingDirectory = \"NO\"\n      debugDocumentVersioning = \"YES\">\n      <MacroExpansion>\n         <BuildableReference\n            BuildableIdentifier = \"primary\"\n            BlueprintIdentifier = \"49AA00FB1D65158C0081989A\"\n            BuildableName = \"AirshipNotificationServiceExtension.framework\"\n            BlueprintName = \"AirshipNotificationServiceExtension\"\n            ReferencedContainer = \"container:AirshipExtensions.xcodeproj\">\n         </BuildableReference>\n      </MacroExpansion>\n   </ProfileAction>\n   <AnalyzeAction\n      buildConfiguration = \"Debug\">\n   </AnalyzeAction>\n   <ArchiveAction\n      buildConfiguration = \"Release\"\n      revealArchiveInOrganizer = \"YES\">\n   </ArchiveAction>\n</Scheme>\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>en</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>FMWK</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>$(CURRENT_PROJECT_VERSION)</string>\n\t<key>NSPrincipalClass</key>\n\t<string></string>\n</dict>\n</plist>\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Source/AirshipExtensionConfig.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n/// Airship extension config. Can be supplied with the property `airshipExtensionConfig` in `UANotificationServiceExtension`.\npublic struct AirshipExtensionConfig {\n    /// Log level. For no logs, use `nil`. Defaults to `error`.\n    public var logLevel: AirshipExtensionLogLevel?\n\n    /// Log handler. If `nil`, no logs will be logged. Defaults to `.defaultLogger`.\n    public var logHandler: (any AirshipExtensionLogHandler)?\n\n    public init(\n        logLevel: AirshipExtensionLogLevel? = .error,\n        logHandler: (any AirshipExtensionLogHandler)? = .defaultLogger\n    ) {\n        self.logLevel = logLevel\n        self.logHandler = logHandler\n    }\n}\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Source/AirshipExtensionLogger.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport os\n\n/// Represents the possible log levels.\npublic enum AirshipExtensionLogLevel: String, Sendable, Decodable {\n    /**\n     * Log error messages.\n     *\n     * Used for critical errors, parse exceptions and other situations that cannot be gracefully handled.\n     */\n    case error\n\n    /**\n     * Log warning messages.\n     *\n     * Used for API deprecations, invalid setup and other potentially problematic situations.\n     */\n    case warn\n\n    /**\n     * Log informative messages.\n     *\n     * Used for reporting general SDK status.\n     */\n    case info\n\n    /**\n     * Log debugging messages.\n     *\n     * Used for reporting general SDK status with more detailed information.\n     */\n    case debug\n\n    /**\n     * Log detailed verbose messages.\n     *\n     * Used for reporting highly detailed SDK status that can be useful when debugging and troubleshooting.\n     */\n    case verbose\n}\n\n/// Protocol used by AirshipExtension to log all messages..\npublic protocol AirshipExtensionLogHandler: Sendable {\n\n    /// Called to log a message.\n    /// - Parameters:\n    ///     - logLevel: The Airship log level.\n    ///     - message: The log message.\n    ///     - fileID: The file ID.\n    ///     - line: The line number.\n    ///     - function: The function.\n    func log(\n        logLevel: AirshipExtensionLogLevel,\n        message: String,\n        fileID: String,\n        line: UInt,\n        function: String\n    )\n}\n\n///\n/// Airship extension logger.\n///\nfinal class AirshipExtensionLogger: Sendable {\n\n    private let logHandler: (any AirshipExtensionLogHandler)?\n    private let logLevel: AirshipExtensionLogLevel?\n\n    init(\n        logHandler: (any AirshipExtensionLogHandler)? = .defaultLogger,\n        logLevel: AirshipExtensionLogLevel? = .error\n    ) {\n        self.logHandler = logHandler\n        self.logLevel = logLevel\n    }\n\n    func trace(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: .verbose,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    func debug(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: .debug,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    func info(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: .info,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    func warn(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: .warn,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    func error(\n        _ message: @autoclosure () -> String,\n        fileID: String = #fileID,\n        line: UInt = #line,\n        function: String = #function\n    ) {\n        log(\n            logLevel: .error,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n\n    func log(\n        logLevel: AirshipExtensionLogLevel,\n        message: @autoclosure () -> String,\n        fileID: String,\n        line: UInt,\n        function: String\n    ) {\n        guard\n            let logHandler,\n            let configuredLevel = self.logLevel,\n            configuredLevel.intValue >= logLevel.intValue\n        else {\n            return\n        }\n\n        logHandler.log(\n            logLevel: logLevel,\n            message: message(),\n            fileID: fileID,\n            line: line,\n            function: function\n        )\n    }\n}\n\npublic extension AirshipExtensionLogHandler where Self == DefaultAirshipExtensionLogHandler {\n    /// Default logger\n    static var defaultLogger: Self {\n        return .init(logPublic: false)\n    }\n\n    /// Logger that logs publically\n    static var publicLogger: Self {\n        return .init(logPublic: true)\n    }\n}\n\n\npublic final class DefaultAirshipExtensionLogHandler: AirshipExtensionLogHandler {\n\n    private let logPublic: Bool\n\n    init(logPublic: Bool = false) {\n        self.logPublic = logPublic\n    }\n\n    private static let logger: Logger = Logger(\n        subsystem: Bundle.main.bundleIdentifier ?? \"\",\n        category: \"AirshipNotificationExtension\"\n    )\n\n    public func log(\n        logLevel: AirshipExtensionLogLevel,\n        message: String,\n        fileID: String,\n        line: UInt,\n        function: String\n    ) {\n        let logMessage = \"[\\(logLevel.initial)] \\(fileID) \\(function) [Line \\(line)] \\(message)\"\n        if (logPublic) {\n            Self.logger.notice(\n                \"\\(logMessage, privacy: .public)\"\n            )\n        } else {\n            Self.logger.log(\n                level: logLevel.logType,\n                \"\\(logMessage, privacy: .private)\"\n            )\n        }\n    }\n}\n\nextension AirshipExtensionLogLevel {\n    fileprivate var initial: String {\n        switch self {\n        case .verbose: return \"V\"\n        case .debug: return \"D\"\n        case .info: return \"I\"\n        case .warn: return \"W\"\n        case .error: return \"E\"\n        }\n    }\n\n    fileprivate var logType: OSLogType {\n        switch self {\n        case .verbose: return OSLogType.debug\n        case .debug: return OSLogType.debug\n        case .info: return OSLogType.info\n        case .warn: return OSLogType.default\n        case .error: return OSLogType.error\n        }\n    }\n\n    fileprivate var intValue: Int {\n        switch(self) {\n        case .error:\n            1\n        case .warn:\n            2\n        case .info:\n            3\n        case .debug:\n            4\n        case .verbose:\n            5\n        }\n    }\n}\n\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Source/AirshipNotificationMutationProvider.swift",
    "content": "import UniformTypeIdentifiers\n\n@preconcurrency\nimport UserNotifications\n\nfinal class AirshipNotificationMutationProvider: Sendable {\n\n    static let supportedExtensions = [\"jpg\", \"jpeg\", \"png\", \"gif\", \"aif\", \"aiff\", \"mp3\", \"mpg\", \"mpeg\", \"mp4\", \"m4a\", \"wav\", \"avi\"]\n\n    let logger: AirshipExtensionLogger\n    init(logger: AirshipExtensionLogger) {\n        self.logger = logger\n    }\n\n    func mutations(for args: MediaAttachmentPayload) async throws -> AirshipNotificationMutations? {\n        let attachments = try await withThrowingTaskGroup(of: AirshipAttachment?.self) { [weak self, args] group in\n            try Task.checkCancellation()\n\n            var attachments: [AirshipAttachment] = []\n\n            args.media.forEach { media in\n                group.addTask { [weak self] in\n                    try Task.checkCancellation()\n                    return try await self?.load(\n                        attachment: media,\n                        defaultOptions: args.options,\n                        thumbnailID: args.thumbnailID\n                    )\n                }\n            }\n\n            for try await result in group {\n                try Task.checkCancellation()\n\n                if let result = result {\n                    attachments.append(result)\n                }\n            }\n\n            return attachments\n        }\n\n        return AirshipNotificationMutations(\n            title: args.textContent?.title,\n            subtitle: args.textContent?.subtitle,\n            body: args.textContent?.body,\n            attachments: attachments\n        )\n    }\n\n    private func load(\n        attachment: MediaAttachmentPayload.ContentMedia,\n        defaultOptions: MediaAttachmentPayload.PayloadOptions,\n        thumbnailID: String?\n    ) async throws -> AirshipAttachment {\n\n        try Task.checkCancellation()\n\n        logger.debug(\"Downloading attachment \\(attachment.url)\")\n\n        let (localURL, response) = try await download(url: attachment.url)\n\n        logger.debug(\"Downloading attachment result: \\(response)\")\n\n        try Task.checkCancellation()\n\n        var mimeType = response.mimeType\n        if mimeType == nil, let httpResponse = response as? HTTPURLResponse {\n            mimeType = httpResponse.allHeaderFields[\"Content-Type\"] as? String\n        }\n\n        let identifier = attachment.urldID ?? \"\"\n        let hideThumbnail = thumbnailID != nil && thumbnailID != identifier\n\n        return try makeAttachement(\n            localURL: localURL,\n            remoteURL: attachment.url,\n            mimeType: mimeType,\n            options: defaultOptions.generateNotificationAttachmentOptions(hideThumbnail: hideThumbnail),\n            identifier: identifier\n        )\n    }\n\n    private func makeAttachement(\n        localURL: URL,\n        remoteURL: URL,\n        mimeType: String?,\n        options: [String: any Sendable],\n        identifier: String\n    ) throws -> AirshipAttachment {\n        let fileURL = try correctFileExtension(for: localURL, original: remoteURL)\n\n        var mutableOptions = options\n\n        let hasExtension = Self.supportedExtensions.contains { item in\n            return fileURL.lastPathComponent.lowercased().hasSuffix(item)\n        }\n\n        if !hasExtension, let hint = hintMimeType(for: fileURL, mimeType: mimeType) {\n            mutableOptions[UNNotificationAttachmentOptionsTypeHintKey] = hint\n        }\n\n        return AirshipAttachment(identifier: identifier, url: fileURL, options: mutableOptions)\n    }\n\n    private func hintMimeType(for file: URL, mimeType: String?) -> String? {\n        if\n            let type = mimeType,\n            let uti = UTType(mimeType: type),\n            let fileExtension = uti.preferredFilenameExtension,\n            Self.supportedExtensions.contains(fileExtension) {\n            return uti.identifier\n        }\n\n        if let data = try? Data(contentsOf: file, options: .mappedRead) {\n            return FileHeader.supportedHeaders.first(where: { $0.matches(data: data) })?.type\n        }\n\n        return nil\n    }\n\n    private func correctFileExtension(for localURL: URL, original: URL) throws -> URL {\n        let destination = URL(fileURLWithPath: localURL.path.appending(\"-\\(original.lastPathComponent)\"))\n\n        if FileManager.default.fileExists(atPath: destination.path) {\n            try FileManager.default.removeItem(at: destination)\n        }\n\n        try FileManager.default.moveItem(at: localURL, to: destination)\n        return destination\n    }\n\n    private func download(url: URL) async throws -> (URL, URLResponse) {\n        let session = URLSession(\n            configuration: .default,\n            delegate: ChallengeResolver.shared,\n            delegateQueue: nil\n        )\n\n        return try await session.download(from: url)\n    }\n\n    private struct FileHeader {\n        let type: String\n        let offset: Int\n        let headers: [[UInt8]]\n\n        func matches(data: Data) -> Bool {\n            var result = false\n            for expectedHeader in headers {\n                if data.count < offset + expectedHeader.count { continue }\n\n                var currentHeader = [UInt8](repeating: 0, count: expectedHeader.count)\n                data.copyBytes(to: &currentHeader, from: offset..<(offset + expectedHeader.count))\n\n                if currentHeader == expectedHeader {\n                    result = true\n                    break\n                }\n            }\n\n            return result\n        }\n\n        static let supportedHeaders = [\n            FileHeader(type: \"public.jpeg\", offset: 0, headers: [\n                [0xFF, 0xD8, 0xFF, 0xE0],\n                [0xFF, 0xD8, 0xFF, 0xE2],\n                [0xFF, 0xD8, 0xFF, 0xE3]\n            ]),\n            FileHeader(type: \"public.png\", offset: 0, headers: [[0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]]),\n            FileHeader(type: \"com.compuserve.gif\", offset: 0, headers: [[0x47, 0x49, 0x46, 0x38]]),\n            FileHeader(type: \"public.aiff-audio\", offset: 0, headers: [[0x46, 0x4F, 0x52, 0x4D, 0x00]]),\n            FileHeader(type: \"com.microsoft.waveform-audio\", offset: 8, headers: [[0x57, 0x41, 0x56, 0x45]]),\n            FileHeader(type: \"public.avi\", offset: 8, headers: [[0x41, 0x56, 0x49, 0x20]]),\n            FileHeader(type: \"public.mp3\", offset: 0, headers: [[0x49, 0x44, 0x33]]),\n            FileHeader(type: \"public.mpeg-4\", offset: 4, headers: [\n                [0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x31],\n                [0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34, 0x32],\n                [0x66, 0x74, 0x79, 0x70, 0x6D, 0x6D, 0x70, 0x34],\n                [0x66, 0x74, 0x79, 0x70, 0x69, 0x73, 0x6f, 0x6d]\n            ]),\n            FileHeader(type: \"public.mpeg-4-audio\", offset: 4, headers: [[0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41, 0x20]]),\n            FileHeader(type: \"public.mpeg\", offset: 0, headers: [\n                [0x00, 0x00, 0x01, 0xBA],\n                [0x00, 0x00, 0x01, 0xB3]\n            ])\n        ]\n    }\n}\n\nstruct AirshipNotificationMutations: Sendable {\n    var title: String?\n    var subtitle: String?\n    var body: String?\n    var attachments: [AirshipAttachment]\n\n    func apply(to notificationContent: UNMutableNotificationContent) throws {\n        try attachments\n            .map { try $0.notificationAttachment }\n            .forEach {\n                notificationContent.attachments.append($0)\n            }\n\n        if let title = title {\n            notificationContent.title = title\n        }\n        \n        if let subtitle = subtitle {\n            notificationContent.subtitle = subtitle\n        }\n        \n        if let body = body {\n            notificationContent.body = body\n        }\n    }\n}\n\n\nstruct AirshipAttachment: Sendable {\n    var identifier: String\n    var url: URL\n    var options: [String : any Sendable]\n\n    var notificationAttachment: UNNotificationAttachment {\n        get throws {\n            try UNNotificationAttachment(\n                identifier: self.identifier,\n                url: self.url,\n                options: self.options\n            )\n        }\n    }\n}\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Source/ChallengeResolver.swift",
    "content": "/* Copyright Airship and Contributors */\n\npublic import Foundation\n\n/**\n * Authentication challenge resolver class\n * @note For internal use only. :nodoc:\n */\npublic final class ChallengeResolver: NSObject, Sendable  {\n    \n    public static let shared = ChallengeResolver()\n    \n    @MainActor\n    var resolver: ChallengeResolveClosure?\n    \n    private override init() {}\n    \n    public func resolve(\n        _ challenge: URLAuthenticationChallenge\n    ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n        guard\n            let resolver = await self.resolver,\n            challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust,\n            challenge.protectionSpace.serverTrust != nil\n        else {\n            return (.performDefaultHandling, nil)\n        }\n        \n        return resolver(challenge)\n    }\n}\n\nextension ChallengeResolver: URLSessionTaskDelegate {\n    \n    public func urlSession(\n        _ session: URLSession,\n        didReceive challenge: URLAuthenticationChallenge\n    ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n\n        return await self.resolve(challenge)\n    }\n    \n    public func urlSession(\n        _ session: URLSession,\n        task: URLSessionTask,\n        didReceive challenge: URLAuthenticationChallenge\n    ) async -> (URLSession.AuthChallengeDisposition, URLCredential?) {\n        return await self.resolve(challenge)\n    }\n    \n}\n\n\npublic typealias ChallengeResolveClosure = @Sendable (URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?)\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Source/MediaAttachmentPayload.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n#if !os(tvOS)\nimport UserNotifications\n\nstruct MediaAttachmentPayload: Sendable, Decodable {\n\n    let media: [ContentMedia]\n    let textContent: ContentText?\n    let options: PayloadOptions\n    let thumbnailID: String?\n    \n    enum CodingKeys: String, CodingKey {\n        case url = \"url\"\n        case urlArray = \"urls\"\n        case thumbnail = \"thumbnail_id\"\n        case options = \"options\"\n        case content = \"content\"\n    }\n    \n    init(from decoder: any Decoder) throws {\n        let container = try decoder.container(keyedBy: CodingKeys.self)\n        \n        let payloadUrl: [[String: String]]\n        if container.contains(.urlArray) {\n            payloadUrl = try container.decode([[String: String]].self, forKey: .urlArray)\n        } else if container.contains(.url) {\n            let urls: [String]\n            do {\n                urls = try container.decode([String].self, forKey: .url)\n            } catch {\n                urls = [try container.decode(String.self, forKey: .url)]\n            }\n            payloadUrl = urls.map({ [ContentMedia.CodingKeys.url.rawValue: $0] })\n        } else {\n            throw DecodingError.keyNotFound(CodingKeys.url, .init(codingPath: container.codingPath, debugDescription: \"Failed to parse URLs\"))\n        }\n        \n        self.media = payloadUrl.compactMap(ContentMedia.init)\n        self.thumbnailID = try container.decodeIfPresent(String.self, forKey: .thumbnail)\n        self.options = try container.decodeIfPresent(PayloadOptions.self, forKey: .options) ?? PayloadOptions()\n        self.textContent = try container.decodeIfPresent(ContentText.self, forKey: .content)\n    }\n    \n    struct ContentText: Decodable, Sendable {\n        let title: String?\n        let subtitle: String?\n        let body: String?\n        \n        enum CodingKeys: String, CodingKey {\n            case title\n            case subtitle\n            case body\n        }\n    }\n    \n    struct ContentMedia: Sendable {\n        let url: URL\n        let urldID: String?\n        \n        enum CodingKeys: String, CodingKey {\n            case url = \"url\"\n            case urlID = \"url_id\"\n        }\n        \n        init?(source: [String: String]) {\n            guard\n                let urlString = source[CodingKeys.url.rawValue],\n                let url = URL(string: urlString)\n            else {\n                return nil\n            }\n            \n            self.url = url\n            \n            var isValid = true\n            (isValid, self.urldID) = validateAndParse(source[CodingKeys.urlID.rawValue])\n            if !isValid { return nil }\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            let urlString = try container.decode(String.self, forKey: .url)\n            guard let url = URL(string: urlString) else {\n                throw DecodingError.typeMismatch(URL.self, .init(codingPath: container.codingPath, debugDescription: \"Failed to parse URL \\(urlString)\"))\n            }\n            self.url = url\n            self.urldID = try container.decodeIfPresent(String.self, forKey: .urlID)\n        }\n    }\n    \n    struct PayloadOptions: Decodable, Sendable {\n        private static let cropRequiredFields = [\"x\", \"y\", \"width\", \"height\"]\n        let crop: [String: Double]?\n        let time: Double?\n        let hidden: Bool?\n        \n        enum CodingKeys: String, CodingKey {\n            case crop = \"crop\"\n            case time = \"time\"\n            case hidden = \"hidden\"\n        }\n        \n        init() {\n            self.crop = nil\n            self.time = nil\n            self.hidden = nil\n        }\n        \n        init(from decoder: any Decoder) throws {\n            let container = try decoder.container(keyedBy: CodingKeys.self)\n            self.time = try container.decodeIfPresent(Double.self, forKey: .time)\n            self.hidden = try container.decodeIfPresent(Bool.self, forKey: .hidden)\n            self.crop = try container.decodeIfPresent([String: Double].self, forKey: .crop)\n            \n            try validate()\n        }\n        \n        private func validate() throws {\n            guard let crop = self.crop else { return }\n            \n            for requiredKey in Self.cropRequiredFields {\n                guard\n                    let value = crop[requiredKey],\n                    value >= 0.0 && value <= 1.0\n                else {\n                    throw DecodingError.dataCorrupted(.init(codingPath: [], debugDescription: \"Failed to crop key \\(requiredKey) \\(crop)\"))\n                }\n            }\n        }\n        \n        func generateNotificationAttachmentOptions(hideThumbnail: Bool) -> [String: any Sendable] {\n            var result: [String: any Sendable] = [UNNotificationAttachmentOptionsThumbnailHiddenKey: hideThumbnail]\n\n            if let crop = self.crop {\n                let normalized = crop.reduce(into: [String: Double]()) { partialResult, entry in\n                    partialResult[entry.key.capitalized] = entry.value\n                }\n                result[UNNotificationAttachmentOptionsThumbnailClippingRectKey] = normalized\n            }\n            \n            if let time = self.time {\n                result[UNNotificationAttachmentOptionsThumbnailTimeKey] = time\n            }\n            \n            if let hidden = self.hidden {\n                result[UNNotificationAttachmentOptionsThumbnailHiddenKey] = hidden\n            }\n            \n            return result\n        }\n    }\n}\n\nextension MediaAttachmentPayload {\n    private static func validateAndParse<T>(_ value: Any?) -> (Bool, T?) {\n        guard let value = value else { return (true, nil) }\n        guard let parsed = value as? T else { return (false, nil) }\n        \n        return (true, parsed)\n    }\n}\n#endif\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Source/UANotificationServiceExtension.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\n\n@preconcurrency\npublic import UserNotifications\n\n#if !os(tvOS)\n@objc\nopen class UANotificationServiceExtension: UNNotificationServiceExtension {\n    open var airshipConfig: AirshipExtensionConfig { .init() }\n    private var onExpire: (@Sendable () -> Void)?\n\n    open override func didReceive(\n        _ request: UNNotificationRequest,\n        withContentHandler contentHandler: @Sendable @escaping (UNNotificationContent) -> Void\n    ) {\n\n        let config = airshipConfig\n        let logger = AirshipExtensionLogger(\n            logHandler: config.logHandler,\n            logLevel: config.logLevel\n        )\n        \n        let downloadTask = Task { @MainActor in\n            logger.debug(\n                \"New request received: \\(request)\"\n            )\n\n            guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else {\n                logger.error(\n                    \"Unable to make mutable copy of request\"\n                )\n                try Task.checkCancellation()\n                contentHandler(request.content)\n                return\n            }\n\n            do {\n                let args = try request.mediaAttachmentPayload\n                guard let args else {\n                    logger.debug(\n                        \"Finishing request, no Airship args: \\(request.identifier)\"\n                    )\n                    try Task.checkCancellation()\n                    contentHandler(request.content)\n                    return\n                }\n\n                logger.info(\n                    \"Found Airship arguments for request \\(request.identifier): \\(args)\"\n                )\n                let mutationsProvider = AirshipNotificationMutationProvider(\n                    logger: logger\n                )\n                try await mutationsProvider.mutations(for: args)?.apply(to: mutableContent)\n            } catch {\n                logger.error(\n                    \"Failed to apply mutations to request \\(request.identifier): \\(error)\"\n                )\n            }\n\n            try Task.checkCancellation()\n\n            logger.info(\n                \"Finished processing request: \\(request.identifier): \\(mutableContent)\"\n            )\n            contentHandler(mutableContent)\n        }\n\n        self.onExpire = {\n            logger.error(\n                \"serviceExtensionTimeWillExpire expiring, canceling airshipTask\"\n            )\n            downloadTask.cancel()\n            contentHandler(request.content)\n        }\n    }\n\n    open override func serviceExtensionTimeWillExpire() {\n        self.onExpire?()\n    }\n}\n\nextension UNNotificationRequest {\n    fileprivate static let airshipMediaAttachment = \"com.urbanairship.media_attachment\"\n\n    // Checks if the request is from Airship\n    public var isAirship: Bool {\n        return containsAirshipMediaAttachments ||\n        self.content.userInfo[\"com.urbanairship.metadata\"] != nil ||\n        self.content.userInfo[\"_\"] != nil\n    }\n\n    /// Checks if the request is from Airship and contains media attachments\n    public var containsAirshipMediaAttachments: Bool {\n        return self.content.userInfo[Self.airshipMediaAttachment] != nil\n    }\n\n    var mediaAttachmentPayload: MediaAttachmentPayload? {\n        get throws {\n            guard\n                let source = content.userInfo[Self.airshipMediaAttachment],\n                let payloadInfo = source as? [String: Any]\n            else {\n                return nil\n            }\n            let data = try JSONSerialization.data(withJSONObject: payloadInfo)\n            return try JSONDecoder().decode(MediaAttachmentPayload.self, from: data)\n        }\n    }\n}\n\n\n\n#endif\n\n\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Tests/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>CFBundleDevelopmentRegion</key>\n\t<string>$(DEVELOPMENT_LANGUAGE)</string>\n\t<key>CFBundleExecutable</key>\n\t<string>$(EXECUTABLE_NAME)</string>\n\t<key>CFBundleIdentifier</key>\n\t<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>\n\t<key>CFBundleInfoDictionaryVersion</key>\n\t<string>6.0</string>\n\t<key>CFBundleName</key>\n\t<string>$(PRODUCT_NAME)</string>\n\t<key>CFBundlePackageType</key>\n\t<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>\n\t<key>CFBundleShortVersionString</key>\n\t<string>1.0</string>\n\t<key>CFBundleVersion</key>\n\t<string>1</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Tests/MediaAttachmentPayloadTest.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable\nimport AirshipNotificationServiceExtension\nimport UserNotifications\n\n@Suite(\"Media Attachment Payload\")\nstruct MediaAttachmentPayloadTest {\n    \n    @Test\n    func airshipEmptyPayload() {\n        let payload = decodeFrom(source: [:])\n        #expect(payload == nil)\n    }\n    \n    @Test\n    func airshipURLPayloads() {\n        //invalid payload\n        let decoded = decodeFrom(source: [ \"url\": [:] ])\n        #expect(decoded == nil)\n        \n        // Test valid contents of the url when it is an empty array\n        var payload = decodeFrom(source: [ \"url\": [] ])\n        #expect(payload != nil)\n        #expect(0 == payload?.media.count)\n        \n        // Airship payload\n        payload = decodeFrom(source: [ \"url\": \"https://sample.url\" ])\n        #expect(payload != nil)\n        #expect(1 == payload?.media.count)\n        #expect(\"https://sample.url\" == payload?.media.first?.url.absoluteString)\n        \n        // Test contents of the url when it is an array with valid urls\n        payload = decodeFrom(source: [ \"url\": [\"https://sample.url\", \"http://sample1.url\"] ])\n        #expect(payload != nil)\n        #expect(2 == payload?.media.count)\n        #expect(\"https://sample.url\" == payload?.media.first?.url.absoluteString)\n        #expect(\"http://sample1.url\" == payload?.media.last?.url.absoluteString)\n    }\n    \n    @Test\n    func airshipURLSPayloads() {\n        //invalid\n        #expect(decodeFrom(source: [\"urls\": [:]]) == nil)\n        \n        // Test contents of the url when it is an array with invalid urls\n        var payload = decodeFrom(source: [\"urls\": [1]])\n        #expect(payload == nil)\n        \n        // VALID PAYLOADS\n        // \"url\" key is ignored if \"urls\" is present\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.url\",\n            \"urls\": []\n        ])\n        #expect(payload != nil)\n        #expect(0 == payload?.media.count)\n        \n        // Test contents of the url when it is an array with valid urls\n        payload = decodeFrom(source: [\n            \"urls\": [\n                [\n                    \"url\": \"http://sample1.url\",\n                    \"url_id\": \"sample-1-id\"\n                ],\n                [\n                    \"url\": \"http://sample2.url\",\n                    \"url_id\": \"sample-2-id\"\n                ],\n            ]\n        ])\n        \n        #expect(\"http://sample1.url\" == payload?.media.first?.url.absoluteString)\n        #expect(\"sample-1-id\" == payload?.media.first?.urldID)\n        #expect(\"http://sample2.url\" == payload?.media.last?.url.absoluteString)\n        #expect(\"sample-2-id\" == payload?.media.last?.urldID)\n    }\n    \n    @Test\n    func airshipOptionsPayloads() {\n        // NOT VALID PAYLOADS\n        #expect(\n            decodeFrom(\n                source: [\n                    \"url\": \"https://test.ur\",\n                    \"options\": []\n                ]\n            ) == nil\n        )\n        \n        // VALID PAYLOADS\n        let payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [:]\n        ])\n        #expect(payload != nil)\n        #expect(payload?.options != nil)\n        #expect(payload?.options.crop == nil)\n        #expect(payload?.options.hidden == nil)\n        #expect(payload?.options.time == nil)\n    }\n    \n    @Test\n    func airshipCropOptionsPayloads() {\n        // NOT VALID PAYLOADS\n        var payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [ \"crop\": \"\" ]\n        ])\n        #expect(payload == nil)\n        \n        // Empty crop dictionary\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [ \"crop\": [:] ]\n        ])\n        #expect(payload == nil)\n        \n        // Missing crop option\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [ \"crop\": [\n                \"y\": 0,\n                \"width\": 0.5,\n                \"height\": 1\n            ] ]\n        ])\n        #expect(payload == nil)\n        \n        // Non-valid crop option\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [ \"crop\": [\n                \"x\": 10,\n                \"y\": 0,\n                \"width\": 0.5,\n                \"height\": 1\n            ] ]\n        ])\n        #expect(payload == nil)\n        \n        // valid crop options\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [ \"crop\": [\n                \"x\": 1,\n                \"y\": 1,\n                \"width\": 0.5,\n                \"height\": 1\n            ] ]\n        ])\n        #expect(payload != nil)\n        \n        let generatedCrop = payload?.options.generateNotificationAttachmentOptions(hideThumbnail: false)[UNNotificationAttachmentOptionsThumbnailClippingRectKey] as? [String: Double]\n        #expect(1 == generatedCrop?[\"X\"])\n        #expect(1 == generatedCrop?[\"Y\"])\n        #expect(0.5 == generatedCrop?[\"Width\"])\n        #expect(1 == generatedCrop?[\"Height\"])\n    }\n    \n    @Test\n    func airshipTimeOptionPayloads() {\n        // NOT VALID PAYLOADS\n        #expect(\n            decodeFrom(\n                source: [\n                    \"url\": \"https://test.ur\",\n                    \"options\": [\"time\": \"\"]\n                ]\n            ) == nil\n        )\n        \n        // VALID PAYLOADS\n        let payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [\n                \"time\": 1.0\n            ]\n            \n        ])\n        #expect(payload != nil)\n        #expect(1 == payload?.options.time)\n        #expect(1 == payload?.options.generateNotificationAttachmentOptions(hideThumbnail: false)[UNNotificationAttachmentOptionsThumbnailTimeKey] as? Double)\n    }\n    \n    @Test\n    func airshipHiddenOptionPayloads() {\n        // NOT VALID PAYLOADS\n        #expect(\n            decodeFrom(\n                source: [\n                    \"url\": \"https://test.ur\",\n                    \"options\": [\"hidden\": \"\"]\n                ]\n            ) == nil\n        )\n        \n        // VALID PAYLOADS\n        let payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"options\": [\n                \"hidden\": true\n            ]\n            \n        ])\n        #expect(payload != nil)\n        #expect(true == payload?.options.hidden)\n        #expect(true == payload?.options.generateNotificationAttachmentOptions(hideThumbnail: false)[UNNotificationAttachmentOptionsThumbnailHiddenKey] as? Bool)\n    }\n    \n    @Test\n    func airshipContentPayloads() {\n        // NOT VALID PAYLOADS\n        #expect(\n            decodeFrom(\n                source: [\n                    \"url\": \"https://test.ur\",\n                    \"content\": \"\"\n                ]\n            )\n            == nil\n        )\n        \n        // non-valid content\n        #expect(\n            decodeFrom(\n                source: [\n                    \"url\": \"https://test.ur\",\n                    \"content\": [ \"body\": [:] ]\n                ]\n            )\n            == nil\n        )\n        \n        // VALID PAYLOADS\n        // empty content\n        var payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"content\": [:]\n        ])\n        #expect(payload != nil)\n        #expect(payload?.textContent != nil)\n        #expect(payload?.textContent?.title == nil)\n        #expect(payload?.textContent?.subtitle == nil)\n        #expect(payload?.textContent?.body == nil)\n        \n        // minimal content\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"content\": [\"title\" : \"sample title\" ]\n        ])\n        #expect(payload != nil)\n        #expect(payload?.textContent != nil)\n        #expect(\"sample title\" == payload?.textContent?.title)\n        #expect(payload?.textContent?.subtitle == nil)\n        #expect(payload?.textContent?.body == nil)\n        \n        // complete content\n        payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"content\": [\n                \"title\" : \"sample title\",\n                \"subtitle\": \"sample subtitle\",\n                \"body\": \"sample body\"\n            ]\n        ])\n        #expect(payload != nil)\n        #expect(payload?.textContent != nil)\n        #expect(\"sample title\" == payload?.textContent?.title)\n        #expect(\"sample subtitle\" == payload?.textContent?.subtitle)\n        #expect(\"sample body\" == payload?.textContent?.body)\n    }\n    \n    @Test\n    func airshipThumbnailIDPayloads() {\n        // NOT VALID PAYLOADS\n        #expect(\n            decodeFrom(\n                source: [\n                    \"url\": \"https://test.ur\",\n                    \"thumbnail_id\": 1\n                ]\n            )\n            == nil\n        )\n        \n        // VALID PAYLOADS\n        let payload = decodeFrom(source: [\n            \"url\": \"https://test.ur\",\n            \"thumbnail_id\": \"test-thumbnail\"\n        ])\n        \n        #expect(payload != nil)\n        #expect(\"test-thumbnail\" == payload?.thumbnailID)\n    }\n    \n    @Test\n    func accengageThumbnailIDPayloads() {\n        // NOT VALID PAYLOADS\n        #expect(\n            decodeFrom(\n                source: [\n                    \"a4sid\": \"id\",\n                    \"url\": \"https://test.ur\",\n                    \"thumbnail_id\": 1\n                ]\n            )\n            == nil\n        )\n        \n        // VALID PAYLOADS\n        let payload = decodeFrom(source: [\n            \"a4sid\": \"id\",\n            \"url\": \"https://test.ur\",\n            \"thumbnail_id\": \"test-thumbnail\"\n        ])\n        \n        #expect(payload != nil)\n        #expect(\"test-thumbnail\" == payload?.thumbnailID)\n    }\n    \n    private func decodeFrom(source: [String: Any]) -> MediaAttachmentPayload? {\n        let data = try! JSONSerialization.data(withJSONObject: source)\n        return try? JSONDecoder().decode(MediaAttachmentPayload.self, from: data)\n    }\n}\n"
  },
  {
    "path": "AirshipExtensions/AirshipNotificationServiceExtension/Tests/UANotificationServiceExtensionTests.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Testing\n\n@testable\nimport AirshipNotificationServiceExtension\nimport UserNotifications\nimport Foundation\n\n@Suite(\"U A Notification Service Extension\")\nstruct UANotificationServiceExtensionTests {\n    \n    private class BundleFinder {}\n    let subject = UANotificationServiceExtension()\n    \n    @Test\n    func emptyContent() async throws {\n        // 1. Setup\n        let content = UNNotificationContent()\n        let request = UNNotificationRequest(\n            identifier: \"identifier\", content: content, trigger: nil\n        )\n        \n        let deliveredContent: UNNotificationContent = try await withCheckedThrowingContinuation { continuation in\n            subject.didReceive(request) { result in\n                continuation.resume(returning: result)\n            }\n        }\n        \n        // 3. Assertions\n        #expect(deliveredContent.attachments.isEmpty)\n        #expect(deliveredContent.badge == nil)\n        #expect(deliveredContent.sound == nil)\n        #expect(deliveredContent.body.isEmpty)\n        #expect(deliveredContent.title.isEmpty)\n        #expect(deliveredContent.subtitle.isEmpty)\n        #expect(deliveredContent.categoryIdentifier.isEmpty)\n        #expect(deliveredContent.launchImageName.isEmpty)\n        #expect(deliveredContent.threadIdentifier.isEmpty)\n        #expect(deliveredContent.userInfo.isEmpty)\n        #expect(deliveredContent.targetContentIdentifier == nil)\n    }\n    \n    @Test\n    func sampleContent() async throws {\n        \n        let fileUrl = try #require(\n            Bundle(for: BundleFinder.self)\n                .url(forResource: \"airship\", withExtension: \"jpg\")\n        )\n        \n        let content = UNMutableNotificationContent()\n        content.body = \"oh hi\"\n        content.categoryIdentifier = \"news\"\n        content.userInfo = [\n            \"_\": \"a323385b-010a-401c-93ae-936cb58dff04\",\n            \"apps\": [\n                \"alert\": \"oh hi\",\n                \"category\": \"news\",\n                \"mutable-content\": true\n            ],\n            \"com.urbanairship.metadata\": \"eyJ2ZXJzaW9uX2lkIjoxLCJ0aW1lIjoxNTg3NTc2Mzk2NDM1LCJwdXNoX2lkIjoiNmUyNzQ1N2MtZDllNi00MWQ3LWJlZDYtNTAyMTkyNDA0NDI2In0=\",\n            \"com.urbanairship.media_attachment\": [\n                \"url\": fileUrl.absoluteString,\n                \"content\": [\n                    \"title\": \"Moustache Twirl\",\n                    \"subtitle\": \"The saga of a bendy stache.\",\n                    \"body\": \"Have you ever seen a moustache like this?!\"\n                ],\n                \"options\": [\n                    \"crop\": [\n                        \"x\": 0.25,\n                        \"y\": 0.25,\n                        \"width\": 0.5,\n                        \"height\": 0.5\n                    ],\n                    \"time\": 15.0\n                ]\n            ],\n        ]\n        \n        let request = UNNotificationRequest(identifier: \"4B2D08E6-8955-4964-8C15-6F7FEBC0EBB4\", content: content, trigger: nil)\n        \n        let deliveredContent: UNNotificationContent = try await withCheckedThrowingContinuation { continuation in\n            subject.didReceive(request) { result in\n                continuation.resume(returning: result)\n            }\n        }\n        \n        try #require(deliveredContent.attachments.count == 1)\n        let attachment = deliveredContent.attachments[0]\n        \n        \n        #expect(FileManager.default.contentsEqual(atPath: attachment.url.path, andPath: fileUrl.path))\n        #expect(\"public.jpeg\" == attachment.type)\n        \n        #expect(deliveredContent.badge == nil)\n        #expect(deliveredContent.sound == nil)\n        #expect(deliveredContent.targetContentIdentifier == nil)\n        #expect(\"Moustache Twirl\" == deliveredContent.title)\n        #expect(\"The saga of a bendy stache.\" == deliveredContent.subtitle)\n        #expect(\"Have you ever seen a moustache like this?!\" == deliveredContent.body)\n        #expect(\"news\" == deliveredContent.categoryIdentifier)\n        #expect(\"\" == deliveredContent.launchImageName)\n        #expect(\"\" == deliveredContent.threadIdentifier)\n    }\n    \n}\n"
  },
  {
    "path": "AirshipServiceExtension.podspec",
    "content": "AIRSHIP_VERSION=\"20.6.2\"\n\nPod::Spec.new do |s|\n    s.version                 = AIRSHIP_VERSION\n    s.name                    = \"AirshipServiceExtension\"\n    s.summary                 = \"Airship iOS Service Extension\"\n    s.documentation_url       = \"https://docs.airship.com/platform/ios\"\n    s.homepage                = \"https://www.airship.com\"\n    s.license                 = { :type => \"Apache License, Version 2.0\", :file => \"LICENSE\" }\n    s.author                  = { \"Airship\" => \"support@airship.com\" }\n    s.source                  = { :git => \"https://github.com/urbanairship/ios-library.git\", :tag => s.version.to_s }\n    s.source_files            = \"AirshipExtensions/AirshipNotificationServiceExtension/Source/**/*.{swift}\"\n    s.weak_frameworks         = \"UserNotifications\"\n    s.module_name             = \"AirshipServiceExtension\"\n    s.requires_arc            = true\n    s.ios.deployment_target      = \"15.0\"\n    s.watchos.deployment_target  = \"11.0\"\n    s.swift_versions             = \"5.0\"\n    s.pod_target_xcconfig     = { 'DEFINES_MODULE' => 'YES' }\nend\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "\n# iOS 20.x Changelog\n\n[Migration Guides](https://github.com/urbanairship/ios-library/tree/main/Documentation/Migration)\n[All Releases](https://github.com/urbanairship/ios-library/releases)\n\n## Version 20.6.2 - April 2, 2026\nPatch release that improves border rendering in Scenes.\n\n### Changes\n- Support per-corner border for shapes drawn in Scenes\n\n## Version 20.6.1 - March 26, 2026\nPatch release that improves diagnostic logging for In-App Automation trigger processing.\n\n### Changes\n- Improved trigger processor logging to aid diagnosis of chain trigger drop-off and unexpected count resets.\n\n## Version 20.6.0 - March 20, 2026\nMinor release that adds Objective-C wrappers for the Message Center native bridge to support .NET bindings, adds subscript and superscript text support in Scenes, and improves Message Center reliability.\n\n### Changes\n- Added `UAMessageCenterNativeBridge` and `UAMessageCenterNativeBridgeDelegate` to support .NET MAUI bindings for Message Center web view deep link handling.\n- Added subscript and superscript text support in Scenes.\n- Fixed Message Center mark-as-read not updating the list UI.\n- Improved Message Center content type handling.\n- Improved Message Center behavior when a message is unavailable or deleted.\n\n## Version 20.5.0 - March XX, 2026\nMinor release that improves video playback and improves pager navigation reliability in Scenes.\n\n### Changes\n- Improved pager navigation reliability by fixing a race condition that could cause desync when swiping rapidly during scroll.\n- Improved NPS score selector tap targets to remain consistent when toggling between selected and unselected states.\n- Improved video playback lifecycle handling to prevent potential memory leaks on dismiss.\n- Improved In-App Automation schedule parsing to retry failed schedules when remote data is updated.\n- Removed remaining Objective-C from AirshipBasement.\n- Replaced Objective-C based swizzling with a more reliable Swift implementation.\n- Fixed video aspect ratio to avoid cropping content when using center-inside display mode.\n\n## Version 20.4.0 - February 24, 2026\nMinor release that adds support for Native Message Center. Native content type requires displaying the message content in a `MessageCenterMessageView`. Apps that do not use Airship's message views (e.g. using a WebView directly) should filter out messages where `message.contentType` is not `.html`.\n\n### Changes\n- Added support for Native Message Center.\n- Removed gzip encoding using internal UACompression class and `libz` dependency from AirshipBasement.\n- Added `ExpressibleBy` protocol conformances to `AirshipJSON` allowing initialization with literals.\n- Added `UAEmbeddedViewControllerFactory` to the `AirshipObjectiveC` module for embedding `AirshipEmbeddedView` in Objective-C applications.\n- Improved accessibility for single choice and multiple choice questions in Scenes.\n\n## Version 20.3.2 - February 23, 2026\nPatch release that fixes Message Center unread indicator behavior, improves spinner fallback on older iOS versions, and resolves visionOS availability checks.\n\n### Changes\n- Fixed Message Center unread indicator rendering so the unread indicator is only shown for unread messages.\n- Fixed spinner behavior on iOS versions earlier than 18 by adding a rotating fallback icon.\n- Fixed visionOS compilation and availability handling for newer iOS and visionOS APIs.\n\n## Version 20.3.1 - February 18, 2026\nPatch release that fixes an Xcode 26.4 beta compilation issue and improves Scene stability.\n\n### Changes\n- Fixed an Xcode 26.4 beta compilation issue.\n- Improved Scene stability by adding guards for non-finite layout values used in SwiftUI frame, offset, and position calculations.\n- Added additional Scene layout safety checks for container, pager, story indicator, video controls, and wrapping layout views.\n- Improved pager timer progression by safely handling zero and invalid page delays.\n\n## Version 20.3.0 - January 30, 2026\nMinor release that adds Objective-C wrapper for deep link processing and fixes a Message Center migration issue when upgrading from 17.x or older to 20.x.\n\n### Changes\n- Added `UAirship.processDeepLink(_:completionHandler:)` Objective-C wrapper for programmatic deep link handling.\n- Fixed a Message Center migration issue when upgrading from 17.x or older to 20.x\n\n## Version 20.2.0 - January 29, 2026\nMinor release that adds Objective-C wrappers for .NET bindings and improves bundle resource lookup for SPM and Tuist projects.\n\n### Changes\n- Added Objective-C wrappers for permissions manager and push notification status APIs to support .NET bindings.\n- Improved bundle resource lookup to better support Swift Package Manager and Tuist generated projects.\n- Fixed page tabbing, radio buttons, and checkbox accessibility in Scenes.\n- Improved banner IAA message accessibility.  \n\n## Version 20.1.1 - January 16, 2026\nPatch release that improves VoiceOver focus control and sizing for the progress bar indicator in Story views.\n\n### Changes\n- Added support for sizing inactive segments in Story view progress indicators.\n- Improved VoiceOver focus handling for Message Center Web content.\n\n## Version 20.1.0 - January 9, 2026\nMinor release that includes several fixes and improvements for Scenes, In-App Automations, and Message Center.\n\n### Changes\n- Added support for Story pause/resume and back/next controls.\n- Added Scenes content support in Message Center.\n- Added support for additional text styles in Scenes.\n- In-App Automations and Scenes that were not available during app launch can now be triggered by events that happened in the previous 30 seconds.\n- Fixed pinned container background image scaling when the keyboard is visible in Scenes.\n- Fixed banner presentation and layout in Scenes.\n- Fixed progress icon rendering in Scenes.\n- Fixed container layout alignment in Scenes.\n\n## Version 20.0.3 - December 17, 2025\nPatch release that fixes keyboard safe area with Scenes.\n\n### Changes\n- Fixed safe area handling during keyboard presentation in Scenes.\n\n## Version 20.0.2 - November 24, 2025\nPatch release that fixes an issue with delayed video playback in Scenes when initially loading or paging and addresses a direct open attribution race condition which could cause direct open events to be missed in some edge cases.\n\n### Changes\n- Fixed an issue where the video ready callback was not assigned before observers were set up, causing the pager to miss the ready signal and advance before they loaded completely.\n- Fixed a potential race condition that could result in missed direct open attributions by ensuring notification response handling completes synchronously before the app becomes active.\n\n## Version 20.0.1 - November 14, 2025\nPatch release that fixes several minor bugs and adds accessibility improvements.\n\n### Changes\n- Fixed looping behavior in video views within Scenes.\n- Fixed Message Center icon display when icons are enabled.\n- Fixed pager indicator accessibility to prevent duplicate VoiceOver announcements.\n- Added dismiss action to banner in-app messages for improved VoiceOver accessibility.\n- Fixed YouTube video embedding to comply with YouTube API Client identification requirements.\n\n## Version 20.0.0 - October 9, 2025\nMajor SDK release with several breaking changes. See the [Migration Guide](https://github.com/urbanairship/ios-library/blob/main/Documentation/Migration/migration-guide-19-20.md) for more info.\n\n### Changes\n- Xcode 26+ is now required.\n- Updated minimum deployment target to iOS 16+.\n- Refactored Message Center and Preference Center UI to provide clearer separation between navigation and content views. See the migration guide for API changes.\n- Introduced modern, block-based and async APIs as alternatives to common delegate protocols (`PushNotificationDelegate`, `DeepLinkDelegate`, etc.). The delegate pattern is still supported but will be deprecated in a future release.\n- Refactored core Airship components to use protocols instead of concrete classes, improving testability and modularity. See the migration guide for protocol renames and class-to-protocol conversions.\n- Added support for split view in the Message Center, improving the layout on larger devices.\n- Updated the Preference Center with a refreshed design and fixed UI issues on tvOS and visionOS.\n- Fixed Package.swift to remove macOS as a supported platform.\n- CustomViews within a Scene can now programmatically control their parent Scene, enabling more dynamic and interactive custom content.\n- Accessibility updates for Scenes.\n- New AirshipDebug package that exposes insights and debugging capabilities into the Airship SDK for development builds, providing enhanced visibility into SDK behavior and performance.\n- Removed automatic collection of `connection_type` and `carrier` device properties\n"
  },
  {
    "path": "DevApp/AirshipConfig.plist.sample",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>detectProvisioningMode</key>\n    <true/>\n    <key>developmentAppKey</key>\n    <string>Your Development App Key</string>\n    <key>developmentAppSecret</key>\n    <string>Your Development App Secret</string>\n    <key>productionAppKey</key>\n    <string>Your Production App Key</string>\n    <key>productionAppSecret</key>\n    <string>Your Production App Secret</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/Dev App/AirshipConfig.plist.sample",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>detectProvisioningMode</key>\n    <true/>\n    <key>developmentAppKey</key>\n    <string>Your Development App Key</string>\n    <key>developmentAppSecret</key>\n    <string>Your Development App Secret</string>\n    <key>productionAppKey</key>\n    <string>Your Production App Key</string>\n    <key>productionAppSecret</key>\n    <string>Your Production App Secret</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/Dev App/AppRouter.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport SwiftUI\nimport AirshipMessageCenter\n\n@MainActor\nfinal class AppRouter: ObservableObject {\n    let preferenceCenterID: String = \"app_default\"\n\n    @Published\n    var selectedTab: Tabs = .home\n\n    @Published\n    var homePath: [HomeRoute] = []\n\n    enum Tabs: Sendable, Equatable, Hashable {\n        case home\n        case messageCenter\n        case preferenceCenter\n    }\n\n    enum HomeRoute: Hashable {\n        case namedUser\n        case thomas(ThomasRoute)\n    }\n    \n    enum ThomasRoute: Hashable {\n        case home\n        case layoutList(LayoutType)\n    }\n\n    let messageCenterController: MessageCenterController = MessageCenterController()\n\n    public func navigateMessagCenter(messageID: String? = nil) {\n        messageCenterController.navigate(messageID: messageID)\n        selectedTab = .messageCenter\n    }\n}\n\nextension AppRouter.HomeRoute {\n    @MainActor\n    @ViewBuilder\n    func destination() -> some View {\n        switch self {\n        case .namedUser: NamedUserView()\n        case .thomas(let route): route.destination()\n        }\n    }\n}\n\nextension AppRouter.ThomasRoute {\n    \n    @MainActor\n    @ViewBuilder\n    func destination() -> some View {\n        switch self {\n        case .home: ThomasLayoutListView()\n        case .layoutList(let type):\n            if case .sceneEmbedded = type {\n                EmbeddedPlaygroundMenuView()\n                    .navigationTitle(\"Embedded\")\n            } else {\n                LayoutsList(layoutType: type, onOpen: ThomasLayoutViewModel.saveToRecent)\n                    .navigationTitle(type.navigationTitle)\n            }\n        }\n    }\n}\n\nprivate extension LayoutType {\n    var navigationTitle: String {\n        switch(self) {\n        case .sceneModal: return \"Modals\"\n        case .sceneBanner: return \"Banners\"\n        case .sceneEmbedded: return \"Embedded\"\n        case .messageModal: return \"Modals\"\n        case .messageBanner: return \"Banners\"\n        case .messageFullscreen: return \"Fullscreen\"\n        case .messageHTML: return \"HTML\"\n        }\n    }\n}\n\n\n"
  },
  {
    "path": "DevApp/Dev App/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"1.000\",\n          \"green\" : \"0.294\",\n          \"red\" : \"0.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"Icon-App-20x20@2x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"Icon-App-20x20@3x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"Icon-App-29x29@2x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"Icon-App-29x29@3x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"Icon-App-40x40@2x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"Icon-App-40x40@3x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"Icon-App-60x60@2x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"Icon-App-60x60@3x.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"Icon-App-20x20@1x.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"Icon-App-20x20@2x-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"Icon-App-29x29@1x.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"Icon-App-29x29@2x-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"Icon-App-40x40@1x.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"Icon-App-40x40@2x-1.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"Icon-App-76x76@1x.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"Icon-App-76x76@2x.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"Icon-App-83.5x83.5@2x.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"83.5x83.5\"\n    },\n    {\n      \"filename\" : \"ItunesArtwork@2x.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"filename\" : \"Icon-24@2x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"notificationCenter\",\n      \"scale\" : \"2x\",\n      \"size\" : \"24x24\",\n      \"subtype\" : \"38mm\"\n    },\n    {\n      \"filename\" : \"Icon-27.5@2x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"notificationCenter\",\n      \"scale\" : \"2x\",\n      \"size\" : \"27.5x27.5\",\n      \"subtype\" : \"42mm\"\n    },\n    {\n      \"filename\" : \"Icon-29@2x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"companionSettings\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"Icon-29@3x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"companionSettings\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"notificationCenter\",\n      \"scale\" : \"2x\",\n      \"size\" : \"33x33\",\n      \"subtype\" : \"45mm\"\n    },\n    {\n      \"filename\" : \"Icon-40@2x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\",\n      \"subtype\" : \"38mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"44x44\",\n      \"subtype\" : \"40mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"46x46\",\n      \"subtype\" : \"41mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"50x50\",\n      \"subtype\" : \"44mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"51x51\",\n      \"subtype\" : \"45mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"54x54\",\n      \"subtype\" : \"49mm\"\n    },\n    {\n      \"filename\" : \"Icon-86@2x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"86x86\",\n      \"subtype\" : \"38mm\"\n    },\n    {\n      \"filename\" : \"Icon-98@2x.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"98x98\",\n      \"subtype\" : \"42mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"108x108\",\n      \"subtype\" : \"44mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"117x117\",\n      \"subtype\" : \"45mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"129x129\",\n      \"subtype\" : \"49mm\"\n    },\n    {\n      \"idiom\" : \"watch-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Assets.xcassets/HomeHeroImage.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"airshipMark.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"1x\"\n    },\n    {\n      \"filename\" : \"airshipMark-1.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"2x\"\n    },\n    {\n      \"filename\" : \"airshipMark-2.png\",\n      \"idiom\" : \"universal\",\n      \"scale\" : \"3x\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/DevApp.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>aps-environment</key>\n\t<string>development</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/Dev App/MainApp.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\n#if DEBUG && canImport(AirshipDebug)\nimport AirshipDebug\n#endif\n\n@main\nstruct MainApp: App {\n    \n    let appRouter: AppRouter = AppRouter()\n    let toast: Toast = Toast()\n\n    @Environment(\\.scenePhase) private var scenePhase\n\n    init() {\n        do {\n            // Initialize Airship\n            try AirshipInitializer.initialize()\n\n            // Setup optional features\n            LiveActivityHandler.setup()\n            PushNotificationHandler.setup()\n            DeepLinkHandler.setup(router: appRouter) { [weak toast] error in\n                toast?.message = .init(text: \"Invalid deepLink \\(error)\", duration: 2.0)\n            }\n        } catch {\n            toast.message = .init(text: \"Failed to initialize airship \\(error)\", duration: 2.0)\n        }\n    }\n\n    var body: some Scene {\n        WindowGroup {\n            AppView()\n                .environmentObject(appRouter)\n                .environmentObject(toast)\n                .airshipOnChangeOf(scenePhase) { phase in\n                    if phase == .active {\n                        print(\"App became active!\")\n\n                        // Clear the badge on active\n                        Task {\n                            try await Airship.push.resetBadge()\n                        }\n                    }\n                }\n                .airshipDebug(triggers: [.shake, .cmdShiftD])\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Setup/AirshipInitializer.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\n\n/// Example Airship SDK initialization handler.\n///\n/// This is a sample implementation showing how to configure and initialize\n/// the Airship SDK with basic settings for development and production builds.\n///\n/// - Note: This is an example - customize for your app's needs.\nstruct AirshipInitializer {\n\n    private init() {}\n    \n    /// Initializes Airship with example configuration.\n    ///\n    /// - Throws: An error if Airship initialization fails\n    @MainActor\n    static func initialize() throws {\n        var config = try AirshipConfig.default()\n        config.productionLogLevel = .verbose\n        config.developmentLogLevel = .verbose\n\n        #if DEBUG\n        config.inProduction = false\n        config.isAirshipDebugEnabled = true\n        config.isWebViewInspectionEnabled = true\n        #else\n        config.inProduction = true\n        #endif\n\n        try Airship.takeOff(config)\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Setup/DeepLinkHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport AirshipMessageCenter\nimport AirshipPreferenceCenter\nimport Foundation\n\n/// Example deep linking and UI routing handler.\n///\n/// This is a sample implementation showing how to handle deep links and\n/// route Airship features to your app's navigation system.\n///\n/// - Note: This is an example - customize for your app's needs.\nstruct DeepLinkHandler {\n\n    /// Error types that can occur during deep link processing\n    public enum DeepLinkError: Error, LocalizedError {\n        case invalidURL(String)\n        case unsupportedHost(String)\n        case unsupportedPath(String)\n        case routerNotAvailable\n\n        public var errorDescription: String? {\n            switch self {\n            case .invalidURL(let url):\n                return \"Invalid deep link URL: \\(url)\"\n            case .unsupportedHost(let host):\n                return \"Unsupported deep link host: \\(host)\"\n            case .unsupportedPath(let path):\n                return \"Unsupported deep link path: \\(path)\"\n            case .routerNotAvailable:\n                return \"App router is not available\"\n            }\n        }\n    }\n\n    /// Sets up example deep linking and UI routing.\n    ///\n    /// - Parameters:\n    ///   - router: The app router that manages navigation state\n    ///   - onError: Optional callback for handling deep link errors\n    @MainActor\n    static func setup(\n        router: AppRouter,\n        onError: (@MainActor @Sendable (DeepLinkError) -> Void)? = nil\n    ) {\n        Airship.onDeepLink = { [weak router] url in\n            do {\n                try processDeepLink(url: url, router: router, onError: onError)\n            } catch let error as DeepLinkError {\n                onError?(error)\n            } catch {\n                onError?(.invalidURL(url.absoluteString))\n            }\n        }\n\n        // Preference Center routing\n        Airship.preferenceCenter.onDisplay = { [weak router] identifier in\n            guard identifier == router?.preferenceCenterID else {\n                return false\n            }\n            router?.selectedTab = .preferenceCenter\n            return true\n        }\n\n        // Message Center routing\n        Airship.messageCenter.onDisplay = { [weak router] messageID in\n            router?.navigateMessagCenter(messageID: messageID)\n            return true\n        }\n    }\n\n    @MainActor\n    private static func processDeepLink(\n        url: URL,\n        router: AppRouter?,\n        onError: ((DeepLinkError) -> Void)?\n    ) throws {\n        guard let router = router else {\n            throw DeepLinkError.routerNotAvailable\n        }\n\n        guard url.host?.lowercased() == \"deeplink\" else {\n            throw DeepLinkError.unsupportedHost(url.host ?? \"nil\")\n        }\n\n        let components = url.path.lowercased().split(separator: \"/\")\n        guard let firstComponent = components.first else {\n            throw DeepLinkError.unsupportedPath(url.path)\n        }\n\n        switch firstComponent {\n        case \"home\":\n            router.homePath = []\n            router.selectedTab = .home\n        case \"preferences\":\n            router.selectedTab = .preferenceCenter\n        case \"message_center\":\n            router.selectedTab = .messageCenter\n        default:\n            throw DeepLinkError.unsupportedPath(String(firstComponent))\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Setup/LiveActivityHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\n\n#if canImport(ActivityKit)\nimport ActivityKit\n#endif\n\n/// Example Live Activity integration handler.\n///\n/// This is a sample implementation showing how to integrate iOS Live Activities\n/// with Airship.\n///\n/// - Note: This is an example - customize for your app's needs.\nstruct LiveActivityHandler {\n\n    /// Sets up example Live Activity integration.\n    static func setup() {\n\n#if canImport(ActivityKit) && !os(macOS)\n\n        // Restores live activity tracking\n        Airship.channel.restoreLiveActivityTracking { restorer in\n            // Call this for every type of Live Activity that you want\n            // to update through Airship\n            await restorer.restore(\n                forType: Activity<DeliveryAttributes>.self\n            )\n        }\n\n        // Important for APNS started Live Activties. Watch for any actvities and makes sure\n        // they are tracked on Airship. This will get called for all activities that are started\n        // whenever a new live activity is started and on first invoke.\n        Activity<DeliveryAttributes>.airshipWatchActivities { activity in\n            // Track the live activity with Airship with the order number as the name\n            Airship.channel.trackLiveActivity(activity, name: activity.attributes.orderNumber)\n        }\n\n#endif\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Setup/PushNotificationHandler.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\n\n/// Example push notification configuration handler.\n///\n/// This is a sample implementation showing how to configure push notification\n/// settings and callbacks for the Airship SDK.\n///\n/// - Note: This is an example - customize for your app's needs.\nstruct PushNotificationHandler {\n\n    /// Sets up example push notification handling.\n    @MainActor\n    static func setup() {\n        // Default presentation options\n        Airship.push.defaultPresentationOptions = [.sound, .banner, .list]\n\n        // APNS registration\n        Airship.push.onAPNSRegistrationFinished = { result in\n            switch(result) {\n            case .success(deviceToken: let deviceToken):\n                print(\"APNS registration succeeded :) \\(deviceToken)\")\n            case .failure(error: let error):\n                print(\"APNS registration failed :( \\(error)\")\n            @unknown default:\n                fatalError()\n            }\n        }\n\n        // Notification registration\n        Airship.push.onNotificationRegistrationFinished = { result in\n            print(\"Notification registration finished \\(result.status)\")\n        }\n\n        Airship.push.onNotificationAuthorizedSettingsDidChange = { settings in\n            print(\"Authorized notification settings changed \\(settings)\")\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/CustomView/Examples/AdView.swift",
    "content": "///* Copyright Airship and Contributors */\n//\n//import SwiftUI\n//import GoogleMobileAds\n//\n//struct AdView: UIViewControllerRepresentable {\n//    var keywords: [String]?\n//\n//    @State private var viewWidth: CGFloat = UIScreen.main.bounds.width\n//    @State private var isLoadingAd = true  // Track ad loading state\n//    \n//    let googleTestAdUnitID = \"ca-app-pub-3940256099942544/2934735716\"\n//\n//    func makeUIViewController(context: Context) -> UIViewController {\n//        let bannerViewController = UIViewController()\n//        let bannerView = GADBannerView(adSize: GADAdSizeFluid)\n//        bannerView.adUnitID = googleTestAdUnitID\n//        bannerView.rootViewController = bannerViewController\n//        bannerView.delegate = context.coordinator\n//        bannerView.translatesAutoresizingMaskIntoConstraints = false\n//        bannerViewController.view.addSubview(bannerView)\n//        bannerView.isAutoloadEnabled = true\n//\n//        let loadingIndicator = UIActivityIndicatorView(style: .large)\n//        loadingIndicator.startAnimating()\n//        loadingIndicator.translatesAutoresizingMaskIntoConstraints = false\n//        bannerViewController.view.addSubview(loadingIndicator)\n//\n//        NSLayoutConstraint.activate([\n//            loadingIndicator.centerXAnchor.constraint(equalTo: bannerViewController.view.centerXAnchor),\n//            loadingIndicator.centerYAnchor.constraint(equalTo: bannerViewController.view.centerYAnchor)\n//        ])\n//\n//        NSLayoutConstraint.activate([\n//            bannerView.topAnchor.constraint(equalTo: bannerViewController.view.safeAreaLayoutGuide.topAnchor),\n//            bannerView.leadingAnchor.constraint(equalTo: bannerViewController.view.safeAreaLayoutGuide.leadingAnchor),\n//            bannerView.trailingAnchor.constraint(equalTo: bannerViewController.view.safeAreaLayoutGuide.trailingAnchor),\n//            bannerView.bottomAnchor.constraint(equalTo: bannerViewController.view.safeAreaLayoutGuide.bottomAnchor)\n//        ])\n//\n//        return bannerViewController\n//    }\n//\n//    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {\n//        if let loadingIndicator = uiViewController.view.subviews.first(where: { $0 is UIActivityIndicatorView }) as? UIActivityIndicatorView {\n//            loadingIndicator.isHidden = !isLoadingAd\n//        }\n//    }\n//\n//    func makeCoordinator() -> Coordinator {\n//        Coordinator(self)\n//    }\n//\n//    class Coordinator: NSObject, GADBannerViewDelegate {\n//        var parent: AdView\n//\n//        init(_ parent: AdView) {\n//            self.parent = parent\n//        }\n//\n//        func bannerViewDidReceiveAd(_ bannerView: GADBannerView) {\n//            print(\"Banner did receive ad\")\n//            // Update loading state\n//            DispatchQueue.main.async {\n//                self.parent.isLoadingAd = false\n//            }\n//        }\n//\n//        func bannerView(_ bannerView: GADBannerView, didFailToReceiveAdWithError error: Error) {\n//            print(\"Banner failed to receive ad with error: \\(error.localizedDescription)\")\n//            // Update loading state\n//            DispatchQueue.main.async {\n//                self.parent.isLoadingAd = true\n//            }\n//        }\n//    }\n//}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/CustomView/Examples/BiometricLoginView.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if !os(tvOS) && !os(visionOS) && !os(macOS)\nimport SwiftUI\nimport LocalAuthentication\n\nenum LoginState {\n    case ready\n    case authenticated\n    case fallback\n    case loading\n}\n\n@MainActor\nclass BiometricLoginViewModel:ObservableObject {\n    init(context: LAContext = LAContext(), state: LoginState = .ready) {\n        self.context = context\n        self.state = state\n    }\n    \n    var context:LAContext = LAContext()\n\n    @Published var state:LoginState = .ready\n\n    private func onAppear() {\n        context.localizedCancelTitle = \"Enter Username/Password\"\n    }\n\n    func updateState(state:LoginState){\n        withAnimation{\n            self.state = state\n        }\n    }\n\n    private func testPolicy(){\n        var error: NSError?\n        guard context.canEvaluatePolicy(.deviceOwnerAuthentication, error: &error) else {\n            print(error?.localizedDescription ?? \"Can't evaluate policy\")\n\n            self.updateState(state: .fallback)\n            return\n        }\n    }\n\n    func evaluatePolicy() {\n        self.updateState(state: .loading)\n\n        Task {\n            do {\n\n                try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: \"Log in to your account\")\n                self.updateState(state: .authenticated)\n\n            } catch let error {\n                print(error.localizedDescription)\n                self.updateState(state: .fallback)\n            }\n        }\n    }\n}\n\nstruct BiometricLoginView: View {\n    @StateObject var viewModel:BiometricLoginViewModel = BiometricLoginViewModel()\n\n    var loginButton: some View {\n        Button(action: viewModel.evaluatePolicy) {\n            HStack {\n                Image(systemName: \"faceid\")\n                    .resizable()\n                    .aspectRatio(contentMode: .fit)\n                    .frame(width: 100, height: 100)\n                Text(\"Login with Face ID\").font(.title)\n            }\n            .padding()\n            .foregroundColor(.white)\n            .background(Color.cyan.opacity(0.7))\n            .cornerRadius(8)\n        }\n    }\n\n    var readyView: some View{\n        VStack(alignment: .center) {\n            Spacer()\n            loginButton\n            Spacer()\n        }\n    }\n\n    var authenticatedView: some View{\n        VStack(alignment: .center) {\n            Spacer()\n            Text(\"Authenticated!\").font(.title).foregroundColor(.white)\n            Spacer()\n        }\n    }\n\n    var fallbackView: some View{\n        VStack(alignment: .center) {\n            Spacer()\n            Text(\"Log in with password instead\").font(.title).foregroundColor(.white)\n            Spacer()\n        }\n    }\n\n    var loadingView: some View{\n        VStack(alignment: .center) {\n            Spacer()\n            Text(\"Loading...\").font(.title).foregroundColor(.white)\n            Spacer()\n        }\n    }\n\n    private func makeGlassmorphic<T: View>(_ content: T) -> some View {\n        ZStack(alignment: .center) {\n            CameraView().edgesIgnoringSafeArea(.all).offset(x:40)\n\n            LinearGradient(colors: [Color.cyan.opacity(0.7), Color.purple.opacity(0.3)], startPoint: .topLeading, endPoint: .bottomTrailing)\n\n            Circle()\n                .frame(width: 300)\n                .foregroundColor(Color.blue.opacity(0.3))\n                .blur(radius: 10)\n                .offset(x: -100, y: -150)\n\n            RoundedRectangle(cornerRadius: 30, style: .continuous)\n                .frame(width: 500, height: 500)\n                .foregroundStyle(LinearGradient(colors: [Color.purple.opacity(0.6), Color.mint.opacity(0.5)], startPoint: .top, endPoint: .leading))\n                .offset(x: 300)\n                .blur(radius: 30)\n                .rotationEffect(.degrees(30))\n\n            Circle()\n                .frame(width: 450)\n                .foregroundStyle(Color.pink.opacity(0.6))\n                .blur(radius: 20)\n                .offset(x: 200, y: -200)\n            content\n        }\n        .edgesIgnoringSafeArea(.all)\n    }\n\n    var body: some View {\n        makeGlassmorphic(    Group {\n            switch viewModel.state {\n            case .ready:\n                readyView\n            case .authenticated:\n                authenticatedView\n            case .fallback:\n                fallbackView\n            case .loading:\n                loadingView\n            }\n        }\n      )\n    }\n}\n#endif\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/CustomView/Examples/CameraView.swift",
    "content": "#if !os(visionOS) && !os(macOS)\n/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AVFoundation\n\nstruct CameraView : UIViewControllerRepresentable {\n    func makeUIViewController(context: UIViewControllerRepresentableContext<CameraView>) -> UIViewController {\n        let controller = CameraViewController()\n        return controller\n    }\n    func updateUIViewController(_ uiViewController: CameraView.UIViewControllerType, context: UIViewControllerRepresentableContext<CameraView>) {}\n}\n\nclass CameraViewController : UIViewController {\n\n    override func viewDidLoad() {\n        super.viewDidLoad()\n        loadCamera()\n    }\n\n    func loadCamera() {\n        let avSession = AVCaptureSession()\n\n        guard let captureDevice = AVCaptureDevice.default(for: .video) else { return }\n        guard let input = try? AVCaptureDeviceInput(device : captureDevice) else { return }\n        avSession.addInput(input)\n        avSession.startRunning()\n\n        let cameraPreview = AVCaptureVideoPreviewLayer(session: avSession)\n        view.layer.addSublayer(cameraPreview)\n        cameraPreview.frame = view.frame\n        cameraPreview.videoGravity = .resizeAspectFill\n    }\n}\n#endif\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/CustomView/Examples/MapRouteView.swift",
    "content": "#if !os(tvOS) && !os(macOS)\nimport CoreLocation\nimport MapKit\nimport SwiftUI\n\nstruct MapRouteView: View {\n    @StateObject private var locationManager = LocationManager()\n\n    var body: some View {\n        Group {\n            if let userLocation = locationManager.location {\n                ZStack(alignment: .center) {\n                    MapView(userLocation: userLocation, destinationCoordinate: CLLocationCoordinate2D(latitude: 45.559020, longitude: -122.672970))\n                    VStack {\n                        Spacer()\n                        Text(\"Lat:\\(userLocation.latitude) Lon:\\(userLocation.longitude)\")\n                            .padding(8)\n                            .font(.footnote)\n                            .foregroundColor(.white)\n                            .background(Capsule()\n                                .foregroundColor(.gray)\n                                .opacity(0.8)\n                                .shadow(radius: 4))\n                    }\n                }.padding(8)\n            } else {\n                Text(\"Determining your location...\")\n            }\n        }\n    }\n}\n\n@MainActor\nclass LocationManager: NSObject, ObservableObject, CLLocationManagerDelegate {\n    @Published var location: CLLocationCoordinate2D?\n    private let locationManager = CLLocationManager()\n\n    override init() {\n        super.init()\n        locationManager.delegate = self\n        locationManager.requestWhenInUseAuthorization()\n        locationManager.startUpdatingLocation()\n    }\n\n\n    nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {\n        if let location = locations.last?.coordinate {\n            DispatchQueue.main.async {\n                self.location = location\n            }\n        }\n    }\n}\n\nstruct MapView: UIViewRepresentable {\n    var userLocation: CLLocationCoordinate2D\n    let destinationCoordinate: CLLocationCoordinate2D\n\n    func makeUIView(context: Context) -> MKMapView {\n        let mapView = MKMapView()\n        mapView.delegate = context.coordinator\n\n        // Update the region to center on the user's location\n        let region = MKCoordinateRegion(center: userLocation, latitudinalMeters: 900, longitudinalMeters: 900)\n        mapView.setRegion(region, animated: true)\n\n        // Set up and calculate the route\n        let request = MKDirections.Request()\n        request.source = MKMapItem(placemark: MKPlacemark(coordinate: userLocation))\n        request.destination = MKMapItem(placemark: MKPlacemark(coordinate: destinationCoordinate))\n        request.transportType = .automobile\n\n        let directions = MKDirections(request: request)\n        Task {\n            guard\n                let response = try? await directions.calculate(),\n                let route = response.routes.first\n            else { return }\n            \n            mapView.addOverlay(route.polyline)\n            mapView.setVisibleMapRect(route.polyline.boundingMapRect, animated: true)\n        }\n\n        // Manage annotations\n        updateAnnotations(mapView: mapView)\n\n        return mapView\n    }\n\n    func updateUIView(_ uiView: MKMapView, context: Context) {\n\n\n    }\n\n    func updateAnnotations(mapView: MKMapView) {\n        mapView.removeAnnotations(mapView.annotations)\n\n        // Add user location annotation\n        let userAnnotation = MKPointAnnotation()\n        userAnnotation.coordinate = userLocation\n        userAnnotation.title = \"Your Location\"\n        mapView.addAnnotation(userAnnotation)\n\n        // Add destination annotation\n        let destinationAnnotation = MKPointAnnotation()\n        destinationAnnotation.coordinate = destinationCoordinate\n        destinationAnnotation.title = \"NETs Site 🚑\"\n        destinationAnnotation.subtitle = \"Your local NETs meeting point\"\n        mapView.addAnnotation(destinationAnnotation)\n    }\n\n    func makeCoordinator() -> Coordinator {\n        Coordinator(self)\n    }\n\n    class Coordinator: NSObject, MKMapViewDelegate {\n        var parent: MapView\n\n        init(_ parent: MapView) {\n            self.parent = parent\n        }\n\n        func mapView(_ mapView: MKMapView, rendererFor overlay: MKOverlay) -> MKOverlayRenderer {\n            let renderer = MKPolylineRenderer(overlay: overlay)\n            renderer.strokeColor = .systemGreen\n            renderer.lineWidth = 5\n            return renderer\n        }\n\n        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {\n            let identifier = \"Placemark\"\n            var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier)\n\n            if annotationView == nil {\n                annotationView = MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: identifier)\n                annotationView?.canShowCallout = true\n            } else {\n                annotationView?.annotation = annotation\n            }\n\n            return annotationView\n        }\n    }\n}\n#endif\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/CustomView/Examples/Weather/WeatherView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\nstruct BackgroundView: View {\n    var body: some View {\n        let colorScheme = [\n                           Color(red: 141/255, green: 87/255, blue: 151/255),\n                           Color(red: 20/255, green: 31/255, blue: 78/255),\n                           Color.black\n        ]\n\n        let gradient = Gradient(colors: colorScheme)\n        let linearGradient = LinearGradient(gradient: gradient, startPoint: .top, endPoint: .bottom)\n\n        let background = Rectangle()\n            .fill(linearGradient)\n            .blur(radius: 20, opaque: true)\n            .edgesIgnoringSafeArea(.all)\n\n        return background\n    }\n}\n\nstruct WeatherView: View {\n    @StateObject var data: WeatherViewModel = WeatherViewModel()\n\n    var foreground:some View {\n        VStack(alignment: .leading) {\n            HStack {\n                Image(data.icon)\n                    .resizable()\n                    .frame(width: 45, height: 45)\n                Text(data.summary)\n                    .font(.system(size: 25))\n                    .fontWeight(.light)\n            }.padding(0)\n\n            HStack {\n                Text(data.temperature)\n                    .font(.system(size: 120))\n                    .fontWeight(.ultraLight)\n                Spacer()\n                HStack {\n                    Spacer()\n                    VStack(alignment: .leading) {\n                        HStack {\n                            Text(\"FEELS LIKE\")\n                            Spacer()\n                            Text(data.apparentTemperature)\n                        }.padding(.bottom, 1)\n\n                        HStack {\n                            Text(\"WIND SPEED\")\n                            Spacer()\n                            Text(data.windSpeed)\n                        }.padding(.bottom, 1)\n\n                        HStack {\n                            Text(\"HUMIDITY\")\n                            Spacer()\n                            Text(data.humidity)\n                        }.padding(.bottom, 1)\n\n                        HStack {\n                            Text(\"PRECIPITATION\")\n                            Spacer()\n                            Text(data.precipProbability)\n                        }.padding(.bottom, 1)\n                    }\n\n                }.frame(maxWidth:150).font(.caption)\n            }.padding(0)\n        }\n    }\n\n    var body: some View {\n        ZStack {\n            BackgroundView()\n\n            VStack {\n                Spacer()\n\n                VStack {\n                    Text(\"PORTLAND, OREGON\").font(.title).fontWeight(.light)\n                    Text(data.time).foregroundColor(.gray)\n                }\n                Spacer()\n\n                foreground\n\n                Spacer()\n            }.padding(22)\n        }.colorScheme(.dark)\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/CustomView/Examples/Weather/WeatherViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport Combine\nimport SwiftUI\n\n@MainActor\nclass WeatherViewModel: ObservableObject {\n    @Published var time: String = \"Loading...\"\n    let summary: String\n    let icon: String\n    let precipProbability: String\n    let temperature: String\n    let apparentTemperature: String\n    let humidity: String\n    let windSpeed: String\n\n    private var timer: AnyCancellable?\n\n    private func updateTime() {\n        withAnimation {\n            self.time = getCurrentDateTimeString()\n        }\n    }\n\n    private func getCurrentDateTimeString() -> String {\n        let now = Date()\n        let formatter = DateFormatter()\n        formatter.dateFormat = \"h:mm a MMMM dd, yyyy\"\n        return formatter.string(from: now)\n    }\n\n    init() {\n        self.summary = \"Storm Advisory\"\n\n        self.icon = \"rain\"\n\n        self.precipProbability = \"100%\"\n\n        self.temperature = \"53º\"\n\n        self.apparentTemperature = \"52º\"\n\n        self.humidity = \"100%\"\n\n        self.windSpeed = \"22 mph\"\n\n        timer = Timer.publish(every: 1, on: .main, in: .common)\n            .autoconnect()\n            .sink { [weak self] _ in\n                self?.updateTime()\n            }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Embedded Playground View/EmbeddedPlaygroundMenuView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\ninternal import Yams\n\nstruct Item {\n    var icon: String\n    var title: String\n    var description: String\n    var destination: AnyView\n}\n\n@MainActor\nclass EmbeddedPlaygroundMenuViewModel: ObservableObject {\n    @Published var selectedFileID: String = \"\" {\n        didSet {\n            selectedEmbeddedID = extractEmbeddedId(selectedFileID: selectedFileID) ?? \"\"\n        }\n    }\n\n    @Published var isShowingPlaceholder: Bool = false\n\n    @Published var selectedEmbeddedID: String = \"\"\n\n    private let viewsPath = \"/Scenes/Embedded\"\n\n    func extractEmbeddedId(selectedFileID: String) -> String? {\n        guard let resourcePath = Bundle.main.resourcePath else {\n            print(\"Error: Could not find resource path.\")\n            return nil\n        }\n\n        let filePath = resourcePath + viewsPath + \"/\\(self.selectedFileID).yml\"\n\n        // Parse the YAML string into a dictionary\n        if let fileContents = try? String(contentsOfFile: filePath, encoding: String.Encoding.utf8), let parsedYaml = try? Yams.load(yaml: fileContents) as? [String: Any] {\n\n            // Navigate through the dictionary to retrieve the embedded_id\n            if let presentation = parsedYaml[\"presentation\"] as? [String: Any],\n               let embeddedId = presentation[\"embedded_id\"] as? String {\n                return embeddedId\n            }\n        }\n\n        return nil\n    }\n\n    lazy var embeddedViewIds: [String] = {\n        let fileManager = FileManager.default\n        guard let resourcePath = Bundle.main.resourcePath else {\n            print(\"Error: Could not find resource path.\")\n            return []\n        }\n\n        let fullPath = resourcePath + viewsPath\n\n        do {\n            let fileNames = try fileManager.contentsOfDirectory(atPath: fullPath)\n            return fileNames.map { (fileName) -> String in\n                return (fileName as NSString).deletingPathExtension\n            }\n        } catch {\n            print(\"Error while enumerating files \\(fullPath): \\(error.localizedDescription)\")\n            return []\n        }\n    }()\n}\n\nstruct EmbeddedPlaygroundMenuView: View {\n    @StateObject var model = EmbeddedPlaygroundMenuViewModel()\n\n    private var listItems: [Item] {\n        [\n            Item(icon: \"arrow.left.and.right.square\",\n                 title: \"Unbounded horizontally\",\n                 description: \"Embedded scene that can grow unbounded horizontally\",\n                 destination: AnyView(EmbeddedUnboundedHorizontalScrollView().environmentObject(model))),\n            Item(icon: \"arrow.up.and.down.square\",\n                 title: \"Unbounded vertically\",\n                 description: \"Embedded scene that can grow unbounded in a vertical scroll view\",\n                 destination: AnyView(EmbeddedUnboundedVerticalScrollView().environmentObject(model))),\n            Item(icon: \"arrow.left.arrow.right.square\",\n                 title: \"Horizontal scroll\",\n                 description: \"Embedded scene in a horizontal scroll view\",\n                 destination: AnyView(EmbeddedHorizontalScrollView().environmentObject(model))),\n            Item(icon: \"square\",\n                 title: \"Fixed frame\",\n                 description: \"Embedded scene that is bounded to a fixed frame size\",\n                 destination: AnyView(EmbeddedFixedFrameView()\n                    .environmentObject(model)))\n        ]\n    }\n\n    private var listView: some View {\n        Form {\n            Section {\n                EmbeddedPlaygroundPicker(selectedID: $model.selectedFileID, embeddedIds:model.embeddedViewIds)\n                    .frame(height:120)\n            }\n\n            ForEach(listItems, id: \\.title) { item in\n                    NavigationLink(destination: item.destination) {\n                        HStack(spacing: 16) {\n                            Image(systemName: item.icon)\n                                .resizable()\n                                .frame(width: 44, height: 44)\n                                .foregroundColor(.primary)\n                            VStack(alignment: .leading) {\n                                Text(item.title).font(.title3)\n                                Text(item.description)\n                                    .font(.caption2).foregroundColor(.secondary)\n                            }\n                        }.frame(height: 54)\n                    }\n            }\n        }\n    }\n\n    var body: some View {\n        listView\n        .listStyle(.plain)\n    }\n}\n\n#Preview {\n    EmbeddedPlaygroundMenuView()\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Embedded Playground View/EmbeddedPlaygroundPicker.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport Foundation\n\nstruct EmbeddedPlaygroundPicker: View {\n    @Binding var selectedID: String\n    var embeddedIds: [String]\n\n    var body: some View {\n        VStack {\n            Picker(\"Embedded View\", selection: $selectedID) {\n                ForEach(embeddedIds, id: \\.self) { id in\n                    Text(id).tag(id)\n                }\n            }\n#if !os(tvOS) && !os(macOS)\n            .pickerStyle(WheelPickerStyle())\n#endif\n        }\n        .onAppear {\n            if !embeddedIds.isEmpty {\n                selectedID = embeddedIds.first!\n            }\n        }\n    }\n}\n\n#Preview {\n    EmbeddedPlaygroundPicker(selectedID:Binding.constant(\"home_rating\"), embeddedIds: [\"home_rating\", \"home_special_offer\"])\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Embedded Playground View/EmbeddedPlaygroundView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\nimport AirshipCore\n\nprotocol EmbeddedViewMaker {}\nextension EmbeddedViewMaker {\n\n    @MainActor\n    func makeEmbeddedView<Content: View>(\n        id: String, \n        parentWidth: CGFloat? = nil,\n        parentHeight: CGFloat? = nil,\n        isShowingPlaceholder: Bool,\n        @ViewBuilder placeholder: @escaping () -> Content\n    ) -> some View {\n        AirshipEmbeddedView(\n            embeddedID: isShowingPlaceholder ? \"nonexistent view id\" : id,\n            embeddedSize: AirshipEmbeddedSize(parentWidth: parentWidth, parentHeight: parentHeight),\n            placeholder:placeholder\n        )\n    }\n}\n\nstruct EmbeddedUnboundedHorizontalScrollView: View, EmbeddedViewMaker {\n    @EnvironmentObject var model: EmbeddedPlaygroundMenuViewModel\n\n    @State\n    private var size: CGSize?\n\n    @ViewBuilder\n    private var embeddedView: some View {\n        let keyItems = [KeyItem(name: \"Embedded view frame\",\n                                color: .red),\n                        KeyItem(name: \"Scroll view frame\",\n                                color: .gray),\n                        KeyItem(name: \"Placeholder view\",\n                                color: .green)]\n\n        ScrollView(.horizontal) {\n            let exampleItem = Text(\"Example item\")\n                .font(.largeTitle)\n                .frame(width: 200, height: 200)\n                .background(Color.orange)\n\n            HStack(spacing: 20) {\n                exampleItem\n                exampleItem\n\n                makeEmbeddedView(\n                    id: model.selectedEmbeddedID,\n                    parentWidth: $size.wrappedValue?.width,\n                    isShowingPlaceholder: model.isShowingPlaceholder\n                ) {\n                    Text(\"Placeholder\")\n                        .font(.largeTitle)\n                        .frame(width: 200, height: 200)\n                        .background(Color.green)\n                }\n                .id(model.isShowingPlaceholder)\n\n                exampleItem\n                exampleItem\n            }\n        }\n        .airshipMeasureView($size)\n        .border(Color.gray, width: 3)\n        .addKeyView(keyItems:keyItems)\n        .addPlaceholderToggle(state: $model.isShowingPlaceholder)\n        .navigationTitle(model.selectedFileID)\n    }\n\n    var body: some View {\n        embeddedView\n    }\n}\n\nstruct EmbeddedUnboundedVerticalScrollView: View, EmbeddedViewMaker {\n    @EnvironmentObject var model: EmbeddedPlaygroundMenuViewModel\n    \n    @State\n    private var size: CGSize?\n\n    private var embeddedView: some View {\n        let keyItems = [KeyItem(name: \"Embedded view frame\",\n                                color: .red),\n                        KeyItem(name: \"Scroll view frame\",\n                                color: .gray),\n                        KeyItem(name: \"Placeholder view\",\n                                color: .green)]\n\n        let exampleItem = Text(\"Example item\")\n            .font(.largeTitle)\n            .frame(width: 200, height: 200)\n            .background(Color.orange)\n\n        return ScrollView(.vertical) {\n\n            VStack(spacing: 20) {\n                exampleItem\n                exampleItem\n                makeEmbeddedView(\n                    id: model.selectedEmbeddedID,\n                    parentWidth: size?.width,\n                    parentHeight: size?.height,\n                    isShowingPlaceholder: model.isShowingPlaceholder\n                ) {\n                    Text(\"Placeholder\")\n                        .font(.largeTitle)\n                        .frame(width: 200, height: 200)\n                        .background(Color.green)\n                }\n                .id(model.isShowingPlaceholder)\n                exampleItem\n                exampleItem\n            }\n        }\n        .airshipMeasureView($size)\n        .border(Color.gray, width: 3)\n        .addKeyView(keyItems:keyItems)\n        .addPlaceholderToggle(state: $model.isShowingPlaceholder)\n        .navigationTitle(model.selectedFileID)\n\n    }\n\n    var body: some View {\n        embeddedView\n    }\n}\n\nstruct EmbeddedHorizontalScrollView: View, EmbeddedViewMaker {\n    @EnvironmentObject var model: EmbeddedPlaygroundMenuViewModel\n\n    @State\n    private var size: CGSize?\n\n\n    var embeddedView: some View {\n        let keyItems = [KeyItem(name: \"Embedded view frame\",\n                                color: .red),\n                        KeyItem(name: \"Scroll view frame\",\n                                color: .gray),\n                        KeyItem(name: \"Placeholder view\",\n                                color: .green)]\n\n        return ScrollView(.horizontal) {\n            let exampleItem = Text(\"Example item\")\n                .font(.largeTitle)\n                .frame(width: 200, height: 200)\n                .background(Color.orange)\n\n            HStack(spacing: 20) {\n                exampleItem\n                exampleItem\n                makeEmbeddedView(\n                    id: model.selectedEmbeddedID,\n                    parentWidth: size?.width,\n                    parentHeight: size?.height,\n                    isShowingPlaceholder: model.isShowingPlaceholder\n                ) {\n                    Text(\"Placeholder\")\n                        .font(.largeTitle)\n                        .frame(width: 200, height: 200)\n                        .background(Color.green)\n                }.id(model.isShowingPlaceholder)\n                exampleItem\n                exampleItem\n            }\n        }\n        .airshipMeasureView($size)\n        .border(Color.gray, width: 3)\n        .addKeyView(keyItems:keyItems)\n        .addPlaceholderToggle(state: $model.isShowingPlaceholder)\n        .navigationTitle(model.selectedFileID)\n\n\n    }\n\n    var body: some View {\n        embeddedView\n    }\n}\n\nstruct EmbeddedFixedFrameView: View, EmbeddedViewMaker {\n    @EnvironmentObject var model: EmbeddedPlaygroundMenuViewModel\n\n    let keyItems = [KeyItem(name: \"Fixed size frame\",\n                            color: .red),\n                    KeyItem(name: \"Placeholder view\",\n                            color: .green)]\n\n    private var embeddedView: some View {\n        Group {\n            makeEmbeddedView(\n                id: model.selectedEmbeddedID,\n                isShowingPlaceholder: model.isShowingPlaceholder\n            ) {\n                Text(\"Placeholder\")\n                    .font(.largeTitle)\n                    .frame(width: 200, height: 200)\n                    .background(Color.green)\n            }\n            .frame(maxWidth: 200, maxHeight:200)\n            .border(Color.red, width: 3)\n            .id(model.isShowingPlaceholder)\n            .addKeyView(keyItems: keyItems)\n            .addPlaceholderToggle(state: $model.isShowingPlaceholder)\n        }\n        .navigationTitle(model.selectedFileID)\n    }\n\n    var body: some View {\n        embeddedView\n    }\n}\n\n#Preview {\n    EmbeddedUnboundedHorizontalScrollView()\n}\n\n\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Embedded Playground View/KeyView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nstruct KeyItem {\n    var name: String\n    var color: Color\n}\n\nstruct KeyView: View {\n    var keyItems: [KeyItem]\n\n    var body: some View {\n        VStack(alignment: .leading, spacing: 4) {\n            ForEach(keyItems, id: \\.name) { item in\n                HStack {\n                    Image(systemName:\"square.fill\")\n                        .resizable()\n                        .foregroundColor(item.color)\n                        .frame(width: 16, height: 16)\n                    Text(item.name).font(.caption).foregroundColor(.black)\n                }\n            }\n        }\n        .padding(8)\n        .background(\n            RoundedRectangle(cornerRadius: 2)\n                .foregroundColor(.white)\n                .overlay(\n                    RoundedRectangle(cornerRadius: 2)\n                        .stroke(Color.gray, lineWidth: 1)\n                )\n        )\n    }\n}\n\n\nstruct KeyViewModifier: ViewModifier {\n    var keyItems: [KeyItem]\n\n    func body(content: Content) -> some View {\n        ZStack(alignment: .center) {\n            content\n            VStack {\n                Spacer()\n                HStack {\n                    Spacer()\n                    KeyView(keyItems: keyItems).padding()\n                }\n            }\n        }\n    }\n}\n\nextension View {\n    func addKeyView(keyItems: [KeyItem]) -> some View {\n        self.modifier(KeyViewModifier(keyItems: keyItems))\n    }\n}\n\n#Preview {\n    KeyView(keyItems: [\n        KeyItem(name: \"Unbounded scroll view\", color: .red),\n        KeyItem(name: \"Embedded view\", color: .green),\n        KeyItem(name: \"Embedded view frame\", color: .blue)\n    ])\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Embedded Playground View/PlaceholderToggleView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Foundation\nimport SwiftUI\n\nstruct EmbeddedToggleModifier: ViewModifier {\n    @Binding var state:Bool\n\n    func body(content: Content) -> some View {\n        ZStack(alignment: .center) {\n            content\n            VStack {\n                Spacer()\n                HStack {\n                    Button {\n                        withAnimation {\n                            state.toggle()\n                        }\n                    } label: {\n                        HStack(spacing:4) {\n                            Image(systemName: state ? \"square.fill\" : \"square\")\n                                .resizable()\n                                .frame(width: 16, height: 16)\n                            Text(\"Toggle placeholder\").font(.caption).foregroundColor(.black)\n                        }.padding(8)\n                    }.background(\n                        RoundedRectangle(cornerRadius: 2)\n                            .foregroundColor(.white)\n                            .overlay(\n                                RoundedRectangle(cornerRadius: 2)\n                                    .stroke(Color.gray, lineWidth: 1)\n                            )\n                    ).padding()\n                    Spacer()\n                }\n            }\n        }\n    }\n}\n\nextension View {\n    func addPlaceholderToggle(state:Binding<Bool>) -> some View {\n        self.modifier(EmbeddedToggleModifier(state:state))\n    }\n}\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Layouts.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\ninternal import Yams\nimport AirshipAutomation\n\n@MainActor\nfinal class LayoutLoader: Sendable {\n    private var cache: [LayoutType: [LayoutFile]] = [:]\n\n    func load(type: LayoutType) -> [LayoutFile] {\n        if let cached = cache[type] {\n            return cached\n        }\n\n        let layouts = switch(type) {\n        case .sceneModal:\n            loadLayouts(directory: \"/Scenes/Modal\", type: .sceneModal)\n        case .sceneBanner:\n            loadLayouts(directory: \"/Scenes/Banner\", type: .sceneBanner)\n        case .sceneEmbedded:\n            loadLayouts(directory: \"/Scenes/Embedded\", type: .sceneEmbedded)\n        case .messageModal:\n            loadLayouts(directory: \"/Messages/Modal\", type: .messageModal)\n        case .messageBanner:\n            loadLayouts(directory: \"/Messages/Banner\", type: .messageBanner)\n        case .messageFullscreen:\n            loadLayouts(directory: \"/Messages/Fullscreen\", type: .messageFullscreen)\n        case .messageHTML:\n            loadLayouts(directory: \"/Messages/HTML\", type: .messageHTML)\n        }\n\n        self.cache[type] = layouts\n        return layouts\n    }\n\n    private func loadLayouts(directory: String, type: LayoutType) -> [LayoutFile] {\n        let path = Bundle.main.resourcePath! + directory\n        do {\n            return try FileManager.default.contentsOfDirectory(atPath: path).sorted().map { fileName in\n                LayoutFile(directory: directory, fileName: fileName, type: type)\n            }\n        } catch {\n            return []\n        }\n    }\n}\n\n\nstruct LayoutFile: Equatable, Hashable, Codable, Identifiable {\n    let directory: String\n    let fileName: String\n    let type: LayoutType\n    var id: String { directory + \"/\" + fileName }\n}\n\nenum LayoutType: Equatable, Hashable, Codable {\n    case sceneModal\n    case sceneBanner\n    case sceneEmbedded\n    case messageModal\n    case messageBanner\n    case messageFullscreen\n    case messageHTML\n}\n\nextension LayoutFile {\n    @MainActor\n    func open() throws {\n        let filePath = Bundle.main.resourcePath! + directory + \"/\" + fileName\n        let data = try loadData(filePath: filePath)\n\n        switch self.type {\n        case .sceneModal, .sceneBanner, .sceneEmbedded:\n            try displayScene(data)\n        case .messageModal, .messageBanner, .messageFullscreen, .messageHTML:\n            try displayMessage(data)\n        }\n    }\n\n    private func loadData(filePath: String) throws -> Data {\n        /// Retrieve the content\n        let stringContent = try getContentOfFile(filePath: filePath)\n\n        /// If we already have json in the file, don't bother to convert it from yaml\n        if isJSONString(stringContent) {\n            guard let jsonData = stringContent.data(using: .utf8),\n                  let jsonObject = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any] else {\n                throw NSError(domain: \"Invalid JSON\", code: 1001, userInfo: nil)\n            }\n            return try JSONSerialization.data(withJSONObject: jsonObject, options: .prettyPrinted)\n        }\n\n        // Convert YML file to json\n        return try getJsonContentFromYmlContent(ymlContent: stringContent)\n    }\n\n    /// Extracts a layout object from a potentially wrapped JSON payload.\n    ///\n    /// Supports multiple wrapper formats by traversing known key paths:\n    /// - `in_app_message.message.display.layout`\n    /// - `message.display.layout`\n    /// - `display.layout`\n    /// - `layout`\n    ///\n    /// If the payload is already a raw layout (has version, presentation, view), returns it as-is.\n    private func extractLayoutFromPayload(_ data: Data) throws -> Data {\n        guard let jsonObject = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {\n            return data\n        }\n\n        if let layout = extractLayout(from: jsonObject) {\n            return try JSONSerialization.data(withJSONObject: layout, options: .prettyPrinted)\n        }\n\n        return data\n    }\n\n    /// Traverses the JSON structure to find the layout object.\n    private func extractLayout(from json: [String: Any]) -> [String: Any]? {\n        // Check if this is already a valid layout\n        if isValidLayout(json) {\n            return json\n        }\n\n        // Define the possible key paths to the layout, from most to least nested\n        let keyPaths: [[String]] = [\n            [\"in_app_message\", \"message\", \"display\", \"layout\"],\n            [\"message\", \"display\", \"layout\"],\n            [\"display\", \"layout\"],\n            [\"layout\"]\n        ]\n\n        for keyPath in keyPaths {\n            if let layout = traverse(json: json, keyPath: keyPath), isValidLayout(layout) {\n                return layout\n            }\n        }\n\n        return nil\n    }\n\n    /// Traverses a JSON dictionary along a key path.\n    private func traverse(json: [String: Any], keyPath: [String]) -> [String: Any]? {\n        var current: [String: Any] = json\n        for key in keyPath {\n            guard let next = current[key] as? [String: Any] else {\n                return nil\n            }\n            current = next\n        }\n        return current\n    }\n\n    /// Checks if a dictionary contains the required layout keys.\n    private func isValidLayout(_ json: [String: Any]) -> Bool {\n        json[\"version\"] != nil && json[\"presentation\"] != nil && json[\"view\"] != nil\n    }\n\n    /// Check if a string is JSON\n    func isJSONString(_ jsonString: String) -> Bool {\n        if let jsonData = jsonString.data(using: .utf8) {\n            do {\n                _ = try JSONSerialization.jsonObject(with: jsonData, options: [])\n                return true\n            } catch {\n                return false\n            }\n        }\n        return false\n    }\n\n    /// Convert YML content to json content using Yams\n    func getJsonContentFromYmlContent(ymlContent: String) throws -> Data {\n        guard\n            let jsonContentOfFile = try Yams.load(yaml: ymlContent) as? NSDictionary\n        else {\n            throw AirshipErrors.error(\"Invalid content: \\(ymlContent)\")\n        }\n        return try JSONSerialization.data(\n            withJSONObject: jsonContentOfFile,\n            options: .prettyPrinted\n        )\n    }\n\n    // Returns the file contents\n    private func getContentOfFile(filePath: String) throws -> String {\n        return try String(contentsOfFile: filePath, encoding: String.Encoding.utf8)\n    }\n\n\n    @MainActor\n    private func displayScene(_ data: Data) throws {\n        // Extract layout from potentially wrapped payload (in_app_message.message.display.layout)\n        let layoutData = try extractLayoutFromPayload(data)\n        let layout = try JSONDecoder().decode(AirshipLayout.self, from: layoutData)\n\n        let message = InAppMessage(name: \"thomas\", displayContent: .airshipLayout(layout))\n\n        Task { @MainActor in\n            try await message._display()\n        }\n    }\n\n    @MainActor\n    private func displayMessage(_ data: Data) throws {\n        let message: InAppMessage\n\n        // Try to unwrap server-formatted JSON with in_app_message wrapper\n        if let jsonObject = try? JSONSerialization.jsonObject(with: data) as? [String: Any],\n           let inAppMessage = jsonObject[\"in_app_message\"] as? [String: Any],\n           let messageObject = inAppMessage[\"message\"] as? [String: Any] {\n            // Extract just the message object and decode it\n            let messageData = try JSONSerialization.data(withJSONObject: messageObject)\n            message = try JSONDecoder().decode(InAppMessage.self, from: messageData)\n        } else {\n            // Fall back to direct InAppMessage decoding (for legacy format)\n            message = try JSONDecoder().decode(InAppMessage.self, from: data)\n        }\n\n        Task { @MainActor in\n            try await message._display()\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/LayoutsList.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport SwiftUI\n\nstruct LayoutsList: View {\n\n    @ObservedObject\n    private var viewModel: ViewModel\n    \n    @State var errorMessage: String?\n    @State var showError: Bool = false\n\n    init(\n        layoutType: LayoutType,\n        onOpen: @escaping @MainActor (LayoutFile) -> Void\n    ) {\n        viewModel = .init(layoutType: layoutType, onOpen: onOpen)\n    }\n\n    var body: some View {\n        List {\n            ForEach(viewModel.layouts, id: \\.self) { layout in\n                Button(layout.fileName) {\n                    open(layout)\n                }\n            }\n        }\n        .alert(isPresented: $showError) {\n            Alert(\n                title: Text(\"Error\"),\n                message: Text(self.errorMessage ?? \"error\"),\n                dismissButton: .default(Text(\"OK\"))\n            )\n        }\n    }\n    \n    func open(_ layout: LayoutFile, addToRecents: Bool = true) {\n        do {\n            try viewModel.openLayout(layout)\n        } catch {\n            self.showError = true\n            self.errorMessage = \"Failed to open layout \\(error)\"\n        }\n    }\n}\n\n@MainActor\nprivate class ViewModel: ObservableObject {\n    let layoutLoader = LayoutLoader()\n    let layouts: [LayoutFile]\n    let onOpen: @MainActor (LayoutFile) -> Void\n    \n    init(layoutType: LayoutType, onOpen: @escaping @MainActor (LayoutFile) -> Void) {\n        layouts = layoutLoader.load(type: layoutType)\n        self.onOpen = onOpen\n    }\n    \n    func openLayout(_ layout: LayoutFile) throws {\n        try layout.open()\n        onOpen(layout)\n    }\n}\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-left-jurassic-park-bottom.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n        \"body\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#000000\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 16,\n            \"text\" : \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen.\"\n        },\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#ff0000\",\n                \"border_color\" : \"#000000\",\n                \"border_radius\" : 4,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#000000\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 15,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch the button\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 22,\n            \"text\" : \"Boom\"\n        },\n        \"media\" : {\n            \"description\" : \"Image\",\n            \"type\" : \"image\",\n            \"url\" : \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\" : \"media_right\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-left.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n        \"body\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#000000\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 16,\n            \"text\" : \"Big body\"\n        },\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#63aff2\",\n                \"border_color\" : \"#63aff2\",\n                \"border_radius\" : 2,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#ffffff\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 10,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch it\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 22,\n            \"text\" : \"Boom\"\n        },\n        \"media\" : {\n            \"description\" : \"Image\",\n            \"type\" : \"image\",\n            \"url\" : \"https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Picture_icon_BLACK.svg/1200px-Picture_icon_BLACK.svg.png\"\n        },\n        \"template\" : \"media_left\",\n        \"placement\" : \"top\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-right-jurassic-park-bottom.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n        \"body\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#000000\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 16,\n            \"text\" : \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen.\"\n        },\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#ff0000\",\n                \"border_color\" : \"#000000\",\n                \"border_radius\" : 4,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#000000\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 15,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch the button\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"Jurassic Park\"\n            ],\n            \"size\" : 22,\n            \"text\" : \"22 Center\"\n        },\n        \"media\" : {\n            \"description\" : \"Image\",\n            \"type\" : \"image\",\n            \"url\" : \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\" : \"media_right\",\n        \"placement\" : \"bottom\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-right-jurassic-park-text-alignment-1.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n        \"body\" : {\n            \"alignment\" : \"left\",\n            \"color\" : \"#000000\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 16,\n            \"text\" : \"LEFT Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen.\"\n        },\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#ff0000\",\n                \"border_color\" : \"#000000\",\n                \"border_radius\" : 4,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#000000\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 10,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch it\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"left\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"Jurassic Park\"\n            ],\n            \"size\" : 42,\n            \"text\" : \"Left Title\"\n        },\n        \"media\" : {\n            \"description\" : \"Image\",\n            \"type\" : \"image\",\n            \"url\" : \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\" : \"media_right\",\n        \"placement\" : \"top\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-right-jurassic-park-text-alignment-2.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#ff0000\",\n                \"border_color\" : \"#000000\",\n                \"border_radius\" : 4,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#000000\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 10,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch it\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"right\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"Jurassic Park\"\n            ],\n            \"size\" : 42,\n            \"text\" : \"Right Title\"\n        },\n        \"media\" : {\n            \"description\" : \"Image\",\n            \"type\" : \"image\",\n            \"url\" : \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\" : \"media_right\",\n        \"placement\" : \"top\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-right-jurassic-park-top.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n        \"body\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#000000\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"size\" : 16,\n            \"text\" : \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen.\"\n        },\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#ff0000\",\n                \"border_color\" : \"#000000\",\n                \"border_radius\" : 4,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#000000\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 15,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch the button\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"Jurassic Park\"\n            ],\n            \"size\" : 22,\n            \"text\" : \"22 Center\"\n        },\n        \"media\" : {\n            \"description\" : \"Image\",\n            \"type\" : \"image\",\n            \"url\" : \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\" : \"media_right\",\n        \"placement\" : \"top\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/media-right.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"allow_fullscreen_display\" : true,\n        \"background_color\" : \"#ffffff\",\n        \"border_radius\" : 5,\n        \"button_layout\" : \"stacked\",\n        \"buttons\" : [\n            {\n                \"actions\" : {},\n                \"background_color\" : \"#63aff2\",\n                \"border_color\" : \"#63aff2\",\n                \"border_radius\" : 2,\n                \"id\" : \"d17a055c-ed67-4101-b65f-cd28b5904c84\",\n                \"label\" : {\n                    \"color\" : \"#ffffff\",\n                    \"font_family\" : [\n                        \"sans-serif\"\n                    ],\n                    \"size\" : 10,\n                    \"style\" : [\n                        \"bold\"\n                    ],\n                    \"text\" : \"Touch it\"\n                }\n            }\n        ],\n        \"dismiss_button_color\" : \"#000000\",\n        \"heading\" : {\n            \"alignment\" : \"right\",\n            \"color\" : \"#63aff2\",\n            \"size\" : 22,\n            \"text\" : \"22 Right\"\n        },\n        \"template\" : \"media_right\",\n        \"placement\" : \"top\",\n        \"duration\" : 100.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Banner/small-banner.json",
    "content": "{\n    \"display_type\" : \"banner\",\n    \"name\" : \"woot\",\n    \"source\": \"app-defined\",\n    \"display\" : {\n        \"background_color\" : \"#ffffff\",\n        \"border_radius\" : 5,\n        \"dismiss_button_color\" : \"#000000\",\n        \"body\" : {\n            \"alignment\" : \"center\",\n            \"color\" : \"#63aff2\",\n            \"font_family\" : [\n                \"sans-serif\"\n            ],\n            \"text\" : \"Text body, text body, text body, text body, text body\"\n        },\n        \"template\" : \"media_left\",\n        \"placement\" : \"bottom\",\n        \"duration\" : 3.0\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Fullscreen/header-body-media-joined.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"woot\",\n    \"display_type\": \"fullscreen\",\n    \"display\": {\n        \"allow_fullscreen_display\": true,\n        \"background_color\": \"#FF0000\",\n        \"body\": {\n            \"alignment\": \"center\",\n            \"color\": \"#008000\",\n            \"font_family\": [\"sans-serif\"],\n            \"size\": 11,\n            \"text\": \"This is body text that's pretty long... Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.\"\n        },\n        \"border_radius\": 5,\n        \"button_layout\": \"joined\",\n        \"buttons\": [\n            {\n                \"actions\": {},\n                \"background_color\": \"#00FFFF\",\n                \"border_color\": \"#00FFFF\",\n                \"border_radius\": 0,\n                \"id\": \"button-one\",\n                \"label\": {\n                    \"color\": \"#808080\",\n                    \"size\": 11,\n                    \"font_family\": [\"sans-serif\"],\n                    \"style\": [\"bold\"],\n                    \"text\": \"11p sans-sherif bold Dismiss\"\n                }\n            },\n            {\n                \"actions\": {\"behavior\": \"cancel\"},\n                \"background_color\": \"#0000FF\",\n                \"border_color\": \"#0000FF\",\n                \"border_radius\": 10,\n                \"id\": \"button-two\",\n                \"label\": {\n                    \"color\": \"#808080\",\n                    \"size\": 11,\n                    \"style\": [\"italic\", \"underline\"],\n                    \"text\": \"11p italic underline Cancel\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#008000\",\n        \"heading\": {\n            \"alignment\": \"center\",\n            \"color\": \"#A52A2A\",\n            \"font_family\": [\"sans-serif\"],\n            \"size\": 35,\n            \"text\": \"Header body media with wide image stacked buttons centered\"\n        },\n        \"media\": {\n            \"description\": \"Wide space\",\n            \"type\": \"image\",\n            \"url\": \"https://images.pexels.com/photos/207529/pexels-photo-207529.jpeg\"\n        },\n        \"template\": \"header_body_media\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Fullscreen/header-body-separate.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"myMessage\",\n    \"display_type\": \"fullscreen\",\n    \"display\": {\n        \"dismiss_button_color\": \"#00FF00\",\n        \"background_color\": \"#FF0000\",\n        \"button_layout\": \"separate\",\n        \"template\": \"media_header_body\",\n        \"heading\": {\n            \"text\": \"Media header body with stacked buttons no footer long header. Media header body with stacked buttons no footer long header.\",\n            \"color\": \"#A52A2A\",\n            \"size\": 11\n        },\n        \"body\": {\n            \"text\": \"short body text.\",\n            \"color\": \"#00FF00\",\n            \"size\": 11\n        },\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"label\": {\n                    \"text\": \"36p Dismiss\",\n                    \"color\": \"#0FFFFF\",\n                    \"size\": 36\n                },\n                \"background_color\": \"#0000FF\"\n            },\n            {\n                \"id\": \"button-two\",\n                \"label\": {\n                    \"text\": \"24p Cancel\",\n                    \"color\": \"#F0FFFF\",\n                    \"size\": 24\n                },\n                \"background_color\": \"#0000FF\",\n                \"border_radius\": 10,\n                \"behavior\": \"cancel\"\n            }\n        ]\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Fullscreen/media-header-body-stacked.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"myMessage\",\n    \"display_type\": \"fullscreen\",\n    \"display\": {\n        \"dismiss_button_color\": \"#00FF00\",\n        \"background_color\": \"#FF0000\",\n        \"button_layout\": \"joined\",\n        \"template\": \"media_header_body\",\n        \"media\": {\n            \"description\": \"Wide space\",\n            \"type\": \"image\",\n            \"url\": \"https://images.pexels.com/photos/207529/pexels-photo-207529.jpeg\"\n        },\n        \"heading\": {\n            \"text\": \"Media header body with stacked buttons no footer long header. Media header body with stacked buttons no footer long header.\",\n            \"color\": \"#A52A2A\",\n            \"size\": 11\n        },\n        \"body\": {\n            \"text\": \"short body text.\",\n            \"color\": \"#00FF00\",\n            \"size\": 11\n        },\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"label\": {\n                    \"text\": \"48p Dismiss\",\n                    \"color\": \"#0FFFFF\",\n                    \"size\": 48\n                },\n                \"background_color\": \"#0000FF\"\n            },\n            {\n                \"id\": \"button-two\",\n                \"label\": {\n                    \"text\": \"24p Cancel\",\n                    \"color\": \"#F0FFFF\",\n                    \"size\": 24\n                },\n                \"background_color\": \"#0000FF\",\n                \"border_radius\": 10,\n                \"behavior\": \"cancel\"\n            }\n        ]\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/HTML/fullscreen-airship.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"my HTML message\",\n    \"display_type\": \"html\",\n    \"display\": {\n        \"allow_fullscreen_display\" : true,\n        \"aspect_lock\" : true,\n        \"background_color\" : \"#ff00ffff\",\n        \"border_radius\" : 10,\n        \"dismiss_button_color\" : \"#ff00ff00\",\n        \"require_connectivity\" : true,\n        \"url\" : \"https://airship.com\",\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/HTML/sized-airship.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"my HTML message\",\n    \"display_type\": \"html\",\n    \"display\": {\n        \"allow_fullscreen_display\" : false,\n        \"background_color\" : \"#ff00ffff\",\n        \"border_radius\" : 10,\n        \"dismiss_button_color\" : \"#ff00ff00\",\n        \"require_connectivity\" : true,\n        \"url\" : \"https://airship.com\",\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/HTML/sized-too-tall-airship.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"my HTML message\",\n    \"display_type\": \"html\",\n    \"display\": {\n        \"allow_fullscreen_display\" : false,\n        \"aspect_lock\" : true,\n        \"background_color\" : \"#ff00ffff\",\n        \"border_radius\" : 10,\n        \"dismiss_button_color\" : \"#ff00ff00\",\n        \"require_connectivity\" : true,\n        \"url\" : \"https://airship.com\",\n        \"width\" : 300,\n        \"height\" : 900\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/HTML/sized-too-wide-airship.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"my HTML message\",\n    \"display_type\": \"html\",\n    \"extra\": {\"squareview\": \"true\"},\n    \"display\": {\n        \"allow_fullscreen_display\" : false,\n        \"aspect_lock\" : true,\n        \"background_color\" : \"#ff00ffff\",\n        \"border_radius\" : 10,\n        \"dismiss_button_color\" : \"#ff00ff00\",\n        \"require_connectivity\" : true,\n        \"url\" : \"https://airship.com\",\n        \"width\" : 900,\n        \"height\" : 400\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/a11y-test.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"meghan - Email collection - TKO  - copy\",\n    \"display_type\": \"layout\",\n    \"display\": {\n        \"layout\": {\n            \"version\": 1,\n            \"presentation\": {\n                \"type\": \"modal\",\n                \"placement_selectors\": [\n                    {\n                        \"placement\": {\n                            \"ignore_safe_area\": true,\n                            \"device\": {\n                                \"lock_orientation\": \"portrait\"\n                            },\n                            \"size\": {\n                                \"width\": 414,\n                                \"height\": 770\n                            },\n                            \"position\": {\n                                \"horizontal\": \"center\",\n                                \"vertical\": \"center\"\n                            },\n                            \"shade_color\": {\n                                \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                },\n                                \"selectors\": [\n                                    {\n                                        \"platform\": \"ios\",\n                                        \"dark_mode\": false,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"ios\",\n                                        \"dark_mode\": true,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"android\",\n                                        \"dark_mode\": false,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"android\",\n                                        \"dark_mode\": true,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"web\",\n                                        \"dark_mode\": false,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"web\",\n                                        \"dark_mode\": true,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    }\n                                ]\n                            },\n                            \"web\": {\n                                \"ignore_shade\": false\n                            },\n                            \"border\": {\n                                \"radius\": 11\n                            }\n                        },\n                        \"window_size\": \"small\",\n                        \"orientation\": \"portrait\"\n                    },\n                    {\n                        \"placement\": {\n                            \"ignore_safe_area\": true,\n                            \"size\": {\n                                \"width\": 414,\n                                \"height\": 770\n                            },\n                            \"position\": {\n                                \"horizontal\": \"center\",\n                                \"vertical\": \"center\"\n                            },\n                            \"shade_color\": {\n                                \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                },\n                                \"selectors\": [\n                                    {\n                                        \"platform\": \"ios\",\n                                        \"dark_mode\": false,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"ios\",\n                                        \"dark_mode\": true,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"android\",\n                                        \"dark_mode\": false,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"android\",\n                                        \"dark_mode\": true,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"web\",\n                                        \"dark_mode\": false,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    },\n                                    {\n                                        \"platform\": \"web\",\n                                        \"dark_mode\": true,\n                                        \"color\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#868686\",\n                                            \"alpha\": 0.2\n                                        }\n                                    }\n                                ]\n                            },\n                            \"web\": {},\n                            \"border\": {\n                                \"radius\": 11\n                            }\n                        },\n                        \"window_size\": \"large\",\n                        \"orientation\": \"landscape\"\n                    }\n                ],\n                \"android\": {\n                    \"disable_back_button\": false\n                },\n                \"dismiss_on_touch_outside\": false,\n                \"default_placement\": {\n                    \"ignore_safe_area\": false,\n                    \"size\": {\n                        \"width\": \"90%\",\n                        \"height\": \"65%\"\n                    },\n                    \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                    },\n                    \"shade_color\": {\n                        \"default\": {\n                            \"type\": \"hex\",\n                            \"hex\": \"#868686\",\n                            \"alpha\": 0.2\n                        },\n                        \"selectors\": [\n                            {\n                                \"platform\": \"ios\",\n                                \"dark_mode\": false,\n                                \"color\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                }\n                            },\n                            {\n                                \"platform\": \"ios\",\n                                \"dark_mode\": true,\n                                \"color\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                }\n                            },\n                            {\n                                \"platform\": \"android\",\n                                \"dark_mode\": false,\n                                \"color\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                }\n                            },\n                            {\n                                \"platform\": \"android\",\n                                \"dark_mode\": true,\n                                \"color\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                }\n                            },\n                            {\n                                \"platform\": \"web\",\n                                \"dark_mode\": false,\n                                \"color\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                }\n                            },\n                            {\n                                \"platform\": \"web\",\n                                \"dark_mode\": true,\n                                \"color\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#868686\",\n                                    \"alpha\": 0.2\n                                }\n                            }\n                        ]\n                    },\n                    \"web\": {},\n                    \"border\": {\n                        \"radius\": 19\n                    }\n                }\n            },\n            \"view\": {\n                \"type\": \"state_controller\",\n                \"view\": {\n                    \"type\": \"pager_controller\",\n                    \"identifier\": \"108abc02-4d43-4af3-87b1-a383d0fd37c4\",\n                    \"view\": {\n                        \"type\": \"linear_layout\",\n                        \"direction\": \"vertical\",\n                        \"items\": [\n                            {\n                                \"size\": {\n                                    \"width\": \"100%\",\n                                    \"height\": \"100%\"\n                                },\n                                \"view\": {\n                                    \"identifier\": \"ec58bad5-f5d4-465a-99dc-5ff4b7f0691b\",\n                                    \"type\": \"form_controller\",\n                                    \"form_enabled\": [\n                                        \"form_submission\"\n                                    ],\n                                    \"submit\": \"submit_event\",\n                                    \"response_type\": \"user_feedback\",\n                                    \"view\": {\n                                        \"type\": \"container\",\n                                        \"items\": [\n                                            {\n                                                \"position\": {\n                                                    \"horizontal\": \"center\",\n                                                    \"vertical\": \"center\"\n                                                },\n                                                \"size\": {\n                                                    \"width\": \"100%\",\n                                                    \"height\": \"100%\"\n                                                },\n                                                \"view\": {\n                                                    \"type\": \"pager\",\n                                                    \"disable_swipe\": true,\n                                                    \"items\": [\n                                                        {\n                                                            \"identifier\": \"78a41944-e6a5-485b-a97b-7db2f60d3206\",\n                                                            \"type\": \"pager_item\",\n                                                            \"view\": {\n                                                                \"type\": \"container\",\n                                                                \"items\": [\n                                                                    {\n                                                                        \"size\": {\n                                                                            \"width\": \"100%\",\n                                                                            \"height\": \"100%\"\n                                                                        },\n                                                                        \"position\": {\n                                                                            \"horizontal\": \"center\",\n                                                                            \"vertical\": \"center\"\n                                                                        },\n                                                                        \"ignore_safe_area\": false,\n                                                                        \"view\": {\n                                                                            \"type\": \"container\",\n                                                                            \"items\": [\n                                                                                {\n                                                                                    \"margin\": {\n                                                                                        \"bottom\": 0,\n                                                                                        \"top\": 0,\n                                                                                        \"end\": 0,\n                                                                                        \"start\": 0\n                                                                                    },\n                                                                                    \"position\": {\n                                                                                        \"horizontal\": \"center\",\n                                                                                        \"vertical\": \"center\"\n                                                                                    },\n                                                                                    \"size\": {\n                                                                                        \"width\": \"100%\",\n                                                                                        \"height\": \"100%\"\n                                                                                    },\n                                                                                    \"view\": {\n                                                                                        \"type\": \"linear_layout\",\n                                                                                        \"direction\": \"vertical\",\n                                                                                        \"items\": [\n                                                                                            {\n                                                                                                \"identifier\": \"scroll_container\",\n                                                                                                \"size\": {\n                                                                                                    \"width\": \"100%\",\n                                                                                                    \"height\": \"100%\"\n                                                                                                },\n                                                                                                \"view\": {\n                                                                                                    \"type\": \"scroll_layout\",\n                                                                                                    \"direction\": \"vertical\",\n                                                                                                    \"view\": {\n                                                                                                        \"type\": \"linear_layout\",\n                                                                                                        \"direction\": \"vertical\",\n                                                                                                        \"items\": [\n                                                                                                            {\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"70%\",\n                                                                                                                    \"height\": \"auto\"\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"media\",\n                                                                                                                    \"media_fit\": \"center_inside\",\n                                                                                                                    \"url\": \"https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/35d55eb3-e1a3-47c4-be12-46115c9badd5\",\n                                                                                                                    \"media_type\": \"image\",\n                                                                                                                    \"content_description\": \"brand logo for 23 grande\"\n                                                                                                                },\n                                                                                                                \"identifier\": \"55bab89a-122d-41ca-8ead-4d6accfadf99\",\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 30,\n                                                                                                                    \"bottom\": 0,\n                                                                                                                    \"start\": 0,\n                                                                                                                    \"end\": 0\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"identifier\": \"19690655-0720-4586-a132-19d6a2d5c448\",\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"auto\"\n                                                                                                                },\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 48,\n                                                                                                                    \"bottom\": 8,\n                                                                                                                    \"start\": 16,\n                                                                                                                    \"end\": 16\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"label\",\n                                                                                                                    \"text\": \"ENJOY 20% OFF SITE WIDE\",\n                                                                                                                    \"content_description\": \"ENJOY 20% OFF SITE WIDE\",\n                                                                                                                    \"text_appearance\": {\n                                                                                                                        \"font_size\": 31,\n                                                                                                                        \"color\": {\n                                                                                                                            \"default\": {\n                                                                                                                                \"type\": \"hex\",\n                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                \"alpha\": 1\n                                                                                                                            },\n                                                                                                                            \"selectors\": [\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                }\n                                                                                                                            ]\n                                                                                                                        },\n                                                                                                                        \"alignment\": \"center\",\n                                                                                                                        \"styles\": [\n                                                                                                                            \"bold\"\n                                                                                                                        ],\n                                                                                                                        \"font_families\": [\n                                                                                                                            \"sans-serif\"\n                                                                                                                        ]\n                                                                                                                    }\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"identifier\": \"e804d35c-36b2-464e-9419-4f3e4bbfb1c6\",\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"auto\"\n                                                                                                                },\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 8,\n                                                                                                                    \"bottom\": 8,\n                                                                                                                    \"start\": 16,\n                                                                                                                    \"end\": 16\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"label\",\n                                                                                                                    \"text\": \"when you sign up for email \",\n                                                                                                                    \"content_description\": \"when you sign up for email \",\n                                                                                                                    \"text_appearance\": {\n                                                                                                                        \"font_size\": 18,\n                                                                                                                        \"color\": {\n                                                                                                                            \"default\": {\n                                                                                                                                \"type\": \"hex\",\n                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                \"alpha\": 1\n                                                                                                                            },\n                                                                                                                            \"selectors\": [\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                }\n                                                                                                                            ]\n                                                                                                                        },\n                                                                                                                        \"alignment\": \"center\",\n                                                                                                                        \"styles\": [],\n                                                                                                                        \"font_families\": [\n                                                                                                                            \"sans-serif\"\n                                                                                                                        ]\n                                                                                                                    }\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"container\",\n                                                                                                                    \"items\": [\n                                                                                                                        {\n                                                                                                                            \"identifier\": \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58_container\",\n                                                                                                                            \"position\": {\n                                                                                                                                \"horizontal\": \"center\",\n                                                                                                                                \"vertical\": \"center\"\n                                                                                                                            },\n                                                                                                                            \"size\": {\n                                                                                                                                \"width\": \"100%\",\n                                                                                                                                \"height\": \"100%\"\n                                                                                                                            },\n                                                                                                                            \"view\": {\n                                                                                                                                \"type\": \"linear_layout\",\n                                                                                                                                \"direction\": \"vertical\",\n                                                                                                                                \"items\": [\n                                                                                                                                    {\n                                                                                                                                        \"margin\": {\n                                                                                                                                            \"top\": 4,\n                                                                                                                                            \"bottom\": 8\n                                                                                                                                        },\n                                                                                                                                        \"size\": {\n                                                                                                                                            \"width\": \"100%\",\n                                                                                                                                            \"height\": 50\n                                                                                                                                        },\n                                                                                                                                        \"view\": {\n                                                                                                                                            \"border\": {\n                                                                                                                                                \"radius\": 4,\n                                                                                                                                                \"stroke_width\": 1,\n                                                                                                                                                \"stroke_color\": {\n                                                                                                                                                    \"default\": {\n                                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                                        \"alpha\": 1\n                                                                                                                                                    },\n                                                                                                                                                    \"selectors\": [\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"ios\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"ios\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"android\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"android\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"web\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"web\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        }\n                                                                                                                                                    ]\n                                                                                                                                                }\n                                                                                                                                            },\n                                                                                                                                            \"type\": \"text_input\",\n                                                                                                                                            \"text_appearance\": {\n                                                                                                                                                \"alignment\": \"start\",\n                                                                                                                                                \"font_size\": 18,\n                                                                                                                                                \"font_families\": [\n                                                                                                                                                    \"sans-serif\"\n                                                                                                                                                ],\n                                                                                                                                                \"color\": {\n                                                                                                                                                    \"default\": {\n                                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                                        \"alpha\": 1\n                                                                                                                                                    },\n                                                                                                                                                    \"selectors\": [\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"ios\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"ios\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"android\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"android\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"web\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"web\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        }\n                                                                                                                                                    ]\n                                                                                                                                                },\n                                                                                                                                                \"place_holder_color\": {\n                                                                                                                                                    \"default\": {\n                                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                                        \"hex\": \"#BCBDC2\",\n                                                                                                                                                        \"alpha\": 1\n                                                                                                                                                    },\n                                                                                                                                                    \"selectors\": [\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"ios\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#BCBDC2\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"ios\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 0.5\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"android\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#BCBDC2\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"android\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 0.5\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"web\",\n                                                                                                                                                            \"dark_mode\": false,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#BCBDC2\",\n                                                                                                                                                                \"alpha\": 1\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        {\n                                                                                                                                                            \"platform\": \"web\",\n                                                                                                                                                            \"dark_mode\": true,\n                                                                                                                                                            \"color\": {\n                                                                                                                                                                \"type\": \"hex\",\n                                                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                                                \"alpha\": 0.5\n                                                                                                                                                            }\n                                                                                                                                                        }\n                                                                                                                                                    ]\n                                                                                                                                                }\n                                                                                                                                            },\n                                                                                                                                            \"background_color\": {\n                                                                                                                                                \"default\": {\n                                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                                                                                    \"alpha\": 1\n                                                                                                                                                },\n                                                                                                                                                \"selectors\": [\n                                                                                                                                                    {\n                                                                                                                                                        \"platform\": \"ios\",\n                                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                                        \"color\": {\n                                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                                            \"alpha\": 1\n                                                                                                                                                        }\n                                                                                                                                                    },\n                                                                                                                                                    {\n                                                                                                                                                        \"platform\": \"ios\",\n                                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                                        \"color\": {\n                                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                                            \"alpha\": 1\n                                                                                                                                                        }\n                                                                                                                                                    },\n                                                                                                                                                    {\n                                                                                                                                                        \"platform\": \"android\",\n                                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                                        \"color\": {\n                                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                                            \"alpha\": 1\n                                                                                                                                                        }\n                                                                                                                                                    },\n                                                                                                                                                    {\n                                                                                                                                                        \"platform\": \"android\",\n                                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                                        \"color\": {\n                                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                                            \"alpha\": 1\n                                                                                                                                                        }\n                                                                                                                                                    },\n                                                                                                                                                    {\n                                                                                                                                                        \"platform\": \"web\",\n                                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                                        \"color\": {\n                                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                                            \"alpha\": 1\n                                                                                                                                                        }\n                                                                                                                                                    },\n                                                                                                                                                    {\n                                                                                                                                                        \"platform\": \"web\",\n                                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                                        \"color\": {\n                                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                                            \"alpha\": 1\n                                                                                                                                                        }\n                                                                                                                                                    }\n                                                                                                                                                ]\n                                                                                                                                            },\n                                                                                                                                            \"identifier\": \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58\",\n                                                                                                                                            \"input_type\": \"text\",\n                                                                                                                                            \"required\": false,\n                                                                                                                                            \"content_description\": \"\",\n                                                                                                                                            \"place_holder\": \"Email address\",\n                                                                                                                                            \"view_overrides\": {\n                                                                                                                                                \"icon_end\": [\n                                                                                                                                                    {\n                                                                                                                                                        \"when_state_matches\": {\n                                                                                                                                                            \"scope\": [\n                                                                                                                                                                \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58_error\"\n                                                                                                                                                            ],\n                                                                                                                                                            \"value\": {\n                                                                                                                                                                \"equals\": true\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        \"value\": {\n                                                                                                                                                            \"type\": \"floating\",\n                                                                                                                                                            \"icon\": {\n                                                                                                                                                                \"type\": \"icon\",\n                                                                                                                                                                \"icon\": \"exclamationmark_circle_fill\",\n                                                                                                                                                                \"scale\": 1,\n                                                                                                                                                                \"color\": {\n                                                                                                                                                                    \"default\": {\n                                                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                                                        \"hex\": \"#ff0000\",\n                                                                                                                                                                        \"alpha\": 1\n                                                                                                                                                                    }\n                                                                                                                                                                }\n                                                                                                                                                            }\n                                                                                                                                                        }\n                                                                                                                                                    }\n                                                                                                                                                ],\n                                                                                                                                                \"border\": [\n                                                                                                                                                    {\n                                                                                                                                                        \"when_state_matches\": {\n                                                                                                                                                            \"scope\": [\n                                                                                                                                                                \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58_error\"\n                                                                                                                                                            ],\n                                                                                                                                                            \"value\": {\n                                                                                                                                                                \"equals\": true\n                                                                                                                                                            }\n                                                                                                                                                        },\n                                                                                                                                                        \"value\": {\n                                                                                                                                                            \"radius\": 4,\n                                                                                                                                                            \"stroke_width\": 1,\n                                                                                                                                                            \"stroke_color\": {\n                                                                                                                                                                \"default\": {\n                                                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                                                    \"hex\": \"#ff0000\",\n                                                                                                                                                                    \"alpha\": 1\n                                                                                                                                                                }\n                                                                                                                                                            }\n                                                                                                                                                        }\n                                                                                                                                                    }\n                                                                                                                                                ]\n                                                                                                                                            },\n                                                                                                                                            \"on_error\": {\n                                                                                                                                                \"state_actions\": [\n                                                                                                                                                    {\n                                                                                                                                                        \"type\": \"set\",\n                                                                                                                                                        \"key\": \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58_error\",\n                                                                                                                                                        \"value\": true\n                                                                                                                                                    }\n                                                                                                                                                ]\n                                                                                                                                            },\n                                                                                                                                            \"on_edit\": {\n                                                                                                                                                \"state_actions\": [\n                                                                                                                                                    {\n                                                                                                                                                        \"type\": \"set\",\n                                                                                                                                                        \"key\": \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58_error\"\n                                                                                                                                                    }\n                                                                                                                                                ]\n                                                                                                                                            },\n                                                                                                                                            \"on_valid\": {\n                                                                                                                                                \"state_actions\": [\n                                                                                                                                                    {\n                                                                                                                                                        \"type\": \"set\",\n                                                                                                                                                        \"key\": \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58_error\"\n                                                                                                                                                    }\n                                                                                                                                                ]\n                                                                                                                                            }\n                                                                                                                                        }\n                                                                                                                                    }\n                                                                                                                                ]\n                                                                                                                            }\n                                                                                                                        }\n                                                                                                                    ]\n                                                                                                                },\n                                                                                                                \"identifier\": \"0003f1bc-466c-4c9c-9b5d-6e9a9aeabf58\",\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": 50\n                                                                                                                },\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 8,\n                                                                                                                    \"bottom\": 8,\n                                                                                                                    \"start\": 16,\n                                                                                                                    \"end\": 16\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"identifier\": \"1b7e3a7a-84c5-4ea3-8a97-c37e5d922c78\",\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 8,\n                                                                                                                    \"bottom\": 8,\n                                                                                                                    \"start\": 16,\n                                                                                                                    \"end\": 16\n                                                                                                                },\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"auto\"\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"label_button\",\n                                                                                                                    \"identifier\": \"submit_feedback--SIGN UP\",\n                                                                                                                    \"reporting_metadata\": {\n                                                                                                                        \"trigger_link_id\": \"1b7e3a7a-84c5-4ea3-8a97-c37e5d922c78\"\n                                                                                                                    },\n                                                                                                                    \"label\": {\n                                                                                                                        \"type\": \"label\",\n                                                                                                                        \"text\": \"SIGN UP\",\n                                                                                                                        \"content_description\": \"SIGN UP\",\n                                                                                                                        \"text_appearance\": {\n                                                                                                                            \"font_size\": 16,\n                                                                                                                            \"color\": {\n                                                                                                                                \"default\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                },\n                                                                                                                                \"selectors\": [\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"ios\",\n                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"ios\",\n                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"android\",\n                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"android\",\n                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"web\",\n                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"web\",\n                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    }\n                                                                                                                                ]\n                                                                                                                            },\n                                                                                                                            \"alignment\": \"center\",\n                                                                                                                            \"styles\": [\n                                                                                                                                \"bold\"\n                                                                                                                            ],\n                                                                                                                            \"font_families\": [\n                                                                                                                                \"sans-serif\"\n                                                                                                                            ]\n                                                                                                                        }\n                                                                                                                    },\n                                                                                                                    \"actions\": {},\n                                                                                                                    \"enabled\": [\n                                                                                                                        \"form_validation\"\n                                                                                                                    ],\n                                                                                                                    \"button_click\": [\n                                                                                                                        \"form_submit\",\n                                                                                                                        \"dismiss\"\n                                                                                                                    ],\n                                                                                                                    \"background_color\": {\n                                                                                                                        \"default\": {\n                                                                                                                            \"type\": \"hex\",\n                                                                                                                            \"hex\": \"#E9338C\",\n                                                                                                                            \"alpha\": 1\n                                                                                                                        },\n                                                                                                                        \"selectors\": [\n                                                                                                                            {\n                                                                                                                                \"platform\": \"ios\",\n                                                                                                                                \"dark_mode\": false,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"ios\",\n                                                                                                                                \"dark_mode\": true,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"android\",\n                                                                                                                                \"dark_mode\": false,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"android\",\n                                                                                                                                \"dark_mode\": true,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"web\",\n                                                                                                                                \"dark_mode\": false,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"web\",\n                                                                                                                                \"dark_mode\": true,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            }\n                                                                                                                        ]\n                                                                                                                    },\n                                                                                                                    \"border\": {\n                                                                                                                        \"radius\": 3,\n                                                                                                                        \"stroke_width\": 0,\n                                                                                                                        \"stroke_color\": {\n                                                                                                                            \"default\": {\n                                                                                                                                \"type\": \"hex\",\n                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                \"alpha\": 1\n                                                                                                                            },\n                                                                                                                            \"selectors\": [\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                }\n                                                                                                                            ]\n                                                                                                                        }\n                                                                                                                    },\n                                                                                                                    \"event_handlers\": [\n                                                                                                                        {\n                                                                                                                            \"type\": \"tap\",\n                                                                                                                            \"state_actions\": [\n                                                                                                                                {\n                                                                                                                                    \"type\": \"set\",\n                                                                                                                                    \"key\": \"submitted\",\n                                                                                                                                    \"value\": true\n                                                                                                                                }\n                                                                                                                            ]\n                                                                                                                        }\n                                                                                                                    ]\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"identifier\": \"c4f5f28a-0941-426c-81ce-cbb6273ab890\",\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 8,\n                                                                                                                    \"bottom\": 16,\n                                                                                                                    \"start\": 16,\n                                                                                                                    \"end\": 16\n                                                                                                                },\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"auto\"\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"label_button\",\n                                                                                                                    \"identifier\": \"dismiss--NO THANKS\",\n                                                                                                                    \"reporting_metadata\": {\n                                                                                                                        \"trigger_link_id\": \"c4f5f28a-0941-426c-81ce-cbb6273ab890\"\n                                                                                                                    },\n                                                                                                                    \"label\": {\n                                                                                                                        \"type\": \"label\",\n                                                                                                                        \"text\": \"NO THANKS\",\n                                                                                                                        \"content_description\": \"NO THANKS\",\n                                                                                                                        \"text_appearance\": {\n                                                                                                                            \"font_size\": 16,\n                                                                                                                            \"color\": {\n                                                                                                                                \"default\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                },\n                                                                                                                                \"selectors\": [\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"ios\",\n                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"ios\",\n                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"android\",\n                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"android\",\n                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"web\",\n                                                                                                                                        \"dark_mode\": false,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#FFFFFF\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                        \"platform\": \"web\",\n                                                                                                                                        \"dark_mode\": true,\n                                                                                                                                        \"color\": {\n                                                                                                                                            \"type\": \"hex\",\n                                                                                                                                            \"hex\": \"#000000\",\n                                                                                                                                            \"alpha\": 1\n                                                                                                                                        }\n                                                                                                                                    }\n                                                                                                                                ]\n                                                                                                                            },\n                                                                                                                            \"alignment\": \"center\",\n                                                                                                                            \"styles\": [\n                                                                                                                                \"bold\"\n                                                                                                                            ],\n                                                                                                                            \"font_families\": [\n                                                                                                                                \"sans-serif\"\n                                                                                                                            ]\n                                                                                                                        }\n                                                                                                                    },\n                                                                                                                    \"actions\": {},\n                                                                                                                    \"enabled\": [],\n                                                                                                                    \"button_click\": [\n                                                                                                                        \"dismiss\"\n                                                                                                                    ],\n                                                                                                                    \"background_color\": {\n                                                                                                                        \"default\": {\n                                                                                                                            \"type\": \"hex\",\n                                                                                                                            \"hex\": \"#E9338C\",\n                                                                                                                            \"alpha\": 1\n                                                                                                                        },\n                                                                                                                        \"selectors\": [\n                                                                                                                            {\n                                                                                                                                \"platform\": \"ios\",\n                                                                                                                                \"dark_mode\": false,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"ios\",\n                                                                                                                                \"dark_mode\": true,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"android\",\n                                                                                                                                \"dark_mode\": false,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"android\",\n                                                                                                                                \"dark_mode\": true,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"web\",\n                                                                                                                                \"dark_mode\": false,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            },\n                                                                                                                            {\n                                                                                                                                \"platform\": \"web\",\n                                                                                                                                \"dark_mode\": true,\n                                                                                                                                \"color\": {\n                                                                                                                                    \"type\": \"hex\",\n                                                                                                                                    \"hex\": \"#E9338C\",\n                                                                                                                                    \"alpha\": 1\n                                                                                                                                }\n                                                                                                                            }\n                                                                                                                        ]\n                                                                                                                    },\n                                                                                                                    \"border\": {\n                                                                                                                        \"radius\": 3,\n                                                                                                                        \"stroke_width\": 0,\n                                                                                                                        \"stroke_color\": {\n                                                                                                                            \"default\": {\n                                                                                                                                \"type\": \"hex\",\n                                                                                                                                \"hex\": \"#FFFFFF\",\n                                                                                                                                \"alpha\": 1\n                                                                                                                            },\n                                                                                                                            \"selectors\": [\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                }\n                                                                                                                            ]\n                                                                                                                        }\n                                                                                                                    }\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"100%\"\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"linear_layout\",\n                                                                                                                    \"direction\": \"horizontal\",\n                                                                                                                    \"items\": []\n                                                                                                                }\n                                                                                                            }\n                                                                                                        ]\n                                                                                                    }\n                                                                                                }\n                                                                                            }\n                                                                                        ]\n                                                                                    }\n                                                                                }\n                                                                            ]\n                                                                        }\n                                                                    }\n                                                                ],\n                                                                \"background_color\": {\n                                                                    \"default\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#000000\",\n                                                                        \"alpha\": 1\n                                                                    },\n                                                                    \"selectors\": [\n                                                                        {\n                                                                            \"platform\": \"ios\",\n                                                                            \"dark_mode\": false,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#000000\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"ios\",\n                                                                            \"dark_mode\": true,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"android\",\n                                                                            \"dark_mode\": false,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#000000\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"android\",\n                                                                            \"dark_mode\": true,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"web\",\n                                                                            \"dark_mode\": false,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#000000\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"web\",\n                                                                            \"dark_mode\": true,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        }\n                                                                    ]\n                                                                }\n                                                            }\n                                                        }\n                                                    ]\n                                                },\n                                                \"ignore_safe_area\": false\n                                            },\n                                            {\n                                                \"position\": {\n                                                    \"horizontal\": \"end\",\n                                                    \"vertical\": \"top\"\n                                                },\n                                                \"size\": {\n                                                    \"width\": 48,\n                                                    \"height\": 48\n                                                },\n                                                \"view\": {\n                                                    \"type\": \"image_button\",\n                                                    \"image\": {\n                                                        \"scale\": 0.4,\n                                                        \"type\": \"icon\",\n                                                        \"icon\": \"close\",\n                                                        \"color\": {\n                                                            \"default\": {\n                                                                \"type\": \"hex\",\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"alpha\": 1\n                                                            },\n                                                            \"selectors\": [\n                                                                {\n                                                                    \"platform\": \"ios\",\n                                                                    \"dark_mode\": false,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"ios\",\n                                                                    \"dark_mode\": true,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"android\",\n                                                                    \"dark_mode\": false,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"android\",\n                                                                    \"dark_mode\": true,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"web\",\n                                                                    \"dark_mode\": false,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"web\",\n                                                                    \"dark_mode\": true,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                }\n                                                            ]\n                                                        }\n                                                    },\n                                                    \"identifier\": \"dismiss_button\",\n                                                    \"button_click\": [\n                                                        \"dismiss\"\n                                                    ]\n                                                }\n                                            }\n                                        ]\n                                    }\n                                }\n                            }\n                        ]\n                    }\n                }\n            }\n        }\n    },\n    \"audience\": {\n        \"miss_behavior\": \"skip\",\n        \"tags\": {\n            \"and\": [\n                {\n                    \"or\": [\n                        {\n                            \"tag\": \"meghan\"\n                        },\n                        {\n                            \"tag\": \"meghan-test\"\n                        }\n                    ]\n                }\n            ]\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/accessibility-modal.json",
    "content": "{\n    \"in_app_message\": {\n        \"message\": {\n            \"name\": \"Ryan heading test\",\n            \"display_type\": \"layout\",\n            \"display\": {\n                \"layout\": {\n                    \"version\": 1,\n                    \"presentation\": {\n                        \"type\": \"modal\",\n                        \"placement_selectors\": [\n                            {\n                                \"placement\": {\n                                    \"ignore_safe_area\": true,\n                                    \"device\": {\n                                        \"lock_orientation\": \"landscape\"\n                                    },\n                                    \"size\": {\n                                        \"width\": \"70%\",\n                                        \"height\": \"50%\"\n                                    },\n                                    \"position\": {\n                                        \"horizontal\": \"center\",\n                                        \"vertical\": \"center\"\n                                    },\n                                    \"shade_color\": {\n                                        \"default\": {\n                                            \"type\": \"hex\",\n                                            \"hex\": \"#09BE25\",\n                                            \"alpha\": 0.2\n                                        },\n                                        \"selectors\": [\n                                            {\n                                                \"platform\": \"ios\",\n                                                \"dark_mode\": false,\n                                                \"color\": {\n                                                    \"type\": \"hex\",\n                                                    \"hex\": \"#09BE25\",\n                                                    \"alpha\": 0.2\n                                                }\n                                            },\n                                            {\n                                                \"platform\": \"ios\",\n                                                \"dark_mode\": true,\n                                                \"color\": {\n                                                    \"type\": \"hex\",\n                                                    \"hex\": \"#09BE25\",\n                                                    \"alpha\": 0.2\n                                                }\n                                            },\n                                            {\n                                                \"platform\": \"android\",\n                                                \"dark_mode\": false,\n                                                \"color\": {\n                                                    \"type\": \"hex\",\n                                                    \"hex\": \"#09BE25\",\n                                                    \"alpha\": 0.2\n                                                }\n                                            },\n                                            {\n                                                \"platform\": \"android\",\n                                                \"dark_mode\": true,\n                                                \"color\": {\n                                                    \"type\": \"hex\",\n                                                    \"hex\": \"#09BE25\",\n                                                    \"alpha\": 0.2\n                                                }\n                                            },\n                                            {\n                                                \"platform\": \"web\",\n                                                \"dark_mode\": false,\n                                                \"color\": {\n                                                    \"type\": \"hex\",\n                                                    \"hex\": \"#09BE25\",\n                                                    \"alpha\": 0.2\n                                                }\n                                            },\n                                            {\n                                                \"platform\": \"web\",\n                                                \"dark_mode\": true,\n                                                \"color\": {\n                                                    \"type\": \"hex\",\n                                                    \"hex\": \"#09BE25\",\n                                                    \"alpha\": 0.2\n                                                }\n                                            }\n                                        ]\n                                    },\n                                    \"web\": {},\n                                    \"border\": {\n                                        \"radius\": 15\n                                    }\n                                },\n                                \"window_size\": \"small\",\n                                \"orientation\": \"landscape\"\n                            }\n                        ],\n                        \"android\": {\n                            \"disable_back_button\": false\n                        },\n                        \"dismiss_on_touch_outside\": false,\n                        \"default_placement\": {\n                            \"ignore_safe_area\": false,\n                            \"device\": {\n                                \"lock_orientation\": \"portrait\"\n                            },\n                            \"size\": {\n                                \"width\": \"100%\",\n                                \"height\": \"100%\"\n                            },\n                            \"position\": {\n                                \"horizontal\": \"center\",\n                                \"vertical\": \"center\"\n                            },\n                            \"shade_color\": {\n                                \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#000000\",\n                                    \"alpha\": 0.2\n                                }\n                            },\n                            \"web\": {\n                                \"ignore_shade\": true\n                            }\n                        }\n                    },\n                    \"view\": {\n                        \"type\": \"pager_controller\",\n                        \"identifier\": \"a37a1196-ee19-4323-96f3-11b526f6dc80\",\n                        \"view\": {\n                            \"type\": \"linear_layout\",\n                            \"direction\": \"vertical\",\n                            \"items\": [\n                                {\n                                    \"size\": {\n                                        \"width\": \"100%\",\n                                        \"height\": \"100%\"\n                                    },\n                                    \"view\": {\n                                        \"type\": \"container\",\n                                        \"items\": [\n                                            {\n                                                \"position\": {\n                                                    \"horizontal\": \"center\",\n                                                    \"vertical\": \"center\"\n                                                },\n                                                \"size\": {\n                                                    \"width\": \"100%\",\n                                                    \"height\": \"100%\"\n                                                },\n                                                \"view\": {\n                                                    \"type\": \"pager\",\n                                                    \"disable_swipe\": true,\n                                                    \"items\": [\n                                                        {\n                                                            \"identifier\": \"47f4ec52-1f67-4e97-8394-f796a7f1e842\",\n                                                            \"type\": \"pager_item\",\n                                                            \"view\": {\n                                                                \"type\": \"container\",\n                                                                \"items\": [\n                                                                    {\n                                                                        \"size\": {\n                                                                            \"width\": \"100%\",\n                                                                            \"height\": \"100%\"\n                                                                        },\n                                                                        \"position\": {\n                                                                            \"horizontal\": \"center\",\n                                                                            \"vertical\": \"center\"\n                                                                        },\n                                                                        \"ignore_safe_area\": false,\n                                                                        \"view\": {\n                                                                            \"type\": \"container\",\n                                                                            \"items\": [\n                                                                                {\n                                                                                    \"margin\": {\n                                                                                        \"bottom\": 0,\n                                                                                        \"top\": 0,\n                                                                                        \"end\": 0,\n                                                                                        \"start\": 0\n                                                                                    },\n                                                                                    \"position\": {\n                                                                                        \"horizontal\": \"center\",\n                                                                                        \"vertical\": \"center\"\n                                                                                    },\n                                                                                    \"size\": {\n                                                                                        \"width\": \"100%\",\n                                                                                        \"height\": \"100%\"\n                                                                                    },\n                                                                                    \"view\": {\n                                                                                        \"type\": \"linear_layout\",\n                                                                                        \"direction\": \"vertical\",\n                                                                                        \"items\": [\n                                                                                            {\n                                                                                                \"identifier\": \"scroll_container\",\n                                                                                                \"size\": {\n                                                                                                    \"width\": \"100%\",\n                                                                                                    \"height\": \"100%\"\n                                                                                                },\n                                                                                                \"view\": {\n                                                                                                    \"type\": \"scroll_layout\",\n                                                                                                    \"direction\": \"vertical\",\n                                                                                                    \"view\": {\n                                                                                                        \"type\": \"linear_layout\",\n                                                                                                        \"direction\": \"vertical\",\n                                                                                                        \"items\": [\n                                                                                                            {\n                                                                                                                \"identifier\": \"acd7c4a2-9684-46a1-9247-7749472025e6\",\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"auto\"\n                                                                                                                },\n                                                                                                                \"margin\": {\n                                                                                                                    \"top\": 48,\n                                                                                                                    \"bottom\": 8,\n                                                                                                                    \"start\": 16,\n                                                                                                                    \"end\": 16\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"label\",\n                                                                                                                    \"text\": \"Cool beans\",\n                                                                                                                    \"content_description\": \"Cool beans\",\n                                                                                                                    \"text_appearance\": {\n                                                                                                                        \"font_size\": 24,\n                                                                                                                        \"color\": {\n                                                                                                                            \"default\": {\n                                                                                                                                \"type\": \"hex\",\n                                                                                                                                \"hex\": \"#000000\",\n                                                                                                                                \"alpha\": 1\n                                                                                                                            },\n                                                                                                                            \"selectors\": [\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"ios\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"android\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": false,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#000000\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                },\n                                                                                                                                {\n                                                                                                                                    \"platform\": \"web\",\n                                                                                                                                    \"dark_mode\": true,\n                                                                                                                                    \"color\": {\n                                                                                                                                        \"type\": \"hex\",\n                                                                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                                                                        \"alpha\": 1\n                                                                                                                                    }\n                                                                                                                                }\n                                                                                                                            ]\n                                                                                                                        },\n                                                                                                                        \"alignment\": \"start\",\n                                                                                                                        \"styles\": [\n                                                                                                                            \"bold\"\n                                                                                                                        ],\n                                                                                                                        \"font_families\": [\n                                                                                                                            \"more fancy fonts  MORE\"\n                                                                                                                        ]\n                                                                                                                    },\n                                                                                                                    \"accessibility_role\": {\n                                                                                                                        \"type\": \"heading\",\n                                                                                                                        \"level\": 1\n                                                                                                                    }\n                                                                                                                }\n                                                                                                            },\n                                                                                                            {\n                                                                                                                \"size\": {\n                                                                                                                    \"width\": \"100%\",\n                                                                                                                    \"height\": \"100%\"\n                                                                                                                },\n                                                                                                                \"view\": {\n                                                                                                                    \"type\": \"linear_layout\",\n                                                                                                                    \"direction\": \"horizontal\",\n                                                                                                                    \"items\": []\n                                                                                                                }\n                                                                                                            }\n                                                                                                        ]\n                                                                                                    }\n                                                                                                }\n                                                                                            }\n                                                                                        ]\n                                                                                    }\n                                                                                }\n                                                                            ]\n                                                                        }\n                                                                    }\n                                                                ],\n                                                                \"background_color\": {\n                                                                    \"default\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#BFBFB0\",\n                                                                        \"alpha\": 1\n                                                                    },\n                                                                    \"selectors\": [\n                                                                        {\n                                                                            \"platform\": \"ios\",\n                                                                            \"dark_mode\": false,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#BFBFB0\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"ios\",\n                                                                            \"dark_mode\": true,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#BFBFB0\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"android\",\n                                                                            \"dark_mode\": false,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#BFBFB0\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"android\",\n                                                                            \"dark_mode\": true,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#BFBFB0\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"web\",\n                                                                            \"dark_mode\": false,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#BFBFB0\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        },\n                                                                        {\n                                                                            \"platform\": \"web\",\n                                                                            \"dark_mode\": true,\n                                                                            \"color\": {\n                                                                                \"type\": \"hex\",\n                                                                                \"hex\": \"#BFBFB0\",\n                                                                                \"alpha\": 1\n                                                                            }\n                                                                        }\n                                                                    ]\n                                                                }\n                                                            }\n                                                        }\n                                                    ]\n                                                },\n                                                \"ignore_safe_area\": false\n                                            },\n                                            {\n                                                \"position\": {\n                                                    \"horizontal\": \"end\",\n                                                    \"vertical\": \"top\"\n                                                },\n                                                \"size\": {\n                                                    \"width\": 48,\n                                                    \"height\": 48\n                                                },\n                                                \"view\": {\n                                                    \"type\": \"image_button\",\n                                                    \"image\": {\n                                                        \"scale\": 0.4,\n                                                        \"type\": \"icon\",\n                                                        \"icon\": \"close\",\n                                                        \"color\": {\n                                                            \"default\": {\n                                                                \"type\": \"hex\",\n                                                                \"hex\": \"#CD5C5C\",\n                                                                \"alpha\": 1\n                                                            },\n                                                            \"selectors\": [\n                                                                {\n                                                                    \"platform\": \"ios\",\n                                                                    \"dark_mode\": false,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#CD5C5C\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"ios\",\n                                                                    \"dark_mode\": true,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#F08080\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"android\",\n                                                                    \"dark_mode\": false,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#CD5C5C\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"android\",\n                                                                    \"dark_mode\": true,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#F08080\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"web\",\n                                                                    \"dark_mode\": false,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#CD5C5C\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                },\n                                                                {\n                                                                    \"platform\": \"web\",\n                                                                    \"dark_mode\": true,\n                                                                    \"color\": {\n                                                                        \"type\": \"hex\",\n                                                                        \"hex\": \"#F08080\",\n                                                                        \"alpha\": 1\n                                                                    }\n                                                                }\n                                                            ]\n                                                        }\n                                                    },\n                                                    \"identifier\": \"dismiss_button\",\n                                                    \"button_click\": [\n                                                        \"dismiss\"\n                                                    ]\n                                                }\n                                            }\n                                        ]\n                                    }\n                                }\n                            ]\n                        }\n                    }\n                }\n            },\n            \"audience\": {\n                \"miss_behavior\": \"penalize\",\n                \"tags\": {\n                    \"and\": [\n                        {\n                            \"tag\": \"ryan\"\n                        }\n                    ]\n                }\n            }\n        },\n        \"triggers\": [\n            {\n                \"type\": \"active_session\",\n                \"goal\": 1\n            }\n        ],\n        \"edit_grace_period\": 14,\n        \"scopes\": [\n            \"app\"\n        ],\n        \"reporting_context\": {\n            \"content_types\": [\n                \"scene\"\n            ],\n            \"experiment_id\": \"\"\n        },\n        \"message_type\": \"transactional\"\n    }\n}"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/header-body-media-joined.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": true,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"joined\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 55,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\": \"media_header_body\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/header-body-media-stacked.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"stacked\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\": \"media_header_body\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/header-media-body-joined.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"joined\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\": \"header_body_media\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/header-media-body-stacked.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"stacked\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\": \"header_body_media\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/media-header-body-joined.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"joined\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\": \"media_header_body\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/media-header-body-separate-tall-image.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"separate\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://i.pinimg.com/originals/28/49/fa/2849fa05df8d2c93552d388b8babfed7.png\"\n        },\n        \"template\": \"media_header_body\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/media-header-body-separate-wide-image.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"separate\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://upload.wikimedia.org/wikipedia/commons/9/9e/Jiangjunosaurus_junggarensis.png\"\n        },\n        \"template\": \"media_header_body\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/media-header-body-separate.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"Jurassic Park\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"allow_fullscreen_display\": false,\n        \"background_color\": \"#000000\",\n        \"body\": {\n            \"text\": \"Jurassic Park is a 1993 American science-fiction adventure film directed by Steven Spielberg and produced by Kathleen Kennedy and Gerald R. Molen. The first installment in the Jurassic Park franchise, it is based on the 1990 novel of the same name by Michael Crichton and a screenplay written by Crichton and David Koepp. The film is set on the fictional islet of Isla Nublar, located off Central America's Pacific Coast near Costa Rica, where a billionaire philanthropist and a small team of genetic scientists have created a wildlife park of cloned dinosaurs.\",\n            \"color\": \"#FFFFFF\",\n            \"alignment\": \"center\",\n            \"font_family\": [\n                \"sans-serif\"\n            ],\n            \"size\": 16\n        },\n        \"border_radius\": 10,\n        \"button_layout\": \"separate\",\n        \"buttons\": [\n            {\n                \"id\": \"button-one\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"It's a Unix system, I know this.\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_one\"\n                }\n            },\n            {\n                \"id\": \"button-two\",\n                \"background_color\": \"#FF0000\",\n                \"border_color\": \"#FF0000\",\n                \"border_radius\": 8,\n                \"label\": {\n                    \"text\": \"Hold on to your butts!\",\n                    \"color\": \"#000000\",\n                    \"font_family\": [\n                        \"sans-serif\"\n                    ],\n                    \"size\": 10,\n                    \"style\": [\n                        \"bold\"\n                    ]\n                },\n                \"actions\": {\n                    \"add_tag_action\": \"button_two\"\n                }\n            }\n        ],\n        \"dismiss_button_color\": \"#A9A9A9\",\n        \"heading\": {\n            \"text\": \"Jurassic Park\",\n            \"color\": \"#808080\",\n            \"font_family\": [\n                \"Jurassic Park\"\n            ],\n            \"size\": 24,\n            \"alignment\": \"center\"\n        },\n        \"media\": {\n            \"description\": \"Jurassic park\",\n            \"type\": \"image\",\n            \"url\": \"https://logonoid.com/images/jurassic-park-logo.png\"\n        },\n        \"template\": \"media_header_body\"\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/media-header-body-stacked.json",
    "content": "{\n    \"source\": \"app-defined\",\n    \"name\": \"240515_CRM_CRMOther_Salvatore_Ferragamo_Afines - FINAL\",\n    \"display_type\": \"modal\",\n    \"display\": {\n        \"template\": \"media_header_body\",\n        \"background_color\": \"#F7F7F7\",\n        \"dismiss_button_color\": \"#000000\",\n        \"border_radius\": 12,\n        \"heading\": {\n            \"text\": \"UP TO 60 FREE  MINUTES\",\n            \"color\": \"#222222\",\n            \"alignment\": \"center\",\n            \"size\": 20,\n            \"style\": [\"bold\"],\n            \"font_family\": [\"sans-serif\"]\n        },\n        \"body\": {\n            \"text\": \"Share your invite code with your friends and receive up to 60 min of calls!\",\n            \"color\": \"#222222\",\n            \"alignment\": \"center\",\n            \"size\": 16,\n            \"font_family\": [\"sans-serif\"]\n        },\n        \"media\": {\n            \"url\": \"https://dl.asnapieu.com/binary/public/MeyaP1anT0yhEsDDaOrmWw/4ff29f47-6eaf-463e-b66c-f4619976e367\",\n            \"type\": \"image\",\n            \"description\": \"Image\"\n        },\n        \"buttons\": [\n            {\n                \"label\": {\n                    \"text\": \"I SHARE\",\n                    \"style\": [\"bold\"],\n                    \"color\": \"#FFFFFF\",\n                    \"font_family\": [\"sans-serif\"],\n                    \"size\": 16\n                },\n                \"id\": \"808b94dc-5e1e-4e1d-93cf-f74cd8cd4b29\",\n                \"actions\": {\n                    \"add_tags_action\": {\n                        \"device\": [\"click_inapp_referral_parrain\"]\n                    },\n                    \"deep_link_action\": \"libon://referrer\"\n                },\n                \"background_color\": \"#1CB877\",\n                \"border_color\": \"#1CB877\",\n                \"border_radius\": 12,\n                \"behavior\": \"cancel\"\n            }\n        ]\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/swipe-gesture-test.json",
    "content": "{\"source\": \"app-defined\",\"name\": \"Armando Test\", \"display_type\": \"layout\", \"display\": {\"layout\": {\"version\": 1, \"presentation\": {\"type\": \"modal\", \"placement_selectors\": [{\"placement\": {\"ignore_safe_area\": true, \"device\": {\"lock_orientation\": \"portrait\"}, \"size\": {\"width\": \"70%\", \"height\": \"60%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#868686\", \"alpha\": 0.2}}]}, \"web\": {\"ignore_shade\": false}, \"border\": {\"radius\": 15}}, \"window_size\": \"large\", \"orientation\": \"portrait\"}], \"android\": {\"disable_back_button\": false}, \"dismiss_on_touch_outside\": false, \"default_placement\": {\"ignore_safe_area\": false, \"device\": {\"lock_orientation\": \"portrait\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"top\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 0.2}}, \"web\": {\"ignore_shade\": true}}}, \"view\": {\"type\": \"pager_controller\", \"identifier\": \"fe2b618e-7762-49a7-9a18-e6ad84baf104\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"container\", \"items\": [{\"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"pager\", \"disable_swipe\": true, \"items\": [{\"identifier\": \"2adbe45d-6a6e-463d-93dc-740c5039e326\", \"type\": \"pager_item\", \"view\": {\"type\": \"container\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"ignore_safe_area\": false, \"view\": {\"type\": \"container\", \"items\": [{\"margin\": {\"bottom\": 0, \"top\": 0, \"end\": 0, \"start\": 0}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"scroll_container\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"scroll_layout\", \"direction\": \"vertical\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"media\", \"media_fit\": \"center_inside\", \"url\": \"https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/bc0a06d5-e9fa-4054-8ff9-7994ce104f54\", \"media_type\": \"image\"}, \"identifier\": \"4ff3f18a-6940-4eff-b233-9a9fe6a1dc24\", \"margin\": {\"top\": 0, \"bottom\": 0, \"start\": 0, \"end\": 0}}, {\"identifier\": \"dee44a8f-d164-434a-9083-c1363a9008ae\", \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"margin\": {\"top\": 8, \"bottom\": 8, \"start\": 16, \"end\": 16}, \"view\": {\"type\": \"label\", \"text\": \"This is a test for swipe\", \"content_description\": \"This is a test for swipe\", \"text_appearance\": {\"font_size\": 18, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"start\", \"styles\": [], \"font_families\": [\"sans-serif\"]}}}, {\"identifier\": \"87ec2574-9b74-4d28-b059-a8b64573fb8b\", \"margin\": {\"top\": 8, \"bottom\": 8, \"start\": 16, \"end\": 16}, \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"label_button\", \"identifier\": \"next--Next Screen\", \"reporting_metadata\": {\"trigger_link_id\": \"87ec2574-9b74-4d28-b059-a8b64573fb8b\"}, \"label\": {\"type\": \"label\", \"text\": \"Next Screen\", \"content_description\": \"Next Screen\", \"text_appearance\": {\"font_size\": 16, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"center\", \"styles\": [\"bold\"], \"font_families\": [\"sans-serif\"]}}, \"actions\": {}, \"enabled\": [\"pager_next\"], \"button_click\": [\"pager_next\"], \"background_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}]}, \"border\": {\"radius\": 3, \"stroke_width\": 0, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#63AFF1\", \"alpha\": 1}}]}}}}, {\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": []}}]}}}]}}]}}], \"background_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 0.5}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 0.5}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 0.5}}]}}}, {\"identifier\": \"41f29d1c-aa77-4d74-8f36-3b9251acf1b5\", \"type\": \"pager_item\", \"view\": {\"type\": \"container\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"ignore_safe_area\": false, \"view\": {\"type\": \"container\", \"items\": [{\"margin\": {\"bottom\": 0, \"top\": 0, \"end\": 0, \"start\": 0}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"scroll_container\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"scroll_layout\", \"direction\": \"vertical\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"media\", \"media_fit\": \"center_inside\", \"url\": \"https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/38b6c4c7-5d0c-4cd6-b54b-1f5536a1d6c2\", \"media_type\": \"image\"}, \"identifier\": \"471cbd05-ee58-4089-9cda-3c486cc9c358\", \"margin\": {\"top\": 0, \"bottom\": 0, \"start\": 0, \"end\": 0}}, {\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": []}}]}}}]}}]}}], \"background_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 0.5}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 0.5}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 0.5}}]}}}]}, \"ignore_safe_area\": false}, {\"position\": {\"horizontal\": \"end\", \"vertical\": \"top\"}, \"size\": {\"width\": 48, \"height\": 48}, \"view\": {\"type\": \"image_button\", \"image\": {\"scale\": 0.4, \"type\": \"icon\", \"icon\": \"close\", \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}}, \"identifier\": \"dismiss_button\", \"button_click\": [\"dismiss\"]}}, {\"margin\": {\"top\": 4, \"bottom\": 4, \"end\": 0, \"start\": 0}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"bottom\"}, \"size\": {\"height\": 7, \"width\": \"100%\"}, \"view\": {\"type\": \"pager_indicator\", \"spacing\": 6, \"bindings\": {\"selected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#7B7C84\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#7B7C84\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#7B7C84\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#7B7C84\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}}]}, \"unselected\": {\"shapes\": [{\"type\": \"ellipse\", \"aspect_ratio\": 1, \"scale\": 1, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#BCBDC2\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}}]}}, \"automated_accessibility_actions\": [{\"type\": \"announce\"}]}}]}}]}}}}, \"audience\": {\"miss_behavior\": \"skip\", \"advanced_audience\": {\"and\": [{\"and\": [{\"channel\": \"ed28f1bc-e335-469f-946d-5fae5908b087\"}]}]}}}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Messages/Modal/youtube-video-modal.json",
    "content": "{\"name\": \"Test Anusha YT\", \"source\": \"app-defined\", \"display_type\": \"layout\", \"display\": {\"layout\": {\"version\": 1, \"presentation\": {\"type\": \"modal\", \"placement_selectors\": [], \"android\": {\"disable_back_button\": false}, \"dismiss_on_touch_outside\": false, \"default_placement\": {\"ignore_safe_area\": false, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"top\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 0.2}}, \"web\": {\"ignore_shade\": true}}}, \"view\": {\"type\": \"pager_controller\", \"identifier\": \"7f930f75-10d6-4582-9372-c749c6bf64ec\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"d6da3d8e-616b-4599-bd0e-8b29ff1e0e9e_pager_container_item\", \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"pager\", \"disable_swipe\": true, \"items\": [{\"identifier\": \"79e91ce6-0c15-4143-8aed-65f5886e92d7\", \"type\": \"pager_item\", \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"6a7b3a4a-249d-419e-a8a6-5df4737c2445_main_view_container_item\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"ignore_safe_area\": false, \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"d1632115-dd22-47b4-9fac-07349058101a_container_item\", \"margin\": {\"bottom\": 0, \"top\": 0, \"end\": 0, \"start\": 0}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"scroll_container\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"scroll_layout\", \"direction\": \"vertical\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"4445e841-8fa0-41d7-b747-3edd88114969\", \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"margin\": {\"top\": 48, \"bottom\": 8, \"start\": 16, \"end\": 16}, \"view\": {\"type\": \"label\", \"text\": \"Video below\", \"content_description\": \"Video below\", \"text_appearance\": {\"font_size\": 24, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}, \"alignment\": \"start\", \"styles\": [\"bold\"], \"font_families\": [\"sans-serif\"]}}}, {\"identifier\": \"39bb8d66-2ace-4286-ae39-a6aeccee5129\", \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"media\", \"media_fit\": \"center_inside\", \"url\": \"https://www.youtube.com/embed/l9bXcLw2u44/?autoplay=0&controls=1&loop=0&mute=1\", \"media_type\": \"youtube\", \"video\": {\"aspect_ratio\": 1.7777777777777777, \"show_controls\": true, \"autoplay\": false, \"muted\": true, \"loop\": false}}, \"margin\": {\"top\": 0, \"bottom\": 0, \"start\": 0, \"end\": 0}}, {\"identifier\": \"3ebba369-d80b-4e46-80f2-403b14ea1c43_linear_layout_item\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": []}}]}}}]}}]}}], \"background_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}, \"state_actions\": [{\"type\": \"set\", \"key\": \"79e91ce6-0c15-4143-8aed-65f5886e92d7_next\"}]}]}, \"ignore_safe_area\": false}, {\"position\": {\"horizontal\": \"end\", \"vertical\": \"top\"}, \"size\": {\"width\": 48, \"height\": 48}, \"view\": {\"type\": \"image_button\", \"image\": {\"scale\": 0.4, \"type\": \"icon\", \"icon\": \"close\", \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}}, \"identifier\": \"dismiss_button\", \"button_click\": [\"dismiss\"], \"localized_content_description\": {\"ref\": \"ua_dismiss\", \"fallback\": \"Dismiss\"}}}]}}]}}}}, \"audience\": {\"miss_behavior\": \"penalize\"}}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Banner/banner-bottom.yml",
    "content": "version: 1\npresentation:\n  type: banner\n  default_placement:\n    position: bottom\n    ignore_safe_area: true  # Allow banner to extend beyond safe areas\n    size:\n      width: 100%\n      height: auto\n    margin:\n      top: 32\n      bottom: 0\n      start: 40\n      end: 16\n    corner_radius:\n      bottom_left: 24\n    nub:\n      size:\n        width: 36\n        height: 10\n      margin:\n        top: 8\n      color:\n        default:\n          hex: \"#00FF00\"\n          alpha: 0.09\n    border:\n      border_radius: 24\n      stroke_width: 10\n      stroke_color:\n        default:\n          hex: \"#00FF00\"\n          alpha: 1\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n  duration: 8\n  # Add placement selectors for top and bottom positions\n#  placement_selectors:\n#    - placement:\n#        position: top\n#        ignore_safe_area: true\n#        size:\n#          width: 100%\n#          height: auto\n#        margin:\n#          top: 0  # Extends past top\n#          bottom: 16\n#          start: 16\n#          end: 16\n#        border:\n#          radius: 12\n#          stroke_width: 1\n#          stroke_color:\n#            default:\n#              hex: \"#E2E8F0\"\n#              alpha: 1\n#        background_color:\n#          default:\n#            hex: \"#FFFFFF\"\n#            alpha: 1\n#    - placement:\n#        position: bottom\n#        ignore_safe_area: true\n#        size:\n#          width: 100%\n#          height: auto\n#        margin:\n#          top: 16\n#          bottom: 0  # Extends past bottom\n#          start: 16\n#          end: 16\n#        border:\n#          radius: 12\n#          stroke_width: 1\n#          stroke_color:\n#            default:\n#              hex: \"#E2E8F0\"\n#              alpha: 1\n#        background_color:\n#          default:\n#            hex: \"#FFFFFF\"\n#            alpha: 1\nview:\n  type: container\n  items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: auto\n      view:\n        type: linear_layout\n        direction: horizontal\n        items:\n          # Main content\n          - size:\n              width: 100%\n              height: auto\n            margin:\n              top: 16\n              bottom: 16\n              start: 16\n              end: 16\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 8\n                  view:\n                    type: label\n                    text: \"🎉 Special Offer Inside!\"\n                    text_appearance:\n                      font_size: 18\n                      color:\n                        default:\n                          hex: \"#1A237E\"\n                          alpha: 1\n                      styles:\n                        - bold\n                \n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 12\n                  view:\n                    type: label\n                    text: \"🎁 Check out our amazing deal just for you!\"\n                    text_appearance:\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#4A5568\"\n                          alpha: 1\n                \n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: label_button\n                    identifier: show_offer_button\n                    background_color:\n                      default:\n                        hex: \"#4C1D95\"\n                        alpha: 1\n                    border:\n                      radius: 8\n                    label:\n                      type: label\n                      text: \"✨ Show Me!\"\n                      text_appearance:\n                        font_size: 14\n                        alignment: center\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                    button_click:\n                      - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Banner/banner-safe-area-bottom.yml",
    "content": "version: 1\npresentation:\n  type: banner\n  default_placement:\n    position: bottom\n    size:\n      width: 100%\n      height: auto\n    margin:\n      top: 0\n      bottom: 0\n      start: 16\n      end: 16\n    nub:\n      size:\n        width: 36\n        height: 4\n      margin:\n        top: 8\n      color:\n        default:\n          hex: \"#000000\"\n          alpha: 0.42\n    border:\n      radius: 12\n      stroke_width: 1\n      stroke_color:\n        default:\n          hex: \"#E2E8F0\"\n          alpha: 1\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n  duration: 8\nview:\n  type: container\n  items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: auto\n      view:\n        type: linear_layout\n        direction: horizontal\n        items:\n          # Main content\n          - size:\n              width: 100%\n              height: auto\n            margin:\n              top: 16\n              bottom: 16\n              start: 16\n              end: 16\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 8\n                  view:\n                    type: label\n                    text: \"🎉 Special Offer Inside!\"\n                    text_appearance:\n                      font_size: 18\n                      color:\n                        default:\n                          hex: \"#1A237E\"\n                          alpha: 1\n                      styles:\n                        - bold\n                \n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 12\n                  view:\n                    type: label\n                    text: \"🎁 Check out our amazing deal just for you!\"\n                    text_appearance:\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#4A5568\"\n                          alpha: 1\n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: label_button\n                    identifier: show_offer_button\n                    background_color:\n                      default:\n                        hex: \"#4C1D95\"\n                        alpha: 1\n                    border:\n                      radius: 8\n                    label:\n                      type: label\n                      text: \"✨ Show Me!\"\n                      text_appearance:\n                        font_size: 14\n                        alignment: center\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                    button_click:\n                      - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Banner/banner-safe-area-top.yml",
    "content": "version: 1\npresentation:\n  type: banner\n  default_placement:\n    position: top\n    size:\n      width: 100%\n      height: auto\n    margin:\n      top: 0\n      bottom: 0\n      start: 16\n      end: 16\n    nub:\n      size:\n        width: 36\n        height: 4\n      margin:\n        bottom: 8\n      color:\n        default:\n          hex: \"#000000\"\n          alpha: 0.42\n    border:\n      radius: 12\n      stroke_width: 1\n      stroke_color:\n        default:\n          hex: \"#E2E8F0\"\n          alpha: 1\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n  duration: 8\nview:\n  type: container\n  items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: auto\n      view:\n        type: linear_layout\n        direction: horizontal\n        items:\n          # Main content\n          - size:\n              width: 100%\n              height: auto\n            margin:\n              top: 16\n              bottom: 16\n              start: 16\n              end: 16\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 8\n                  view:\n                    type: label\n                    text: \"🎉 Special Offer Inside!\"\n                    text_appearance:\n                      font_size: 18\n                      color:\n                        default:\n                          hex: \"#1A237E\"\n                          alpha: 1\n                      styles:\n                        - bold\n                \n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 12\n                  view:\n                    type: label\n                    text: \"🎁 Check out our amazing deal just for you!\"\n                    text_appearance:\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#4A5568\"\n                          alpha: 1\n                \n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: label_button\n                    identifier: show_offer_button\n                    background_color:\n                      default:\n                        hex: \"#4C1D95\"\n                        alpha: 1\n                    border:\n                      radius: 8\n                    label:\n                      type: label\n                      text: \"✨ Show Me!\"\n                      text_appearance:\n                        font_size: 14\n                        alignment: center\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                    button_click:\n                      - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Banner/banner-top-old.yml",
    "content": "---\nversion: 1\npresentation:\n  type: banner\n  default_placement:\n    size:\n      width: 80%\n      height: auto\n    position: top\n    ignore_safe_area: true\n    nub:\n      size:\n        width: 36\n        height: 4\n      margin:\n        bottom: 8\n      color:\n        default:\n          hex: \"#000000\"\n          alpha: 0.42\n    background_color:\n      default:\n        hex: \"#FF0000\"\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\n    background_color:\n      default:\n        hex: \"#FFFF00\"\n    border:\n      stroke_color:\n        default:\n          hex: \"#00FF00\"\n      stroke_width: 3\n      radius: 15\nview:\n  type: container\n  items:\n  - position:\n      horizontal: end\n      vertical: top\n    size:\n      height: auto\n      width: auto\n    margin:\n      top: 50\n      bottom: 50\n      start: 50\n      end: 50\n    view:\n      type: label\n      text: Sup Buddy\n      text_appearance:\n        font_size: 14\n        color:\n          default:\n            hex: \"#333333\"\n        alignment: start\n        styles:\n          - italic\n        font_families:\n          - permanent_marker\n          - casual\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: auto\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: auto\n        weight: 1\n        view:\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            margin:\n              top: 75\n              bottom: 50\n              start: 50\n              end: 50\n            view:\n              type: label\n              text: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do\n                eiusmod tempor incididunt ut labore et dolore magna aliqua. In arcu\n                cursus euismod quis viverra nibh. Lobortis feugiat vivamus at augue\n                eget arcu dictum. Imperdiet dui accumsan sit amet nulla. Ultrices\n                neque ornare aenean euismod elementum. Tincidunt id aliquet risus\n                feugiat in ante metus dictum.\n              text_appearance:\n                font_size: 14\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: start\n                styles:\n                  - italic\n                font_families:\n                  - permanent_marker\n                  - casual\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 50\n          bottom: 50\n          start: 50\n          end: 50\n        view:\n          type: label_button\n          identifier: BUTTON\n          background_color:\n            default:\n              hex: \"#FF0000\"\n          button_click:\n            - dismiss\n          label:\n            type: label\n            text_appearance:\n              font_size: 24\n              alignment: center\n              color:\n                default:\n                  hex: \"#ffffff\"\n              styles:\n                - bold\n              font_families:\n                - casual\n            text: Close\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Banner/banner-top.yml",
    "content": "version: 1\npresentation:\n  type: banner\n  default_placement:\n    position: top\n    ignore_safe_area: true  # Allow banner to extend beyond safe areas\n    size:\n      width: 100%\n      height: auto\n    margin:\n      # Remove top margin to allow extension beyond top\n      top: 0\n      bottom: 16\n      start: 16\n      end: 16\n    nub:\n      size:\n        width: 36\n        height: 4\n      margin:\n        bottom: 8\n      color:\n        default:\n          hex: \"#000000\"\n          alpha: 0.42\n    border:\n      radius: 12\n      stroke_width: 10\n      stroke_color:\n        default:\n          hex: \"#E2E8F0\"\n          alpha: 1\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n  duration: 8\n  # Add placement selectors for top and bottom positions\n#  placement_selectors:\n#    - placement:\n#        position: top\n#        ignore_safe_area: true\n#        size:\n#          width: 100%\n#          height: auto\n#        margin:\n#          top: 0  # Extends past top\n#          bottom: 16\n#          start: 16\n#          end: 16\n#        border:\n#          radius: 12\n#          stroke_width: 1\n#          stroke_color:\n#            default:\n#              hex: \"#E2E8F0\"\n#              alpha: 1\n#        background_color:\n#          default:\n#            hex: \"#FFFFFF\"\n#            alpha: 1\n#    - placement:\n#        position: bottom\n#        ignore_safe_area: true\n#        size:\n#          width: 100%\n#          height: auto\n#        margin:\n#          top: 16\n#          bottom: 0  # Extends past bottom\n#          start: 16\n#          end: 16\n#        border:\n#          radius: 12\n#          stroke_width: 1\n#          stroke_color:\n#            default:\n#              hex: \"#E2E8F0\"\n#              alpha: 1\n#        background_color:\n#          default:\n#            hex: \"#FFFFFF\"\n#            alpha: 1\n\nview:\n  type: container\n  items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: auto\n      view:\n        type: linear_layout\n        direction: horizontal\n        items:\n          # Main content\n          - size:\n              width: 100%\n              height: auto\n            margin:\n              top: 16\n              bottom: 16\n              start: 16\n              end: 16\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 8\n                  view:\n                    type: label\n                    text: \"🎉 Special Offer Inside!\"\n                    text_appearance:\n                      font_size: 18\n                      color:\n                        default:\n                          hex: \"#1A237E\"\n                          alpha: 1\n                      styles:\n                        - bold\n                \n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    bottom: 12\n                  view:\n                    type: label\n                    text: \"🎁 Check out our amazing deal just for you!\"\n                    text_appearance:\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#4A5568\"\n                          alpha: 1\n                \n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: label_button\n                    identifier: show_offer_button\n                    background_color:\n                      default:\n                        hex: \"#4C1D95\"\n                        alpha: 1\n                    border:\n                      radius: 8\n                    label:\n                      type: label\n                      text: \"✨ Show Me!\"\n                      text_appearance:\n                        font_size: 14\n                        alignment: center\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                    button_click:\n                      - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/100pct x 100pct.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  embedded_id: \"100pct x 100pct\"\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    margin:\n        top: 16\n        bottom: 16\n        start: 16\n        end: 16\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0D49\"\n        alpha: 1\n    stroke_width: 2\n  background_color:\n    selectors:\n    - platform: ios\n      dark_mode: false\n      color:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    - platform: ios\n      dark_mode: true\n      color:\n        hex: \"#000000\"\n        alpha: 1\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: \"100% x 100%.\"\n      text_appearance:\n        font_size: 14\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#FFFFFF\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/100pct x auto.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  embedded_id: \"100pct x auto\"\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    margin:\n        top: 16\n        bottom: 16\n        start: 16\n        end: 16\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0D49\"\n        alpha: 1\n    stroke_width: 2\n  background_color:\n    selectors:\n    - platform: ios\n      dark_mode: false\n      color:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    - platform: ios\n      dark_mode: true\n      color:\n        hex: \"#000000\"\n        alpha: 1\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: \"100% x auto\"\n      text_appearance:\n        font_size: 14\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#FFFFFF\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/50pct x 50pct.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  embedded_id: \"50pct x 50pct\"\n  default_placement:\n    size:\n      width: 50%\n      height: 50%\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0D49\"\n        alpha: 1\n    stroke_width: 2\n  background_color:\n    selectors:\n    - platform: ios\n      dark_mode: false\n      color:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    - platform: ios\n      dark_mode: true\n      color:\n        hex: \"#000000\"\n        alpha: 1\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: \"50% x 50%.\"\n      text_appearance:\n        font_size: 14\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#FFFFFF\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/75pct x 200pt.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  embedded_id: \"75pct x 200pt\"\n  default_placement:\n    size:\n      width: 75%\n      height: 200\n    margin:\n        top: 16\n        bottom: 16\n        start: 16\n        end: 16\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0D49\"\n        alpha: 1\n    stroke_width: 2\n  background_color:\n    selectors:\n    - platform: ios\n      dark_mode: false\n      color:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    - platform: ios\n      dark_mode: true\n      color:\n        hex: \"#000000\"\n        alpha: 1\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: \"75% x 200\"\n      text_appearance:\n        font_size: 14\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#FFFFFF\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/auto x 200.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  embedded_id: \"auto x 200\"\n  default_placement:\n    size:\n      width: auto\n      height: 200\n    margin:\n        top: 16\n        bottom: 16\n        start: 16\n        end: 16\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0D49\"\n        alpha: 1\n    stroke_width: 2\n  background_color:\n    selectors:\n    - platform: ios\n      dark_mode: false\n      color:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    - platform: ios\n      dark_mode: true\n      color:\n        hex: \"#000000\"\n        alpha: 1\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: \"auto x 200\"\n      text_appearance:\n        font_size: 14\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#FFFFFF\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/gif_and_vid.yml",
    "content": "version: 1\nview:\n  type: pager_controller\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - view:\n        items:\n        - size:\n            width: 100%\n            height: 100%\n          view:\n            items:\n            - type: pager_item\n              view:\n                background_color:\n                  default:\n                    type: hex\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                  selectors:\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      hex: \"#000000\"\n                      alpha: 1\n                      type: hex\n                  - color:\n                      hex: \"#000000\"\n                      alpha: 1\n                      type: hex\n                    dark_mode: true\n                    platform: android\n                type: container\n                items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  view:\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        items:\n                        - identifier: scroll_container\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: scroll_layout\n                            direction: vertical\n                            view:\n                              type: linear_layout\n                              items:\n                              - margin:\n                                  start: 0\n                                  end: 0\n                                  top: 0\n                                  bottom: 0\n                                size:\n                                  width: 100%\n                                  height: auto\n                                view:\n                                  media_type: image\n                                  url: https://media3.giphy.com/media/tBvPFCFQHSpEI/giphy.gif\n                                  media_fit: center_inside\n                                  type: media\n                              - margin:\n                                  bottom: 0\n                                  end: 0\n                                  top: 0\n                                  start: 0\n                                view:\n                                  media_fit: center_inside\n                                  type: media\n                                  video:\n                                    muted: true\n                                    aspect_ratio: 1.7777777777777777\n                                    autoplay: false\n                                    show_controls: true\n                                    loop: false\n                                  url: https://www.youtube.com/embed/a3ICNMQW7Ok/?autoplay=0&controls=1&loop=0&mute=1\n                                  media_type: youtube\n                                size:\n                                  width: 100%\n                                  height: auto\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  items: []\n                                  direction: horizontal\n                              direction: vertical\n                        type: linear_layout\n                        background_color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            type: hex\n                            alpha: 1\n                          selectors:\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              alpha: 1\n                              type: hex\n                              hex: \"#000000\"\n                          - color:\n                              type: hex\n                              alpha: 1\n                              hex: \"#000000\"\n                            dark_mode: true\n                            platform: android\n                        direction: vertical\n                      margin:\n                        bottom: 16\n                      position:\n                        horizontal: center\n                        vertical: center\n                    type: container\n                  size:\n                    width: 100%\n                    height: 100%\n              identifier: 294acc65-f80c-4663-9840-0d48bf1972b8\n            type: pager\n            disable_swipe: true\n          ignore_safe_area: false\n          position:\n            horizontal: center\n            vertical: center\n        - size:\n            width: 48\n            height: 48\n          view:\n            identifier: dismiss_button\n            button_click:\n            - dismiss\n            type: image_button\n            image:\n              scale: 0.4\n              icon: close\n              type: icon\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 1\n                  type: hex\n                selectors:\n                - color:\n                    alpha: 1\n                    type: hex\n                    hex: \"#FFFFFF\"\n                  platform: ios\n                  dark_mode: true\n                - platform: android\n                  dark_mode: true\n                  color:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n          position:\n            horizontal: end\n            vertical: top\n        type: container\n      size:\n        width: 100%\n        height: 100%\n  identifier: e1fe3c1a-bcf9-4159-a268-c00239433c91\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    size:\n      min_width: 100%\n      min_height: 100%\n      max_height: 100%\n      height: 100%\n      width: 100%\n      max_width: 100%\n    device:\n      lock_orientation: portrait\n    shade_color:\n      default:\n        type: hex\n        alpha: 0.2\n        hex: \"#000000\"\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: top\n  type: embedded\n  embedded_id: \"gif_and_vid\"\n  dismiss_on_touch_outside: false\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/home_image.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  placement_selectors: []\n  default_placement:\n    size:\n      width: 90%\n      height: 90%\n    border:\n      radius: 0\n  embedded_id: home_image\nview:\n  type: pager_controller\n  identifier: 3e3a8863-a365-46c3-aa42-4d10e4bf1b23\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - size:\n        width: 100%\n        height: 100%\n      view:\n        type: container\n        items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          view:\n            type: pager\n            disable_swipe: true\n            items:\n            - identifier: 5caeef0f-2c44-4dd5-8021-6ee1f0535a5c\n              type: pager_item\n              view:\n                type: container\n                items:\n                - size:\n                    width: 100%\n                    height: 100%\n                  position:\n                    horizontal: center\n                    vertical: center\n                  ignore_safe_area: true\n                  view:\n                    type: container\n                    items:\n                    - margin:\n                        bottom: 0\n                        top: 0\n                        end: 0\n                        start: 0\n                      position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - identifier: layout_container\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            border:\n                              stroke_width: 8\n                              stroke_color:\n                                default:\n                                  hex: \"#ff0000\"\n                                  alpha: 1\n                            items:\n                            - size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: media\n                                media_fit: center_crop\n                                url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/5cd5c3b4-9cbf-488c-af32-79145a349fc9\n                                media_type: image\n                              identifier: ed77bcab-4861-437f-a412-7255abb0340a\n                              margin:\n                                top: 0\n                                bottom: 0\n                                start: 0\n                                end: 0\n                background_color:\n                  default:\n                    type: hex\n                    hex: \"#BCBDC2\"\n                    alpha: 1\n                  selectors:\n                  - platform: ios\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#BCBDC2\"\n                      alpha: 1\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 0.5\n                  - platform: android\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#BCBDC2\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 0.5\n                  - platform: web\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#BCBDC2\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 0.5\n          ignore_safe_area: true\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            image:\n              scale: 0.4\n              type: icon\n              icon: close\n              color:\n                default:\n                  type: hex\n                  hex: \"#000000\"\n                  alpha: 1\n                selectors:\n                - platform: ios\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                - platform: ios\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                - platform: android\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                - platform: android\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                - platform: web\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                - platform: web\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n            identifier: dismiss_button\n            button_click:\n            - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/home_rating.yml",
    "content": "---\npresentation:\n  type: embedded\n  embedded_id: \"home_rating\"\n  default_placement:\n    size:\n      height: 400\n      width: 100%\nversion: 1\nview:\n  type: linear_layout\n  direction: vertical\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0D49\"\n        alpha: 1\n    stroke_width: 2\n  background_color:\n    default:\n      alpha: 1\n      hex: \"#FFFFFF\"\n      type: hex\n  items:\n    - size:\n        width: auto\n        height: auto\n      margin:\n        top: 16\n        bottom: 0\n        start: 16\n        end: 16\n      view:\n        type: label\n        text: \"How was your experience with your last order?\"\n        text_appearance:\n          font_size: 16\n          styles: [ ]\n          color:\n            default:\n              hex: \"#000000\"\n          alignment: center\n    - size:\n        width: auto\n        height: auto\n      margin:\n        top: 0\n        bottom: 16\n        start: 16\n        end: 16\n      view:\n        type: linear_layout\n        direction: horizontal\n        items:\n          - size:\n              width: 36\n              height: 36\n            margin:\n              end: 2\n            view:\n              type: label_button\n              identifier: rating_1\n              button_click: [ \"cancel\" ]\n              label:\n                type: label\n                text: \"★\"\n                text_appearance:\n                  font_size: 32\n                  styles: [  ]\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0.33\n                  alignment: center\n          - size:\n              width: 36\n              height: 36\n            margin:\n              end: 2\n            view:\n              type: label_button\n              identifier: rating_2\n              button_click: [ \"cancel\" ]\n              label:\n                type: label\n                text: \"★\"\n                text_appearance:\n                  font_size: 32\n                  styles: [ ]\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0.33\n                  alignment: center\n          - size:\n              width: 36\n              height: 36\n            margin:\n              end: 2\n            view:\n              type: label_button\n              identifier: rating_3\n              button_click: [ \"cancel\" ]\n              label:\n                type: label\n                text: \"★\"\n                text_appearance:\n                  font_size: 32\n                  styles: [ ]\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0.33\n                  alignment: center\n          - size:\n              width: 36\n              height: 36\n            margin:\n              end: 2\n            view:\n              type: label_button\n              identifier: rating_4\n              button_click: [ \"cancel\" ]\n              label:\n                type: label\n                text: \"★\"\n                text_appearance:\n                  font_size: 32\n                  styles: [ ]\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0.33\n                  alignment: center\n          - size:\n              width: 36\n              height: 36\n            margin:\n              end: 0\n            view:\n              type: label_button\n              identifier: rating_5\n              button_click: [ \"cancel\" ]\n              label:\n                type: label\n                text: \"★\"\n                text_appearance:\n                  font_size: 32\n                  styles: [ ]\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0.33\n                  alignment: center\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Embedded/home_special_offer.yml",
    "content": "---\nversion: 1\npresentation:\n  type: embedded\n  embedded_id: \"home_special_offer\"\n  default_placement:\n    size:\n      height: auto\n      width: 100%\nview:\n  type: linear_layout\n  direction: vertical\n  background_color:\n    default:\n      alpha: 1\n      hex: \"#FFFFFF\"\n      type: hex\n  items:\n    - size:\n        height: auto\n        width: 100%\n      margin:\n        top: 16\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: label\n        text: \"Your birthday is coming up!\"\n        text_appearance:\n          font_size: 24\n          styles: [bold]\n          color:\n            default:\n              hex: \"#000000\"\n          alignment: center\n    - size:\n        width: auto\n        height: auto\n      margin:\n        top: 8\n        bottom: 16\n        start: 16\n        end: 16\n      view:\n        type: label_button\n        identifier: button1\n        actions:\n            add_custom_event_action:\n                event_name: 'birthday_offer_tapped'\n            add_tags_action: \"birthday_offer\"\n        label:\n            text: \"Enjoy 15% off on our men's shirts,\\nbecause we know you like them.\"\n            text_appearance:\n              font_size: 16\n              styles: [ ]\n              color:\n                default:\n                  hex: \"#000000\"\n              alignment: center\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/1-pager-different-path.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: 95%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  branching:\n    pager_completions:\n    - when_state_matches:\n        value: \n          equals: \n          - \"is-complete\"\n        scope:\n        - $form\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: auto\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n        - size:\n            height: auto\n            width: 100%\n          margin:\n            top: 16\n            start: 16\n            end: 16\n          view:\n            type: label\n            text: Take a spin\n            text_appearance:\n              alignment: center\n              styles:\n              - bold\n              font_size: 18\n              color:\n                default:\n                  hex: \"#000000\"\n        - size:\n            height: 250\n            width: 100%\n          margin:\n            start: 64\n            end: 64\n            top: 16\n            bottom: 16\n          view:\n            type: pager\n            items:\n            - identifier: \"start-page\"\n              branching:\n                next_page:\n                  selectors:\n                  - when_state_matches:\n                      value: \n                        equals: \"cats\"\n                      scope:\n                      - next_page\n                    page_id: page-cats-1\n                  - when_state_matches:\n                      value: \n                        equals: \"dogs\"\n                      scope:\n                      - next_page\n                    page_id: page-dogs-1\n              view:\n                type: linear_layout\n                background_color:\n                  default:\n                    hex: \"#88FF0000\"\n                direction: vertical\n                items:\n                - size:\n                    height: auto\n                    width: auto\n                  margin:\n                    start: 16\n                    end: 16\n                    top: 16\n                    bottom: 16\n                  view:\n                    type: label_button\n                    identifier: button_cats\n                    event_handlers:\n                    - type: tap\n                      state_actions:\n                      - type: set\n                        key: next_page\n                        value: cats\n                    background_color:\n                      default:\n                        hex: \"#FFD600\"\n                    label:\n                      type: label\n                      text_appearance:\n                        font_size: 10\n                        color:\n                          default:\n                            hex: \"#333333\"\n                        alignment: center\n                      text: 'I like cats'\n                    button_click: [\"pager_next\"]\n                - size:\n                    height: auto\n                    width: auto\n                  margin:\n                    start: 16\n                    end: 16\n                    top: 16\n                    bottom: 16\n                  view:\n                    type: label_button\n                    identifier: button_dogs\n                    event_handlers:\n                    - type: tap\n                      state_actions:\n                      - type: set\n                        key: next_page\n                        value: dogs\n                    background_color:\n                      default:\n                        hex: \"#FFD600\"\n                    label:\n                      type: label\n                      text_appearance:\n                        font_size: 10\n                        color:\n                          default:\n                            hex: \"#333333\"\n                        alignment: center\n                      text: 'I like dogs'\n                    button_click: [\"pager_next\"]\n            - identifier: \"page-cats-1\"\n              branching:\n                next_page:\n                  selectors:\n                  - page_id: page-cats-2\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: Cats page 1\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-cats-2\"\n              branching:\n                next_page:\n                  selectors:\n                  - page_id: page-cats-3\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: Cats page 2\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-cats-3\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: Cats page 3\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-dogs-1\"\n              branching:\n                next_page:\n                  selectors:\n                  - when_state_matches:\n                      value: \n                        equals: \"dogs\"\n                      scope:\n                      - next_page\n                    page_id: page-dogs-2\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#0000FF\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: Dogs page 1\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-dogs-2\"\n              branching:\n                next_page:\n                  selectors:\n                  - page_id: page-dogs-3\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#0000FF\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: Dogs page 2\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-dogs-3\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#0000FF\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: Dogs page 3\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label\n            event_handlers:\n            - type: tap\n              state_actions:\n              - type: set\n                key: next_page\n                value: page-5\n            text_appearance:\n              font_size: 10\n              color:\n                default:\n                  hex: \"#333333\"\n              alignment: center\n            text: 'Unlock page 5'\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Next!'\n            button_click: [\"pager_next\"]\n            enabled: [\"pager_next\"]\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Previous!'\n            button_click: [\"pager_previous\"]\n            enabled: [\"pager_previous\"]\n        - size:\n            height: 30\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: pager_indicator\n            spacing: 16\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  color:\n                    default:\n                      hex: \"#0000FF\"\n                icon:\n                  icon: checkmark\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n                  scale: .8\n              unselected:\n                shapes:\n                - type: rectangle\n                  aspect_ratio: 1\n                  border:\n                    stroke_color:\n                      default:\n                        hex: \"#000000\"\n                    stroke_width: 3\n                    radius: 4\n                  color:\n                    default:\n                      hex: \"#FF0000\"\n                icon:\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n                  scale: .8\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/1-pager-pranching-test.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: 95%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  branching:\n    pager_completions:\n    - when_state_matches:\n        value: \n          equals: \n          - \"is-complete\"\n        scope:\n        - $form\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: auto\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n        - size:\n            height: auto\n            width: 100%\n          margin:\n            top: 16\n            start: 16\n            end: 16\n          view:\n            type: label\n            text: Take a spin\n            text_appearance:\n              alignment: center\n              styles:\n              - bold\n              font_size: 18\n              color:\n                default:\n                  hex: \"#000000\"\n        - size:\n            height: 250\n            width: 100%\n          margin:\n            start: 64\n            end: 64\n            top: 16\n            bottom: 16\n          view:\n            type: pager\n            items:\n            - identifier: \"page-1\"\n              branching:\n                next_page:\n                  selectors:\n                  - when_state_matches:\n                      value: \n                        equals: \"page-3\"\n                      scope:\n                      - next_page\n                    page_id: page-3\n                  - when_state_matches:\n                      value: \n                        equals: \"page-2\"\n                      scope:\n                      - next_page\n                    page_id: page-2\n                previous_page_disabled: \n                  always: true\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#88FF0000\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: This is the first page about stuff.\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n            - identifier: \"page-2\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: More stuff is here on the second page.\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-3\"\n              branching:\n                next_page:\n                  selectors:\n                  - when_state_matches:\n                      value: \n                        equals: \"page-4\"\n                      scope:\n                      - next_page\n                    page_id: page-4\n                  - when_state_matches:\n                      value: \n                        equals: \"page-5\"\n                      scope:\n                      - next_page\n                    page_id: page-5\n                previous_page_disabled: \n                  always: true\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: .8\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: That's not all! There's a third page, too!\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-4\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#0022FF\"\n                    alpha: .8\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: that's page 4\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-5\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#1100FF\"\n                    alpha: .8\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: that's page 5\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            event_handlers:\n            - type: tap\n              state_actions:\n              - type: set\n                key: next_page\n                value: page-2\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Go Next Page 2 -> 3'\n            button_click: [\"pager_next\"]\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            event_handlers:\n            - type: tap\n              state_actions:\n              - type: set\n                key: next_page\n                value: page-3\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Go Next Page 3'\n            button_click: [\"pager_next\"]\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            event_handlers:\n            - type: tap\n              state_actions:\n              - type: set\n                key: next_page\n                value: page-4\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Unlock page 4'\n            button_click: [\"pager_next\"]\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label\n            event_handlers:\n            - type: tap\n              state_actions:\n              - type: set\n                key: next_page\n                value: page-5\n            text_appearance:\n              font_size: 10\n              color:\n                default:\n                  hex: \"#333333\"\n              alignment: center\n            text: 'Unlock page 5'\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Next!'\n            button_click: [\"pager_next\"]\n            enabled: [\"pager_next\"]\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Previous!'\n            button_click: [\"pager_previous\"]\n            enabled: [\"pager_previous\"]\n        - size:\n            height: 30\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: pager_indicator\n            spacing: 16\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  color:\n                    default:\n                      hex: \"#0000FF\"\n                icon:\n                  icon: checkmark\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n                  scale: .8\n              unselected:\n                shapes:\n                - type: rectangle\n                  aspect_ratio: 1\n                  border:\n                    stroke_color:\n                      default:\n                        hex: \"#000000\"\n                    stroke_width: 3\n                    radius: 4\n                  color:\n                    default:\n                      hex: \"#FF0000\"\n                icon:\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n                  scale: .8\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/1qa.yml",
    "content": ""
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/MOBILE_4621.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors:\n  - placement:\n      ignore_safe_area: true\n      size:\n        width: 70%\n        height: 60%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          type: hex\n          hex: \"#868686\"\n          alpha: 1\n        selectors:\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n      web:\n        ignore_shade: false\n      border:\n        radius: 15\n    window_size: large\n    orientation: portrait\n  - placement:\n      ignore_safe_area: true\n      size:\n        width: 100%\n        height: 100%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          type: hex\n          hex: \"#868686\"\n          alpha: 1\n        selectors:\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n      web:\n        ignore_shade: false\n      border:\n        radius: 15\n    window_size: large\n    orientation: landscape\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: true\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web:\n      ignore_shade: true\nview:\n  type: pager_controller\n  identifier: e5dc9815-cdfc-4ea5-be08-3a02fdfe7c3d\n  view:\n    type: container\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: 100%\n      view:\n        type: pager\n        disable_swipe: true\n        items:\n        - identifier: d3479e4e-61b3-47f3-bb6b-97b3ae016400\n          type: pager_item\n          view:\n            type: container\n            items:\n            - margin:\n                start: 0\n                end: 0\n              position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: media\n                media_fit: center_crop\n                url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                media_type: video\n                video:\n                  aspect_ratio: 0.5625\n                  show_controls: false\n                  autoplay: true\n                  muted: true\n                  loop: true\n              ignore_safe_area: true\n            - size:\n                width: 100%\n                height: 100%\n              position:\n                horizontal: center\n                vertical: center\n              ignore_safe_area: true\n              view:\n                type: container\n                items:\n                - margin:\n                    bottom: 0\n                    top: 0\n                    end: 0\n                    start: 0\n                  position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: 100%\n                    height: 100%\n                  ignore_safe_area: true\n                  view:\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                    - identifier: scroll_container\n                      size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: scroll_layout\n                        direction: vertical\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items: []\n            background_color:\n              default:\n                type: hex\n                hex: \"#E81C3C\"\n                alpha: 1\n              selectors:\n              - platform: ios\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#E81C3C\"\n                  alpha: 1\n              - platform: android\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#E81C3C\"\n                  alpha: 1\n              - platform: web\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#E81C3C\"\n                  alpha: 1\n      ignore_safe_area: true\n    - position:\n        horizontal: end\n        vertical: top\n      size:\n        width: 48\n        height: 48\n      view:\n        type: image_button\n        image:\n          scale: 0.4\n          type: icon\n          icon: close\n          color:\n            default:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n            selectors:\n            - platform: ios\n              dark_mode: true\n              color:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n            - platform: android\n              dark_mode: true\n              color:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n            - platform: web\n              dark_mode: true\n              color:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n        identifier: dismiss_button\n        button_click:\n        - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/_a11y_focus.json",
    "content": "{\n  \"in_app_message\": {\n    \"edit_grace_period\": 14,\n    \"features\": {\n      \"embedded\": \"1.0\"\n    },\n    \"forms\": [\n      {\n        \"id\": \"b4cbb892-6e66-48d0-bb9a-4c86f875c358\",\n        \"questions\": [\n          {\n            \"id\": \"163c6741-b9e3-495d-b0ba-ef19cb5182d1\",\n            \"label\": \"Where do you prefer to shop?\",\n            \"responses\": [\n              {\n                \"draft\": \"Online\",\n                \"label\": \"Online\",\n                \"value\": \"c7005ea5-10c7-4aa1-a41e-7252cd7b36b6\"\n              },\n              {\n                \"draft\": \"In-store\",\n                \"label\": \"In-store\",\n                \"value\": \"35e04233-b406-494b-9b5d-8c6553eeb052\"\n              }\n            ],\n            \"type\": \"radio\"\n          },\n          {\n            \"id\": \"9b7081eb-0c70-44e3-90cb-aab772f29925\",\n            \"label\": \"Where do you like to surf? \",\n            \"responses\": [\n              {\n                \"draft\": \"Hawaii\",\n                \"label\": \"Hawaii\",\n                \"value\": \"c7005ea5-10c7-4aa1-a41e-7252cd7b36b6\"\n              },\n              {\n                \"draft\": \"Mexico\",\n                \"label\": \"Mexico\",\n                \"value\": \"35e04233-b406-494b-9b5d-8c6553eeb052\"\n              },\n              {\n                \"draft\": \"California\",\n                \"label\": \"California\",\n                \"value\": \"4097a556-c041-4824-8f34-726ead6123c7\"\n              }\n            ],\n            \"type\": \"checkbox\"\n          }\n        ],\n        \"response_type\": \"user_feedback\",\n        \"type\": \"form\"\n      }\n    ],\n    \"interval\": 2,\n    \"labels\": [\n      {\n        \"id\": \"a20d59fb-1beb-40b3-b865-f3fa2c8f02d8\",\n        \"label\": \"Screen 1\"\n      },\n      {\n        \"id\": \"77bce6f3-9de5-4813-9b7a-7d4066113091\",\n        \"label\": \"Screen 2\"\n      },\n      {\n        \"id\": \"ac248e67-2e74-441c-ab77-5791a423c11d\",\n        \"label\": \"Screen 3\"\n      }\n    ],\n    \"limit\": 0,\n    \"message\": {\n      \"audience\": {},\n      \"display\": {\n        \"layout\": {\n          \"presentation\": {\n            \"default_placement\": {\n              \"border\": {\n                \"radius\": 0\n              },\n              \"size\": {\n                \"height\": \"50%\",\n                \"width\": \"100%\"\n              }\n            },\n            \"embedded_id\": \"a11y_embedded\",\n            \"placement_selectors\": [],\n            \"type\": \"modal\"\n          },\n          \"version\": 1,\n          \"view\": {\n            \"type\": \"state_controller\",\n            \"view\": {\n              \"identifier\": \"5f935a5d-d4b7-4c02-b839-85f27d38b7b3\",\n              \"type\": \"pager_controller\",\n              \"view\": {\n                \"form_enabled\": [\n                  \"form_submission\"\n                ],\n                \"identifier\": \"b4cbb892-6e66-48d0-bb9a-4c86f875c358\",\n                \"response_type\": \"user_feedback\",\n                \"submit\": \"submit_event\",\n                \"type\": \"form_controller\",\n                \"view\": {\n                  \"items\": [\n                    {\n                      \"identifier\": \"47c2c045-df8b-4523-9282-c74fc8135054_pager_container_item\",\n                      \"ignore_safe_area\": true,\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                      },\n                      \"size\": {\n                        \"height\": \"100%\",\n                        \"width\": \"100%\"\n                      },\n                      \"view\": {\n                        \"disable_swipe\": false,\n                        \"items\": [\n                          {\n                            \"identifier\": \"a20d59fb-1beb-40b3-b865-f3fa2c8f02d8\",\n                            \"state_actions\": [\n                              {\n                                \"key\": \"a20d59fb-1beb-40b3-b865-f3fa2c8f02d8_next\",\n                                \"type\": \"set\"\n                              }\n                            ],\n                            \"type\": \"pager_item\",\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#7B7C84\",\n                                  \"type\": \"hex\"\n                                },\n                                \"selectors\": [\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"web\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"web\"\n                                  }\n                                ]\n                              },\n                              \"items\": [\n                                {\n                                  \"identifier\": \"14da2c81-244f-401a-af18-8e505b473653_main_view_container_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"dae1580e-0657-4550-8b07-63df24ce8f15_container_item\",\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"layout_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"0963a1cf-4b4a-4212-b174-65e5333d259a\",\n                                                    \"margin\": {\n                                                      \"bottom\": 8,\n                                                      \"end\": 16,\n                                                      \"start\": 16,\n                                                      \"top\": 16\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"accessibility_hidden\": false,\n                                                      \"accessibility_role\": {\n                                                        \"level\": 1,\n                                                        \"type\": \"heading\"\n                                                      },\n                                                      \"content_description\": \"Do you have time today for a quick survey??\",\n                                                      \"text\": \"Do you have time today for a quick survey??\",\n                                                      \"text_appearance\": {\n                                                        \"alignment\": \"start\",\n                                                        \"color\": {\n                                                          \"default\": {\n                                                            \"alpha\": 1,\n                                                            \"hex\": \"#FFFFFF\",\n                                                            \"type\": \"hex\"\n                                                          },\n                                                          \"selectors\": [\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"web\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"web\"\n                                                            }\n                                                          ]\n                                                        },\n                                                        \"font_families\": [\n                                                          \"sans-serif\"\n                                                        ],\n                                                        \"font_size\": 22,\n                                                        \"styles\": [\n                                                          \"bold\"\n                                                        ]\n                                                      },\n                                                      \"type\": \"label\"\n                                                    }\n                                                  },\n                                                  {\n                                                    \"identifier\": \"96029a26-b49f-492a-b70e-6165a661d5ac\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 0\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"items\": [\n                                                        {\n                                                          \"identifier\": \"96029a26-b49f-492a-b70e-6165a661d5ac_linear_container\",\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"end\": 0,\n                                                            \"start\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"position\": {\n                                                            \"horizontal\": \"center\",\n                                                            \"vertical\": \"center\"\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#7B7C84\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#7B7C84\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#7B7C84\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#7B7C84\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 0\n                                                            },\n                                                            \"direction\": \"horizontal\",\n                                                            \"items\": [\n                                                              {\n                                                                \"identifier\": \"00387580-5d8c-4e08-a72c-c0b2bb03d041\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 16,\n                                                                  \"end\": 16,\n                                                                  \"start\": 16,\n                                                                  \"top\": 8\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"50%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"actions\": {},\n                                                                  \"background_color\": {\n                                                                    \"default\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#7B7C84\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"selectors\": [\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#7B7C84\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#7B7C84\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#7B7C84\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"web\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"web\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"border\": {\n                                                                    \"radius\": 3,\n                                                                    \"stroke_color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#7B7C84\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#7B7C84\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#7B7C84\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#7B7C84\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    },\n                                                                    \"stroke_width\": 0\n                                                                  },\n                                                                  \"button_click\": [\n                                                                    \"dismiss\"\n                                                                  ],\n                                                                  \"content_description\": \"Maybe later — Dismiss the survey\",\n                                                                  \"enabled\": [],\n                                                                  \"identifier\": \"dismiss--Maybe later\",\n                                                                  \"label\": {\n                                                                    \"content_description\": \"Maybe later — Dismiss the survey\",\n                                                                    \"text\": \"Maybe later\",\n                                                                    \"text_appearance\": {\n                                                                      \"alignment\": \"center\",\n                                                                      \"color\": {\n                                                                        \"default\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#000000\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"selectors\": [\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"web\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"web\"\n                                                                          }\n                                                                        ]\n                                                                      },\n                                                                      \"font_families\": [\n                                                                        \"sans-serif\"\n                                                                      ],\n                                                                      \"font_size\": 16,\n                                                                      \"styles\": [\n                                                                        \"bold\"\n                                                                      ]\n                                                                    },\n                                                                    \"type\": \"label\"\n                                                                  },\n                                                                  \"localized_content_description\": {\n                                                                    \"fallback\": \"Dismiss\",\n                                                                    \"ref\": \"ua_dismiss\"\n                                                                  },\n                                                                  \"reporting_metadata\": {\n                                                                    \"button_action\": \"dismiss\",\n                                                                    \"button_id\": \"00387580-5d8c-4e08-a72c-c0b2bb03d041\",\n                                                                    \"trigger_link_id\": \"00387580-5d8c-4e08-a72c-c0b2bb03d041\"\n                                                                  },\n                                                                  \"type\": \"label_button\"\n                                                                }\n                                                              },\n                                                              {\n                                                                \"identifier\": \"739cd27e-8fc8-4fb3-a454-964a4d4f594e\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 8,\n                                                                  \"end\": 16,\n                                                                  \"start\": 16,\n                                                                  \"top\": 8\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"50%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"actions\": {},\n                                                                  \"background_color\": {\n                                                                    \"default\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#63AFF1\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"selectors\": [\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#63AFF1\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#63AFF1\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#63AFF1\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#63AFF1\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#63AFF1\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"web\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#63AFF1\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"web\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"border\": {\n                                                                    \"radius\": 3,\n                                                                    \"stroke_color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#63AFF1\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#63AFF1\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#63AFF1\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#63AFF1\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#63AFF1\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#63AFF1\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#63AFF1\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    },\n                                                                    \"stroke_width\": 0\n                                                                  },\n                                                                  \"button_click\": [\n                                                                    \"pager_next\"\n                                                                  ],\n                                                                  \"content_description\": \"Yes — Advance to next screen\",\n                                                                  \"enabled\": [\n                                                                    \"pager_next\"\n                                                                  ],\n                                                                  \"identifier\": \"next--Yes\",\n                                                                  \"label\": {\n                                                                    \"content_description\": \"Yes — Advance to next screen\",\n                                                                    \"text\": \"Yes\",\n                                                                    \"text_appearance\": {\n                                                                      \"alignment\": \"center\",\n                                                                      \"color\": {\n                                                                        \"default\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"selectors\": [\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"web\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"web\"\n                                                                          }\n                                                                        ]\n                                                                      },\n                                                                      \"font_families\": [\n                                                                        \"sans-serif\"\n                                                                      ],\n                                                                      \"font_size\": 16,\n                                                                      \"styles\": [\n                                                                        \"bold\"\n                                                                      ]\n                                                                    },\n                                                                    \"type\": \"label\"\n                                                                  },\n                                                                  \"localized_content_description\": {\n                                                                    \"fallback\": \"Next\",\n                                                                    \"ref\": \"ua_next\"\n                                                                  },\n                                                                  \"reporting_metadata\": {\n                                                                    \"button_action\": \"next\",\n                                                                    \"button_id\": \"739cd27e-8fc8-4fb3-a454-964a4d4f594e\",\n                                                                    \"trigger_link_id\": \"739cd27e-8fc8-4fb3-a454-964a4d4f594e\"\n                                                                  },\n                                                                  \"type\": \"label_button\"\n                                                                }\n                                                              }\n                                                            ],\n                                                            \"type\": \"linear_layout\"\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"container\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"linear_layout\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\"\n                            }\n                          },\n                          {\n                            \"identifier\": \"77bce6f3-9de5-4813-9b7a-7d4066113091\",\n                            \"state_actions\": [\n                              {\n                                \"key\": \"77bce6f3-9de5-4813-9b7a-7d4066113091_next\",\n                                \"type\": \"set\"\n                              }\n                            ],\n                            \"type\": \"pager_item\",\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#7B7C84\",\n                                  \"type\": \"hex\"\n                                },\n                                \"selectors\": [\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"web\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"web\"\n                                  }\n                                ]\n                              },\n                              \"items\": [\n                                {\n                                  \"identifier\": \"cd834ed7-1c15-4762-93d5-dc66c40d01e8_main_view_container_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"13b384e1-fd8a-4e91-80a8-3e5734bacb81_container_item\",\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"layout_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"163c6741-b9e3-495d-b0ba-ef19cb5182d1\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 16,\n                                                      \"start\": 16,\n                                                      \"top\": 16\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"direction\": \"vertical\",\n                                                      \"items\": [\n                                                        {\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"Where do you prefer to shop?\",\n                                                            \"identifier\": \"163c6741-b9e3-495d-b0ba-ef19cb5182d1_title\",\n                                                            \"text\": \"Where do you prefer to shop?\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"start\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#000000\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#000000\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#000000\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"sans-serif\"\n                                                              ],\n                                                              \"font_size\": 19,\n                                                              \"styles\": [\n                                                                \"bold\"\n                                                              ]\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"attribute_name\": {},\n                                                            \"identifier\": \"163c6741-b9e3-495d-b0ba-ef19cb5182d1\",\n                                                            \"required\": false,\n                                                            \"type\": \"radio_input_controller\",\n                                                            \"view\": {\n                                                              \"direction\": \"vertical\",\n                                                              \"items\": [\n                                                                {\n                                                                  \"margin\": {\n                                                                    \"bottom\": 8,\n                                                                    \"top\": 0\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"100%\",\n                                                                    \"width\": \"100%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"direction\": \"horizontal\",\n                                                                    \"items\": [\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 8\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": 20,\n                                                                          \"width\": 20\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"content_description\": \"Online\",\n                                                                          \"reporting_value\": \"c7005ea5-10c7-4aa1-a41e-7252cd7b36b6\",\n                                                                          \"style\": {\n                                                                            \"bindings\": {\n                                                                              \"selected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 20,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"ellipse\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#000000\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 0.6,\n                                                                                    \"type\": \"ellipse\"\n                                                                                  }\n                                                                                ]\n                                                                              },\n                                                                              \"unselected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 20,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"ellipse\"\n                                                                                  }\n                                                                                ]\n                                                                              }\n                                                                            },\n                                                                            \"type\": \"checkbox\"\n                                                                          },\n                                                                          \"type\": \"radio_input\"\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 16\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": \"auto\",\n                                                                          \"width\": \"100%\"\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"accessibility_hidden\": false,\n                                                                          \"content_description\": \"Online\",\n                                                                          \"text\": \"Online\",\n                                                                          \"text_appearance\": {\n                                                                            \"alignment\": \"start\",\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"selectors\": [\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"web\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"web\"\n                                                                                }\n                                                                              ]\n                                                                            },\n                                                                            \"font_families\": [\n                                                                              \"sans-serif\"\n                                                                            ],\n                                                                            \"font_size\": 19,\n                                                                            \"styles\": [\n                                                                              \"bold\"\n                                                                            ]\n                                                                          },\n                                                                          \"type\": \"label\"\n                                                                        }\n                                                                      }\n                                                                    ],\n                                                                    \"type\": \"linear_layout\"\n                                                                  }\n                                                                },\n                                                                {\n                                                                  \"margin\": {\n                                                                    \"bottom\": 8,\n                                                                    \"top\": 0\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"100%\",\n                                                                    \"width\": \"100%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"direction\": \"horizontal\",\n                                                                    \"items\": [\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 8\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": 20,\n                                                                          \"width\": 20\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"content_description\": \"In-store\",\n                                                                          \"reporting_value\": \"35e04233-b406-494b-9b5d-8c6553eeb052\",\n                                                                          \"style\": {\n                                                                            \"bindings\": {\n                                                                              \"selected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 20,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"ellipse\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#000000\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 0.6,\n                                                                                    \"type\": \"ellipse\"\n                                                                                  }\n                                                                                ]\n                                                                              },\n                                                                              \"unselected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 20,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"ellipse\"\n                                                                                  }\n                                                                                ]\n                                                                              }\n                                                                            },\n                                                                            \"type\": \"checkbox\"\n                                                                          },\n                                                                          \"type\": \"radio_input\"\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 16\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": \"auto\",\n                                                                          \"width\": \"100%\"\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"accessibility_hidden\": false,\n                                                                          \"content_description\": \"In-store\",\n                                                                          \"text\": \"In-store\",\n                                                                          \"text_appearance\": {\n                                                                            \"alignment\": \"start\",\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"selectors\": [\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"web\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"web\"\n                                                                                }\n                                                                              ]\n                                                                            },\n                                                                            \"font_families\": [\n                                                                              \"sans-serif\"\n                                                                            ],\n                                                                            \"font_size\": 19,\n                                                                            \"styles\": [\n                                                                              \"bold\"\n                                                                            ]\n                                                                          },\n                                                                          \"type\": \"label\"\n                                                                        }\n                                                                      }\n                                                                    ],\n                                                                    \"type\": \"linear_layout\"\n                                                                  }\n                                                                }\n                                                              ],\n                                                              \"randomize_children\": false,\n                                                              \"type\": \"linear_layout\"\n                                                            }\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"linear_layout\"\n                                                    }\n                                                  },\n                                                  {\n                                                    \"identifier\": \"2ca890c3-42da-4c80-84e1-9c345370c871\",\n                                                    \"margin\": {\n                                                      \"bottom\": 4,\n                                                      \"end\": 16,\n                                                      \"start\": 16,\n                                                      \"top\": 0\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"50%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"actions\": {},\n                                                      \"background_color\": {\n                                                        \"default\": {\n                                                          \"alpha\": 1,\n                                                          \"hex\": \"#63AFF1\",\n                                                          \"type\": \"hex\"\n                                                        },\n                                                        \"selectors\": [\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": false,\n                                                            \"platform\": \"ios\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": true,\n                                                            \"platform\": \"ios\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": false,\n                                                            \"platform\": \"android\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": true,\n                                                            \"platform\": \"android\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": false,\n                                                            \"platform\": \"web\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": true,\n                                                            \"platform\": \"web\"\n                                                          }\n                                                        ]\n                                                      },\n                                                      \"border\": {\n                                                        \"radius\": 3,\n                                                        \"stroke_color\": {\n                                                          \"default\": {\n                                                            \"alpha\": 1,\n                                                            \"hex\": \"#63AFF1\",\n                                                            \"type\": \"hex\"\n                                                          },\n                                                          \"selectors\": [\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"web\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"web\"\n                                                            }\n                                                          ]\n                                                        },\n                                                        \"stroke_width\": 0\n                                                      },\n                                                      \"button_click\": [\n                                                        \"pager_next\"\n                                                      ],\n                                                      \"content_description\": \"Next — Advance to next screen\",\n                                                      \"enabled\": [\n                                                        \"pager_next\"\n                                                      ],\n                                                      \"identifier\": \"next--Next\",\n                                                      \"label\": {\n                                                        \"content_description\": \"Next — Advance to next screen\",\n                                                        \"text\": \"Next\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#FFFFFF\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#000000\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#000000\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#000000\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"sans-serif\"\n                                                          ],\n                                                          \"font_size\": 16,\n                                                          \"styles\": [\n                                                            \"bold\"\n                                                          ]\n                                                        },\n                                                        \"type\": \"label\"\n                                                      },\n                                                      \"localized_content_description\": {\n                                                        \"fallback\": \"Next\",\n                                                        \"ref\": \"ua_next\"\n                                                      },\n                                                      \"reporting_metadata\": {\n                                                        \"button_action\": \"next\",\n                                                        \"button_id\": \"2ca890c3-42da-4c80-84e1-9c345370c871\",\n                                                        \"trigger_link_id\": \"2ca890c3-42da-4c80-84e1-9c345370c871\"\n                                                      },\n                                                      \"type\": \"label_button\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"linear_layout\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\"\n                            }\n                          },\n                          {\n                            \"identifier\": \"ac248e67-2e74-441c-ab77-5791a423c11d\",\n                            \"state_actions\": [\n                              {\n                                \"key\": \"ac248e67-2e74-441c-ab77-5791a423c11d_next\",\n                                \"type\": \"set\"\n                              }\n                            ],\n                            \"type\": \"pager_item\",\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#7B7C84\",\n                                  \"type\": \"hex\"\n                                },\n                                \"selectors\": [\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#7B7C84\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"web\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"web\"\n                                  }\n                                ]\n                              },\n                              \"items\": [\n                                {\n                                  \"identifier\": \"04297707-e15d-4b80-be07-7e2fcab07e83_main_view_container_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"4f9bf2c4-7ca4-4e54-9488-d5a979087704_container_item\",\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"layout_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"9b7081eb-0c70-44e3-90cb-aab772f29925\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 16,\n                                                      \"start\": 16,\n                                                      \"top\": 16\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"direction\": \"vertical\",\n                                                      \"items\": [\n                                                        {\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"Where do you like to surf? \",\n                                                            \"identifier\": \"9b7081eb-0c70-44e3-90cb-aab772f29925_title\",\n                                                            \"text\": \"Where do you like to surf? \",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"start\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#000000\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#000000\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#000000\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"sans-serif\"\n                                                              ],\n                                                              \"font_size\": 19,\n                                                              \"styles\": [\n                                                                \"bold\"\n                                                              ]\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"identifier\": \"9b7081eb-0c70-44e3-90cb-aab772f29925\",\n                                                            \"required\": false,\n                                                            \"type\": \"checkbox_controller\",\n                                                            \"view\": {\n                                                              \"direction\": \"vertical\",\n                                                              \"items\": [\n                                                                {\n                                                                  \"margin\": {\n                                                                    \"bottom\": 8,\n                                                                    \"top\": 0\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"100%\",\n                                                                    \"width\": \"100%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"direction\": \"horizontal\",\n                                                                    \"items\": [\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 8\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": 20,\n                                                                          \"width\": 20\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"content_description\": \"Hawaii\",\n                                                                          \"reporting_value\": \"c7005ea5-10c7-4aa1-a41e-7252cd7b36b6\",\n                                                                          \"style\": {\n                                                                            \"bindings\": {\n                                                                              \"selected\": {\n                                                                                \"icon\": {\n                                                                                  \"color\": {\n                                                                                    \"default\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#FFFFFF\",\n                                                                                      \"type\": \"hex\"\n                                                                                    }\n                                                                                  },\n                                                                                  \"icon\": \"checkmark\",\n                                                                                  \"scale\": 0.8,\n                                                                                  \"type\": \"icon\"\n                                                                                },\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 4,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#000000\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"rectangle\"\n                                                                                  }\n                                                                                ]\n                                                                              },\n                                                                              \"unselected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 4,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"rectangle\"\n                                                                                  }\n                                                                                ]\n                                                                              }\n                                                                            },\n                                                                            \"type\": \"checkbox\"\n                                                                          },\n                                                                          \"type\": \"checkbox\"\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 16\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": \"auto\",\n                                                                          \"width\": \"100%\"\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"accessibility_hidden\": false,\n                                                                          \"content_description\": \"Hawaii\",\n                                                                          \"text\": \"Hawaii\",\n                                                                          \"text_appearance\": {\n                                                                            \"alignment\": \"start\",\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"selectors\": [\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"web\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"web\"\n                                                                                }\n                                                                              ]\n                                                                            },\n                                                                            \"font_families\": [\n                                                                              \"sans-serif\"\n                                                                            ],\n                                                                            \"font_size\": 19,\n                                                                            \"styles\": [\n                                                                              \"bold\"\n                                                                            ]\n                                                                          },\n                                                                          \"type\": \"label\"\n                                                                        }\n                                                                      }\n                                                                    ],\n                                                                    \"type\": \"linear_layout\"\n                                                                  }\n                                                                },\n                                                                {\n                                                                  \"margin\": {\n                                                                    \"bottom\": 8,\n                                                                    \"top\": 0\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"100%\",\n                                                                    \"width\": \"100%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"direction\": \"horizontal\",\n                                                                    \"items\": [\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 8\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": 20,\n                                                                          \"width\": 20\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"content_description\": \"Mexico\",\n                                                                          \"reporting_value\": \"35e04233-b406-494b-9b5d-8c6553eeb052\",\n                                                                          \"style\": {\n                                                                            \"bindings\": {\n                                                                              \"selected\": {\n                                                                                \"icon\": {\n                                                                                  \"color\": {\n                                                                                    \"default\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#FFFFFF\",\n                                                                                      \"type\": \"hex\"\n                                                                                    }\n                                                                                  },\n                                                                                  \"icon\": \"checkmark\",\n                                                                                  \"scale\": 0.8,\n                                                                                  \"type\": \"icon\"\n                                                                                },\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 4,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#000000\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"rectangle\"\n                                                                                  }\n                                                                                ]\n                                                                              },\n                                                                              \"unselected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 4,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"rectangle\"\n                                                                                  }\n                                                                                ]\n                                                                              }\n                                                                            },\n                                                                            \"type\": \"checkbox\"\n                                                                          },\n                                                                          \"type\": \"checkbox\"\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 16\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": \"auto\",\n                                                                          \"width\": \"100%\"\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"accessibility_hidden\": false,\n                                                                          \"content_description\": \"Mexico\",\n                                                                          \"text\": \"Mexico\",\n                                                                          \"text_appearance\": {\n                                                                            \"alignment\": \"start\",\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"selectors\": [\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"web\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"web\"\n                                                                                }\n                                                                              ]\n                                                                            },\n                                                                            \"font_families\": [\n                                                                              \"sans-serif\"\n                                                                            ],\n                                                                            \"font_size\": 19,\n                                                                            \"styles\": [\n                                                                              \"bold\"\n                                                                            ]\n                                                                          },\n                                                                          \"type\": \"label\"\n                                                                        }\n                                                                      }\n                                                                    ],\n                                                                    \"type\": \"linear_layout\"\n                                                                  }\n                                                                },\n                                                                {\n                                                                  \"margin\": {\n                                                                    \"bottom\": 8,\n                                                                    \"top\": 0\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"100%\",\n                                                                    \"width\": \"100%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"direction\": \"horizontal\",\n                                                                    \"items\": [\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 8\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": 20,\n                                                                          \"width\": 20\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"content_description\": \"California\",\n                                                                          \"reporting_value\": \"4097a556-c041-4824-8f34-726ead6123c7\",\n                                                                          \"style\": {\n                                                                            \"bindings\": {\n                                                                              \"selected\": {\n                                                                                \"icon\": {\n                                                                                  \"color\": {\n                                                                                    \"default\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#FFFFFF\",\n                                                                                      \"type\": \"hex\"\n                                                                                    }\n                                                                                  },\n                                                                                  \"icon\": \"checkmark\",\n                                                                                  \"scale\": 0.8,\n                                                                                  \"type\": \"icon\"\n                                                                                },\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 4,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#000000\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"rectangle\"\n                                                                                  }\n                                                                                ]\n                                                                              },\n                                                                              \"unselected\": {\n                                                                                \"shapes\": [\n                                                                                  {\n                                                                                    \"aspect_ratio\": 1,\n                                                                                    \"border\": {\n                                                                                      \"radius\": 4,\n                                                                                      \"stroke_color\": {\n                                                                                        \"default\": {\n                                                                                          \"alpha\": 1,\n                                                                                          \"hex\": \"#000000\",\n                                                                                          \"type\": \"hex\"\n                                                                                        }\n                                                                                      },\n                                                                                      \"stroke_width\": 2\n                                                                                    },\n                                                                                    \"color\": {\n                                                                                      \"default\": {\n                                                                                        \"alpha\": 1,\n                                                                                        \"hex\": \"#FFFFFF\",\n                                                                                        \"type\": \"hex\"\n                                                                                      }\n                                                                                    },\n                                                                                    \"scale\": 1,\n                                                                                    \"type\": \"rectangle\"\n                                                                                  }\n                                                                                ]\n                                                                              }\n                                                                            },\n                                                                            \"type\": \"checkbox\"\n                                                                          },\n                                                                          \"type\": \"checkbox\"\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"margin\": {\n                                                                          \"end\": 16\n                                                                        },\n                                                                        \"size\": {\n                                                                          \"height\": \"auto\",\n                                                                          \"width\": \"100%\"\n                                                                        },\n                                                                        \"view\": {\n                                                                          \"accessibility_hidden\": false,\n                                                                          \"content_description\": \"California\",\n                                                                          \"text\": \"California\",\n                                                                          \"text_appearance\": {\n                                                                            \"alignment\": \"start\",\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"selectors\": [\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"ios\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"android\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#FFFFFF\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": false,\n                                                                                  \"platform\": \"web\"\n                                                                                },\n                                                                                {\n                                                                                  \"color\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#000000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  },\n                                                                                  \"dark_mode\": true,\n                                                                                  \"platform\": \"web\"\n                                                                                }\n                                                                              ]\n                                                                            },\n                                                                            \"font_families\": [\n                                                                              \"sans-serif\"\n                                                                            ],\n                                                                            \"font_size\": 19,\n                                                                            \"styles\": [\n                                                                              \"bold\"\n                                                                            ]\n                                                                          },\n                                                                          \"type\": \"label\"\n                                                                        }\n                                                                      }\n                                                                    ],\n                                                                    \"type\": \"linear_layout\"\n                                                                  }\n                                                                }\n                                                              ],\n                                                              \"randomize_children\": false,\n                                                              \"type\": \"linear_layout\"\n                                                            }\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"linear_layout\"\n                                                    }\n                                                  },\n                                                  {\n                                                    \"identifier\": \"1322c70d-a8a2-4dc2-a95a-e703e838404e\",\n                                                    \"margin\": {\n                                                      \"bottom\": 4,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 0\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"actions\": {},\n                                                      \"background_color\": {\n                                                        \"default\": {\n                                                          \"alpha\": 1,\n                                                          \"hex\": \"#63AFF1\",\n                                                          \"type\": \"hex\"\n                                                        },\n                                                        \"selectors\": [\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": false,\n                                                            \"platform\": \"ios\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": true,\n                                                            \"platform\": \"ios\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": false,\n                                                            \"platform\": \"android\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": true,\n                                                            \"platform\": \"android\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": false,\n                                                            \"platform\": \"web\"\n                                                          },\n                                                          {\n                                                            \"color\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#63AFF1\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"dark_mode\": true,\n                                                            \"platform\": \"web\"\n                                                          }\n                                                        ]\n                                                      },\n                                                      \"border\": {\n                                                        \"radius\": 3,\n                                                        \"stroke_color\": {\n                                                          \"default\": {\n                                                            \"alpha\": 1,\n                                                            \"hex\": \"#63AFF1\",\n                                                            \"type\": \"hex\"\n                                                          },\n                                                          \"selectors\": [\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"web\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#63AFF1\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"web\"\n                                                            }\n                                                          ]\n                                                        },\n                                                        \"stroke_width\": 0\n                                                      },\n                                                      \"button_click\": [\n                                                        \"form_submit\"\n                                                      ],\n                                                      \"content_description\": \"Submit — Submit responses and dismisses the survey. \",\n                                                      \"enabled\": [\n                                                        \"form_validation\"\n                                                      ],\n                                                      \"event_handlers\": [\n                                                        {\n                                                          \"state_actions\": [\n                                                            {\n                                                              \"key\": \"submitted\",\n                                                              \"type\": \"set\",\n                                                              \"value\": true\n                                                            }\n                                                          ],\n                                                          \"type\": \"tap\"\n                                                        }\n                                                      ],\n                                                      \"identifier\": \"submit_feedback--Submit\",\n                                                      \"label\": {\n                                                        \"content_description\": \"Submit — Submit responses and dismisses the survey. \",\n                                                        \"text\": \"Submit\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#FFFFFF\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#000000\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#000000\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFFFFF\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#000000\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"sans-serif\"\n                                                          ],\n                                                          \"font_size\": 16,\n                                                          \"styles\": [\n                                                            \"bold\"\n                                                          ]\n                                                        },\n                                                        \"type\": \"label\",\n                                                        \"view_overrides\": {\n                                                          \"icon_start\": [\n                                                            {\n                                                              \"value\": {\n                                                                \"icon\": {\n                                                                  \"color\": {\n                                                                    \"default\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"selectors\": [\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#000000\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#000000\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"web\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#000000\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"web\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"icon\": \"progress_spinner\",\n                                                                  \"scale\": 1,\n                                                                  \"type\": \"icon\"\n                                                                },\n                                                                \"space\": 8,\n                                                                \"type\": \"floating\"\n                                                              },\n                                                              \"when_state_matches\": {\n                                                                \"scope\": [\n                                                                  \"$forms\",\n                                                                  \"current\",\n                                                                  \"status\",\n                                                                  \"type\"\n                                                                ],\n                                                                \"value\": {\n                                                                  \"equals\": \"validating\"\n                                                                }\n                                                              }\n                                                            }\n                                                          ],\n                                                          \"text\": [\n                                                            {\n                                                              \"value\": \"Submit\",\n                                                              \"when_state_matches\": {\n                                                                \"scope\": [\n                                                                  \"$forms\",\n                                                                  \"current\",\n                                                                  \"status\",\n                                                                  \"type\"\n                                                                ],\n                                                                \"value\": {\n                                                                  \"equals\": \"validating\"\n                                                                }\n                                                              }\n                                                            }\n                                                          ],\n                                                          \"text_appearance\": [\n                                                            {\n                                                              \"value\": {\n                                                                \"alignment\": \"center\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"sans-serif\"\n                                                                ],\n                                                                \"font_size\": 16,\n                                                                \"styles\": [\n                                                                  \"bold\"\n                                                                ]\n                                                              }\n                                                            }\n                                                          ]\n                                                        }\n                                                      },\n                                                      \"reporting_metadata\": {\n                                                        \"button_action\": \"submit_feedback\",\n                                                        \"button_id\": \"1322c70d-a8a2-4dc2-a95a-e703e838404e\",\n                                                        \"trigger_link_id\": \"1322c70d-a8a2-4dc2-a95a-e703e838404e\"\n                                                      },\n                                                      \"type\": \"label_button\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"linear_layout\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\",\n                                    \"visibility\": {\n                                      \"default\": true,\n                                      \"invert_when_state_matches\": {\n                                        \"key\": \"submitted\",\n                                        \"value\": {\n                                          \"equals\": true\n                                        }\n                                      }\n                                    }\n                                  }\n                                },\n                                {\n                                  \"identifier\": \"2f8a8449-6713-4be5-91d8-dedb98ab833e_confirmation_screen_content_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"background_color\": {\n                                      \"default\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#7B7C84\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"selectors\": [\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#7B7C84\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"ios\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"ios\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#7B7C84\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"android\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"android\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#7B7C84\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"web\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"web\"\n                                        }\n                                      ]\n                                    },\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"fcd7d766-4674-49e1-9c1c-005d8f746540_container_item\",\n                                        \"ignore_safe_area\": true,\n                                        \"margin\": {\n                                          \"end\": 0,\n                                          \"start\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"horizontal\",\n                                          \"items\": [],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      },\n                                      {\n                                        \"identifier\": \"ff8a83c4-fdf6-4b96-8362-453b4c7f29bc_container_item\",\n                                        \"ignore_safe_area\": false,\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"layout_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"307a6b80-818f-4db9-98a9-5a8568be05a0\",\n                                                    \"margin\": {\n                                                      \"bottom\": 8,\n                                                      \"end\": 16,\n                                                      \"start\": 16,\n                                                      \"top\": 48\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"accessibility_hidden\": false,\n                                                      \"content_description\": \"Survey submitted successfully! \",\n                                                      \"text\": \"Survey submitted successfully! \",\n                                                      \"text_appearance\": {\n                                                        \"alignment\": \"center\",\n                                                        \"color\": {\n                                                          \"default\": {\n                                                            \"alpha\": 1,\n                                                            \"hex\": \"#FFFFFF\",\n                                                            \"type\": \"hex\"\n                                                          },\n                                                          \"selectors\": [\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"web\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"web\"\n                                                            }\n                                                          ]\n                                                        },\n                                                        \"font_families\": [\n                                                          \"sans-serif\"\n                                                        ],\n                                                        \"font_size\": 24,\n                                                        \"styles\": [\n                                                          \"bold\"\n                                                        ]\n                                                      },\n                                                      \"type\": \"label\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"linear_layout\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\",\n                                    \"visibility\": {\n                                      \"default\": false,\n                                      \"invert_when_state_matches\": {\n                                        \"key\": \"submitted\",\n                                        \"value\": {\n                                          \"equals\": true\n                                        }\n                                      }\n                                    }\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\",\n                              \"visibility\": {\n                                \"default\": true,\n                                \"invert_when_state_matches\": {\n                                  \"key\": \"confirmation_screen_container\",\n                                  \"value\": {\n                                    \"equals\": true\n                                  }\n                                }\n                              }\n                            }\n                          }\n                        ],\n                        \"type\": \"pager\"\n                      }\n                    },\n                    {\n                      \"position\": {\n                        \"horizontal\": \"end\",\n                        \"vertical\": \"top\"\n                      },\n                      \"size\": {\n                        \"height\": 48,\n                        \"width\": 48\n                      },\n                      \"view\": {\n                        \"button_click\": [\n                          \"dismiss\"\n                        ],\n                        \"identifier\": \"dismiss_button\",\n                        \"image\": {\n                          \"color\": {\n                            \"default\": {\n                              \"alpha\": 1,\n                              \"hex\": \"#FFFFFF\",\n                              \"type\": \"hex\"\n                            },\n                            \"selectors\": [\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#FFFFFF\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": false,\n                                \"platform\": \"ios\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#000000\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": true,\n                                \"platform\": \"ios\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#FFFFFF\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": false,\n                                \"platform\": \"android\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#000000\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": true,\n                                \"platform\": \"android\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#FFFFFF\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": false,\n                                \"platform\": \"web\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#000000\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": true,\n                                \"platform\": \"web\"\n                              }\n                            ]\n                          },\n                          \"icon\": \"close\",\n                          \"scale\": 0.4,\n                          \"type\": \"icon\"\n                        },\n                        \"localized_content_description\": {\n                          \"fallback\": \"Dismiss\",\n                          \"ref\": \"ua_dismiss\"\n                        },\n                        \"reporting_metadata\": {\n                          \"button_action\": \"dismiss\",\n                          \"button_id\": \"dismiss_button\"\n                        },\n                        \"type\": \"image_button\"\n                      }\n                    },\n                    {\n                      \"margin\": {\n                        \"bottom\": 4,\n                        \"end\": 0,\n                        \"start\": 0,\n                        \"top\": 4\n                      },\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"bottom\"\n                      },\n                      \"size\": {\n                        \"height\": 14,\n                        \"width\": \"100%\"\n                      },\n                      \"view\": {\n                        \"automated_accessibility_actions\": [\n                          {\n                            \"type\": \"announce\"\n                          }\n                        ],\n                        \"bindings\": {\n                          \"selected\": {\n                            \"shapes\": [\n                              {\n                                \"aspect_ratio\": 2,\n                                \"border\": {\n                                  \"radius\": 16\n                                },\n                                \"color\": {\n                                  \"default\": {\n                                    \"alpha\": 1,\n                                    \"hex\": \"#63AFF1\",\n                                    \"type\": \"hex\"\n                                  },\n                                  \"selectors\": [\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#63AFF1\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#63AFF1\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#63AFF1\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#63AFF1\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#63AFF1\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"web\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#63AFF1\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"web\"\n                                    }\n                                  ]\n                                },\n                                \"scale\": 1,\n                                \"type\": \"rectangle\"\n                              }\n                            ]\n                          },\n                          \"unselected\": {\n                            \"shapes\": [\n                              {\n                                \"aspect_ratio\": 1,\n                                \"color\": {\n                                  \"default\": {\n                                    \"alpha\": 1,\n                                    \"hex\": \"#487399\",\n                                    \"type\": \"hex\"\n                                  },\n                                  \"selectors\": [\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#487399\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#487399\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#487399\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#487399\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#487399\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"web\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#487399\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"web\"\n                                    }\n                                  ]\n                                },\n                                \"scale\": 0.5,\n                                \"type\": \"ellipse\"\n                              }\n                            ]\n                          }\n                        },\n                        \"spacing\": 0,\n                        \"type\": \"pager_indicator\"\n                      }\n                    }\n                  ],\n                  \"type\": \"container\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"display_type\": \"layout\",\n      \"name\": \"Validation Dec 2025: App Embedded Survey - LA Testing\"\n    },\n    \"override_limits\": true,\n    \"product_id\": \"embedded\",\n    \"queue\": \"embedded\",\n    \"reporting_context\": {\n      \"content_types\": [\n        \"scene\",\n        \"survey\",\n        \"embedded\"\n      ],\n      \"experiment_id\": \"\"\n    },\n    \"requires_eligibility\": false,\n    \"scopes\": [\n      \"app\"\n    ],\n    \"triggers\": [\n      {\n        \"goal\": 1,\n        \"predicate\": {\n          \"or\": [\n            {\n              \"and\": [\n                {\n                  \"ignore_case\": true,\n                  \"key\": \"event_name\",\n                  \"value\": {\n                    \"equals\": \"as_iaa_fullscreen\"\n                  }\n                }\n              ]\n            }\n          ]\n        },\n        \"type\": \"custom_event_count\"\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/_vimeo.ml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web: {}\nview:\n  type: pager_controller\n  identifier: 7d97a133-bef8-4c18-b52a-b7a29061768e\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          type: container\n          items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                  - identifier: 2a35dbf3-1700-4813-8e2c-78747ff4a00c\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - size:\n                            width: 100%\n                            height: 100%\n                          position:\n                            horizontal: center\n                            vertical: center\n                          ignore_safe_area: false\n                          view:\n                            type: container\n                            items:\n                              - margin:\n                                  bottom: 0\n                                  top: 0\n                                  end: 0\n                                  start: 0\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        direction: vertical\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                type: media\n                                                media_fit: center_inside\n                                                url: https://player.vimeo.com/video/714680147?autoplay=0&loop=1&controls=1&muted=1&unmute_button=0\n                                                media_type: vimeo\n                                                video:\n                                                  aspect_ratio: 1.7777777777777777\n                                                  autoplay: false\n                                                  loop: true\n                                                  muted: true\n                                                  show_controls: true\n                                              identifier: 94c693a7-7392-4ff2-b2d2-fc2d9828bf21\n                                              margin:\n                                                top: 0\n                                                bottom: 0\n                                                start: 0\n                                                end: 0\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items: []\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                        selectors:\n                          - platform: ios\n                            dark_mode: false\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                          - platform: android\n                            dark_mode: false\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: android\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                          - platform: web\n                            dark_mode: false\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: web\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n              ignore_safe_area: false\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                    selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                identifier: dismiss_button\n                button_click:\n                  - dismiss\n                localized_content_description:\n                  ref: ua_dismiss\n                  fallback: Dismiss\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/a-gif-and-youtube.yml",
    "content": "version: 1\nview:\n  type: pager_controller\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - view:\n        items:\n        - size:\n            width: 100%\n            height: 100%\n          view:\n            items:\n            - type: pager_item\n              view:\n                background_color:\n                  default:\n                    type: hex\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                  selectors:\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      hex: \"#000000\"\n                      alpha: 1\n                      type: hex\n                  - color:\n                      hex: \"#000000\"\n                      alpha: 1\n                      type: hex\n                    dark_mode: true\n                    platform: android\n                type: container\n                items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  view:\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        items:\n                        - identifier: scroll_container\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: scroll_layout\n                            direction: vertical\n                            view:\n                              type: linear_layout\n                              items:\n                              - margin:\n                                  start: 0\n                                  end: 0\n                                  top: 0\n                                  bottom: 0\n                                size:\n                                  width: 100%\n                                  height: auto\n                                view:\n                                  media_type: image\n                                  url: https://media3.giphy.com/media/tBvPFCFQHSpEI/giphy.gif\n                                  media_fit: center_inside\n                                  type: media\n                              - margin:\n                                  bottom: 0\n                                  end: 0\n                                  top: 0\n                                  start: 0\n                                view:\n                                  media_fit: center_inside\n                                  type: media\n                                  video:\n                                    muted: true\n                                    aspect_ratio: 1.7777777777777777\n                                    autoplay: false\n                                    show_controls: true\n                                    loop: false\n                                  url: https://www.youtube.com/embed/a3ICNMQW7Ok/?autoplay=0&controls=1&loop=0&mute=1\n                                  media_type: youtube\n                                size:\n                                  width: 100%\n                                  height: auto\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  items: []\n                                  direction: horizontal\n                              direction: vertical\n                        type: linear_layout\n                        background_color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            type: hex\n                            alpha: 1\n                          selectors:\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              alpha: 1\n                              type: hex\n                              hex: \"#000000\"\n                          - color:\n                              type: hex\n                              alpha: 1\n                              hex: \"#000000\"\n                            dark_mode: true\n                            platform: android\n                        direction: vertical\n                      margin:\n                        bottom: 16\n                      position:\n                        horizontal: center\n                        vertical: center\n                    type: container\n                  size:\n                    width: 100%\n                    height: 100%\n              identifier: 294acc65-f80c-4663-9840-0d48bf1972b8\n            type: pager\n            disable_swipe: true\n          ignore_safe_area: false\n          position:\n            horizontal: center\n            vertical: center\n        - size:\n            width: 48\n            height: 48\n          view:\n            identifier: dismiss_button\n            button_click:\n            - dismiss\n            type: image_button\n            image:\n              scale: 0.4\n              icon: close\n              type: icon\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 1\n                  type: hex\n                selectors:\n                - color:\n                    alpha: 1\n                    type: hex\n                    hex: \"#FFFFFF\"\n                  platform: ios\n                  dark_mode: true\n                - platform: android\n                  dark_mode: true\n                  color:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n          position:\n            horizontal: end\n            vertical: top\n        type: container\n      size:\n        width: 100%\n        height: 100%\n  identifier: e1fe3c1a-bcf9-4159-a268-c00239433c91\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    size:\n      min_width: 100%\n      min_height: 100%\n      max_height: 100%\n      height: 100%\n      width: 100%\n      max_width: 100%\n    device:\n      lock_orientation: portrait\n    shade_color:\n      default:\n        type: hex\n        alpha: 0.2\n        hex: \"#000000\"\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: top\n  type: modal\n  dismiss_on_touch_outside: false\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/a-landscape-video.yml",
    "content": "---\nversion: 1\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    device:\n      lock_orientation: landscape\n    ignore_safe_area: true\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n  dismiss_on_touch_outside: false\n  placement_selectors: []\n  type: modal\nview:\n  identifier: \"18e97cb3-36bd-42af-b1e5-6015f7b6953d\"\n  type: pager_controller\n  view:\n    items:\n      - ignore_safe_area: true\n        position:\n          horizontal: center\n          vertical: center\n        size:\n          height: 100%\n          width: 100%\n        view:\n          disable_swipe: true\n          items:\n            - identifier: \"e95c6874-9c5e-4b31-ac46-37370a0dfdfd\"\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#BFBFB0\"\n                    type: hex\n                  selectors:\n                    - color:\n                        alpha: 1\n                        hex: \"#BFBFB0\"\n                        type: hex\n                      dark_mode: true\n                      platform: ios\n                    - color:\n                        alpha: 1\n                        hex: \"#BFBFB0\"\n                        type: hex\n                      dark_mode: true\n                      platform: android\n                    - color:\n                        alpha: 1\n                        hex: \"#BFBFB0\"\n                        type: hex\n                      dark_mode: true\n                      platform: web\n                items:\n                  - ignore_safe_area: true\n                    margin:\n                      end: 0\n                      start: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      media_fit: center_crop\n                      media_type: video\n                      type: media\n                      url: \"https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/HorizBoardSmall.mp4\"\n                      video:\n                        aspect_ratio: 1.77777777777778\n                        autoplay: true\n                        loop: true\n                        muted: true\n                        show_controls: false\n                  - ignore_safe_area: true\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      items:\n                        - margin:\n                            bottom: 0\n                            end: 0\n                            start: 0\n                            top: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: vertical\n                                  type: scroll_layout\n                                  view:\n                                    direction: vertical\n                                    items:\n                                      - identifier: \"9a8d7d1b-e5f2-42b1-a443-5066e735ed06\"\n                                        margin:\n                                          bottom: 8\n                                          end: 16\n                                          start: 16\n                                          top: 48\n                                        size:\n                                          height: auto\n                                          width: 100%\n                                        view:\n                                          text: \"Enjoy a nice summer\"\n                                          text_appearance:\n                                            alignment: start\n                                            color:\n                                              default:\n                                                alpha: 1\n                                                hex: \"#000000\"\n                                                type: hex\n                                              selectors:\n                                                - color:\n                                                    alpha: 1\n                                                    hex: \"#FFFFFF\"\n                                                    type: hex\n                                                  dark_mode: true\n                                                  platform: ios\n                                                - color:\n                                                    alpha: 1\n                                                    hex: \"#FFFFFF\"\n                                                    type: hex\n                                                  dark_mode: true\n                                                  platform: android\n                                                - color:\n                                                    alpha: 1\n                                                    hex: \"#FFFFFF\"\n                                                    type: hex\n                                                  dark_mode: true\n                                                  platform: web\n                                            font_families:\n                                              - \"more fancy fonts  MORE\"\n                                            font_size: 24\n                                            styles:\n                                              - bold\n                                          type: label\n                                      - size:\n                                          height: 100%\n                                          width: 100%\n                                        view:\n                                          direction: horizontal\n                                          items: []\n                                          type: linear_layout\n                                    type: linear_layout\n                            type: linear_layout\n                      type: container\n                type: container\n          type: pager\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          height: 48\n          width: 48\n        view:\n          button_click:\n            - dismiss\n          identifier: dismiss_button\n          localized_content_description:\n            ref: \"ua_dismiss\"\n            fallback: \"cool\"\n          image:\n            color:\n              default:\n                alpha: 1\n                hex: \"#000000\"\n                type: hex\n              selectors:\n                - color:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                  dark_mode: true\n                  platform: ios\n                - color:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                  dark_mode: true\n                  platform: android\n                - color:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                  dark_mode: true\n                  platform: web\n            icon: close\n            scale: 0.4\n            type: icon\n          type: image_button\n    type: container\ndisplay_type: layout\nname: Landscape forced\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/aaa-modal-bg-image.yaml",
    "content": "---\nview:\n  type: state_controller\n  view:\n    identifier: 931a61a3-16fb-4289-8394-acbb259999bb\n    type: pager_controller\n    view:\n      items:\n      - view:\n          identifier: bef63604-5372-402c-b079-bbe47ba08868\n          response_type: user_feedback\n          form_enabled:\n          - form_submission\n          type: form_controller\n          submit: submit_event\n          view:\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              view:\n                type: pager\n                items:\n                - identifier: 212a2dd6-ac96-480f-b294-2df5216f9be7\n                  view:\n                    items:\n                    - view:\n                        type: media\n                        media_fit: center_crop\n                        url: https://hangar-dl.urbanairship.com/binary/public/VWDwdOFjRTKLRxCeXTVP6g/5a82e758-6d9b-4c4e-8e0c-925a6f7bf82d\n                        media_type: image\n                      position:\n                        vertical: center\n                        horizontal: center\n                      ignore_safe_area: false\n                      margin:\n                        end: 0\n                        start: 0\n                      size:\n                        width: 100%\n                        height: 100%\n                    - view:\n                        items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          view:\n                            items:\n                            - size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                view:\n                                  items:\n                                  - view:\n                                      type: container\n                                      items:\n                                      - view:\n                                          items:\n                                          - margin:\n                                              end: 16\n                                              start: 16\n                                              top: 8\n                                              bottom: 8\n                                            view:\n                                              items:\n                                              - size:\n                                                  width: 100%\n                                                  height: auto\n                                                margin:\n                                                  top: 0\n                                                  bottom: 8\n                                                view:\n                                                  text: Text field underneath this\n                                                    heading\n                                                  content_description: Text field\n                                                    underneath this heading\n                                                  type: label\n                                                  text_appearance:\n                                                    font_families:\n                                                    - sans-serif\n                                                    styles: []\n                                                    alignment: start\n                                                    color:\n                                                      default:\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 0.9\n                                                        type: hex\n                                                      selectors:\n                                                      - color:\n                                                          alpha: 0.9\n                                                          hex: \"#FFFFFF\"\n                                                          type: hex\n                                                        dark_mode: false\n                                                        platform: ios\n                                                      - color:\n                                                          alpha: 1\n                                                          hex: \"#000000\"\n                                                          type: hex\n                                                        platform: ios\n                                                        dark_mode: true\n                                                      - platform: android\n                                                        color:\n                                                          hex: \"#FFFFFF\"\n                                                          type: hex\n                                                          alpha: 0.9\n                                                        dark_mode: false\n                                                      - platform: android\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        dark_mode: true\n                                                      - color:\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 0.9\n                                                          type: hex\n                                                        dark_mode: false\n                                                        platform: web\n                                                      - dark_mode: true\n                                                        color:\n                                                          hex: \"#000000\"\n                                                          type: hex\n                                                          alpha: 1\n                                                        platform: web\n                                                    font_size: 20\n                                              - margin:\n                                                  bottom: 8\n                                                  top: 0\n                                                size:\n                                                  width: 100%\n                                                  height: 75\n                                                view:\n                                                  border:\n                                                    radius: 2\n                                                    stroke_color:\n                                                      selectors:\n                                                      - dark_mode: false\n                                                        color:\n                                                          alpha: 0.9\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                        platform: ios\n                                                      - dark_mode: true\n                                                        color:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                        platform: ios\n                                                      - dark_mode: false\n                                                        platform: android\n                                                        color:\n                                                          type: hex\n                                                          alpha: 0.9\n                                                          hex: \"#FFFFFF\"\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          alpha: 1\n                                                          hex: \"#000000\"\n                                                          type: hex\n                                                      - color:\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 0.9\n                                                          type: hex\n                                                        platform: web\n                                                        dark_mode: false\n                                                      - platform: web\n                                                        color:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                        dark_mode: true\n                                                      default:\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 0.9\n                                                        type: hex\n                                                    stroke_width: 1\n                                                  identifier: bb34b013-8de1-4bc5-a4fc-c241ed1b58e3\n                                                  input_type: text_multiline\n                                                  required: false\n                                                  type: text_input\n                                                  text_appearance:\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        alpha: 1\n                                                        hex: \"#000000\"\n                                                      selectors:\n                                                      - platform: ios\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        dark_mode: false\n                                                      - color:\n                                                          type: hex\n                                                          alpha: 1\n                                                          hex: \"#000000\"\n                                                        platform: ios\n                                                        dark_mode: true\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                          type: hex\n                                                      - platform: android\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        dark_mode: true\n                                                      - dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          alpha: 1\n                                                          hex: \"#000000\"\n                                                        platform: web\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          hex: \"#000000\"\n                                                          type: hex\n                                                          alpha: 1\n                                                    font_size: 14\n                                                    alignment: start\n                                              direction: vertical\n                                              type: linear_layout\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                          - margin:\n                                              top: 0\n                                              start: 0\n                                              end: 0\n                                              bottom: 0\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: container\n                                              items:\n                                              - view:\n                                                  background_color:\n                                                    default:\n                                                      alpha: 0\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                    selectors:\n                                                    - dark_mode: false\n                                                      color:\n                                                        hex: \"#000000\"\n                                                        alpha: 0\n                                                        type: hex\n                                                      platform: ios\n                                                    - dark_mode: true\n                                                      color:\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 0\n                                                        type: hex\n                                                      platform: ios\n                                                    - platform: android\n                                                      color:\n                                                        hex: \"#000000\"\n                                                        alpha: 0\n                                                        type: hex\n                                                      dark_mode: false\n                                                    - dark_mode: true\n                                                      color:\n                                                        hex: \"#FFFFFF\"\n                                                        type: hex\n                                                        alpha: 0\n                                                      platform: android\n                                                    - platform: web\n                                                      color:\n                                                        type: hex\n                                                        alpha: 0\n                                                        hex: \"#000000\"\n                                                      dark_mode: false\n                                                    - platform: web\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 0\n                                                  type: linear_layout\n                                                  items:\n                                                  - margin:\n                                                      top: 8\n                                                      bottom: 0\n                                                      end: 16\n                                                      start: 16\n                                                    view:\n                                                      direction: horizontal\n                                                      items:\n                                                      - margin:\n                                                          top: 4\n                                                          bottom: 16\n                                                          end: 0\n                                                          start: 0\n                                                        size:\n                                                          width: 100%\n                                                          height: auto\n                                                        view:\n                                                          reporting_metadata:\n                                                            trigger_link_id: f21a0ca9-0330-45f1-92e2-cbe45b7a5c19\n                                                          border:\n                                                            stroke_width: 20\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 0.05\n                                                              selectors:\n                                                              - color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 0.05\n                                                                platform: ios\n                                                                dark_mode: false\n                                                              - color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 0\n                                                                platform: ios\n                                                                dark_mode: true\n                                                              - platform: android\n                                                                color:\n                                                                  hex: \"#000000\"\n                                                                  type: hex\n                                                                  alpha: 0.05\n                                                                dark_mode: false\n                                                              - dark_mode: true\n                                                                color:\n                                                                  hex: \"#FFFFFF\"\n                                                                  type: hex\n                                                                  alpha: 0\n                                                                platform: android\n                                                              - dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 0.05\n                                                                platform: web\n                                                              - color:\n                                                                  alpha: 0\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                dark_mode: true\n                                                                platform: web\n                                                            radius: 30\n                                                          button_click:\n                                                          - form_submit\n                                                          - dismiss\n                                                          enabled:\n                                                          - form_validation\n                                                          identifier: submit_feedback--A\n                                                            button with a border\n                                                          background_color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#63AFF1\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                                type: hex\n                                                            - color:\n                                                                type: hex\n                                                                alpha: 1\n                                                                hex: \"#63AFF1\"\n                                                              platform: ios\n                                                              dark_mode: true\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                                type: hex\n                                                            - platform: android\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                                type: hex\n                                                              dark_mode: true\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                                type: hex\n                                                            - color:\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                                type: hex\n                                                              platform: web\n                                                              dark_mode: true\n                                                          event_handlers:\n                                                          - state_actions:\n                                                            - key: submitted\n                                                              type: set\n                                                              value: true\n                                                            type: tap\n                                                          label:\n                                                            content_description: A\n                                                              button with a border\n                                                            text_appearance:\n                                                              font_size: 16\n                                                              styles: []\n                                                              font_families:\n                                                              - sans-serif\n                                                              alignment: center\n                                                              color:\n                                                                selectors:\n                                                                - color:\n                                                                    type: hex\n                                                                    hex: \"#000000\"\n                                                                    alpha: 0.95\n                                                                  platform: ios\n                                                                  dark_mode: false\n                                                                - dark_mode: true\n                                                                  platform: ios\n                                                                  color:\n                                                                    alpha: 1\n                                                                    hex: \"#FFFFFF\"\n                                                                    type: hex\n                                                                - platform: android\n                                                                  dark_mode: false\n                                                                  color:\n                                                                    alpha: 0.95\n                                                                    type: hex\n                                                                    hex: \"#000000\"\n                                                                - platform: android\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    alpha: 1\n                                                                    hex: \"#FFFFFF\"\n                                                                    type: hex\n                                                                - color:\n                                                                    type: hex\n                                                                    alpha: 0.95\n                                                                    hex: \"#000000\"\n                                                                  dark_mode: false\n                                                                  platform: web\n                                                                - dark_mode: true\n                                                                  platform: web\n                                                                  color:\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                                    type: hex\n                                                                default:\n                                                                  hex: \"#000000\"\n                                                                  type: hex\n                                                                  alpha: 0.95\n                                                            type: label\n                                                            text: A button with a\n                                                              border\n                                                          type: label_button\n                                                          actions: {}\n                                                      type: linear_layout\n                                                      background_color:\n                                                        default:\n                                                          type: hex\n                                                          alpha: 0\n                                                          hex: \"#000000\"\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            hex: \"#000000\"\n                                                            alpha: 0\n                                                            type: hex\n                                                        - platform: ios\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 0\n                                                          dark_mode: true\n                                                        - dark_mode: false\n                                                          platform: android\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 0\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            hex: \"#FFFFFF\"\n                                                            type: hex\n                                                            alpha: 0\n                                                        - dark_mode: false\n                                                          color:\n                                                            hex: \"#000000\"\n                                                            alpha: 0\n                                                            type: hex\n                                                          platform: web\n                                                        - platform: web\n                                                          color:\n                                                            alpha: 0\n                                                            hex: \"#FFFFFF\"\n                                                            type: hex\n                                                          dark_mode: true\n                                                    size:\n                                                      width: 100%\n                                                      height: auto\n                                                  - view:\n                                                      direction: horizontal\n                                                      type: linear_layout\n                                                      background_color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 0\n                                                        selectors:\n                                                        - color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 0\n                                                          platform: ios\n                                                          dark_mode: false\n                                                        - dark_mode: true\n                                                          color:\n                                                            alpha: 0\n                                                            hex: \"#FFFFFF\"\n                                                            type: hex\n                                                          platform: ios\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            alpha: 0\n                                                            hex: \"#000000\"\n                                                        - dark_mode: true\n                                                          color:\n                                                            alpha: 0\n                                                            hex: \"#FFFFFF\"\n                                                            type: hex\n                                                          platform: android\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            alpha: 0\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                        - color:\n                                                            alpha: 0\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                          dark_mode: true\n                                                          platform: web\n                                                      items:\n                                                      - size:\n                                                          height: auto\n                                                          width: 100%\n                                                        margin:\n                                                          bottom: 16\n                                                          start: 0\n                                                          end: 0\n                                                          top: 4\n                                                        view:\n                                                          border:\n                                                            radius: 0\n                                                            stroke_color:\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: false\n                                                                color:\n                                                                  alpha: 1\n                                                                  type: hex\n                                                                  hex: \"#63AFF1\"\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  alpha: 1\n                                                                  type: hex\n                                                                  hex: \"#63AFF1\"\n                                                              - dark_mode: false\n                                                                platform: android\n                                                                color:\n                                                                  alpha: 1\n                                                                  hex: \"#63AFF1\"\n                                                                  type: hex\n                                                              - color:\n                                                                  type: hex\n                                                                  hex: \"#63AFF1\"\n                                                                  alpha: 1\n                                                                platform: android\n                                                                dark_mode: true\n                                                              - color:\n                                                                  type: hex\n                                                                  alpha: 1\n                                                                  hex: \"#63AFF1\"\n                                                                platform: web\n                                                                dark_mode: false\n                                                              - color:\n                                                                  hex: \"#63AFF1\"\n                                                                  type: hex\n                                                                  alpha: 1\n                                                                dark_mode: true\n                                                                platform: web\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                            stroke_width: 0\n                                                          type: label_button\n                                                          enabled: []\n                                                          identifier: dismiss--Rectangle\n                                                            button\n                                                          background_color:\n                                                            selectors:\n                                                            - dark_mode: false\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                type: hex\n                                                                alpha: 1\n                                                              platform: ios\n                                                            - platform: ios\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                type: hex\n                                                                alpha: 1\n                                                              dark_mode: true\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                alpha: 1\n                                                                type: hex\n                                                                hex: \"#63AFF1\"\n                                                            - dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                              platform: android\n                                                            - color:\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                                type: hex\n                                                              dark_mode: false\n                                                              platform: web\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                alpha: 1\n                                                                hex: \"#63AFF1\"\n                                                                type: hex\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#63AFF1\"\n                                                              alpha: 1\n                                                          actions: {}\n                                                          button_click:\n                                                          - dismiss\n                                                          label:\n                                                            text_appearance:\n                                                              styles: []\n                                                              font_families:\n                                                              - sans-serif\n                                                              color:\n                                                                default:\n                                                                  hex: \"#FFFFFF\"\n                                                                  type: hex\n                                                                  alpha: 1\n                                                                selectors:\n                                                                - color:\n                                                                    alpha: 1\n                                                                    hex: \"#FFFFFF\"\n                                                                    type: hex\n                                                                  platform: ios\n                                                                  dark_mode: false\n                                                                - dark_mode: true\n                                                                  platform: ios\n                                                                  color:\n                                                                    hex: \"#000000\"\n                                                                    type: hex\n                                                                    alpha: 1\n                                                                - platform: android\n                                                                  dark_mode: false\n                                                                  color:\n                                                                    alpha: 1\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                - color:\n                                                                    type: hex\n                                                                    alpha: 1\n                                                                    hex: \"#000000\"\n                                                                  dark_mode: true\n                                                                  platform: android\n                                                                - color:\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                                    type: hex\n                                                                  platform: web\n                                                                  dark_mode: false\n                                                                - dark_mode: true\n                                                                  platform: web\n                                                                  color:\n                                                                    alpha: 1\n                                                                    hex: \"#000000\"\n                                                                    type: hex\n                                                              alignment: center\n                                                              font_size: 16\n                                                            type: label\n                                                            content_description: Rectangle\n                                                              button\n                                                            text: Rectangle button\n                                                          reporting_metadata:\n                                                            trigger_link_id: 04d91d55-6b2d-433b-91c0-fe802e8c476c\n                                                    margin:\n                                                      bottom: 0\n                                                      start: 16\n                                                      end: 16\n                                                      top: 8\n                                                    size:\n                                                      height: auto\n                                                      width: 100%\n                                                  direction: vertical\n                                                position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 100%\n                                                  height: auto\n                                                margin:\n                                                  bottom: 0\n                                                  end: 0\n                                                  top: 0\n                                                  start: 0\n                                          background_color:\n                                            selectors:\n                                            - color:\n                                                alpha: 0\n                                                hex: \"#000000\"\n                                                type: hex\n                                              platform: ios\n                                              dark_mode: false\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                alpha: 0\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                            - color:\n                                                hex: \"#000000\"\n                                                type: hex\n                                                alpha: 0\n                                              platform: android\n                                              dark_mode: false\n                                            - color:\n                                                alpha: 0\n                                                hex: \"#FFFFFF\"\n                                                type: hex\n                                              platform: android\n                                              dark_mode: true\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                alpha: 0\n                                                type: hex\n                                                hex: \"#000000\"\n                                            - dark_mode: true\n                                              color:\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                                type: hex\n                                              platform: web\n                                            default:\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                              type: hex\n                                          direction: vertical\n                                          type: linear_layout\n                                        margin:\n                                          top: 0\n                                          bottom: 0\n                                          start: 0\n                                          end: 0\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        position:\n                                          horizontal: center\n                                          vertical: center\n                                    margin:\n                                      start: 20\n                                      top: 44\n                                      end: 20\n                                      bottom: 0\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                  - size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      direction: horizontal\n                                      items: []\n                                      type: linear_layout\n                                  direction: vertical\n                                  type: linear_layout\n                                direction: vertical\n                                type: scroll_layout\n                            direction: vertical\n                            type: linear_layout\n                          margin:\n                            top: 0\n                            end: 0\n                            start: 0\n                            bottom: 0\n                          size:\n                            width: 100%\n                            height: 100%\n                        type: container\n                      size:\n                        width: 100%\n                        height: 100%\n                      ignore_safe_area: false\n                      position:\n                        horizontal: center\n                        vertical: center\n                    background_color:\n                      selectors:\n                      - dark_mode: false\n                        color:\n                          alpha: 1\n                          type: hex\n                          hex: \"#63AFF1\"\n                        platform: ios\n                      - platform: ios\n                        color:\n                          type: hex\n                          alpha: 1\n                          hex: \"#63AFF1\"\n                        dark_mode: true\n                      - color:\n                          type: hex\n                          alpha: 1\n                          hex: \"#63AFF1\"\n                        platform: android\n                        dark_mode: false\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#63AFF1\"\n                          alpha: 1\n                      - dark_mode: false\n                        color:\n                          alpha: 1\n                          type: hex\n                          hex: \"#63AFF1\"\n                        platform: web\n                      - color:\n                          alpha: 1\n                          type: hex\n                          hex: \"#63AFF1\"\n                        platform: web\n                        dark_mode: true\n                      default:\n                        hex: \"#63AFF1\"\n                        type: hex\n                        alpha: 1\n                    type: container\n                disable_swipe: true\n              ignore_safe_area: false\n              size:\n                height: 100%\n                width: 100%\n            - view:\n                button_click:\n                - dismiss\n                image:\n                  icon: close\n                  color:\n                    selectors:\n                    - dark_mode: false\n                      platform: ios\n                      color:\n                        alpha: 1\n                        type: hex\n                        hex: \"#BCBDC2\"\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        alpha: 0.5\n                        hex: \"#FFFFFF\"\n                    - dark_mode: false\n                      color:\n                        hex: \"#BCBDC2\"\n                        alpha: 1\n                        type: hex\n                      platform: android\n                    - platform: android\n                      color:\n                        alpha: 0.5\n                        type: hex\n                        hex: \"#FFFFFF\"\n                      dark_mode: true\n                    - color:\n                        type: hex\n                        hex: \"#BCBDC2\"\n                        alpha: 1\n                      platform: web\n                      dark_mode: false\n                    - dark_mode: true\n                      platform: web\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 0.5\n                    default:\n                      alpha: 1\n                      type: hex\n                      hex: \"#BCBDC2\"\n                  scale: 0.4\n                  type: icon\n                type: image_button\n                identifier: dismiss_button\n              size:\n                width: 48\n                height: 48\n              position:\n                vertical: top\n                horizontal: end\n            type: container\n        size:\n          width: 100%\n          height: 100%\n      direction: vertical\n      type: linear_layout\nversion: 1\npresentation:\n  placement_selectors: []\n  default_placement:\n    border:\n      radius: 30\n    size:\n      width: 80%\n      height: 80%\n    ignore_safe_area: false\n    shade_color:\n      default:\n        alpha: 0.2\n        type: hex\n        hex: \"#000000\"\n      selectors:\n      - platform: ios\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#000000\"\n          alpha: 0.2\n      - platform: ios\n        color:\n          hex: \"#FFFFFF\"\n          type: hex\n          alpha: 0\n        dark_mode: true\n      - dark_mode: false\n        platform: android\n        color:\n          hex: \"#000000\"\n          type: hex\n          alpha: 0.2\n      - color:\n          alpha: 0\n          type: hex\n          hex: \"#FFFFFF\"\n        platform: android\n        dark_mode: true\n      - platform: web\n        dark_mode: false\n        color:\n          hex: \"#000000\"\n          alpha: 0.2\n          type: hex\n      - platform: web\n        color:\n          type: hex\n          alpha: 0\n          hex: \"#FFFFFF\"\n        dark_mode: true\n    position:\n      vertical: center\n      horizontal: center\n  type: modal\n  dismiss_on_touch_outside: false\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/aaa-modal.json",
    "content": "{\"in_app_message\": {\"message\": {\"name\": \"meghan radio button test 2\", \"display_type\": \"layout\", \"display\": {\"layout\": {\"version\": 1, \"presentation\": {\"type\": \"modal\", \"placement_selectors\": [{\"placement\": {\"ignore_safe_area\": false, \"size\": {\"width\": \"60%\", \"height\": \"40%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}]}, \"web\": {\"ignore_shade\": false}, \"border\": {\"radius\": 10, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}}, \"window_size\": \"large\", \"orientation\": \"portrait\"}, {\"placement\": {\"ignore_safe_area\": false, \"size\": {\"width\": \"70%\", \"height\": \"80%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}]}, \"web\": {\"ignore_shade\": false}, \"border\": {\"radius\": 10, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}}, \"window_size\": \"small\", \"orientation\": \"landscape\"}, {\"placement\": {\"ignore_safe_area\": false, \"size\": {\"width\": \"70%\", \"height\": \"80%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}]}, \"web\": {\"ignore_shade\": false}, \"border\": {\"radius\": 10, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}}, \"window_size\": \"medium\", \"orientation\": \"landscape\"}, {\"placement\": {\"ignore_safe_area\": false, \"size\": {\"width\": \"40%\", \"height\": \"70%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}]}, \"web\": {\"ignore_shade\": false}, \"border\": {\"radius\": 10, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}}, \"window_size\": \"large\", \"orientation\": \"landscape\"}], \"android\": {\"disable_back_button\": false}, \"dismiss_on_touch_outside\": false, \"default_placement\": {\"ignore_safe_area\": false, \"size\": {\"width\": \"90%\", \"height\": \"65%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"shade_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 0.2}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#989893\", \"alpha\": 1}}]}, \"web\": {}, \"border\": {\"radius\": 14, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}}}, \"view\": {\"type\": \"state_controller\", \"view\": {\"type\": \"pager_controller\", \"identifier\": \"6a823111-dd55-4a38-af6c-20a4b175f47e\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"identifier\": \"d58cb520-9ae9-46f8-a032-a6983811e77a\", \"type\": \"form_controller\", \"form_enabled\": [\"form_submission\"], \"submit\": \"submit_event\", \"response_type\": \"user_feedback\", \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"c4517e2f-d34f-413b-a96f-fedd1c41cde5_pager_container_item\", \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"pager\", \"disable_swipe\": true, \"items\": [{\"identifier\": \"3636f2bc-de6e-436d-8e92-b46da805e631\", \"type\": \"pager_item\", \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"d098d881-1bee-4877-a8ed-e36e1c4ffe9f_main_view_container_item\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"ignore_safe_area\": false, \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"6df6d847-7533-416a-9531-fd257c493fe5_container_item\", \"margin\": {\"bottom\": 0, \"top\": 0, \"end\": 0, \"start\": 0}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"scroll_container\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"scroll_layout\", \"direction\": \"vertical\", \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"c4904f51-6c58-4095-868e-327fb5e8e8d7\", \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"margin\": {\"end\": 16, \"top\": 28, \"start\": 16, \"bottom\": 8}, \"view\": {\"type\": \"label\", \"text\": \"The New Marketing Mindset\", \"content_description\": \"The New Marketing Mindset\", \"text_appearance\": {\"font_size\": 24, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"center\", \"styles\": [\"bold\"], \"font_families\": [\"sans-serif\"]}, \"accessibility_hidden\": false}}, {\"identifier\": \"bd516cef-c48e-48aa-9c21-78ab698d41e4\", \"size\": {\"width\": \"70%\", \"height\": 150}, \"view\": {\"type\": \"media\", \"media_fit\": \"center_inside\", \"url\": \"https://c00001-dl.asnapieu.com/binary/public/YtDP8p2vSbW-KQkjUVsBAw/3c5dc557-cb4c-4e5b-9613-d159206fcba0\", \"media_type\": \"image\"}, \"margin\": {\"end\": 0, \"top\": 0, \"start\": 0, \"bottom\": 0}}, {\"identifier\": \"65054c82-adca-493f-b6f5-5ebf3a43c54d\", \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"margin\": {\"end\": 16, \"top\": 10, \"start\": 16, \"bottom\": 8}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"margin\": {\"top\": 0, \"bottom\": 8}, \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"label\", \"text\": \"Who shall we interview in our next episode:\", \"content_description\": \"Who shall we interview in our next episode:\", \"text_appearance\": {\"font_size\": 16, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"start\", \"styles\": [\"bold\"], \"font_families\": [\"sans-serif\"]}, \"accessibility_hidden\": false, \"identifier\": \"65054c82-adca-493f-b6f5-5ebf3a43c54d_title\"}}, {\"margin\": {\"top\": 0, \"bottom\": 25}, \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"radio_input_controller\", \"identifier\": \"65054c82-adca-493f-b6f5-5ebf3a43c54d\", \"required\": false, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"size\": {\"height\": \"100%\", \"width\": \"100%\"}, \"margin\": {\"top\": 0, \"bottom\": 8}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": [{\"margin\": {\"end\": 8}, \"size\": {\"width\": 20, \"height\": 20}, \"view\": {\"type\": \"radio_input\", \"reporting_value\": \"5754a361-1898-4156-b77a-74284d19d10a\", \"content_description\": \"Dex Varnos\", \"style\": {\"type\": \"checkbox\", \"bindings\": {\"selected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"border\": {\"radius\": 20, \"stroke_width\": 2, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}}, {\"type\": \"ellipse\", \"scale\": 0.6, \"aspect_ratio\": 1, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}]}, \"unselected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"border\": {\"radius\": 20, \"stroke_width\": 2, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}}]}}}}}, {\"margin\": {\"end\": 16}, \"size\": {\"height\": \"auto\", \"width\": \"100%\"}, \"view\": {\"type\": \"label\", \"text\": \"Dex Varnos\", \"content_description\": \"Dex Varnos\", \"text_appearance\": {\"font_size\": 14, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"start\", \"styles\": [], \"font_families\": [\"sans-serif\"]}, \"accessibility_hidden\": false}}]}}, {\"size\": {\"height\": \"100%\", \"width\": \"100%\"}, \"margin\": {\"top\": 0, \"bottom\": 8}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": [{\"margin\": {\"end\": 8}, \"size\": {\"width\": 20, \"height\": 20}, \"view\": {\"type\": \"radio_input\", \"reporting_value\": \"d6a916a7-561e-4753-862c-fc446e1eb3a0\", \"content_description\": \"Zane Kolmec\", \"style\": {\"type\": \"checkbox\", \"bindings\": {\"selected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"border\": {\"radius\": 20, \"stroke_width\": 2, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}}, {\"type\": \"ellipse\", \"scale\": 0.6, \"aspect_ratio\": 1, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}]}, \"unselected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"border\": {\"radius\": 20, \"stroke_width\": 2, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}}]}}}}}, {\"margin\": {\"end\": 16}, \"size\": {\"height\": \"auto\", \"width\": \"100%\"}, \"view\": {\"type\": \"label\", \"text\": \"Zane Kolmec\", \"content_description\": \"Zane Kolmec\", \"text_appearance\": {\"font_size\": 14, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"start\", \"styles\": [], \"font_families\": [\"sans-serif\"]}, \"accessibility_hidden\": false}}]}}, {\"size\": {\"height\": \"100%\", \"width\": \"100%\"}, \"margin\": {\"top\": 0, \"bottom\": 8}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": [{\"margin\": {\"end\": 8}, \"size\": {\"width\": 20, \"height\": 20}, \"view\": {\"type\": \"radio_input\", \"reporting_value\": \"343d0109-c9a9-419e-95ec-66d6f9f6c400\", \"content_description\": \"Brett Gavix\", \"style\": {\"type\": \"checkbox\", \"bindings\": {\"selected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"border\": {\"radius\": 20, \"stroke_width\": 2, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}}, {\"type\": \"ellipse\", \"scale\": 0.6, \"aspect_ratio\": 1, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}]}, \"unselected\": {\"shapes\": [{\"type\": \"ellipse\", \"scale\": 1, \"aspect_ratio\": 1, \"border\": {\"radius\": 20, \"stroke_width\": 2, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}}, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}}]}}}}}, {\"margin\": {\"end\": 16}, \"size\": {\"height\": \"auto\", \"width\": \"100%\"}, \"view\": {\"type\": \"label\", \"text\": \"Brett Gavix\", \"content_description\": \"Brett Gavix\", \"text_appearance\": {\"font_size\": 14, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}, \"alignment\": \"start\", \"styles\": [], \"font_families\": [\"sans-serif\"]}, \"accessibility_hidden\": false}}]}}], \"randomize_children\": false}, \"attribute_name\": {}}}]}}, {\"identifier\": \"9b955192-e538-4c4b-9c1a-14c64074904c_linear_layout_item\", \"size\": {\"width\": \"100%\", \"height\": \"100%\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"horizontal\", \"items\": []}}]}}}, {\"identifier\": \"25c2cf58-1629-4962-94e3-dca0e3bd292a\", \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"container\", \"items\": [{\"identifier\": \"25c2cf58-1629-4962-94e3-dca0e3bd292a_linear_container\", \"margin\": {\"top\": 0, \"bottom\": 0, \"start\": 0, \"end\": 0}, \"position\": {\"horizontal\": \"center\", \"vertical\": \"center\"}, \"size\": {\"width\": \"100%\", \"height\": \"auto\"}, \"view\": {\"type\": \"linear_layout\", \"direction\": \"vertical\", \"items\": [{\"identifier\": \"27ebefe0-1520-4c59-93e8-6ffd70496151\", \"margin\": {\"end\": 16, \"top\": 8, \"start\": 16, \"bottom\": 8}, \"size\": {\"width\": \"50%\", \"height\": \"auto\"}, \"view\": {\"type\": \"label_button\", \"identifier\": \"submit_feedback--Submit\", \"reporting_metadata\": {\"trigger_link_id\": \"27ebefe0-1520-4c59-93e8-6ffd70496151\", \"button_id\": \"27ebefe0-1520-4c59-93e8-6ffd70496151\", \"button_action\": \"submit_feedback\"}, \"label\": {\"type\": \"label\", \"text\": \"Submit\", \"content_description\": \"Submit\", \"text_appearance\": {\"font_size\": 16, \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}, \"alignment\": \"center\", \"styles\": [\"bold\"], \"font_families\": [\"sans-serif\"]}, \"view_overrides\": {\"icon_start\": [{\"value\": {\"type\": \"floating\", \"space\": 8, \"icon\": {\"type\": \"icon\", \"icon\": \"progress_spinner\", \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}, \"scale\": 1}}, \"when_state_matches\": {\"scope\": [\"$forms\", \"current\", \"status\", \"type\"], \"value\": {\"equals\": \"validating\"}}}], \"text\": [{\"value\": \"Submit\", \"when_state_matches\": {\"scope\": [\"$forms\", \"current\", \"status\", \"type\"], \"value\": {\"equals\": \"validating\"}}}]}}, \"actions\": {}, \"enabled\": [\"form_validation\"], \"button_click\": [\"form_submit\", \"dismiss\"], \"background_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}]}, \"border\": {\"radius\": 5, \"stroke_width\": 0, \"stroke_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#2A55DD\", \"alpha\": 1}}]}}, \"event_handlers\": [{\"type\": \"tap\", \"state_actions\": [{\"type\": \"set\", \"key\": \"submitted\", \"value\": true}]}], \"content_description\": \"Submit\"}}], \"border\": {\"radius\": 0}}}]}, \"margin\": {\"top\": 20, \"bottom\": 16, \"start\": 0, \"end\": 0}}]}}]}}], \"background_color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#FFFFFF\", \"alpha\": 1}}]}}, \"state_actions\": [{\"type\": \"set\", \"key\": \"3636f2bc-de6e-436d-8e92-b46da805e631_next\"}]}]}, \"ignore_safe_area\": false}, {\"position\": {\"horizontal\": \"end\", \"vertical\": \"top\"}, \"size\": {\"width\": 48, \"height\": 48}, \"view\": {\"type\": \"image_button\", \"image\": {\"scale\": 0.4, \"type\": \"icon\", \"icon\": \"close\", \"color\": {\"default\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}, \"selectors\": [{\"platform\": \"ios\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"ios\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"android\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": false, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}, {\"platform\": \"web\", \"dark_mode\": true, \"color\": {\"type\": \"hex\", \"hex\": \"#000000\", \"alpha\": 1}}]}}, \"identifier\": \"dismiss_button\", \"button_click\": [\"dismiss\"], \"localized_content_description\": {\"ref\": \"ua_dismiss\", \"fallback\": \"Dismiss\"}, \"reporting_metadata\": {\"button_id\": \"dismiss_button\", \"button_action\": \"dismiss\"}}}]}}}]}}}}}, \"audience\": {\"miss_behavior\": \"penalize\", \"tags\": {\"and\": [{\"tag\": \"meghan\"}]}}}, \"triggers\": [{\"type\": \"custom_event_count\", \"goal\": 1, \"predicate\": {\"or\": [{\"and\": [{\"key\": \"event_name\", \"ignore_case\": true, \"value\": {\"equals\": \"radio-new\"}}]}]}}], \"edit_grace_period\": 14, \"scopes\": [\"app\", \"web\"], \"reporting_context\": {\"content_types\": [\"scene\", \"survey\"], \"experiment_id\": \"\", \"message_type\": \"transactional\"}, \"limit\": 0, \"interval\": 5, \"forms\": [{\"id\": \"d58cb520-9ae9-46f8-a032-a6983811e77a\", \"type\": \"form\", \"questions\": [{\"id\": \"65054c82-adca-493f-b6f5-5ebf3a43c54d\", \"label\": \"Who shall we interview in our next episode:\", \"type\": \"radio\", \"responses\": [{\"draft\": \"Dex Varnos\", \"label\": \"Dex Varnos\", \"value\": \"5754a361-1898-4156-b77a-74284d19d10a\"}, {\"draft\": \"Zane Kolmec\", \"label\": \"Zane Kolmec\", \"value\": \"d6a916a7-561e-4753-862c-fc446e1eb3a0\"}, {\"draft\": \"Brett Gavix\", \"label\": \"Brett Gavix\", \"value\": \"343d0109-c9a9-419e-95ec-66d6f9f6c400\"}]}], \"response_type\": \"user_feedback\"}], \"labels\": [{\"id\": \"3636f2bc-de6e-436d-8e92-b46da805e631\", \"label\": \"Screen 1\"}], \"requires_eligibility\": false, \"message_type\": \"transactional\"}}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/accessibility-action-story.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\n    ignore_safe_area: true\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    border:\n      radius: 30\n      stroke_width: 2\n      stroke_color:\n        default:\n          hex: \"#333333\"\n          alpha: 0.8\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: 100%\n      margin:\n        top: 0\n        bottom: 0\n        start: 0\n        end: 0\n      view:\n        border:\n          stroke_width: 1\n        media_fit: center_crop\n        media_type: image\n        type: media\n        url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/bc692c5f-09ce-4ea4-bb55-108b1b5d28a8\n    - position:\n        vertical: center\n        horizontal: center\n      size:\n        height: 100%\n        width: 100%\n      border:\n        radius: 25\n      view:\n        type: pager\n        gestures:\n        - type: tap\n          identifier: \"tap-gesture-start-id\"\n          location: start\n          behavior:\n            behaviors:\n            - pager_previous\n        - type: tap\n          identifier: \"tap-gesture-end-id\"\n          location: end\n          behavior:\n            behaviors:\n            - pager_next\n        - type: hold\n          identifier: \"hold-gesture-any-id\"\n          press_behavior:\n            behaviors:\n            - pager_pause\n          release_behavior:\n            behaviors:\n            - pager_resume\n        items:\n        - identifier: \"pager-page-1-id\"\n          automated_actions:\n          - delay: 0\n            identifier: \"automated-action-1-delay0-id\"\n            actions:\n            - add_tags_action: 'pager-page-1x-automated'\n            - add_tags_action: 'pager-page-1y-automated'\n          - delay: 4\n            identifier: \"automated-action-1-delay4-id\"\n            reporting_metadata:\n              \"key1\": \"value1\"\n              \"key2\": \"value2\"\n            behaviors:\n            - pager_next\n          display_actions:\n            add_tags_action: 'pager-page-1x'\n          accessibility_actions:\n            - type: default\n              reporting_metadata:\n                key: \"page_1_next\"\n              localized_content_description:\n                fallback: \"Next Page\"\n                ref: \"ua_next\"\n              actions:\n                - add_tags_action: \"page_1_next_action\"\n              behaviors: \n                - pager_next\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#FF0000\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: label\n                text: This is the first page about stuff.\n                accessibility_role:\n                  type: heading\n                  level: 1\n                text_appearance:\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                  font_size: 14\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_1\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n        - identifier: \"pager-page-2-id\"\n          automated_actions:\n          - delay: 1.03\n            identifier: \"automated-action-2-delay1-id\"\n            actions:\n            - add_tags_action: 'pager-page-2x-automated'\n          - delay: 6\n            identifier: \"automated-action-2-delay6-id\"\n            behaviors:\n            - pager_next\n          display_actions:\n            add_tags_action: 'pager-page-2x'\n          accessibility_actions:\n            - type: default\n              reporting_metadata:\n                key: \"page_2_next\"\n              localized_content_description:\n                fallback: \"Next Page\"\n                ref: \"ua_next\"\n              actions:\n                - add_tags_action: \"page_2_next_action\"\n              behaviors: \n                - pager_next\n            - type: default\n              reporting_metadata:\n                key: \"page_2_previous\"\n              localized_content_description:\n                fallback: \"Previous Page\"\n                ref: \"ua_previous\"\n              actions:\n                - add_tags_action: \"page_2_previous_action\"\n              behaviors: \n                - pager_previous\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#00FF00\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: label\n                text: More stuff is here on the second page.\n                accessibility_role:\n                  type: heading\n                  level: 2\n                text_appearance:\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                  font_size: 14\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_2\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n        - identifier: \"pager-page-3-id\"\n          automated_actions:\n          - delay: 4.01\n            identifier: \"automated-action-3-delay4-id\"\n            actions:\n            - add_tags_action: 'pager-page-3x-automated'\n          - delay: 8\n            identifier: \"automated-action-3-delay8-id\"\n            behaviors:\n            - pager_next_or_first\n            - form_submit\n          display_actions:\n            add_tags_action: 'pager-page-3x'\n          accessibility_actions:\n            - type: default\n              reporting_metadata:\n                key: \"page_3_previous\"\n              localized_content_description:\n                fallback: \"Previous Page\"\n                ref: \"ua_previous\"\n              actions:\n                - add_tags_action: \"page_3_previous_action\"\n              behaviors: \n                - pager_previous\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#0000FF\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: nps_form_controller\n                identifier: page_3_nps_form\n                nps_identifier: score_identifier\n                submit: submit_event\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - size:\n                      width: 100%\n                      height: auto\n                    margin:\n                      start: 8\n                      end: 8\n                    view:\n                      type: score\n                      identifier: score_identifier\n                      required: true\n                      style:\n                        type: number_range\n                        start: 0\n                        end: 10\n                        spacing: 2\n                        bindings:\n                          selected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#FFFFFF\"\n                                  alpha: 0\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                          unselected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                  - size:\n                      width: auto\n                      height: auto\n                    margin:\n                      start: 16\n                      end: 16\n                    view:\n                      type: label_button\n                      identifier: submit_button\n                      background_color:\n                        default:\n                          hex: \"#ffffff\"\n                          alpha: 1\n                      button_click: [\"form_submit\", \"cancel\"]\n                      enabled: [\"form_validation\"]\n                      display_actions:\n                        add_tags_action: 'pager-page-3-form-submit'\n                      label:\n                        type: label\n                        text: SuBmIt!1!1@\n                        text_appearance:\n                          font_size: 14\n                          alignment: center\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 1\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_3\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n    - size:\n        height: 10\n        width: 80%\n      position:\n        vertical: top\n        horizontal: center\n      margin:\n        bottom: 8\n      view:\n        type: story_indicator\n        source:\n          type: pager\n        style:\n          type: linear_progress\n          direction: horizontal\n          sizing: equal\n          spacing: 4\n          inactive_segment_scaler: 0.2\n          progress_color:\n            default:\n              hex: \"#E6E6FA\"\n              alpha: 1\n          track_color:\n            default:\n              hex: \"#E6E6FA\"\n              alpha: 0.7\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/accessibility-actions-modal-pager-fullsize.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: 80%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    border:\n      radius: 30\n      stroke_width: 20\n      stroke_color:\n        default:\n          hex: \"#333333\"\n          alpha: 0.8\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n    - position:\n        vertical: center\n        horizontal: center\n      size:\n        height: 100%\n        width: 100%\n      border:\n        radius: 25\n      view:\n        type: pager\n        items:\n        - identifier: \"pager-page-1-id\"\n          display_actions:\n            add_tags_action: 'pager-page-1x'\n          accessibility_actions:\n            - type: default\n              reporting_metadata:\n                key: \"page_1_next\"\n              localized_content_description:\n                fallback: \"Next Page\"\n                ref: \"ua_next\"\n              actions:\n                - add_tags_action: \"page_1_next_action\"\n              behaviors: \n                - pager_next\n            - type: escape\n              reporting_metadata:\n                key: \"page_1_escape\"\n              localized_content_description:\n                fallback: \"Dismiss\"\n                ref: \"ua_escape\"\n              actions:\n                - add_tags_action: \"page_1_dismiss_action\"\n              behaviors: \n                - dismiss\n          automated_actions:\n            - identifier: \"auto_announce_page_1\"\n              delay: 0.5\n              behaviors: [\"pager_pause\"]\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#FF0000\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: nps_form_controller\n                identifier: page_1_nps_form\n                nps_identifier: score_identifier_1\n                submit: submit_event\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - size:\n                      width: auto\n                      height: auto\n                    margin:\n                      start: 8\n                      end: 8\n                    view:\n                      type: score\n                      identifier: score_identifier_1\n                      required: true\n                      style:\n                        type: number_range\n                        start: 0\n                        end: 10\n                        spacing: 0\n                        wrapping:\n                          line_spacing: 0\n                          max_items_per_line: 11\n                        spacing: 2\n                        bindings:\n                          selected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#FFFFFF\"\n                                  alpha: 0\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                          unselected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                      content_description: \"Rate your experience from 0 to 10\"\n                  - size:\n                      width: auto\n                      height: auto\n                    margin:\n                      start: 16\n                      end: 16\n                    view:\n                      type: label_button\n                      identifier: submit_button\n                      background_color:\n                        default:\n                          hex: \"#ffffff\"\n                          alpha: 1\n                      button_click: [\"form_submit\", \"cancel\"]\n                      enabled: [\"form_validation\"]\n                      display_actions:\n                        add_tags_action: 'pager-page-1-form-submit'\n                      label:\n                        type: label\n                        text: width:auto height:auto\n                        text_appearance:\n                          font_size: 14\n                          alignment: center\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 1\n                      content_description: \"Submit button\"\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_1\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                content_description: \"Close button\"\n        - identifier: \"pager-page-2-id\"\n          display_actions:\n            add_tags_action: 'pager-page-2x'\n          accessibility_actions:\n            - type: default\n              reporting_metadata:\n                key: \"page_2_previous\"\n              localized_content_description:\n                fallback: \"Previous Page\"\n                ref: \"ua_previous\"\n              actions:\n                - add_tags_action: \"page_2_previous_action\"\n              behaviors: \n                - pager_previous\n            - type: escape\n              reporting_metadata:\n                key: \"page_2_escape\"\n              localized_content_description:\n                fallback: \"Dismiss\"\n                ref: \"ua_escape\"\n              actions:\n                - add_tags_action: \"page_2_dismiss_action\"\n              behaviors: \n                - dismiss\n          automated_actions:\n            - identifier: \"auto_announce_page_2\"\n              delay: 0.5\n              behaviors: [\"pager_pause\"]\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#00FF00\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: nps_form_controller\n                identifier: page_2_nps_form\n                nps_identifier: score_identifier_2\n                submit: submit_event\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - size:\n                      width: 200\n                      height: auto\n                    margin:\n                      start: 8\n                      end: 8\n                    view:\n                      type: score\n                      identifier: score_identifier_2\n                      required: true\n                      style:\n                        type: number_range\n                        start: 0\n                        end: 10\n                        spacing: 0\n                        wrapping:\n                          line_spacing: 0\n                          max_items_per_line: 11\n                        spacing: 2\n                        bindings:\n                          selected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#FFFFFF\"\n                                  alpha: 0\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                          unselected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                      content_description: \"Rate your experience from 0 to 10\"\n                  - size:\n                      width: auto\n                      height: auto\n                    margin:\n                      start: 16\n                      end: 16\n                    view:\n                      type: label_button\n                      identifier: submit_button\n                      background_color:\n                        default:\n                          hex: \"#ffffff\"\n                          alpha: 1\n                      button_click: [\"form_submit\", \"cancel\"]\n                      enabled: [\"form_validation\"]\n                      display_actions:\n                        add_tags_action: 'pager-page-2-form-submit'\n                      label:\n                        type: label\n                        text: width:200 height:auto\n                        text_appearance:\n                          font_size: 14\n                          alignment: center\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 1\n                      content_description: \"Submit button\"\n        - identifier: \"pager-page-3-id\"\n          display_actions:\n            add_tags_action: 'pager-page-3x'\n          accessibility_actions:\n            - type: default\n              reporting_metadata:\n                key: \"page_3_previous\"\n              localized_content_description:\n                fallback: \"Previous Page\"\n                ref: \"ua_previous\"\n              actions:\n                - add_tags_action: \"page_3_previous_action\"\n              behaviors:\n                - pager_previous\n            - type: escape\n              reporting_metadata:\n                key: \"page_3_escape\"\n              localized_content_description:\n                fallback: \"Dismiss\"\n                ref: \"ua_escape\"\n              actions:\n                - add_tags_action: \"page_3_dismiss_action\"\n              behaviors:\n                - dismiss\n          automated_actions:\n            - identifier: \"auto_announce_page_3\"\n              delay: 0.5\n              behaviors: [\"pager_pause\"]\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#00FFF0\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: nps_form_controller\n                identifier: page_2_nps_form\n                nps_identifier: score_identifier_2\n                submit: submit_event\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - size:\n                      width: 50\n                      height: auto\n                    margin:\n                      start: 8\n                      end: 8\n                    view:\n                      type: score\n                      identifier: score_identifier_2\n                      required: true\n                      style:\n                        type: number_range\n                        start: 0\n                        end: 10\n                        spacing: 0\n                        wrapping:\n                          line_spacing: 0\n                          max_items_per_line: 11\n                        spacing: 2\n                        bindings:\n                          selected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#FFFFFF\"\n                                  alpha: 0\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                          unselected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                      content_description: \"Rate your experience from 0 to 10\"\n                  - size:\n                      width: auto\n                      height: auto\n                    margin:\n                      start: 16\n                      end: 16\n                    view:\n                      type: label_button\n                      identifier: submit_button\n                      background_color:\n                        default:\n                          hex: \"#ffffff\"\n                          alpha: 1\n                      button_click: [\"form_submit\", \"cancel\"]\n                      enabled: [\"form_validation\"]\n                      display_actions:\n                        add_tags_action: 'pager-page-2-form-submit'\n                      label:\n                        type: label\n                        text: width:50 height:auto\n                        text_appearance:\n                          font_size: 14\n                          alignment: center\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 1\n                      content_description: \"Submit button\"\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_2\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                content_description: \"Close button\"\n        gestures:\n          - type: swipe\n            identifier: \"swipe_next\"\n            direction: up\n            behavior:\n              behaviors: [\"pager_next\"]\n          - type: tap\n            identifier: \"tap_next\"\n            location: end\n            behavior:\n              behaviors: [\"pager_next\"]\n    - size:\n        height: 16\n        width: auto\n      position:\n        vertical: bottom\n        horizontal: center\n      margin:\n        bottom: 8\n      view:\n        type: pager_indicator\n        spacing: 4\n        bindings:\n          selected:\n            shapes:\n            - type: rectangle\n              aspect_ratio: 2.25\n              scale: 0.9\n              border:\n                radius: 3\n                stroke_width: 1\n                stroke_color:\n                  default:\n                    hex: \"#ffffff\"\n                    alpha: 0.7\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n          unselected:\n            shapes:\n            - type: rectangle\n              aspect_ratio: 2.25\n              scale: .9\n              border:\n                radius: 3\n                stroke_width: 1\n                stroke_color:\n                  default:\n                    hex: \"#ffffff\"\n                    alpha: 0.7\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 0\n        automated_accessibility_actions:\n          - type: announce\n        background_color:\n          default:\n            hex: \"#333333\"\n            alpha: 0.7\n        border:\n          radius: 8\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/accessibility-test-modal.json",
    "content": "{\n   \"version\":1,\n   \"presentation\":{\n      \"type\":\"modal\",\n      \"placement_selectors\":[\n         {\n            \"placement\":{\n               \"ignore_safe_area\":true,\n               \"size\":{\n                  \"width\":\"80%\",\n                  \"height\":\"70%\"\n               },\n               \"position\":{\n                  \"horizontal\":\"center\",\n                  \"vertical\":\"center\"\n               },\n               \"shade_color\":{\n                  \"default\":{\n                     \"type\":\"hex\",\n                     \"hex\":\"#FFFFFF\",\n                     \"alpha\":1\n                  },\n                  \"selectors\":[\n                     {\n                        \"platform\":\"ios\",\n                        \"dark_mode\":false,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#FFFFFF\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"ios\",\n                        \"dark_mode\":true,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#000000\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"android\",\n                        \"dark_mode\":false,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#FFFFFF\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"android\",\n                        \"dark_mode\":true,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#000000\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"web\",\n                        \"dark_mode\":false,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#FFFFFF\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"web\",\n                        \"dark_mode\":true,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#000000\",\n                           \"alpha\":1\n                        }\n                     }\n                  ]\n               },\n               \"web\":{\n                  \"ignore_shade\":false\n               },\n               \"border\":{\n                  \"radius\":0,\n                  \"stroke_color\":{\n                     \"default\":{\n                        \"type\":\"hex\",\n                        \"hex\":\"#000000\",\n                        \"alpha\":1\n                     },\n                     \"selectors\":[\n                        {\n                           \"platform\":\"ios\",\n                           \"dark_mode\":false,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"ios\",\n                           \"dark_mode\":true,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"android\",\n                           \"dark_mode\":false,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"android\",\n                           \"dark_mode\":true,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"web\",\n                           \"dark_mode\":false,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"web\",\n                           \"dark_mode\":true,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        }\n                     ]\n                  }\n               }\n            },\n            \"window_size\":\"small\",\n            \"orientation\":\"portrait\"\n         },\n         {\n            \"placement\":{\n               \"ignore_safe_area\":true,\n               \"size\":{\n                  \"width\":\"80%\",\n                  \"height\":\"70%\"\n               },\n               \"position\":{\n                  \"horizontal\":\"center\",\n                  \"vertical\":\"center\"\n               },\n               \"shade_color\":{\n                  \"default\":{\n                     \"type\":\"hex\",\n                     \"hex\":\"#FFFFFF\",\n                     \"alpha\":1\n                  },\n                  \"selectors\":[\n                     {\n                        \"platform\":\"ios\",\n                        \"dark_mode\":false,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#FFFFFF\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"ios\",\n                        \"dark_mode\":true,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#000000\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"android\",\n                        \"dark_mode\":false,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#FFFFFF\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"android\",\n                        \"dark_mode\":true,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#000000\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"web\",\n                        \"dark_mode\":false,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#FFFFFF\",\n                           \"alpha\":1\n                        }\n                     },\n                     {\n                        \"platform\":\"web\",\n                        \"dark_mode\":true,\n                        \"color\":{\n                           \"type\":\"hex\",\n                           \"hex\":\"#000000\",\n                           \"alpha\":1\n                        }\n                     }\n                  ]\n               },\n               \"web\":{\n                  \"ignore_shade\":false\n               },\n               \"border\":{\n                  \"radius\":0,\n                  \"stroke_color\":{\n                     \"default\":{\n                        \"type\":\"hex\",\n                        \"hex\":\"#000000\",\n                        \"alpha\":1\n                     },\n                     \"selectors\":[\n                        {\n                           \"platform\":\"ios\",\n                           \"dark_mode\":false,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"ios\",\n                           \"dark_mode\":true,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"android\",\n                           \"dark_mode\":false,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"android\",\n                           \"dark_mode\":true,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"web\",\n                           \"dark_mode\":false,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        },\n                        {\n                           \"platform\":\"web\",\n                           \"dark_mode\":true,\n                           \"color\":{\n                              \"type\":\"hex\",\n                              \"hex\":\"#000000\",\n                              \"alpha\":1\n                           }\n                        }\n                     ]\n                  }\n               }\n            },\n            \"window_size\":\"large\",\n            \"orientation\":\"portrait\"\n         }\n      ],\n      \"android\":{\n         \"disable_back_button\":false\n      },\n      \"dismiss_on_touch_outside\":false,\n      \"default_placement\":{\n         \"ignore_safe_area\":false,\n         \"size\":{\n            \"width\":\"100%\",\n            \"height\":\"100%\"\n         },\n         \"position\":{\n            \"horizontal\":\"center\",\n            \"vertical\":\"top\"\n         },\n         \"shade_color\":{\n            \"default\":{\n               \"type\":\"hex\",\n               \"hex\":\"#000000\",\n               \"alpha\":0.2\n            }\n         },\n         \"web\":{\n            \n         }\n      }\n   },\n   \"view\":{\n      \"type\":\"state_controller\",\n      \"view\":{\n         \"type\":\"pager_controller\",\n         \"identifier\":\"c7ae5cc6-c4a9-42cd-ade5-54cece99748b\",\n         \"view\":{\n            \"type\":\"linear_layout\",\n            \"direction\":\"vertical\",\n            \"items\":[\n               {\n                  \"size\":{\n                     \"width\":\"100%\",\n                     \"height\":\"100%\"\n                  },\n                  \"view\":{\n                     \"identifier\":\"7f9a7b79-6e1c-4952-b04c-d01b87f29736\",\n                     \"nps_identifier\":\"31b37c30-248e-473a-a161-9eb959446fbc\",\n                     \"type\":\"nps_form_controller\",\n                     \"submit\":\"submit_event\",\n                     \"form_enabled\":[\n                        \"form_submission\"\n                     ],\n                     \"response_type\":\"nps\",\n                     \"view\":{\n                        \"type\":\"container\",\n                        \"items\":[\n                           {\n                              \"identifier\":\"e0f07fd3-68d4-4995-a6d9-f40afc8482ee_pager_container_item\",\n                              \"position\":{\n                                 \"horizontal\":\"center\",\n                                 \"vertical\":\"center\"\n                              },\n                              \"size\":{\n                                 \"width\":\"100%\",\n                                 \"height\":\"100%\"\n                              },\n                              \"view\":{\n                                 \"type\":\"pager\",\n                                 \"disable_swipe\":false,\n                                 \"items\":[\n                                    {\n                                       \"identifier\":\"e33f0e64-6a4d-4fdb-9b60-b00c6dac9d12\",\n                                       \"type\":\"pager_item\",\n                                       \"view\":{\n                                          \"type\":\"container\",\n                                          \"items\":[\n                                             {\n                                                \"identifier\":\"9cc66c9f-494e-49a8-86c1-6cac0add65d4_main_view_container_item\",\n                                                \"size\":{\n                                                   \"width\":\"100%\",\n                                                   \"height\":\"100%\"\n                                                },\n                                                \"position\":{\n                                                   \"horizontal\":\"center\",\n                                                   \"vertical\":\"center\"\n                                                },\n                                                \"ignore_safe_area\":false,\n                                                \"view\":{\n                                                   \"type\":\"container\",\n                                                   \"items\":[\n                                                      {\n                                                         \"identifier\":\"78a74d3f-81b2-4858-ab1d-4802c9859d8b_container_item\",\n                                                         \"margin\":{\n                                                            \"bottom\":0,\n                                                            \"top\":0,\n                                                            \"end\":0,\n                                                            \"start\":0\n                                                         },\n                                                         \"position\":{\n                                                            \"horizontal\":\"center\",\n                                                            \"vertical\":\"center\"\n                                                         },\n                                                         \"size\":{\n                                                            \"width\":\"100%\",\n                                                            \"height\":\"100%\"\n                                                         },\n                                                         \"view\":{\n                                                            \"type\":\"linear_layout\",\n                                                            \"direction\":\"vertical\",\n                                                            \"items\":[\n                                                               {\n                                                                  \"identifier\":\"scroll_container\",\n                                                                  \"size\":{\n                                                                     \"width\":\"100%\",\n                                                                     \"height\":\"100%\"\n                                                                  },\n                                                                  \"view\":{\n                                                                     \"type\":\"scroll_layout\",\n                                                                     \"direction\":\"vertical\",\n                                                                     \"view\":{\n                                                                        \"type\":\"linear_layout\",\n                                                                        \"direction\":\"vertical\",\n                                                                        \"items\":[\n                                                                           {\n                                                                              \"identifier\":\"730835c5-2fe3-4fd6-b3ce-f3683acac033_wrapped_linear_layout\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":350\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"container\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"identifier\":\"730835c5-2fe3-4fd6-b3ce-f3683acac033\",\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":0,\n                                                                                          \"start\":0,\n                                                                                          \"end\":0\n                                                                                       },\n                                                                                       \"position\":{\n                                                                                          \"horizontal\":\"center\",\n                                                                                          \"vertical\":\"center\"\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"100%\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"linear_layout\",\n                                                                                          \"direction\":\"horizontal\",\n                                                                                          \"items\":[\n                                                                                             {\n                                                                                                \"identifier\":\"26e1cd82-a352-4435-8053-fd4619bd7bc2\",\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"100%\",\n                                                                                                   \"height\":\"auto\"\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"media\",\n                                                                                                   \"media_fit\":\"center_inside\",\n                                                                                                   \"url\":\"https://hangar-dl.urbanairship.com/binary/public/M4QKrhI2RNe1hzV8EmrLow/8e07b6d4-fff8-4ec9-b447-cbd6509cff81\",\n                                                                                                   \"media_type\":\"image\"\n                                                                                                },\n                                                                                                \"margin\":{\n                                                                                                   \"top\":0,\n                                                                                                   \"bottom\":0,\n                                                                                                   \"start\":0,\n                                                                                                   \"end\":0\n                                                                                                }\n                                                                                             }\n                                                                                          ],\n                                                                                          \"background_color\":{\n                                                                                             \"default\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FF0D49\",\n                                                                                                \"alpha\":1\n                                                                                             },\n                                                                                             \"selectors\":[\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FF0D49\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FF0D49\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FF0D49\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FF0D49\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FF0D49\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FF0D49\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                }\n                                                                                             ]\n                                                                                          }\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":0,\n                                                                                 \"bottom\":0,\n                                                                                 \"start\":0,\n                                                                                 \"end\":0\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"6aa6605d-a3ce-43e6-9fed-cdc9fab22385\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"auto\"\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":20,\n                                                                                 \"bottom\":8,\n                                                                                 \"start\":16,\n                                                                                 \"end\":16\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"label\",\n                                                                                 \"text\":\"Help Us Improve Your Pharmacy Experience\",\n                                                                                 \"content_description\":\"Help Us Improve Your Pharmacy Experience\",\n                                                                                 \"text_appearance\":{\n                                                                                    \"font_size\":25,\n                                                                                    \"color\":{\n                                                                                       \"default\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#222222\",\n                                                                                          \"alpha\":1\n                                                                                       },\n                                                                                       \"selectors\":[\n                                                                                          {\n                                                                                             \"platform\":\"ios\",\n                                                                                             \"dark_mode\":false,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"ios\",\n                                                                                             \"dark_mode\":true,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"android\",\n                                                                                             \"dark_mode\":false,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"android\",\n                                                                                             \"dark_mode\":true,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"web\",\n                                                                                             \"dark_mode\":false,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"web\",\n                                                                                             \"dark_mode\":true,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          }\n                                                                                       ]\n                                                                                    },\n                                                                                    \"alignment\":\"center\",\n                                                                                    \"styles\":[\n                                                                                       \"bold\"\n                                                                                    ],\n                                                                                    \"font_families\":[\n                                                                                       \"sans-serif\"\n                                                                                    ]\n                                                                                 }\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"fa08fdad-c969-4ca0-a2dc-b7e56326f936\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"auto\"\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":8,\n                                                                                 \"bottom\":8,\n                                                                                 \"start\":16,\n                                                                                 \"end\":16\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"label\",\n                                                                                 \"text\":\"We'd love to hear about your recent prescription refill pick-up. \\n\\n**Do you have a few moments to share your feedback?**\",\n                                                                                 \"content_description\":\"We'd love to hear about your recent prescription refill pick-up. \\n\\n**Do you have a few moments to share your feedback?**\",\n                                                                                 \"text_appearance\":{\n                                                                                    \"font_size\":18,\n                                                                                    \"color\":{\n                                                                                       \"default\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#222222\",\n                                                                                          \"alpha\":1\n                                                                                       },\n                                                                                       \"selectors\":[\n                                                                                          {\n                                                                                             \"platform\":\"ios\",\n                                                                                             \"dark_mode\":false,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"ios\",\n                                                                                             \"dark_mode\":true,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"android\",\n                                                                                             \"dark_mode\":false,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"android\",\n                                                                                             \"dark_mode\":true,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"web\",\n                                                                                             \"dark_mode\":false,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          },\n                                                                                          {\n                                                                                             \"platform\":\"web\",\n                                                                                             \"dark_mode\":true,\n                                                                                             \"color\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             }\n                                                                                          }\n                                                                                       ]\n                                                                                    },\n                                                                                    \"alignment\":\"center\",\n                                                                                    \"styles\":[\n                                                                                       \n                                                                                    ],\n                                                                                    \"font_families\":[\n                                                                                       \"sans-serif\"\n                                                                                    ]\n                                                                                 }\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"30c0a4d7-623c-478c-8930-92485f093ff9_wrapped_linear_layout\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"auto\"\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"container\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"identifier\":\"30c0a4d7-623c-478c-8930-92485f093ff9\",\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":0,\n                                                                                          \"start\":0,\n                                                                                          \"end\":0\n                                                                                       },\n                                                                                       \"position\":{\n                                                                                          \"horizontal\":\"center\",\n                                                                                          \"vertical\":\"center\"\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"auto\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"linear_layout\",\n                                                                                          \"direction\":\"horizontal\",\n                                                                                          \"items\":[\n                                                                                             {\n                                                                                                \"identifier\":\"b703810f-aaf7-4121-93b3-e98336f88335\",\n                                                                                                \"margin\":{\n                                                                                                   \"top\":8,\n                                                                                                   \"bottom\":8,\n                                                                                                   \"start\":16,\n                                                                                                   \"end\":16\n                                                                                                },\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"60%\",\n                                                                                                   \"height\":50\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"label_button\",\n                                                                                                   \"identifier\":\"dismiss--Maybe later\",\n                                                                                                   \"reporting_metadata\":{\n                                                                                                      \"trigger_link_id\":\"b703810f-aaf7-4121-93b3-e98336f88335\"\n                                                                                                   },\n                                                                                                   \"label\":{\n                                                                                                      \"type\":\"label\",\n                                                                                                      \"text\":\"Maybe later\",\n                                                                                                      \"content_description\":\"Dismisses the survey\",\n                                                                                                      \"text_appearance\":{\n                                                                                                         \"font_size\":19,\n                                                                                                         \"color\":{\n                                                                                                            \"default\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#222222\",\n                                                                                                               \"alpha\":1\n                                                                                                            },\n                                                                                                            \"selectors\":[\n                                                                                                               {\n                                                                                                                  \"platform\":\"ios\",\n                                                                                                                  \"dark_mode\":false,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#222222\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"ios\",\n                                                                                                                  \"dark_mode\":true,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#FFFFFF\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"android\",\n                                                                                                                  \"dark_mode\":false,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#222222\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"android\",\n                                                                                                                  \"dark_mode\":true,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#FFFFFF\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"web\",\n                                                                                                                  \"dark_mode\":false,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#222222\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"web\",\n                                                                                                                  \"dark_mode\":true,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#FFFFFF\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            ]\n                                                                                                         },\n                                                                                                         \"alignment\":\"center\",\n                                                                                                         \"styles\":[\n                                                                                                            \"bold\"\n                                                                                                         ],\n                                                                                                         \"font_families\":[\n                                                                                                            \"sans-serif\"\n                                                                                                         ]\n                                                                                                      }\n                                                                                                   },\n                                                                                                   \"actions\":{\n                                                                                                      \"add_tags_action\":[\n                                                                                                         \"prescription-maybe-later\"\n                                                                                                      ]\n                                                                                                   },\n                                                                                                   \"enabled\":[\n                                                                                                      \n                                                                                                   ],\n                                                                                                   \"button_click\":[\n                                                                                                      \"dismiss\"\n                                                                                                   ],\n                                                                                                   \"localized_content_description\":{\n                                                                                                      \"fallback\":\"Dismiss\",\n                                                                                                      \"ref\":\"ua_dismiss\"\n                                                                                                   },\n                                                                                                   \"background_color\":{\n                                                                                                      \"default\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      },\n                                                                                                      \"selectors\":[\n                                                                                                         {\n                                                                                                            \"platform\":\"ios\",\n                                                                                                            \"dark_mode\":false,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"ios\",\n                                                                                                            \"dark_mode\":true,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#000000\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"android\",\n                                                                                                            \"dark_mode\":false,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"android\",\n                                                                                                            \"dark_mode\":true,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#000000\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"web\",\n                                                                                                            \"dark_mode\":false,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"web\",\n                                                                                                            \"dark_mode\":true,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#000000\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         }\n                                                                                                      ]\n                                                                                                   },\n                                                                                                   \"border\":{\n                                                                                                      \"radius\":15,\n                                                                                                      \"stroke_width\":3,\n                                                                                                      \"stroke_color\":{\n                                                                                                         \"default\":{\n                                                                                                            \"type\":\"hex\",\n                                                                                                            \"hex\":\"#222222\",\n                                                                                                            \"alpha\":1\n                                                                                                         },\n                                                                                                         \"selectors\":[\n                                                                                                            {\n                                                                                                               \"platform\":\"ios\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"ios\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"android\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"android\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"web\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"web\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            }\n                                                                                                         ]\n                                                                                                      }\n                                                                                                   },\n                                                                                                   \"content_description\":\"Dismisses the survey\"\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"identifier\":\"59f6d4ce-bf6e-47ef-8f45-1dec5a3045cf\",\n                                                                                                \"margin\":{\n                                                                                                   \"top\":8,\n                                                                                                   \"bottom\":8,\n                                                                                                   \"start\":16,\n                                                                                                   \"end\":16\n                                                                                                },\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"60%\",\n                                                                                                   \"height\":50\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"label_button\",\n                                                                                                   \"identifier\":\"next--Yes!\",\n                                                                                                   \"reporting_metadata\":{\n                                                                                                      \"trigger_link_id\":\"59f6d4ce-bf6e-47ef-8f45-1dec5a3045cf\"\n                                                                                                   },\n                                                                                                   \"label\":{\n                                                                                                      \"type\":\"label\",\n                                                                                                      \"text\":\"Yes!\",\n                                                                                                      \"content_description\":\"Next screen\",\n                                                                                                      \"text_appearance\":{\n                                                                                                         \"font_size\":19,\n                                                                                                         \"color\":{\n                                                                                                            \"default\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            },\n                                                                                                            \"selectors\":[\n                                                                                                               {\n                                                                                                                  \"platform\":\"ios\",\n                                                                                                                  \"dark_mode\":false,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#FFFFFF\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"ios\",\n                                                                                                                  \"dark_mode\":true,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#000000\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"android\",\n                                                                                                                  \"dark_mode\":false,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#FFFFFF\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"android\",\n                                                                                                                  \"dark_mode\":true,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#000000\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"web\",\n                                                                                                                  \"dark_mode\":false,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#FFFFFF\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               },\n                                                                                                               {\n                                                                                                                  \"platform\":\"web\",\n                                                                                                                  \"dark_mode\":true,\n                                                                                                                  \"color\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#000000\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            ]\n                                                                                                         },\n                                                                                                         \"alignment\":\"center\",\n                                                                                                         \"styles\":[\n                                                                                                            \"bold\"\n                                                                                                         ],\n                                                                                                         \"font_families\":[\n                                                                                                            \"sans-serif\"\n                                                                                                         ]\n                                                                                                      }\n                                                                                                   },\n                                                                                                   \"actions\":{\n                                                                                                      \n                                                                                                   },\n                                                                                                   \"enabled\":[\n                                                                                                      \"pager_next\"\n                                                                                                   ],\n                                                                                                   \"button_click\":[\n                                                                                                      \"pager_next\"\n                                                                                                   ],\n                                                                                                   \"localized_content_description\":{\n                                                                                                      \"fallback\":\"Next\",\n                                                                                                      \"ref\":\"ua_next\"\n                                                                                                   },\n                                                                                                   \"background_color\":{\n                                                                                                      \"default\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      },\n                                                                                                      \"selectors\":[\n                                                                                                         {\n                                                                                                            \"platform\":\"ios\",\n                                                                                                            \"dark_mode\":false,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#222222\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"ios\",\n                                                                                                            \"dark_mode\":true,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"android\",\n                                                                                                            \"dark_mode\":false,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#222222\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"android\",\n                                                                                                            \"dark_mode\":true,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"web\",\n                                                                                                            \"dark_mode\":false,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#222222\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"platform\":\"web\",\n                                                                                                            \"dark_mode\":true,\n                                                                                                            \"color\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#FFFFFF\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         }\n                                                                                                      ]\n                                                                                                   },\n                                                                                                   \"border\":{\n                                                                                                      \"radius\":15,\n                                                                                                      \"stroke_width\":14,\n                                                                                                      \"stroke_color\":{\n                                                                                                         \"default\":{\n                                                                                                            \"type\":\"hex\",\n                                                                                                            \"hex\":\"#222222\",\n                                                                                                            \"alpha\":1\n                                                                                                         },\n                                                                                                         \"selectors\":[\n                                                                                                            {\n                                                                                                               \"platform\":\"ios\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"ios\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"android\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"android\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"web\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"web\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            }\n                                                                                                         ]\n                                                                                                      }\n                                                                                                   },\n                                                                                                   \"content_description\":\"Next screen\"\n                                                                                                }\n                                                                                             }\n                                                                                          ],\n                                                                                          \"background_color\":{\n                                                                                             \"default\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             },\n                                                                                             \"selectors\":[\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#000000\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#000000\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#000000\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                }\n                                                                                             ]\n                                                                                          }\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":16,\n                                                                                 \"bottom\":0,\n                                                                                 \"start\":0,\n                                                                                 \"end\":0\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"49ff5e07-1b62-411b-99b8-9ee03e5c888a_linear_layout_item\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"100%\"\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"linear_layout\",\n                                                                                 \"direction\":\"horizontal\",\n                                                                                 \"items\":[\n                                                                                    \n                                                                                 ]\n                                                                              }\n                                                                           }\n                                                                        ]\n                                                                     }\n                                                                  }\n                                                               }\n                                                            ]\n                                                         }\n                                                      }\n                                                   ]\n                                                }\n                                             }\n                                          ],\n                                          \"background_color\":{\n                                             \"default\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#FFFFFF\",\n                                                \"alpha\":1\n                                             },\n                                             \"selectors\":[\n                                                {\n                                                   \"platform\":\"ios\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"ios\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"android\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"android\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"web\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"web\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                }\n                                             ]\n                                          }\n                                       }\n                                    },\n                                    {\n                                       \"identifier\":\"bc24f9c7-75e3-4ec8-8a70-f125b8b2ecdc\",\n                                       \"type\":\"pager_item\",\n                                       \"view\":{\n                                          \"type\":\"container\",\n                                          \"items\":[\n                                             {\n                                                \"identifier\":\"4017713e-c982-4a57-b5c0-dd0491f22258_main_view_container_item\",\n                                                \"size\":{\n                                                   \"width\":\"100%\",\n                                                   \"height\":\"100%\"\n                                                },\n                                                \"position\":{\n                                                   \"horizontal\":\"center\",\n                                                   \"vertical\":\"center\"\n                                                },\n                                                \"ignore_safe_area\":false,\n                                                \"view\":{\n                                                   \"type\":\"container\",\n                                                   \"items\":[\n                                                      {\n                                                         \"identifier\":\"e8abb103-280c-44a7-a44a-4891e5375ca7_container_item\",\n                                                         \"margin\":{\n                                                            \"bottom\":0,\n                                                            \"top\":0,\n                                                            \"end\":0,\n                                                            \"start\":0\n                                                         },\n                                                         \"position\":{\n                                                            \"horizontal\":\"center\",\n                                                            \"vertical\":\"center\"\n                                                         },\n                                                         \"size\":{\n                                                            \"width\":\"100%\",\n                                                            \"height\":\"100%\"\n                                                         },\n                                                         \"view\":{\n                                                            \"type\":\"linear_layout\",\n                                                            \"direction\":\"vertical\",\n                                                            \"items\":[\n                                                               {\n                                                                  \"identifier\":\"scroll_container\",\n                                                                  \"size\":{\n                                                                     \"width\":\"100%\",\n                                                                     \"height\":\"100%\"\n                                                                  },\n                                                                  \"view\":{\n                                                                     \"type\":\"scroll_layout\",\n                                                                     \"direction\":\"vertical\",\n                                                                     \"view\":{\n                                                                        \"type\":\"linear_layout\",\n                                                                        \"direction\":\"vertical\",\n                                                                        \"items\":[\n                                                                           {\n                                                                              \"identifier\":\"f839d1b5-5e96-48c6-999e-5302149867c1_wrapped_linear_layout\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":285\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"container\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"identifier\":\"f839d1b5-5e96-48c6-999e-5302149867c1\",\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":0,\n                                                                                          \"start\":0,\n                                                                                          \"end\":0\n                                                                                       },\n                                                                                       \"position\":{\n                                                                                          \"horizontal\":\"center\",\n                                                                                          \"vertical\":\"center\"\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"100%\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"linear_layout\",\n                                                                                          \"direction\":\"horizontal\",\n                                                                                          \"items\":[\n                                                                                             {\n                                                                                                \"identifier\":\"763b720d-fee8-49eb-9e4a-0051c185c079\",\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"100%\",\n                                                                                                   \"height\":\"auto\"\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"media\",\n                                                                                                   \"media_fit\":\"center_inside\",\n                                                                                                   \"url\":\"https://hangar-dl.urbanairship.com/binary/public/M4QKrhI2RNe1hzV8EmrLow/37c7828a-3ff7-4954-8dcd-45d658776742\",\n                                                                                                   \"media_type\":\"image\"\n                                                                                                },\n                                                                                                \"margin\":{\n                                                                                                   \"top\":8,\n                                                                                                   \"bottom\":0,\n                                                                                                   \"start\":0,\n                                                                                                   \"end\":0\n                                                                                                }\n                                                                                             }\n                                                                                          ]\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":0,\n                                                                                 \"bottom\":0,\n                                                                                 \"start\":0,\n                                                                                 \"end\":0\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"31b37c30-248e-473a-a161-9eb959446fbc\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"auto\"\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":20,\n                                                                                 \"bottom\":8,\n                                                                                 \"start\":16,\n                                                                                 \"end\":16\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"linear_layout\",\n                                                                                 \"direction\":\"vertical\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"auto\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"linear_layout\",\n                                                                                          \"direction\":\"vertical\",\n                                                                                          \"items\":[\n                                                                                             {\n                                                                                                \"margin\":{\n                                                                                                   \"top\":4,\n                                                                                                   \"bottom\":8\n                                                                                                },\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"100%\",\n                                                                                                   \"height\":\"auto\"\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"label\",\n                                                                                                   \"text\":\"How would you rate your recent prescription refill pick-up experience?\",\n                                                                                                   \"content_description\":\"How would you rate your recent prescription refill pick-up experience?\",\n                                                                                                   \"text_appearance\":{\n                                                                                                      \"font_size\":19,\n                                                                                                      \"color\":{\n                                                                                                         \"default\":{\n                                                                                                            \"type\":\"hex\",\n                                                                                                            \"hex\":\"#222222\",\n                                                                                                            \"alpha\":1\n                                                                                                         },\n                                                                                                         \"selectors\":[\n                                                                                                            {\n                                                                                                               \"platform\":\"ios\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"ios\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"android\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"android\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"web\",\n                                                                                                               \"dark_mode\":false,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#222222\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            },\n                                                                                                            {\n                                                                                                               \"platform\":\"web\",\n                                                                                                               \"dark_mode\":true,\n                                                                                                               \"color\":{\n                                                                                                                  \"type\":\"hex\",\n                                                                                                                  \"hex\":\"#FFFFFF\",\n                                                                                                                  \"alpha\":1\n                                                                                                               }\n                                                                                                            }\n                                                                                                         ]\n                                                                                                      },\n                                                                                                      \"alignment\":\"center\",\n                                                                                                      \"styles\":[\n                                                                                                         \"bold\"\n                                                                                                      ],\n                                                                                                      \"font_families\":[\n                                                                                                         \"sans-serif\"\n                                                                                                      ]\n                                                                                                   }\n                                                                                                }\n                                                                                             }\n                                                                                          ]\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"auto\"\n                                                                                       },\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":0\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"linear_layout\",\n                                                                                          \"direction\":\"vertical\",\n                                                                                          \"items\":[\n                                                                                             {\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"100%\",\n                                                                                                   \"height\":\"auto\"\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"linear_layout\",\n                                                                                                   \"direction\":\"horizontal\",\n                                                                                                   \"items\":[\n                                                                                                      {\n                                                                                                         \"size\":{\n                                                                                                            \"width\":\"50%\",\n                                                                                                            \"height\":\"auto\"\n                                                                                                         },\n                                                                                                         \"margin\":{\n                                                                                                            \"end\":4,\n                                                                                                            \"bottom\":4\n                                                                                                         },\n                                                                                                         \"view\":{\n                                                                                                            \"type\":\"label\",\n                                                                                                            \"text\":\" \",\n                                                                                                            \"content_description\":\" \",\n                                                                                                            \"text_appearance\":{\n                                                                                                               \"font_size\":16,\n                                                                                                               \"color\":{\n                                                                                                                  \"default\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#222222\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  },\n                                                                                                                  \"selectors\":[\n                                                                                                                     {\n                                                                                                                        \"platform\":\"ios\",\n                                                                                                                        \"dark_mode\":false,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#222222\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"ios\",\n                                                                                                                        \"dark_mode\":true,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#FFFFFF\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"android\",\n                                                                                                                        \"dark_mode\":false,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#222222\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"android\",\n                                                                                                                        \"dark_mode\":true,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#FFFFFF\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"web\",\n                                                                                                                        \"dark_mode\":false,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#222222\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"web\",\n                                                                                                                        \"dark_mode\":true,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#FFFFFF\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     }\n                                                                                                                  ]\n                                                                                                               },\n                                                                                                               \"alignment\":\"start\",\n                                                                                                               \"styles\":[\n                                                                                                                  \n                                                                                                               ],\n                                                                                                               \"font_families\":[\n                                                                                                                  \"sans-serif\"\n                                                                                                               ]\n                                                                                                            }\n                                                                                                         }\n                                                                                                      },\n                                                                                                      {\n                                                                                                         \"size\":{\n                                                                                                            \"width\":\"50%\",\n                                                                                                            \"height\":\"auto\"\n                                                                                                         },\n                                                                                                         \"margin\":{\n                                                                                                            \"start\":4,\n                                                                                                            \"bottom\":4\n                                                                                                         },\n                                                                                                         \"view\":{\n                                                                                                            \"type\":\"label\",\n                                                                                                            \"text\":\" \",\n                                                                                                            \"content_description\":\" \",\n                                                                                                            \"text_appearance\":{\n                                                                                                               \"font_size\":16,\n                                                                                                               \"color\":{\n                                                                                                                  \"default\":{\n                                                                                                                     \"type\":\"hex\",\n                                                                                                                     \"hex\":\"#222222\",\n                                                                                                                     \"alpha\":1\n                                                                                                                  },\n                                                                                                                  \"selectors\":[\n                                                                                                                     {\n                                                                                                                        \"platform\":\"ios\",\n                                                                                                                        \"dark_mode\":false,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#222222\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"ios\",\n                                                                                                                        \"dark_mode\":true,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#FFFFFF\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"android\",\n                                                                                                                        \"dark_mode\":false,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#222222\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"android\",\n                                                                                                                        \"dark_mode\":true,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#FFFFFF\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"web\",\n                                                                                                                        \"dark_mode\":false,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#222222\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     },\n                                                                                                                     {\n                                                                                                                        \"platform\":\"web\",\n                                                                                                                        \"dark_mode\":true,\n                                                                                                                        \"color\":{\n                                                                                                                           \"type\":\"hex\",\n                                                                                                                           \"hex\":\"#FFFFFF\",\n                                                                                                                           \"alpha\":1\n                                                                                                                        }\n                                                                                                                     }\n                                                                                                                  ]\n                                                                                                               },\n                                                                                                               \"alignment\":\"end\",\n                                                                                                               \"styles\":[\n                                                                                                                  \n                                                                                                               ],\n                                                                                                               \"font_families\":[\n                                                                                                                  \"sans-serif\"\n                                                                                                               ]\n                                                                                                            }\n                                                                                                         }\n                                                                                                      }\n                                                                                                   ]\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"100%\",\n                                                                                                   \"height\":\"auto\"\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"linear_layout\",\n                                                                                                   \"direction\":\"horizontal\",\n                                                                                                   \"items\":[\n                                                                                                      {\n                                                                                                         \"size\":{\n                                                                                                            \"height\":\"auto\",\n                                                                                                            \"width\":\"100%\"\n                                                                                                         },\n                                                                                                         \"view\":{\n                                                                                                            \"type\":\"score\",\n                                                                                                            \"style\":{\n                                                                                                               \"type\":\"number_range\",\n                                                                                                               \"start\":0,\n                                                                                                               \"end\":10,\n                                                                                                               \"spacing\":2,\n                                                                                                               \"wrapping\":{\n                                                                                                                  \"line_spacing\":16,\n                                                                                                                  \"max_items_per_line\":6\n                                                                                                               },\n                                                                                                               \"bindings\":{\n                                                                                                                  \"selected\":{\n                                                                                                                     \"shapes\":[\n                                                                                                                        {\n                                                                                                                           \"type\":\"rectangle\",\n                                                                                                                           \"scale\":1,\n                                                                                                                           \"border\":{\n                                                                                                                              \"radius\":15,\n                                                                                                                              \"stroke_width\":1,\n                                                                                                                              \"stroke_color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#222222\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 },\n                                                                                                                                 \"selectors\":[\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"ios\",\n                                                                                                                                       \"dark_mode\":false,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#222222\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"ios\",\n                                                                                                                                       \"dark_mode\":true,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"android\",\n                                                                                                                                       \"dark_mode\":false,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#222222\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"android\",\n                                                                                                                                       \"dark_mode\":true,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"web\",\n                                                                                                                                       \"dark_mode\":false,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#222222\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"web\",\n                                                                                                                                       \"dark_mode\":true,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    }\n                                                                                                                                 ]\n                                                                                                                              }\n                                                                                                                           },\n                                                                                                                           \"color\":{\n                                                                                                                              \"default\":{\n                                                                                                                                 \"type\":\"hex\",\n                                                                                                                                 \"hex\":\"#222222\",\n                                                                                                                                 \"alpha\":1\n                                                                                                                              },\n                                                                                                                              \"selectors\":[\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"ios\",\n                                                                                                                                    \"dark_mode\":false,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#222222\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"ios\",\n                                                                                                                                    \"dark_mode\":true,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#FFFFFF\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"android\",\n                                                                                                                                    \"dark_mode\":false,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#222222\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"android\",\n                                                                                                                                    \"dark_mode\":true,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#FFFFFF\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"web\",\n                                                                                                                                    \"dark_mode\":false,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#222222\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"web\",\n                                                                                                                                    \"dark_mode\":true,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#FFFFFF\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              ]\n                                                                                                                           }\n                                                                                                                        }\n                                                                                                                     ],\n                                                                                                                     \"text_appearance\":{\n                                                                                                                        \"alignment\":\"center\",\n                                                                                                                        \"font_families\":[\n                                                                                                                           \"sans-serif\"\n                                                                                                                        ],\n                                                                                                                        \"font_size\":24,\n                                                                                                                        \"color\":{\n                                                                                                                           \"default\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           },\n                                                                                                                           \"selectors\":[\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"ios\",\n                                                                                                                                 \"dark_mode\":false,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"ios\",\n                                                                                                                                 \"dark_mode\":true,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"android\",\n                                                                                                                                 \"dark_mode\":false,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"android\",\n                                                                                                                                 \"dark_mode\":true,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"web\",\n                                                                                                                                 \"dark_mode\":false,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"web\",\n                                                                                                                                 \"dark_mode\":true,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           ]\n                                                                                                                        }\n                                                                                                                     }\n                                                                                                                  },\n                                                                                                                  \"unselected\":{\n                                                                                                                     \"shapes\":[\n                                                                                                                        {\n                                                                                                                           \"type\":\"rectangle\",\n                                                                                                                           \"scale\":1,\n                                                                                                                           \"border\":{\n                                                                                                                              \"radius\":15,\n                                                                                                                              \"stroke_width\":1,\n                                                                                                                              \"stroke_color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#222222\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 },\n                                                                                                                                 \"selectors\":[\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"ios\",\n                                                                                                                                       \"dark_mode\":false,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#222222\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"ios\",\n                                                                                                                                       \"dark_mode\":true,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"android\",\n                                                                                                                                       \"dark_mode\":false,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#222222\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"android\",\n                                                                                                                                       \"dark_mode\":true,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"web\",\n                                                                                                                                       \"dark_mode\":false,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#222222\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    },\n                                                                                                                                    {\n                                                                                                                                       \"platform\":\"web\",\n                                                                                                                                       \"dark_mode\":true,\n                                                                                                                                       \"color\":{\n                                                                                                                                          \"type\":\"hex\",\n                                                                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                                                                          \"alpha\":1\n                                                                                                                                       }\n                                                                                                                                    }\n                                                                                                                                 ]\n                                                                                                                              }\n                                                                                                                           },\n                                                                                                                           \"color\":{\n                                                                                                                              \"default\":{\n                                                                                                                                 \"type\":\"hex\",\n                                                                                                                                 \"hex\":\"#FFFFFF\",\n                                                                                                                                 \"alpha\":1\n                                                                                                                              },\n                                                                                                                              \"selectors\":[\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"ios\",\n                                                                                                                                    \"dark_mode\":false,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#FFFFFF\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"ios\",\n                                                                                                                                    \"dark_mode\":true,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"android\",\n                                                                                                                                    \"dark_mode\":false,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#FFFFFF\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"android\",\n                                                                                                                                    \"dark_mode\":true,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"web\",\n                                                                                                                                    \"dark_mode\":false,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#FFFFFF\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 },\n                                                                                                                                 {\n                                                                                                                                    \"platform\":\"web\",\n                                                                                                                                    \"dark_mode\":true,\n                                                                                                                                    \"color\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              ]\n                                                                                                                           }\n                                                                                                                        }\n                                                                                                                     ],\n                                                                                                                     \"text_appearance\":{\n                                                                                                                        \"font_size\":24,\n                                                                                                                        \"font_families\":[\n                                                                                                                           \"sans-serif\"\n                                                                                                                        ],\n                                                                                                                        \"color\":{\n                                                                                                                           \"default\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           },\n                                                                                                                           \"selectors\":[\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"ios\",\n                                                                                                                                 \"dark_mode\":false,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#222222\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"ios\",\n                                                                                                                                 \"dark_mode\":true,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"android\",\n                                                                                                                                 \"dark_mode\":false,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#222222\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"android\",\n                                                                                                                                 \"dark_mode\":true,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"web\",\n                                                                                                                                 \"dark_mode\":false,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#222222\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              {\n                                                                                                                                 \"platform\":\"web\",\n                                                                                                                                 \"dark_mode\":true,\n                                                                                                                                 \"color\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           ]\n                                                                                                                        }\n                                                                                                                     }\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            },\n                                                                                                            \"identifier\":\"31b37c30-248e-473a-a161-9eb959446fbc\",\n                                                                                                            \"required\":false\n                                                                                                         }\n                                                                                                      }\n                                                                                                   ]\n                                                                                                }\n                                                                                             }\n                                                                                          ]\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"74bf05d8-4463-418e-804f-4ab7cefdbd4d_linear_layout_item\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"100%\"\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"linear_layout\",\n                                                                                 \"direction\":\"horizontal\",\n                                                                                 \"items\":[\n                                                                                    \n                                                                                 ]\n                                                                              }\n                                                                           }\n                                                                        ]\n                                                                     }\n                                                                  }\n                                                               },\n                                                               {\n                                                                  \"identifier\":\"055860e0-fe57-4285-8712-0582f7257892_wrapped_linear_layout\",\n                                                                  \"size\":{\n                                                                     \"width\":\"100%\",\n                                                                     \"height\":\"auto\"\n                                                                  },\n                                                                  \"view\":{\n                                                                     \"type\":\"container\",\n                                                                     \"items\":[\n                                                                        {\n                                                                           \"identifier\":\"055860e0-fe57-4285-8712-0582f7257892\",\n                                                                           \"margin\":{\n                                                                              \"top\":0,\n                                                                              \"bottom\":0,\n                                                                              \"start\":0,\n                                                                              \"end\":0\n                                                                           },\n                                                                           \"position\":{\n                                                                              \"horizontal\":\"center\",\n                                                                              \"vertical\":\"center\"\n                                                                           },\n                                                                           \"size\":{\n                                                                              \"width\":\"100%\",\n                                                                              \"height\":\"auto\"\n                                                                           },\n                                                                           \"view\":{\n                                                                              \"type\":\"linear_layout\",\n                                                                              \"direction\":\"vertical\",\n                                                                              \"items\":[\n                                                                                 {\n                                                                                    \"identifier\":\"5f4734e0-d104-49fd-ba59-d1e070dcd74c\",\n                                                                                    \"margin\":{\n                                                                                       \"top\":0,\n                                                                                       \"bottom\":8,\n                                                                                       \"start\":16,\n                                                                                       \"end\":16\n                                                                                    },\n                                                                                    \"size\":{\n                                                                                       \"width\":\"60%\",\n                                                                                       \"height\":50\n                                                                                    },\n                                                                                    \"view\":{\n                                                                                       \"type\":\"label_button\",\n                                                                                       \"identifier\":\"next--Next\",\n                                                                                       \"reporting_metadata\":{\n                                                                                          \"trigger_link_id\":\"5f4734e0-d104-49fd-ba59-d1e070dcd74c\"\n                                                                                       },\n                                                                                       \"label\":{\n                                                                                          \"type\":\"label\",\n                                                                                          \"text\":\"Next\",\n                                                                                          \"content_description\":\"Next screen\",\n                                                                                          \"text_appearance\":{\n                                                                                             \"font_size\":19,\n                                                                                             \"color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             },\n                                                                                             \"alignment\":\"center\",\n                                                                                             \"styles\":[\n                                                                                                \"bold\"\n                                                                                             ],\n                                                                                             \"font_families\":[\n                                                                                                \"sans-serif\"\n                                                                                             ]\n                                                                                          }\n                                                                                       },\n                                                                                       \"actions\":{\n                                                                                          \n                                                                                       },\n                                                                                       \"enabled\":[\n                                                                                          \"pager_next\"\n                                                                                       ],\n                                                                                       \"button_click\":[\n                                                                                          \"pager_next\"\n                                                                                       ],\n                                                                                       \"localized_content_description\":{\n                                                                                          \"fallback\":\"Next\",\n                                                                                          \"ref\":\"ua_next\"\n                                                                                       },\n                                                                                       \"background_color\":{\n                                                                                          \"default\":{\n                                                                                             \"type\":\"hex\",\n                                                                                             \"hex\":\"#222222\",\n                                                                                             \"alpha\":1\n                                                                                          },\n                                                                                          \"selectors\":[\n                                                                                             {\n                                                                                                \"platform\":\"ios\",\n                                                                                                \"dark_mode\":false,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"ios\",\n                                                                                                \"dark_mode\":true,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"android\",\n                                                                                                \"dark_mode\":false,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"android\",\n                                                                                                \"dark_mode\":true,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"web\",\n                                                                                                \"dark_mode\":false,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"web\",\n                                                                                                \"dark_mode\":true,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             }\n                                                                                          ]\n                                                                                       },\n                                                                                       \"border\":{\n                                                                                          \"radius\":15,\n                                                                                          \"stroke_width\":14,\n                                                                                          \"stroke_color\":{\n                                                                                             \"default\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             },\n                                                                                             \"selectors\":[\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#222222\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#222222\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#222222\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                }\n                                                                                             ]\n                                                                                          }\n                                                                                       },\n                                                                                       \"content_description\":\"Next screen\"\n                                                                                    }\n                                                                                 }\n                                                                              ]\n                                                                           }\n                                                                        }\n                                                                     ]\n                                                                  },\n                                                                  \"margin\":{\n                                                                     \"top\":8,\n                                                                     \"bottom\":20,\n                                                                     \"start\":0,\n                                                                     \"end\":0\n                                                                  }\n                                                               }\n                                                            ]\n                                                         }\n                                                      }\n                                                   ]\n                                                }\n                                             }\n                                          ],\n                                          \"background_color\":{\n                                             \"default\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#FFFFFF\",\n                                                \"alpha\":1\n                                             },\n                                             \"selectors\":[\n                                                {\n                                                   \"platform\":\"ios\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"ios\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"android\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"android\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"web\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"web\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                }\n                                             ]\n                                          }\n                                       }\n                                    },\n                                    {\n                                       \"identifier\":\"ae006f87-adf6-466b-be6a-454021780387\",\n                                       \"type\":\"pager_item\",\n                                       \"view\":{\n                                          \"type\":\"container\",\n                                          \"items\":[\n                                             {\n                                                \"identifier\":\"9d637659-3a10-4303-835e-1da5aec3330c_main_view_container_item\",\n                                                \"size\":{\n                                                   \"width\":\"100%\",\n                                                   \"height\":\"100%\"\n                                                },\n                                                \"position\":{\n                                                   \"horizontal\":\"center\",\n                                                   \"vertical\":\"center\"\n                                                },\n                                                \"ignore_safe_area\":false,\n                                                \"view\":{\n                                                   \"type\":\"container\",\n                                                   \"items\":[\n                                                      {\n                                                         \"identifier\":\"62ff0871-ddd2-44da-89ad-698756a14801_container_item\",\n                                                         \"margin\":{\n                                                            \"bottom\":0,\n                                                            \"top\":0,\n                                                            \"end\":0,\n                                                            \"start\":0\n                                                         },\n                                                         \"position\":{\n                                                            \"horizontal\":\"center\",\n                                                            \"vertical\":\"center\"\n                                                         },\n                                                         \"size\":{\n                                                            \"width\":\"100%\",\n                                                            \"height\":\"100%\"\n                                                         },\n                                                         \"view\":{\n                                                            \"type\":\"linear_layout\",\n                                                            \"direction\":\"vertical\",\n                                                            \"items\":[\n                                                               {\n                                                                  \"identifier\":\"scroll_container\",\n                                                                  \"size\":{\n                                                                     \"width\":\"100%\",\n                                                                     \"height\":\"100%\"\n                                                                  },\n                                                                  \"view\":{\n                                                                     \"type\":\"scroll_layout\",\n                                                                     \"direction\":\"vertical\",\n                                                                     \"view\":{\n                                                                        \"type\":\"linear_layout\",\n                                                                        \"direction\":\"vertical\",\n                                                                        \"items\":[\n                                                                           {\n                                                                              \"identifier\":\"9b1e7de8-501f-4db0-bcb6-c03f7c0bf811_wrapped_linear_layout\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":285\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"container\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"identifier\":\"9b1e7de8-501f-4db0-bcb6-c03f7c0bf811\",\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":0,\n                                                                                          \"start\":0,\n                                                                                          \"end\":0\n                                                                                       },\n                                                                                       \"position\":{\n                                                                                          \"horizontal\":\"center\",\n                                                                                          \"vertical\":\"center\"\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"100%\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"linear_layout\",\n                                                                                          \"direction\":\"horizontal\",\n                                                                                          \"items\":[\n                                                                                             {\n                                                                                                \"identifier\":\"a5061c25-dc72-4703-9dbc-cdfb10ae0ec2\",\n                                                                                                \"size\":{\n                                                                                                   \"width\":\"100%\",\n                                                                                                   \"height\":\"auto\"\n                                                                                                },\n                                                                                                \"view\":{\n                                                                                                   \"type\":\"media\",\n                                                                                                   \"media_fit\":\"center_inside\",\n                                                                                                   \"url\":\"https://hangar-dl.urbanairship.com/binary/public/M4QKrhI2RNe1hzV8EmrLow/37c7828a-3ff7-4954-8dcd-45d658776742\",\n                                                                                                   \"media_type\":\"image\"\n                                                                                                },\n                                                                                                \"margin\":{\n                                                                                                   \"top\":8,\n                                                                                                   \"bottom\":0,\n                                                                                                   \"start\":0,\n                                                                                                   \"end\":0\n                                                                                                }\n                                                                                             }\n                                                                                          ]\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":0,\n                                                                                 \"bottom\":0,\n                                                                                 \"start\":0,\n                                                                                 \"end\":0\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"c2ff2225-c382-44a4-ad4e-9e6cec825fa4\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"auto\"\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":30,\n                                                                                 \"bottom\":8,\n                                                                                 \"start\":20,\n                                                                                 \"end\":20\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"linear_layout\",\n                                                                                 \"direction\":\"vertical\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":8\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"auto\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"label\",\n                                                                                          \"text\":\"Which of the following best describes your experience with our prescription refill reminders?\",\n                                                                                          \"content_description\":\"Which of the following best describes your experience with our prescription refill reminders?\",\n                                                                                          \"text_appearance\":{\n                                                                                             \"font_size\":19,\n                                                                                             \"color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             },\n                                                                                             \"alignment\":\"start\",\n                                                                                             \"styles\":[\n                                                                                                \"bold\"\n                                                                                             ],\n                                                                                             \"font_families\":[\n                                                                                                \"sans-serif\"\n                                                                                             ]\n                                                                                          }\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":8\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"auto\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"radio_input_controller\",\n                                                                                          \"identifier\":\"c2ff2225-c382-44a4-ad4e-9e6cec825fa4\",\n                                                                                          \"required\":false,\n                                                                                          \"view\":{\n                                                                                             \"type\":\"linear_layout\",\n                                                                                             \"direction\":\"vertical\",\n                                                                                             \"items\":[\n                                                                                                {\n                                                                                                   \"size\":{\n                                                                                                      \"width\":\"100%\",\n                                                                                                      \"height\":\"100%\"\n                                                                                                   },\n                                                                                                   \"margin\":{\n                                                                                                      \"top\":0,\n                                                                                                      \"bottom\":8\n                                                                                                   },\n                                                                                                   \"view\":{\n                                                                                                      \"type\":\"linear_layout\",\n                                                                                                      \"direction\":\"horizontal\",\n                                                                                                      \"items\":[\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":8\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"width\":20,\n                                                                                                               \"height\":20\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"radio_input\",\n                                                                                                               \"reporting_value\":\"fb0a1686-2cd4-4303-afad-47625fb6c599\",\n                                                                                                               \"content_description\":\"Always receive them on time\",\n                                                                                                               \"style\":{\n                                                                                                                  \"type\":\"checkbox\",\n                                                                                                                  \"bindings\":{\n                                                                                                                     \"selected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           },\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":0.6,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     },\n                                                                                                                     \"unselected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     }\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":16\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"height\":\"auto\",\n                                                                                                               \"width\":\"100%\"\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"label\",\n                                                                                                               \"text\":\"Always receive them on time\",\n                                                                                                               \"content_description\":\"Always receive them on time\",\n                                                                                                               \"text_appearance\":{\n                                                                                                                  \"font_size\":17,\n                                                                                                                  \"color\":{\n                                                                                                                     \"default\":{\n                                                                                                                        \"type\":\"hex\",\n                                                                                                                        \"hex\":\"#222222\",\n                                                                                                                        \"alpha\":1\n                                                                                                                     },\n                                                                                                                     \"selectors\":[\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        }\n                                                                                                                     ]\n                                                                                                                  },\n                                                                                                                  \"alignment\":\"start\",\n                                                                                                                  \"styles\":[\n                                                                                                                     \n                                                                                                                  ],\n                                                                                                                  \"font_families\":[\n                                                                                                                     \"sans-serif\"\n                                                                                                                  ]\n                                                                                                               }\n                                                                                                            }\n                                                                                                         }\n                                                                                                      ]\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"size\":{\n                                                                                                      \"width\":\"100%\",\n                                                                                                      \"height\":\"100%\"\n                                                                                                   },\n                                                                                                   \"margin\":{\n                                                                                                      \"top\":0,\n                                                                                                      \"bottom\":8\n                                                                                                   },\n                                                                                                   \"view\":{\n                                                                                                      \"type\":\"linear_layout\",\n                                                                                                      \"direction\":\"horizontal\",\n                                                                                                      \"items\":[\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":8\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"width\":20,\n                                                                                                               \"height\":20\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"radio_input\",\n                                                                                                               \"reporting_value\":\"376a5bbd-6aff-4d03-a519-82681fe06c3c\",\n                                                                                                               \"content_description\":\"Sometimes receive them late\",\n                                                                                                               \"style\":{\n                                                                                                                  \"type\":\"checkbox\",\n                                                                                                                  \"bindings\":{\n                                                                                                                     \"selected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           },\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":0.6,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     },\n                                                                                                                     \"unselected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     }\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":16\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"height\":\"auto\",\n                                                                                                               \"width\":\"100%\"\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"label\",\n                                                                                                               \"text\":\"Sometimes receive them late\",\n                                                                                                               \"content_description\":\"Sometimes receive them late\",\n                                                                                                               \"text_appearance\":{\n                                                                                                                  \"font_size\":17,\n                                                                                                                  \"color\":{\n                                                                                                                     \"default\":{\n                                                                                                                        \"type\":\"hex\",\n                                                                                                                        \"hex\":\"#222222\",\n                                                                                                                        \"alpha\":1\n                                                                                                                     },\n                                                                                                                     \"selectors\":[\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        }\n                                                                                                                     ]\n                                                                                                                  },\n                                                                                                                  \"alignment\":\"start\",\n                                                                                                                  \"styles\":[\n                                                                                                                     \n                                                                                                                  ],\n                                                                                                                  \"font_families\":[\n                                                                                                                     \"sans-serif\"\n                                                                                                                  ]\n                                                                                                               }\n                                                                                                            }\n                                                                                                         }\n                                                                                                      ]\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"size\":{\n                                                                                                      \"width\":\"100%\",\n                                                                                                      \"height\":\"100%\"\n                                                                                                   },\n                                                                                                   \"margin\":{\n                                                                                                      \"top\":0,\n                                                                                                      \"bottom\":8\n                                                                                                   },\n                                                                                                   \"view\":{\n                                                                                                      \"type\":\"linear_layout\",\n                                                                                                      \"direction\":\"horizontal\",\n                                                                                                      \"items\":[\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":8\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"width\":20,\n                                                                                                               \"height\":20\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"radio_input\",\n                                                                                                               \"reporting_value\":\"011468c4-4d69-40bb-afd0-9d1a7764b6e7\",\n                                                                                                               \"content_description\":\"Always receive them late \",\n                                                                                                               \"style\":{\n                                                                                                                  \"type\":\"checkbox\",\n                                                                                                                  \"bindings\":{\n                                                                                                                     \"selected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           },\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":0.6,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     },\n                                                                                                                     \"unselected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     }\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":16\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"height\":\"auto\",\n                                                                                                               \"width\":\"100%\"\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"label\",\n                                                                                                               \"text\":\"Always receive them late \",\n                                                                                                               \"content_description\":\"Always receive them late \",\n                                                                                                               \"text_appearance\":{\n                                                                                                                  \"font_size\":17,\n                                                                                                                  \"color\":{\n                                                                                                                     \"default\":{\n                                                                                                                        \"type\":\"hex\",\n                                                                                                                        \"hex\":\"#222222\",\n                                                                                                                        \"alpha\":1\n                                                                                                                     },\n                                                                                                                     \"selectors\":[\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        }\n                                                                                                                     ]\n                                                                                                                  },\n                                                                                                                  \"alignment\":\"start\",\n                                                                                                                  \"styles\":[\n                                                                                                                     \n                                                                                                                  ],\n                                                                                                                  \"font_families\":[\n                                                                                                                     \"sans-serif\"\n                                                                                                                  ]\n                                                                                                               }\n                                                                                                            }\n                                                                                                         }\n                                                                                                      ]\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"size\":{\n                                                                                                      \"width\":\"100%\",\n                                                                                                      \"height\":\"100%\"\n                                                                                                   },\n                                                                                                   \"margin\":{\n                                                                                                      \"top\":0,\n                                                                                                      \"bottom\":8\n                                                                                                   },\n                                                                                                   \"view\":{\n                                                                                                      \"type\":\"linear_layout\",\n                                                                                                      \"direction\":\"horizontal\",\n                                                                                                      \"items\":[\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":8\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"width\":20,\n                                                                                                               \"height\":20\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"radio_input\",\n                                                                                                               \"reporting_value\":\"f5cefb65-4231-4401-8ff1-030efa0afb9a\",\n                                                                                                               \"content_description\":\"I don't receive refill reminders\",\n                                                                                                               \"style\":{\n                                                                                                                  \"type\":\"checkbox\",\n                                                                                                                  \"bindings\":{\n                                                                                                                     \"selected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           },\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":0.6,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#000000\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     },\n                                                                                                                     \"unselected\":{\n                                                                                                                        \"shapes\":[\n                                                                                                                           {\n                                                                                                                              \"type\":\"ellipse\",\n                                                                                                                              \"scale\":1,\n                                                                                                                              \"aspect_ratio\":1,\n                                                                                                                              \"border\":{\n                                                                                                                                 \"radius\":20,\n                                                                                                                                 \"stroke_width\":2,\n                                                                                                                                 \"stroke_color\":{\n                                                                                                                                    \"default\":{\n                                                                                                                                       \"type\":\"hex\",\n                                                                                                                                       \"hex\":\"#000000\",\n                                                                                                                                       \"alpha\":1\n                                                                                                                                    }\n                                                                                                                                 }\n                                                                                                                              },\n                                                                                                                              \"color\":{\n                                                                                                                                 \"default\":{\n                                                                                                                                    \"type\":\"hex\",\n                                                                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                                                                    \"alpha\":1\n                                                                                                                                 }\n                                                                                                                              }\n                                                                                                                           }\n                                                                                                                        ]\n                                                                                                                     }\n                                                                                                                  }\n                                                                                                               }\n                                                                                                            }\n                                                                                                         },\n                                                                                                         {\n                                                                                                            \"margin\":{\n                                                                                                               \"end\":16\n                                                                                                            },\n                                                                                                            \"size\":{\n                                                                                                               \"height\":\"auto\",\n                                                                                                               \"width\":\"100%\"\n                                                                                                            },\n                                                                                                            \"view\":{\n                                                                                                               \"type\":\"label\",\n                                                                                                               \"text\":\"I don't receive refill reminders\",\n                                                                                                               \"content_description\":\"I don't receive refill reminders\",\n                                                                                                               \"text_appearance\":{\n                                                                                                                  \"font_size\":17,\n                                                                                                                  \"color\":{\n                                                                                                                     \"default\":{\n                                                                                                                        \"type\":\"hex\",\n                                                                                                                        \"hex\":\"#222222\",\n                                                                                                                        \"alpha\":1\n                                                                                                                     },\n                                                                                                                     \"selectors\":[\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"ios\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"android\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":false,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#222222\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        },\n                                                                                                                        {\n                                                                                                                           \"platform\":\"web\",\n                                                                                                                           \"dark_mode\":true,\n                                                                                                                           \"color\":{\n                                                                                                                              \"type\":\"hex\",\n                                                                                                                              \"hex\":\"#FFFFFF\",\n                                                                                                                              \"alpha\":1\n                                                                                                                           }\n                                                                                                                        }\n                                                                                                                     ]\n                                                                                                                  },\n                                                                                                                  \"alignment\":\"start\",\n                                                                                                                  \"styles\":[\n                                                                                                                     \n                                                                                                                  ],\n                                                                                                                  \"font_families\":[\n                                                                                                                     \"sans-serif\"\n                                                                                                                  ]\n                                                                                                               }\n                                                                                                            }\n                                                                                                         }\n                                                                                                      ]\n                                                                                                   }\n                                                                                                }\n                                                                                             ],\n                                                                                             \"randomize_children\":false\n                                                                                          },\n                                                                                          \"attribute_name\":{\n                                                                                             \n                                                                                          }\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"7dd43636-fb19-4f7a-a715-0a32b38fea2b\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"auto\"\n                                                                              },\n                                                                              \"margin\":{\n                                                                                 \"top\":8,\n                                                                                 \"bottom\":8,\n                                                                                 \"start\":16,\n                                                                                 \"end\":16\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"linear_layout\",\n                                                                                 \"direction\":\"vertical\",\n                                                                                 \"items\":[\n                                                                                    {\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":8\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":\"auto\"\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"type\":\"label\",\n                                                                                          \"text\":\"What could we have done better to improve your prescription refill pick-up experience?\",\n                                                                                          \"content_description\":\"What could we have done better to improve your prescription refill pick-up experience?\",\n                                                                                          \"text_appearance\":{\n                                                                                             \"font_size\":19,\n                                                                                             \"color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             },\n                                                                                             \"alignment\":\"start\",\n                                                                                             \"styles\":[\n                                                                                                \"bold\"\n                                                                                             ],\n                                                                                             \"font_families\":[\n                                                                                                \"sans-serif\"\n                                                                                             ]\n                                                                                          }\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"margin\":{\n                                                                                          \"top\":0,\n                                                                                          \"bottom\":8\n                                                                                       },\n                                                                                       \"size\":{\n                                                                                          \"width\":\"100%\",\n                                                                                          \"height\":75\n                                                                                       },\n                                                                                       \"view\":{\n                                                                                          \"identifier\":\"7dd43636-fb19-4f7a-a715-0a32b38fea2b\",\n                                                                                          \"input_type\":\"text_multiline\",\n                                                                                          \"required\":false,\n                                                                                          \"place_holder\":\"\",\n                                                                                          \"content_description\":\"\",\n                                                                                          \"type\":\"text_input\",\n                                                                                          \"background_color\":{\n                                                                                             \"default\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#FFFFFF\",\n                                                                                                \"alpha\":1\n                                                                                             },\n                                                                                             \"selectors\":[\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#000000\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#000000\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#000000\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                }\n                                                                                             ]\n                                                                                          },\n                                                                                          \"border\":{\n                                                                                             \"radius\":4,\n                                                                                             \"stroke_width\":1,\n                                                                                             \"stroke_color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#222222\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             }\n                                                                                          },\n                                                                                          \"text_appearance\":{\n                                                                                             \"alignment\":\"start\",\n                                                                                             \"font_size\":14,\n                                                                                             \"font_families\":[\n                                                                                                \"sans-serif\"\n                                                                                             ],\n                                                                                             \"color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#000000\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             },\n                                                                                             \"place_holder_color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#7B7C84\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#7B7C84\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#7B7C84\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#7B7C84\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             }\n                                                                                          },\n                                                                                          \"view_overrides\":{\n                                                                                             \"icon_end\":[\n                                                                                                {\n                                                                                                   \"when_state_matches\":{\n                                                                                                      \"scope\":[\n                                                                                                         \"123\"\n                                                                                                      ],\n                                                                                                      \"value\":{\n                                                                                                         \"equals\":true\n                                                                                                      }\n                                                                                                   },\n                                                                                                   \"value\":{\n                                                                                                      \"type\":\"floating\",\n                                                                                                      \"icon\":{\n                                                                                                         \"type\":\"icon\",\n                                                                                                         \"icon\":\"exclamationmark_circle_fill\",\n                                                                                                         \"scale\":1,\n                                                                                                         \"color\":{\n                                                                                                            \"default\":{\n                                                                                                               \"type\":\"hex\",\n                                                                                                               \"hex\":\"#ff0000\",\n                                                                                                               \"alpha\":1\n                                                                                                            }\n                                                                                                         }\n                                                                                                      }\n                                                                                                   }\n                                                                                                }\n                                                                                             ],\n                                                                                             \"border\":[\n                                                                                                {\n                                                                                                   \"when_state_matches\":{\n                                                                                                      \"scope\":[\n                                                                                                         \"123\"\n                                                                                                      ],\n                                                                                                      \"value\":{\n                                                                                                         \"equals\":true\n                                                                                                      }\n                                                                                                   },\n                                                                                                   \"value\":{\n                                                                                                      \"radius\":4,\n                                                                                                      \"stroke_width\":1,\n                                                                                                      \"stroke_color\":{\n                                                                                                         \"default\":{\n                                                                                                            \"type\":\"hex\",\n                                                                                                            \"hex\":\"#ff0000\",\n                                                                                                            \"alpha\":1\n                                                                                                         }\n                                                                                                      }\n                                                                                                   }\n                                                                                                }\n                                                                                             ]\n                                                                                          },\n                                                                                          \"on_error\":{\n                                                                                             \"state_actions\":[\n                                                                                                {\n                                                                                                   \"type\":\"set\",\n                                                                                                   \"key\":\"123\",\n                                                                                                   \"value\":true\n                                                                                                }\n                                                                                             ]\n                                                                                          },\n                                                                                          \"on_edit\":{\n                                                                                             \"state_actions\":[\n                                                                                                {\n                                                                                                   \"type\":\"set\",\n                                                                                                   \"key\":\"123\"\n                                                                                                }\n                                                                                             ]\n                                                                                          },\n                                                                                          \"on_valid\":{\n                                                                                             \"state_actions\":[\n                                                                                                {\n                                                                                                   \"type\":\"set\",\n                                                                                                   \"key\":\"123\"\n                                                                                                }\n                                                                                             ]\n                                                                                          }\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              }\n                                                                           },\n                                                                           {\n                                                                              \"identifier\":\"3e925b8b-3bdd-4c4a-8330-1edd6b6573e3_linear_layout_item\",\n                                                                              \"size\":{\n                                                                                 \"width\":\"100%\",\n                                                                                 \"height\":\"100%\"\n                                                                              },\n                                                                              \"view\":{\n                                                                                 \"type\":\"linear_layout\",\n                                                                                 \"direction\":\"horizontal\",\n                                                                                 \"items\":[\n                                                                                    \n                                                                                 ]\n                                                                              }\n                                                                           }\n                                                                        ]\n                                                                     }\n                                                                  }\n                                                               },\n                                                               {\n                                                                  \"identifier\":\"6f8cd8b3-5c59-4cc2-a0d5-6aded6e3d8d4_wrapped_linear_layout\",\n                                                                  \"size\":{\n                                                                     \"width\":\"100%\",\n                                                                     \"height\":\"auto\"\n                                                                  },\n                                                                  \"view\":{\n                                                                     \"type\":\"container\",\n                                                                     \"items\":[\n                                                                        {\n                                                                           \"identifier\":\"6f8cd8b3-5c59-4cc2-a0d5-6aded6e3d8d4\",\n                                                                           \"margin\":{\n                                                                              \"top\":0,\n                                                                              \"bottom\":0,\n                                                                              \"start\":0,\n                                                                              \"end\":0\n                                                                           },\n                                                                           \"position\":{\n                                                                              \"horizontal\":\"center\",\n                                                                              \"vertical\":\"center\"\n                                                                           },\n                                                                           \"size\":{\n                                                                              \"width\":\"100%\",\n                                                                              \"height\":\"auto\"\n                                                                           },\n                                                                           \"view\":{\n                                                                              \"type\":\"linear_layout\",\n                                                                              \"direction\":\"vertical\",\n                                                                              \"items\":[\n                                                                                 {\n                                                                                    \"identifier\":\"6da28bdd-de96-44d9-a666-fbb9fb0267b9\",\n                                                                                    \"margin\":{\n                                                                                       \"top\":8,\n                                                                                       \"bottom\":20,\n                                                                                       \"start\":16,\n                                                                                       \"end\":16\n                                                                                    },\n                                                                                    \"size\":{\n                                                                                       \"width\":\"60%\",\n                                                                                       \"height\":50\n                                                                                    },\n                                                                                    \"view\":{\n                                                                                       \"type\":\"label_button\",\n                                                                                       \"identifier\":\"submit_feedback--Submit\",\n                                                                                       \"reporting_metadata\":{\n                                                                                          \"trigger_link_id\":\"6da28bdd-de96-44d9-a666-fbb9fb0267b9\"\n                                                                                       },\n                                                                                       \"label\":{\n                                                                                          \"type\":\"label\",\n                                                                                          \"text\":\"Submit\",\n                                                                                          \"content_description\":\"Submit survey responses and dismiss the survey\",\n                                                                                          \"text_appearance\":{\n                                                                                             \"font_size\":19,\n                                                                                             \"color\":{\n                                                                                                \"default\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                },\n                                                                                                \"selectors\":[\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"ios\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"android\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":false,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#FFFFFF\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   },\n                                                                                                   {\n                                                                                                      \"platform\":\"web\",\n                                                                                                      \"dark_mode\":true,\n                                                                                                      \"color\":{\n                                                                                                         \"type\":\"hex\",\n                                                                                                         \"hex\":\"#000000\",\n                                                                                                         \"alpha\":1\n                                                                                                      }\n                                                                                                   }\n                                                                                                ]\n                                                                                             },\n                                                                                             \"alignment\":\"center\",\n                                                                                             \"styles\":[\n                                                                                                \"bold\"\n                                                                                             ],\n                                                                                             \"font_families\":[\n                                                                                                \"sans-serif\"\n                                                                                             ]\n                                                                                          }\n                                                                                       },\n                                                                                       \"actions\":{\n                                                                                          \n                                                                                       },\n                                                                                       \"enabled\":[\n                                                                                          \"form_validation\"\n                                                                                       ],\n                                                                                       \"button_click\":[\n                                                                                          \"form_submit\",\n                                                                                          \"dismiss\"\n                                                                                       ],\n                                                                                       \"background_color\":{\n                                                                                          \"default\":{\n                                                                                             \"type\":\"hex\",\n                                                                                             \"hex\":\"#222222\",\n                                                                                             \"alpha\":1\n                                                                                          },\n                                                                                          \"selectors\":[\n                                                                                             {\n                                                                                                \"platform\":\"ios\",\n                                                                                                \"dark_mode\":false,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"ios\",\n                                                                                                \"dark_mode\":true,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"android\",\n                                                                                                \"dark_mode\":false,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"android\",\n                                                                                                \"dark_mode\":true,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"web\",\n                                                                                                \"dark_mode\":false,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#222222\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             },\n                                                                                             {\n                                                                                                \"platform\":\"web\",\n                                                                                                \"dark_mode\":true,\n                                                                                                \"color\":{\n                                                                                                   \"type\":\"hex\",\n                                                                                                   \"hex\":\"#FFFFFF\",\n                                                                                                   \"alpha\":1\n                                                                                                }\n                                                                                             }\n                                                                                          ]\n                                                                                       },\n                                                                                       \"border\":{\n                                                                                          \"radius\":15,\n                                                                                          \"stroke_width\":14,\n                                                                                          \"stroke_color\":{\n                                                                                             \"default\":{\n                                                                                                \"type\":\"hex\",\n                                                                                                \"hex\":\"#222222\",\n                                                                                                \"alpha\":1\n                                                                                             },\n                                                                                             \"selectors\":[\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#222222\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"ios\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#222222\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"android\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":false,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#222222\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                },\n                                                                                                {\n                                                                                                   \"platform\":\"web\",\n                                                                                                   \"dark_mode\":true,\n                                                                                                   \"color\":{\n                                                                                                      \"type\":\"hex\",\n                                                                                                      \"hex\":\"#FFFFFF\",\n                                                                                                      \"alpha\":1\n                                                                                                   }\n                                                                                                }\n                                                                                             ]\n                                                                                          }\n                                                                                       },\n                                                                                       \"event_handlers\":[\n                                                                                          {\n                                                                                             \"type\":\"tap\",\n                                                                                             \"state_actions\":[\n                                                                                                {\n                                                                                                   \"type\":\"set\",\n                                                                                                   \"key\":\"submitted\",\n                                                                                                   \"value\":true\n                                                                                                }\n                                                                                             ]\n                                                                                          }\n                                                                                       ],\n                                                                                       \"content_description\":\"Submit survey responses and dismiss the survey\"\n                                                                                    }\n                                                                                 }\n                                                                              ],\n                                                                              \"background_color\":{\n                                                                                 \"default\":{\n                                                                                    \"type\":\"hex\",\n                                                                                    \"hex\":\"#FFFFFF\",\n                                                                                    \"alpha\":1\n                                                                                 },\n                                                                                 \"selectors\":[\n                                                                                    {\n                                                                                       \"platform\":\"ios\",\n                                                                                       \"dark_mode\":false,\n                                                                                       \"color\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                          \"alpha\":1\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"platform\":\"ios\",\n                                                                                       \"dark_mode\":true,\n                                                                                       \"color\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#000000\",\n                                                                                          \"alpha\":1\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"platform\":\"android\",\n                                                                                       \"dark_mode\":false,\n                                                                                       \"color\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                          \"alpha\":1\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"platform\":\"android\",\n                                                                                       \"dark_mode\":true,\n                                                                                       \"color\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#000000\",\n                                                                                          \"alpha\":1\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"platform\":\"web\",\n                                                                                       \"dark_mode\":false,\n                                                                                       \"color\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#FFFFFF\",\n                                                                                          \"alpha\":1\n                                                                                       }\n                                                                                    },\n                                                                                    {\n                                                                                       \"platform\":\"web\",\n                                                                                       \"dark_mode\":true,\n                                                                                       \"color\":{\n                                                                                          \"type\":\"hex\",\n                                                                                          \"hex\":\"#000000\",\n                                                                                          \"alpha\":1\n                                                                                       }\n                                                                                    }\n                                                                                 ]\n                                                                              }\n                                                                           }\n                                                                        }\n                                                                     ]\n                                                                  },\n                                                                  \"margin\":{\n                                                                     \"top\":8,\n                                                                     \"bottom\":8,\n                                                                     \"start\":0,\n                                                                     \"end\":0\n                                                                  }\n                                                               }\n                                                            ]\n                                                         }\n                                                      }\n                                                   ]\n                                                }\n                                             }\n                                          ],\n                                          \"background_color\":{\n                                             \"default\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#FFFFFF\",\n                                                \"alpha\":1\n                                             },\n                                             \"selectors\":[\n                                                {\n                                                   \"platform\":\"ios\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"ios\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"android\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"android\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"web\",\n                                                   \"dark_mode\":false,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#FFFFFF\",\n                                                      \"alpha\":1\n                                                   }\n                                                },\n                                                {\n                                                   \"platform\":\"web\",\n                                                   \"dark_mode\":true,\n                                                   \"color\":{\n                                                      \"type\":\"hex\",\n                                                      \"hex\":\"#000000\",\n                                                      \"alpha\":1\n                                                   }\n                                                }\n                                             ]\n                                          }\n                                       }\n                                    }\n                                 ]\n                              },\n                              \"ignore_safe_area\":false\n                           },\n                           {\n                              \"position\":{\n                                 \"horizontal\":\"end\",\n                                 \"vertical\":\"top\"\n                              },\n                              \"size\":{\n                                 \"width\":48,\n                                 \"height\":48\n                              },\n                              \"view\":{\n                                 \"type\":\"image_button\",\n                                 \"image\":{\n                                    \"scale\":0.4,\n                                    \"type\":\"icon\",\n                                    \"icon\":\"close\",\n                                    \"color\":{\n                                       \"default\":{\n                                          \"type\":\"hex\",\n                                          \"hex\":\"#222222\",\n                                          \"alpha\":1\n                                       },\n                                       \"selectors\":[\n                                          {\n                                             \"platform\":\"ios\",\n                                             \"dark_mode\":false,\n                                             \"color\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#222222\",\n                                                \"alpha\":1\n                                             }\n                                          },\n                                          {\n                                             \"platform\":\"ios\",\n                                             \"dark_mode\":true,\n                                             \"color\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#FFFFFF\",\n                                                \"alpha\":1\n                                             }\n                                          },\n                                          {\n                                             \"platform\":\"android\",\n                                             \"dark_mode\":false,\n                                             \"color\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#222222\",\n                                                \"alpha\":1\n                                             }\n                                          },\n                                          {\n                                             \"platform\":\"android\",\n                                             \"dark_mode\":true,\n                                             \"color\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#FFFFFF\",\n                                                \"alpha\":1\n                                             }\n                                          },\n                                          {\n                                             \"platform\":\"web\",\n                                             \"dark_mode\":false,\n                                             \"color\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#222222\",\n                                                \"alpha\":1\n                                             }\n                                          },\n                                          {\n                                             \"platform\":\"web\",\n                                             \"dark_mode\":true,\n                                             \"color\":{\n                                                \"type\":\"hex\",\n                                                \"hex\":\"#FFFFFF\",\n                                                \"alpha\":1\n                                             }\n                                          }\n                                       ]\n                                    }\n                                 },\n                                 \"identifier\":\"dismiss_button\",\n                                 \"button_click\":[\n                                    \"dismiss\"\n                                 ],\n                                 \"localized_content_description\":{\n                                    \"ref\":\"ua_dismiss\",\n                                    \"fallback\":\"Dismiss\"\n                                 }\n                              }\n                           },\n                           {\n                              \"margin\":{\n                                 \"top\":4,\n                                 \"bottom\":4,\n                                 \"end\":0,\n                                 \"start\":0\n                              },\n                              \"position\":{\n                                 \"horizontal\":\"center\",\n                                 \"vertical\":\"bottom\"\n                              },\n                              \"size\":{\n                                 \"height\":7,\n                                 \"width\":\"100%\"\n                              },\n                              \"view\":{\n                                 \"type\":\"pager_indicator\",\n                                 \"spacing\":6,\n                                 \"bindings\":{\n                                    \"selected\":{\n                                       \"shapes\":[\n                                          {\n                                             \"type\":\"ellipse\",\n                                             \"scale\":1,\n                                             \"aspect_ratio\":1,\n                                             \"color\":{\n                                                \"default\":{\n                                                   \"type\":\"hex\",\n                                                   \"hex\":\"#7B7C84\",\n                                                   \"alpha\":1\n                                                },\n                                                \"selectors\":[\n                                                   {\n                                                      \"platform\":\"ios\",\n                                                      \"dark_mode\":false,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#7B7C84\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"ios\",\n                                                      \"dark_mode\":true,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#FFFFFF\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"android\",\n                                                      \"dark_mode\":false,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#7B7C84\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"android\",\n                                                      \"dark_mode\":true,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#FFFFFF\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"web\",\n                                                      \"dark_mode\":false,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#7B7C84\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"web\",\n                                                      \"dark_mode\":true,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#FFFFFF\",\n                                                         \"alpha\":1\n                                                      }\n                                                   }\n                                                ]\n                                             }\n                                          }\n                                       ]\n                                    },\n                                    \"unselected\":{\n                                       \"shapes\":[\n                                          {\n                                             \"type\":\"ellipse\",\n                                             \"aspect_ratio\":1,\n                                             \"scale\":1,\n                                             \"color\":{\n                                                \"default\":{\n                                                   \"type\":\"hex\",\n                                                   \"hex\":\"#7B7C84\",\n                                                   \"alpha\":1\n                                                },\n                                                \"selectors\":[\n                                                   {\n                                                      \"platform\":\"ios\",\n                                                      \"dark_mode\":false,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#7B7C84\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"ios\",\n                                                      \"dark_mode\":true,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#FFFFFF\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"android\",\n                                                      \"dark_mode\":false,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#7B7C84\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"android\",\n                                                      \"dark_mode\":true,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#FFFFFF\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"web\",\n                                                      \"dark_mode\":false,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#7B7C84\",\n                                                         \"alpha\":1\n                                                      }\n                                                   },\n                                                   {\n                                                      \"platform\":\"web\",\n                                                      \"dark_mode\":true,\n                                                      \"color\":{\n                                                         \"type\":\"hex\",\n                                                         \"hex\":\"#FFFFFF\",\n                                                         \"alpha\":1\n                                                      }\n                                                   }\n                                                ]\n                                             }\n                                          }\n                                       ]\n                                    }\n                                 },\n                                 \"automated_accessibility_actions\":[\n                                    {\n                                       \"type\":\"announce\"\n                                    }\n                                 ]\n                              }\n                           }\n                        ]\n                     }\n                  }\n               }\n            ]\n         }\n      }\n   }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/airship-quiz-1.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 90%\n      height: 85%\n    shade_color:\n      default:\n        hex: '#020202'\n        alpha: 0.75\nview:\n  type: state_controller\n  view:\n    type: pager_controller\n    identifier: airship_survey_controller\n    view:\n      type: container\n      background_color:\n        default:\n          hex: '#004bff'\n          alpha: 1\n      border:\n        radius: 20\n        stroke_width: 2\n        stroke_color:\n          default:\n            hex: '#FFFFFF'\n            alpha: 0.1\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 90%\n          height: 85%\n        view:\n          type: pager\n          disable_swipe: true\n          items:\n          - identifier: rating_page\n            type: pager_item\n            view:\n              type: form_controller\n              validation_mode:\n                type: immediate\n              identifier: rating_form\n              submit: submit_event\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#004bff'\n                    alpha: 1\n                border:\n                  radius: 20\n                  stroke_width: 0\n                items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: 100%\n                    height: 100%\n                  view:\n                    type: container\n                    items:\n                    - margin:\n                        bottom: 0\n                        top: 0\n                        end: 0\n                        start: 0\n                      position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - identifier: scroll_container\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: scroll_layout\n                            direction: vertical\n                            view:\n                              type: linear_layout\n                              direction: vertical\n                              items:\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 40\n                                  bottom: 0\n                                view:\n                                  type: empty_view\n                              - identifier: rating_title_label\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 16\n                                  bottom: 24\n                                  start: 24\n                                  end: 24\n                                view:\n                                  type: label\n                                  text: \"\\u2728 Like Airship? \\u2728\"\n                                  content_description: Rating Section Title\n                                  text_appearance:\n                                    font_size: 30\n                                    color:\n                                      default:\n                                        hex: '#FFFFFF'\n                                        alpha: 1\n                                    alignment: center\n                                    styles:\n                                    - bold\n                                    font_families:\n                                    - sans-serif\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 0\n                                  bottom: 32\n                                  start: 24\n                                  end: 24\n                                view:\n                                  type: label\n                                  text: Please tap to rate your experience\n                                  text_appearance:\n                                    alignment: center\n                                    font_size: 18\n                                    color:\n                                      default:\n                                        hex: '#FFFFFF'\n                                        alpha: 0.9\n                              - identifier: rating_input_section\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 24\n                                  start: 0\n                                  end: 0\n                                view:\n                                  type: score_controller\n                                  identifier: score_radio_controller\n                                  required: true\n                                  view:\n                                    type: container\n                                    items:\n                                    - position:\n                                        horizontal: center\n                                        vertical: center\n                                      size:\n                                        width: auto\n                                        height: auto\n                                      view:\n                                        type: linear_layout\n                                        direction: horizontal\n                                        items:\n                                        - size:\n                                            width: 42\n                                            height: 42\n                                          margin:\n                                            end: 8\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_1\n                                            content_description: Rating 1\n                                            reporting_value: 1  \n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 1\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: heart\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    hex: '#DDDDDD'\n                                                    alpha: 0.7\n                                              view_overrides:\n                                                icon:\n                                                - value:\n                                                    icon: heart_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        hex: '#f1084f'\n                                                        alpha: 1.0\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 1\n                                        - size:\n                                            width: 42\n                                            height: 42\n                                          margin:\n                                            end: 8\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_2\n                                            content_description: Rating 2\n                                            reporting_value: 2\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 2\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: heart\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    hex: '#DDDDDD'\n                                                    alpha: 0.7\n                                              view_overrides:\n                                                icon:\n                                                - value:\n                                                    icon: heart_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        hex: '#f1084f'\n                                                        alpha: 1.0\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 2\n                                        - size:\n                                            width: 42\n                                            height: 42\n                                          margin:\n                                            end: 8\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_3\n                                            content_description: Rating 3\n                                            reporting_value: 3\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 3\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: heart\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    hex: '#DDDDDD'\n                                                    alpha: 0.7\n                                              view_overrides:\n                                                icon:\n                                                - value:\n                                                    icon: heart_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        hex: '#f1084f'\n                                                        alpha: 1.0\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 3\n                                        - size:\n                                            width: 42\n                                            height: 42\n                                          margin:\n                                            end: 8\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_4\n                                            content_description: Rating 4\n                                            reporting_value: 4\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 4\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: heart\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    hex: '#DDDDDD'\n                                                    alpha: 0.7\n                                              view_overrides:\n                                                icon:\n                                                - value:\n                                                    icon: heart_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        hex: '#f1084f'\n                                                        alpha: 1.0\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 4\n                                        - size:\n                                            width: 42\n                                            height: 42\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_5\n                                            content_description: Rating 5\n                                            reporting_value: 5\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 5\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: heart\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    hex: '#DDDDDD'\n                                                    alpha: 0.7\n                                              view_overrides:\n                                                icon:\n                                                - value:\n                                                    icon: heart_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        hex: '#f1084f'\n                                                        alpha: 1.0\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 5\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 16\n                                  bottom: 32\n                                  start: 24\n                                  end: 24\n                                view:\n                                  type: label\n                                  text: ''\n                                  text_appearance:\n                                    alignment: center\n                                    font_size: 72\n                                    color:\n                                      default:\n                                        hex: '#FFFFFF'\n                                        alpha: 0.8\n                                  view_overrides:\n                                    text:\n                                    - value: \"\\U0001FAA8\"\n                                      when_state_matches:\n                                        key: selected_score\n                                        value:\n                                          equals: 1\n                                    - value: \"\\U0001F949\"\n                                      when_state_matches:\n                                        key: selected_score\n                                        value:\n                                          equals: 2\n                                    - value: \"\\U0001F948\"\n                                      when_state_matches:\n                                        key: selected_score\n                                        value:\n                                          equals: 3\n                                    - value: \"\\U0001F947\"\n                                      when_state_matches:\n                                        key: selected_score\n                                        value:\n                                          equals: 4\n                                    - value: \"\\U0001F3C6\"\n                                      when_state_matches:\n                                        key: selected_score\n                                        value:\n                                          equals: 5\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 0\n                                  start: 24\n                                  end: 24\n                                view:\n                                  type: label\n                                  text: Tell us how we can improve\n                                  text_appearance:\n                                    alignment: start\n                                    font_size: 18\n                                    color:\n                                      default:\n                                        hex: '#FFFFFF'\n                                        alpha: 1\n                                    styles:\n                                    - bold\n                                  visibility:\n                                    default: false\n                                    invert_when_state_matches:\n                                      key: selected_score\n                                      value:\n                                        at_most: 4\n                              - size:\n                                  width: 100%\n                                  height: 100\n                                margin:\n                                  top: 8\n                                  bottom: 12\n                                  start: 24\n                                  end: 12\n                                view:\n                                  type: text_input\n                                  place_holder: Please share your feedback...\n                                  identifier: feedback_field\n                                  border:\n                                    radius: 8\n                                    stroke_width: 2\n                                    stroke_color:\n                                      default:\n                                        hex: '#FFFFFF'\n                                        alpha: 0.5\n                                  text_appearance:\n                                    alignment: start\n                                    font_size: 16\n                                    color:\n                                      default:\n                                        hex: '#FFFFFF'\n                                        alpha: 1\n                                  input_type: text_multiline\n                                  required: false\n                                  visibility:\n                                    default: false\n                                    invert_when_state_matches:\n                                      key: selected_score\n                                      value:\n                                        at_most: 4\n                              - size:\n                                  width: 100%\n                                  height: 50\n                                margin:\n                                  top: 12\n                                  bottom: 32\n                                  start: 24\n                                  end: 24\n                                view:\n                                  type: label_button\n                                  identifier: next_button_rating\n                                  background_color:\n                                    default:\n                                      hex: '#f1084f'\n                                      alpha: 1\n                                  border:\n                                    radius: 25\n                                    stroke_width: 0\n                                  button_click:\n                                  - dismiss\n                                  enabled:\n                                  - form_validation\n                                  label:\n                                    type: label\n                                    text: Submit\n                                    text_appearance:\n                                      font_size: 18\n                                      alignment: center\n                                      styles:\n                                      - bold\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                  visibility:\n                                    default: false\n                                    invert_when_state_matches:\n                                      key: selected_score\n                                      value:\n                                        at_most: 3\n                              - size:\n                                  width: 100%\n                                  height: 50\n                                margin:\n                                  top: 12\n                                  bottom: 32\n                                  start: 24\n                                  end: 24\n                                view:\n                                  type: label_button\n                                  identifier: next_button_rating\n                                  background_color:\n                                    default:\n                                      hex: '#f1084f'\n                                      alpha: 1\n                                  border:\n                                    radius: 25\n                                    stroke_width: 0\n                                  button_click:\n                                  - pager_next\n                                  enabled:\n                                  - form_validation\n                                  label:\n                                    type: label\n                                    text: Next\n                                    text_appearance:\n                                      font_size: 18\n                                      alignment: center\n                                      styles:\n                                      - bold\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                  visibility:\n                                    default: false\n                                    invert_when_state_matches:\n                                      key: selected_score\n                                      value:\n                                        at_least: 4\n          - identifier: personal_info_page\n            type: pager_item\n            view:\n              type: form_controller\n              validation_mode:\n                type: immediate\n              identifier: personal_info_form\n              submit: submit_event\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#004bff'\n                    alpha: 1\n                items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: 100%\n                    height: 100%\n                  view:\n                    type: scroll_layout\n                    direction: vertical\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 32\n                          bottom: 24\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: \"\\u2728 Airship Survey \\u2728\"\n                          text_appearance:\n                            alignment: center\n                            font_size: 32\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                            - bold\n                            font_families:\n                            - sans-serif\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 0\n                          bottom: 24\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: Enter your information below to get started!\n                          text_appearance:\n                            alignment: center\n                            font_size: 18\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            font_families:\n                            - sans-serif\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: Your Name\n                          text_appearance:\n                            alignment: start\n                            font_size: 18\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                            - bold\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 16\n                          start: 24\n                          end: 24\n                        view:\n                          type: text_input\n                          place_holder: Enter your full name\n                          identifier: name_field\n                          icon_end:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: asterisk\n                              color:\n                                default:\n                                  hex: '#f1084f'\n                                  alpha: 1.0\n                              scale: 0.75\n                          border:\n                            radius: 8\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 0.5\n                          view_overrides:\n                            icon_end:\n                            - value:\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: exclamationmark_circle_fill\n                                  color:\n                                    default:\n                                      hex: '#f1084f'\n                                      alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                key: name_field_state\n                                value:\n                                  equals: error\n                            - value:\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: checkmark\n                                  color:\n                                    default:\n                                      hex: '#6ca15f'\n                                      alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                key: name_field_state\n                                value:\n                                  equals: valid\n                            border:\n                            - value:\n                                radius: 8\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#f1084f'\n                                    alpha: 1\n                              when_state_matches:\n                                key: name_field_state\n                                value:\n                                  equals: error\n                            - value:\n                                radius: 8\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#6ca15f'\n                                    alpha: 1\n                              when_state_matches:\n                                key: name_field_state\n                                value:\n                                  equals: valid\n                          text_appearance:\n                            alignment: start\n                            font_size: 16\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                          input_type: text\n                          required: true\n                          on_error:\n                            state_actions:\n                            - type: set\n                              key: name_field_state\n                              value: error\n                          on_valid:\n                            state_actions:\n                            - type: set\n                              key: name_field_state\n                              value: valid\n                          on_edit:\n                            state_actions:\n                            - type: set\n                              key: name_field_state\n                              value: editing\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: Email Address\n                          text_appearance:\n                            alignment: start\n                            font_size: 18\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                            - bold\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 16\n                          start: 24\n                          end: 24\n                        view:\n                          type: text_input\n                          place_holder: your.email@example.com\n                          identifier: email_field\n                          icon_end:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: asterisk\n                              color:\n                                default:\n                                  hex: '#f1084f'\n                                  alpha: 1.0\n                              scale: 0.75\n                          border:\n                            radius: 8\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 0.5\n                          view_overrides:\n                            icon_end:\n                            - value:\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: exclamationmark_circle_fill\n                                  color:\n                                    default:\n                                      hex: '#f1084f'\n                                      alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                key: email_field_state\n                                value:\n                                  equals: error\n                            - value:\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: checkmark\n                                  color:\n                                    default:\n                                      hex: '#6ca15f'\n                                      alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                key: email_field_state\n                                value:\n                                  equals: valid\n                            border:\n                            - value:\n                                radius: 8\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#f1084f'\n                                    alpha: 1\n                              when_state_matches:\n                                key: email_field_state\n                                value:\n                                  equals: error\n                            - value:\n                                radius: 8\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#6ca15f'\n                                    alpha: 1\n                              when_state_matches:\n                                key: email_field_state\n                                value:\n                                  equals: valid\n                          text_appearance:\n                            alignment: start\n                            font_size: 16\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                          input_type: email\n                          required: true\n                          on_error:\n                            state_actions:\n                            - type: set\n                              key: email_field_state\n                              value: error\n                          on_valid:\n                            state_actions:\n                            - type: set\n                              key: email_field_state\n                              value: valid\n                          on_edit:\n                            state_actions:\n                            - type: set\n                              key: email_field_state\n                              value: editing\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: Phone Number\n                          text_appearance:\n                            alignment: start\n                            font_size: 18\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                            - bold\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 32\n                          start: 24\n                          end: 24\n                        view:\n                          type: text_input\n                          place_holder: Enter phone number\n                          identifier: phone_field\n                          icon_end:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: asterisk\n                              color:\n                                default:\n                                  hex: '#f1084f'\n                                  alpha: 1.0\n                              scale: 0.75\n                          border:\n                            radius: 8\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 0.5\n                          view_overrides:\n                            icon_end:\n                            - value:\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: exclamationmark_circle_fill\n                                  color:\n                                    default:\n                                      hex: '#f1084f'\n                                      alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                key: phone_field_state\n                                value:\n                                  equals: error\n                            - value:\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: checkmark\n                                  color:\n                                    default:\n                                      hex: '#6ca15f'\n                                      alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                key: phone_field_state\n                                value:\n                                  equals: valid\n                            border:\n                            - value:\n                                radius: 8\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#f1084f'\n                                    alpha: 1\n                              when_state_matches:\n                                key: phone_field_state\n                                value:\n                                  equals: error\n                            - value:\n                                radius: 8\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#6ca15f'\n                                    alpha: 1\n                              when_state_matches:\n                                key: phone_field_state\n                                value:\n                                  equals: valid\n                          text_appearance:\n                            alignment: start\n                            font_size: 16\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                          input_type: sms\n                          locales:\n                          - country_code: US\n                            prefix: '+1'\n                          - country_code: FR\n                            prefix: '+33'\n                          - country_code: UA\n                            prefix: '+380'\n                          required: true\n                          on_error:\n                            state_actions:\n                            - type: set\n                              key: phone_field_state\n                              value: error\n                          on_valid:\n                            state_actions:\n                            - type: set\n                              key: phone_field_state\n                              value: valid\n                          on_edit:\n                            state_actions:\n                            - type: set\n                              key: phone_field_state\n                              value: editing\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          bottom: 32\n                          start: 24\n                          end: 24\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                          - size:\n                              width: 48%\n                              height: 50\n                            margin:\n                              end: 8\n                            view:\n                              type: label_button\n                              identifier: prev_button_personal\n                              background_color:\n                                default:\n                                  hex: '#001f9e'\n                                  alpha: 1\n                              border:\n                                radius: 25\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#FFFFFF'\n                                    alpha: 0.3\n                              button_click:\n                              - pager_previous\n                              label:\n                                type: label\n                                text: Previous\n                                text_appearance:\n                                  font_size: 16\n                                  alignment: center\n                                  styles:\n                                  - bold\n                                  color:\n                                    default:\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n                          - size:\n                              width: 48%\n                              height: 50\n                            margin:\n                              start: 8\n                            view:\n                              type: label_button\n                              identifier: next_button_personal\n                              background_color:\n                                default:\n                                  hex: '#f1084f'\n                                  alpha: 1\n                              border:\n                                radius: 25\n                                stroke_width: 0\n                              button_click:\n                              - pager_next\n                              enabled:\n                              - form_validation\n                              label:\n                                type: label\n                                text: Next\n                                text_appearance:\n                                  font_size: 16\n                                  alignment: center\n                                  styles:\n                                  - bold\n                                  color:\n                                    default:\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n          - identifier: color_page\n            type: pager_item\n            view:\n              type: form_controller\n              validation_mode:\n                type: immediate\n              identifier: color_form\n              submit: submit_event\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#004bff'\n                    alpha: 1\n                items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: 100%\n                    height: 100%\n                  view:\n                    type: scroll_layout\n                    direction: vertical\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 32\n                          bottom: 24\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: \"Color Preferences \\U0001F3A8\"\n                          text_appearance:\n                            alignment: center\n                            font_size: 28\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                            - bold\n                            font_families:\n                            - sans-serif\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          start: 24\n                          end: 24\n                        view:\n                          type: label\n                          text: Select your favorite color\n                          text_appearance:\n                            alignment: start\n                            font_size: 18\n                            color:\n                              default:\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                            - bold\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          bottom: 32\n                          start: 24\n                          end: 24\n                        view:\n                          type: radio_input_controller\n                          identifier: color_selection\n                          required: true\n                          on_error:\n                            state_actions:\n                            - type: set\n                              key: color_selection_state\n                              value: error\n                          on_valid:\n                            state_actions:\n                            - type: set\n                              key: color_selection_state\n                              value: valid\n                          on_edit:\n                            state_actions:\n                            - type: set\n                              key: color_selection_state\n                              value: editing\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - size:\n                                width: 100%\n                                height: 100%\n                              margin:\n                                top: 0\n                                bottom: 16\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                - margin:\n                                    end: 16\n                                  size:\n                                    width: 24\n                                    height: 24\n                                  view:\n                                    type: radio_input\n                                    reporting_value: raspberry_red\n                                    content_description: Raspberry Red\n                                    style:\n                                      type: checkbox\n                                      bindings:\n                                        selected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                          - type: ellipse\n                                            scale: 0.6\n                                            aspect_ratio: 1\n                                            color:\n                                              default:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                        unselected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                - margin:\n                                    end: 16\n                                  size:\n                                    height: auto\n                                    width: 100%\n                                  view:\n                                    type: label\n                                    text: Raspberry Red\n                                    content_description: Raspberry Red\n                                    text_appearance:\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                - size:\n                                    width: 24\n                                    height: 24\n                                  margin:\n                                    end: 8\n                                  view:\n                                    type: empty_view\n                                    background_color:\n                                      default:\n                                        hex: '#f1084f'\n                                        alpha: 1\n                                    border:\n                                      radius: 12\n                                      stroke_width: 0\n                            - size:\n                                width: 100%\n                                height: 100%\n                              margin:\n                                top: 0\n                                bottom: 16\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                - margin:\n                                    end: 16\n                                  size:\n                                    width: 24\n                                    height: 24\n                                  view:\n                                    type: radio_input\n                                    reporting_value: ultramarine_blue\n                                    content_description: Ultramarine Blue\n                                    style:\n                                      type: checkbox\n                                      bindings:\n                                        selected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                          - type: ellipse\n                                            scale: 0.6\n                                            aspect_ratio: 1\n                                            color:\n                                              default:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                        unselected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                - margin:\n                                    end: 16\n                                  size:\n                                    height: auto\n                                    width: 100%\n                                  view:\n                                    type: label\n                                    text: Ultramarine Blue\n                                    content_description: Ultramarine Blue\n                                    text_appearance:\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                - size:\n                                    width: 24\n                                    height: 24\n                                  margin:\n                                    end: 8\n                                  view:\n                                    type: empty_view\n                                    background_color:\n                                      default:\n                                        hex: '#001f9e'\n                                        alpha: 1\n                                    border:\n                                      radius: 12\n                                      stroke_width: 0\n                            - size:\n                                width: 100%\n                                height: 100%\n                              margin:\n                                top: 0\n                                bottom: 16\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                - margin:\n                                    end: 16\n                                  size:\n                                    width: 24\n                                    height: 24\n                                  view:\n                                    type: radio_input\n                                    reporting_value: royal_blue\n                                    content_description: Obsidian\n                                    style:\n                                      type: checkbox\n                                      bindings:\n                                        selected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                          - type: ellipse\n                                            scale: 0.6\n                                            aspect_ratio: 1\n                                            color:\n                                              default:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                        unselected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                - margin:\n                                    end: 16\n                                  size:\n                                    height: auto\n                                    width: 100%\n                                  view:\n                                    type: label\n                                    text: Obsidian\n                                    content_description: Obsidian\n                                    text_appearance:\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                - size:\n                                    width: 24\n                                    height: 24\n                                  margin:\n                                    end: 8\n                                  view:\n                                    type: empty_view\n                                    background_color:\n                                      default:\n                                        hex: '#000000'\n                                        alpha: 1\n                                    border:\n                                      radius: 12\n                                      stroke_width: 0\n                            - size:\n                                width: 100%\n                                height: 100%\n                              margin:\n                                top: 0\n                                bottom: 16\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                - margin:\n                                    end: 16\n                                  size:\n                                    width: 24\n                                    height: 24\n                                  view:\n                                    type: radio_input\n                                    reporting_value: moss_green\n                                    content_description: Moss Green\n                                    style:\n                                      type: checkbox\n                                      bindings:\n                                        selected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                          - type: ellipse\n                                            scale: 0.6\n                                            aspect_ratio: 1\n                                            color:\n                                              default:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                        unselected:\n                                          shapes:\n                                          - type: ellipse\n                                            scale: 1\n                                            aspect_ratio: 1\n                                            border:\n                                              radius: 20\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                hex: '#004bff'\n                                                alpha: 1\n                                - margin:\n                                    end: 16\n                                  size:\n                                    height: auto\n                                    width: 100%\n                                  view:\n                                    type: label\n                                    text: Moss Green\n                                    content_description: Moss Green\n                                    text_appearance:\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                - size:\n                                    width: 24\n                                    height: 24\n                                  margin:\n                                    end: 8\n                                  view:\n                                    type: empty_view\n                                    background_color:\n                                      default:\n                                        hex: '#6ca15f'\n                                        alpha: 1\n                                    border:\n                                      radius: 12\n                                      stroke_width: 0\n                      - size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 16\n                          bottom: 32\n                          start: 24\n                          end: 24\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                          - size:\n                              width: 48%\n                              height: 50\n                            margin:\n                              end: 8\n                            view:\n                              type: label_button\n                              identifier: prev_button_color\n                              background_color:\n                                default:\n                                  hex: '#001f9e'\n                                  alpha: 1\n                              border:\n                                radius: 25\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: '#FFFFFF'\n                                    alpha: 0.3\n                              button_click:\n                              - pager_previous\n                              label:\n                                type: label\n                                text: Previous\n                                text_appearance:\n                                  font_size: 16\n                                  alignment: center\n                                  styles:\n                                  - bold\n                                  color:\n                                    default:\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n                          - size:\n                              width: 48%\n                              height: 50\n                            margin:\n                              start: 8\n                            view:\n                              type: label_button\n                              identifier: next_button_color\n                              background_color:\n                                default:\n                                  hex: '#f1084f'\n                                  alpha: 1\n                              border:\n                                radius: 25\n                                stroke_width: 0\n                              button_click:\n                              - pager_next\n                              enabled:\n                              - form_validation\n                              label:\n                                type: label\n                                text: Next\n                                text_appearance:\n                                  font_size: 16\n                                  alignment: center\n                                  styles:\n                                  - bold\n                                  color:\n                                    default:\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n          - identifier: thank_you_page\n            type: pager_item\n            view:\n              type: container\n              background_color:\n                default:\n                  hex: '#004bff'\n                  alpha: 1\n              items:\n              - position:\n                  horizontal: center\n                  vertical: center\n                size:\n                  width: 100%\n                  height: 100%\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - size:\n                      width: 100%\n                      height: auto\n                    margin:\n                      top: 40\n                      bottom: 24\n                      start: 24\n                      end: 24\n                    view:\n                      type: label\n                      text: \"Thank You! \\U0001F389\"\n                      text_appearance:\n                        alignment: center\n                        font_size: 32\n                        color:\n                          default:\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        styles:\n                        - bold\n                        font_families:\n                        - sans-serif\n                  - size:\n                      width: 100%\n                      height: auto\n                    margin:\n                      top: 16\n                      bottom: 24\n                      start: 24\n                      end: 24\n                    view:\n                      type: label\n                      text: Your survey has been submitted successfully. We appreciate\n                        your feedback!\n                      text_appearance:\n                        alignment: center\n                        font_size: 18\n                        color:\n                          default:\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        font_families:\n                        - sans-serif\n                  - size:\n                      width: 100%\n                      height: 50\n                    margin:\n                      top: 16\n                      bottom: 40\n                      start: 24\n                      end: 24\n                    view:\n                      type: label_button\n                      identifier: done_button\n                      background_color:\n                        default:\n                          hex: '#f1084f'\n                          alpha: 1\n                      border:\n                        radius: 25\n                        stroke_width: 0\n                      button_click:\n                      - dismiss\n                      label:\n                        type: label\n                        text: \"Done \\u2713\"\n                        text_appearance:\n                          font_size: 18\n                          alignment: center\n                          styles:\n                          - bold\n                          color:\n                            default:\n                              hex: '#FFFFFF'\n                              alpha: 1\n      - position:\n          horizontal: end\n          vertical: top\n        margin:\n          top: 16\n          end: 16\n        size:\n          width: 32\n          height: 32\n        view:\n          type: image_button\n          identifier: close_button\n          button_click:\n          - dismiss\n          image:\n            type: icon\n            icon: close\n            color:\n              default:\n                hex: '#FFFFFF'\n                alpha: 0.8\n            scale: 0.7\n      - position:\n          horizontal: center\n          vertical: bottom\n        margin:\n          bottom: 16\n        size:\n          height: 8\n          width: 100%\n        view:\n          type: pager_indicator\n          spacing: 8\n          bindings:\n            selected:\n              shapes:\n              - type: ellipse\n                aspect_ratio: 1\n                scale: 1\n                color:\n                  default:\n                    hex: '#FFFFFF'\n                    alpha: 1\n            unselected:\n              shapes:\n              - type: ellipse\n                aspect_ratio: 1\n                scale: 1\n                color:\n                  default:\n                    hex: '#FFFFFF'\n                    alpha: 0.3\n          visibility:\n            default: false\n            invert_when_state_matches:\n              key: selected_score\n              value:\n                at_least: 5\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/async_view.yaml",
    "content": "---\nversion: 1\npresentation:\n  type: banner\n  default_placement:\n    position: bottom\n    size:\n      width: 100%\n      height: 80%\nview:\n    type: state_controller\n    view:\n      type: async_view_controller\n      identifier: \"async_view\"\n      request:\n        type: content\n        url: https://hangar-dl.urbanairship.com/binary/public/VWDwdOFjRTKLRxCeXTVP6g/6b0620eb-9d11-4196-9e1f-14595b0713a3\n      placeholder:\n        type: container\n        background_color:\n          default:\n            alpha: 1\n            hex: \"#004bff\"\n        border:\n          radius: 20\n          stroke_color:\n            default:\n              alpha: 0.1\n              hex: \"#FFFFFF\"\n          stroke_width: 2\n        items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100\n            height: 100\n          view:\n            type: icon_view\n            visibility:\n              default: true\n              invert_when_state_matches:\n                scope:\n                    - $asyncView\n                    - current\n                    - error\n                value:\n                    is_present: true\n            icon:\n              icon: progress_spinner\n              scale: 1\n              color:\n                default:\n                  hex: \"#DDDDDD\"\n                  alpha: 0.7\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: auto\n            height: auto\n          view:\n            visibility:\n              default: true\n              invert_when_state_matches:\n                scope:\n                    - $asyncView\n                    - current\n                    - error\n                value:\n                    is_present: false\n            type: linear_layout\n            direction: vertical\n            items:\n              - size:\n                    width: auto\n                    height: auto\n                view:\n                  type: label\n                  text: \"Uh oh :(, something went wrong!\"\n                  text_appearance:\n                    alignment: center\n                    font_size: 16\n                    color:\n                      default:\n                        hex: '#FFFFFF'\n                        alpha: 1\n                    styles:\n                        - bold\n                    font_families:\n                        - sans-serif\n              - size:\n                  width: 48%\n                  height: 50\n                margin:\n                  top: 16\n                view:\n                  type: label_button\n                  identifier: retry\n                  background_color:\n                    default:\n                      hex: '#001f9e'\n                      alpha: 1\n                  border:\n                    radius: 25\n                    stroke_width: 2\n                    stroke_color:\n                      default:\n                        hex: '#FFFFFF'\n                        alpha: 0.3\n                  button_click:\n                    - async_view_retry\n                  label:\n                    type: label\n                    text: Retry\n                    text_appearance:\n                      font_size: 16\n                      alignment: center\n                      styles:\n                        - bold\n                      color:\n                        default:\n                          hex: '#FFFFFF'\n                          alpha: 1\n        - position:\n            horizontal: end\n            vertical: top\n          margin:\n            top: 16\n            end: 16\n          size:\n            width: 32\n            height: 32\n          view:\n            type: image_button\n            identifier: close_button\n            button_click:\n            - dismiss\n            image:\n              type: icon\n              icon: close\n              color:\n                default:\n                  hex: '#FFFFFF'\n                  alpha: 0.8\n              scale: 0.7\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/auto_height_modal.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 70%\n      height: auto\n      max_height: 90%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#FFFFFF\"\n        alpha: 1\n      selectors:\n      - platform: ios\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#FFFFFF\"\n          alpha: 1\n      - platform: ios\n        dark_mode: true\n        color:\n          type: hex\n          hex: \"#000000\"\n          alpha: 1\n      - platform: android\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#FFFFFF\"\n          alpha: 1\n      - platform: android\n        dark_mode: true\n        color:\n          type: hex\n          hex: \"#000000\"\n          alpha: 1\n      - platform: web\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#FFFFFF\"\n          alpha: 1\n      - platform: web\n        dark_mode: true\n        color:\n          type: hex\n          hex: \"#000000\"\n          alpha: 1\n    web:\n      ignore_shade: true\n    border:\n      radius: 0\n    shadow:\n      selectors:\n      - platform: ios\n        shadow:\n          box_shadow:\n            color:\n              default:\n                type: hex\n                hex: \"#7B7C84\"\n                alpha: 1\n              selectors:\n              - platform: ios\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: ios\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              - platform: android\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: android\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              - platform: web\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: web\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n            radius: 0\n            blur_radius: 4\n            offset_x: 0\n            offset_y: 0\n      - platform: web\n        shadow:\n          box_shadow:\n            color:\n              default:\n                type: hex\n                hex: \"#7B7C84\"\n                alpha: 1\n              selectors:\n              - platform: ios\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: ios\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              - platform: android\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: android\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              - platform: web\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: web\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n            radius: 0\n            blur_radius: 4\n            offset_x: 0\n            offset_y: 0\n      - platform: android\n        shadow:\n          android_shadow:\n            color:\n              default:\n                type: hex\n                hex: \"#7B7C84\"\n                alpha: 1\n              selectors:\n              - platform: ios\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: ios\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              - platform: android\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: android\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              - platform: web\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#7B7C84\"\n                  alpha: 1\n              - platform: web\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n            elevation: 4\nview:\n  type: pager_controller\n  identifier: b7974084-be21-4d8e-9772-cc673f1a6336\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - size:\n        width: 100%\n        height: auto\n      view:\n        identifier: 6cf9375d-52a5-475e-9f34-61c1b817b166\n        nps_identifier: 4a92669e-2560-4ece-a398-dc8e278c8366\n        type: nps_form_controller\n        submit: submit_event\n        form_enabled:\n        - form_submission\n        response_type: nps\n        view:\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            view:\n              type: pager\n              disable_swipe: true\n              items:\n              - identifier: 0b494bf9-425b-4c69-b800-71cb28710c75\n                type: pager_item\n                view:\n                  type: container\n                  items:\n                  - size:\n                      width: 100%\n                      height: auto\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: 4a92669e-2560-4ece-a398-dc8e278c8366\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 48\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: linear_layout\n                                        direction: vertical\n                                        items:\n                                        - margin:\n                                            top: 4\n                                            bottom: 8\n                                          size:\n                                            width: 100%\n                                            height: auto\n                                          view:\n                                            identifier: id\n                                            type: text_input\n                                            input_type: text_multiline\n                                            text: \"* dffff\"\n                                            content_description: \"* dffff\"\n                                            text_appearance:\n                                              font_size: 24\n                                              color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                                selectors:\n                                                - platform: ios\n                                                  dark_mode: false\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#000000\"\n                                                    alpha: 1\n                                                - platform: ios\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: android\n                                                  dark_mode: false\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#000000\"\n                                                    alpha: 1\n                                                - platform: android\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: web\n                                                  dark_mode: false\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#000000\"\n                                                    alpha: 1\n                                                - platform: web\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                              alignment: start\n                                              styles:\n                                              - bold\n                                              font_families:\n                                              - sans-serif\n                                    - size:\n                                        width: 100%\n                                        height: auto\n                                      margin:\n                                        top: 0\n                                        bottom: 0\n                                      view:\n                                        type: linear_layout\n                                        direction: vertical\n                                        items:\n                                        - size:\n                                            width: 100%\n                                            height: auto\n                                          view:\n                                            type: linear_layout\n                                            direction: horizontal\n                                            items:\n                                            - size:\n                                                width: 50%\n                                                height: auto\n                                              margin:\n                                                end: 4\n                                                bottom: 4\n                                              view:\n                                                type: label\n                                                text: Not Likely\n                                                content_description: Not Likely\n                                                text_appearance:\n                                                  font_size: 24\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                    - platform: ios\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                  alignment: start\n                                                  styles:\n                                                  - bold\n                                                  font_families:\n                                                  - sans-serif\n                                            - size:\n                                                width: 50%\n                                                height: auto\n                                              margin:\n                                                start: 4\n                                                bottom: 4\n                                              view:\n                                                type: label\n                                                text: Very Likely\n                                                content_description: Very Likely\n                                                text_appearance:\n                                                  font_size: 24\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                    - platform: ios\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                  alignment: end\n                                                  styles:\n                                                  - bold\n                                                  font_families:\n                                                  - sans-serif\n                                        - size:\n                                            width: 100%\n                                            height: auto\n                                          view:\n                                            type: linear_layout\n                                            direction: horizontal\n                                            items:\n                                            - size:\n                                                height: 50\n                                                width: 100%\n                                              view:\n                                                type: score\n                                                style:\n                                                  type: number_range\n                                                  start: 0\n                                                  end: 10\n                                                  spacing: 2\n                                                  bindings:\n                                                    selected:\n                                                      shapes:\n                                                      - type: rectangle\n                                                        scale: 1\n                                                        border:\n                                                          radius: 2\n                                                          stroke_width: 1\n                                                          stroke_color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                          selectors:\n                                                          - platform: ios\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: ios\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                      text_appearance:\n                                                        alignment: center\n                                                        font_families:\n                                                        - sans-serif\n                                                        font_size: 24\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                          selectors:\n                                                          - platform: ios\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: ios\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                    unselected:\n                                                      shapes:\n                                                      - type: rectangle\n                                                        scale: 1\n                                                        border:\n                                                          radius: 2\n                                                          stroke_width: 1\n                                                          stroke_color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                          selectors:\n                                                          - platform: ios\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: ios\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                      text_appearance:\n                                                        font_size: 24\n                                                        font_families:\n                                                        - sans-serif\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                          selectors:\n                                                          - platform: ios\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: ios\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                identifier: 4a92669e-2560-4ece-a398-dc8e278c8366\n                                                required: true\n                                - identifier: b4e4f64b-97d7-4928-9305-e13a7ade1fca\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  size:\n                                    width: 100%\n                                    height: 200\n                                  view:\n                                    type: label_button\n                                    identifier: next--button98\n                                    reporting_metadata:\n                                      trigger_link_id: b4e4f64b-97d7-4928-9305-e13a7ade1fca\n                                    label:\n                                      type: label\n                                      text: Buttons\n                                      content_description: ''\n                                      text_appearance:\n                                        font_size: 16\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                        alignment: center\n                                        styles: []\n                                        font_families:\n                                        - sans-serif\n                                    actions: {}\n                                    enabled:\n                                    - pager_next\n                                    button_click:\n                                    - pager_next\n                                    background_color:\n                                      default:\n                                        type: hex\n                                        hex: \"#63AFF1\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                    border:\n                                      radius: 3\n                                      stroke_width: 0\n                                      stroke_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                - identifier: 911c608d-44d3-4218-98d1-49e0387676fa\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  view:\n                                    type: label_button\n                                    identifier: next--button52\n                                    reporting_metadata:\n                                      trigger_link_id: 911c608d-44d3-4218-98d1-49e0387676fa\n                                    label:\n                                      type: label\n                                      text: Button\n                                      content_description: ''\n                                      text_appearance:\n                                        font_size: 16\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                        alignment: center\n                                        styles: []\n                                        font_families:\n                                        - sans-serif\n                                    actions: {}\n                                    enabled:\n                                    - pager_next\n                                    button_click:\n                                    - pager_next\n                                    background_color:\n                                      default:\n                                        type: hex\n                                        hex: \"#63AFF1\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                    border:\n                                      radius: 3\n                                      stroke_width: 0\n                                      stroke_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                - size:\n                                    width: 100%\n                                    height: auto\n                                  view:\n                                    type: linear_layout\n                                    direction: horizontal\n                                    items: []\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FF7F50\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FF7F50\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFF5E\"\n                        alpha: 0.5\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FF7F50\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFF5E\"\n                        alpha: 0.5\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FF7F50\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFF5E\"\n                        alpha: 0.5\n            ignore_safe_area: false\n          - position:\n              horizontal: end\n              vertical: top\n            size:\n              width: 48\n              height: 48\n            view:\n              type: image_button\n              image:\n                scale: 0.4\n                type: icon\n                icon: close\n                color:\n                  default:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                  selectors:\n                  - platform: ios\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n              identifier: dismiss_button\n              button_click:\n              - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/button_layout.yml",
    "content": "presentation:\n  type: modal\n  android:\n    disable_back_button: false\n  default_placement:\n    ignore_safe_area: true\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.2\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n    web:\n      ignore_shade: true\n  dismiss_on_touch_outside: false\n  placement_selectors: []\nversion: 1\nview:\n  type: pager_controller\n  identifier: 682cbf3f-2932-4531-a505-92a45610f643\n  view:\n    type: container\n    items:\n      - ignore_safe_area: true\n        position:\n          horizontal: center\n          vertical: center\n        size:\n          height: 100%\n          width: 100%\n        view:\n          type: pager\n          disable_swipe: false\n          items:\n            #\n            # PAGE 1\n            #\n            - type: pager_item\n              identifier: 8520c6a6-8775-4850-97d0-43a448356b23\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#FFCBCB'\n                    alpha: 1\n                    type: hex\n                items:\n                  - size:\n                      height: 100%\n                      width: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    view:\n                      type: container\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  view:\n                                    type: button_layout\n                                    identifier: page_1_button_layout\n                                    accessibility_role:\n                                        type: button\n                                    content_description: Page 1 Content Description\n                                    tap_effect:\n                                      type: none\n                                    actions:\n                                      toast_action: Page 1 tapped!\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                        - identifier: 141247f5-f335-4c83-aefa-143ce00d7297\n                                          margin:\n                                            bottom: 8\n                                            end: 36\n                                            start: 8\n                                            top: 8\n                                          size:\n                                            height: auto\n                                            width: auto\n                                          view:\n                                            type: label\n                                            content_description: Page 1\n                                            text: Page 1 (Button Layout inside Scroll Container)\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                selectors:\n                                                  - color:\n                                                      hex: '#000000'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: false\n                                                  - color:\n                                                      hex: '#FFFFFF'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: true\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 24\n                                              styles:\n                                                - bold\n                                        - identifier: eba93362-710e-4a6c-93bb-176e90ca8cbf\n                                          margin:\n                                            bottom: 8\n                                            end: 16\n                                            start: 16\n                                            top: 36\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            type: label\n                                            content_description: First text\n                                            text: First text\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                selectors:\n                                                  - color:\n                                                      hex: '#000000'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: false\n                                                  - color:\n                                                      hex: '#FFFFFF'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: true\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 20\n                                        - identifier: c6b202e0-cb3d-40ce-991e-dc1add73fd6a\n                                          margin:\n                                            bottom: 8\n                                            end: 16\n                                            start: 16\n                                            top: 8\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            type: label_button\n                                            identifier: next--Show Toast\n                                            background_color:\n                                              default:\n                                                hex: '#63AFF1'\n                                                alpha: 1\n                                                type: hex\n                                            actions:\n                                              toast_action: Toast 1!\n                                            label:\n                                              type: label\n                                              content_description: Show Toast\n                                              text: Show Toast\n                                              text_appearance:\n                                                alignment: center\n                                                color:\n                                                  default:\n                                                    hex: '#000000'\n                                                    alpha: 1\n                                                    type: hex\n                                                  selectors:\n                                                    - color:\n                                                        hex: '#000000'\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: false\n                                                    - color:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: true\n                                                font_families:\n                                                  - sans-serif\n                                                font_size: 16\n                                            reporting_metadata:\n                                              trigger_link_id: c6b202e0-cb3d-40ce-991e-dc1add73fd6a\n                                        - identifier: 13eeb12a-e12d-48e2-825c-f3da649ba222\n                                          margin:\n                                            bottom: 8\n                                            end: 16\n                                            start: 16\n                                            top: 8\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            type: label\n                                            content_description: Second text\n                                            text: Second text\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                selectors:\n                                                  - color:\n                                                      hex: '#000000'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: false\n                                                  - color:\n                                                      hex: '#FFFFFF'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: true\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 20\n                                              styles: [ ]\n                                        - identifier: 14f31557-6cba-434c-9a01-fba32864d71e\n                                          margin:\n                                            bottom: 8\n                                            end: 16\n                                            start: 16\n                                            top: 8\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            type: label_button\n                                            identifier: share--share\n                                            background_color:\n                                              default:\n                                                hex: '#63AFF1'\n                                                alpha: 1\n                                                type: hex\n                                            border:\n                                              radius: 0\n                                              stroke_color:\n                                                default:\n                                                  hex: '#63AFF1'\n                                                  alpha: 1\n                                                  type: hex\n                                              stroke_width: 16\n                                            actions:\n                                              share_action: Causae vulputate duo ex. Case erroribus\n                                                ut mea, etiam putant nusquam ex eum. Sea ea illud\n                                                tantas, populo facilis sententiae at per. Id vis\n                                                quando docendi pertinacia.\n                                            button_click: [ ]\n                                            content_description: Share\n                                            enabled: [ ]\n                                            label:\n                                              type: label\n                                              content_description: Share\n                                              text: Share\n                                              text_appearance:\n                                                alignment: center\n                                                color:\n                                                  default:\n                                                    hex: '#000000'\n                                                    alpha: 1\n                                                    type: hex\n                                                  selectors:\n                                                    - color:\n                                                        hex: '#000000'\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: false\n                                                    - color:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: true\n                                                font_families:\n                                                  - sans-serif\n                                                font_size: 16\n                                            reporting_metadata:\n                                              trigger_link_id: 14f31557-6cba-434c-9a01-fba32864d71e\n                                        - identifier: c0469a5e-8561-4f6b-aabb-9d0e93e6e729\n                                          margin:\n                                            bottom: 8\n                                            end: 16\n                                            start: 16\n                                            top: 8\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            type: label\n                                            content_description: 3rd text\n                                            text: 3rd text\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                selectors:\n                                                  - color:\n                                                      hex: '#000000'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: false\n                                                  - color:\n                                                      hex: '#FFFFFF'\n                                                      alpha: 1\n                                                      type: hex\n                                                    dark_mode: true\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 20\n                                        - identifier: cbcfe457-022e-48d6-9e24-3b5ebe2400ee\n                                          margin:\n                                            bottom: 36\n                                            end: 16\n                                            start: 16\n                                            top: 8\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            type: label_button\n                                            identifier: next--Next Screen\n                                            background_color:\n                                              default:\n                                                hex: '#63AFF1'\n                                                alpha: 1\n                                                type: hex\n                                            border:\n                                              radius: 0\n                                              stroke_color:\n                                                default:\n                                                  hex: '#63AFF1'\n                                                  alpha: 1\n                                                  type: hex\n                                              stroke_width: 16\n                                            actions: { }\n                                            button_click:\n                                              - pager_next\n                                            enabled:\n                                              - pager_next\n                                            label:\n                                              type: label\n                                              content_description: Next Screen\n                                              text: Next Screen\n                                              text_appearance:\n                                                alignment: center\n                                                color:\n                                                  default:\n                                                    hex: '#000000'\n                                                    alpha: 1\n                                                    type: hex\n                                                  selectors:\n                                                    - color:\n                                                        hex: '#000000'\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: false\n                                                    - color:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: true\n                                                font_families:\n                                                  - sans-serif\n                                                font_size: 16\n                                                styles: [ ]\n                                            reporting_metadata:\n                                              trigger_link_id: cbcfe457-022e-48d6-9e24-3b5ebe2400ee\n            #\n            # PAGE 2\n            #\n            - type: pager_item\n              identifier: 7d7433ef-f7f0-46e3-8d79-043fc8d3db2d\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#F1F7B5'\n                    alpha: 1\n                    type: hex\n                items:\n                  - ignore_safe_area: false\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: button_layout\n                      identifier: page_2_button_layout\n                      accessibility_role:\n                        type: container\n                      content_description: Page 2 Button Layout\n                      actions:\n                        toast_action: Page 2 tapped!\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - identifier: scroll_container\n                            size:\n                              height: 100%\n                              width: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                  - identifier: a814df98-88e0-4761-a682-f766bd7248eb\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label\n                                      content_description: Page 2\n                                      text: Page 2 (Button Layout wraps Scroll Container)\n                                      text_appearance:\n                                        alignment: start\n                                        color:\n                                          default:\n                                            hex: '#000000'\n                                            alpha: 1\n                                            type: hex\n                                          selectors:\n                                            - color:\n                                                hex: '#000000'\n                                                alpha: 1\n                                                type: hex\n                                              dark_mode: false\n                                            - color:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                                type: hex\n                                              dark_mode: true\n                                        font_families:\n                                          - sans-serif\n                                        font_size: 24\n                                        styles:\n                                          - bold\n                                  - identifier: 1f089e11-0108-411d-b038-ffdf469360e7\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 36\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label_button\n                                      identifier: previous--Previous Screen\n                                      background_color:\n                                        default:\n                                          hex: '#63AFF1'\n                                          alpha: 1\n                                          type: hex\n                                      border:\n                                        radius: 0\n                                        stroke_color:\n                                          default:\n                                            hex: '#63AFF1'\n                                            alpha: 1\n                                            type: hex\n                                        stroke_width: 16\n                                      actions: { }\n                                      button_click:\n                                        - pager_previous\n                                      enabled:\n                                        - pager_previous\n                                      label:\n                                        type: label\n                                        content_description: Previous Screen\n                                        text: Previous Screen\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              hex: '#000000'\n                                              alpha: 1\n                                              type: hex\n                                            selectors:\n                                              - color:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                dark_mode: false\n                                              - color:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                                  type: hex\n                                                dark_mode: true\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 16\n                                          styles: [ ]\n                                      reporting_metadata:\n                                        trigger_link_id: 1f089e11-0108-411d-b038-ffdf469360e7\n                                  - identifier: bba18808-a1c2-4475-a740-d99f0b460b95\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label\n                                      content_description: Some other text\n                                      text: Some other text\n                                      text_appearance:\n                                        alignment: start\n                                        color:\n                                          default:\n                                            hex: '#000000'\n                                            alpha: 1\n                                            type: hex\n                                          selectors:\n                                            - color:\n                                                hex: '#000000'\n                                                alpha: 1\n                                                type: hex\n                                              dark_mode: false\n                                            - color:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                                type: hex\n                                              dark_mode: true\n                                        font_families:\n                                          - sans-serif\n                                        font_size: 20\n                                        styles: [ ]\n                                  - identifier: 520aa9e5-3a1c-4e84-baa9-5c5ec6e00965\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label_button\n                                      identifier: next--Show Toast\n                                      background_color:\n                                        default:\n                                          hex: '#63AFF1'\n                                          alpha: 1\n                                          type: hex\n                                      border:\n                                        radius: 0\n                                        stroke_color:\n                                          default:\n                                            hex: '#63AFF1'\n                                            alpha: 1\n                                            type: hex\n                                        stroke_width: 16\n                                      actions:\n                                        toast_action: Toast 2!\n                                      label:\n                                        type: label\n                                        content_description: Show Toast\n                                        text: Show Toast\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              hex: '#000000'\n                                              alpha: 1\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 16\n                                          styles: [ ]\n                                      reporting_metadata:\n                                        trigger_link_id: 520aa9e5-3a1c-4e84-baa9-5c5ec6e00965\n                                  - identifier: 82962ef9-5c05-4824-9658-d4dfd4bd45d8\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label\n                                      content_description: Some text\n                                      text: Sit in erant sanctus. Eam ne legendos partiendo\n                                        reprehendunt, his aeterno alienum omnesque in, per\n                                        probo persius no. Sea augue harum ea, solum repudiandae\n                                        eam an. Cu integre scaevola sit, mea assum instructior\n                                        ut. Ipsum lorem laoreet mea ei, vim an electram iudicabit.\n                                        Vis solum falli an, erant regione eu nam.\n                                      text_appearance:\n                                        alignment: start\n                                        color:\n                                          default:\n                                            hex: '#000000'\n                                            alpha: 1\n                                            type: hex\n                                          selectors:\n                                            - color:\n                                                hex: '#000000'\n                                                alpha: 1\n                                                type: hex\n                                              dark_mode: false\n                                            - color:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                                type: hex\n                                              dark_mode: true\n                                        font_families:\n                                          - sans-serif\n                                        font_size: 20\n                                        styles: [ ]\n                                  - identifier: 32235177-c70b-430f-bd2b-0c1bfecc80e7\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label_button\n                                      identifier: share--share\n                                      background_color:\n                                        default:\n                                          hex: '#63AFF1'\n                                          alpha: 1\n                                          type: hex\n                                      border:\n                                        radius: 0\n                                        stroke_color:\n                                          default:\n                                            hex: '#63AFF1'\n                                            alpha: 1\n                                            type: hex\n                                        stroke_width: 16\n                                      actions:\n                                        share_action: Causae vulputate duo ex. Case erroribus\n                                          ut mea, etiam putant nusquam ex eum. Sea ea illud\n                                          tantas, populo facilis sententiae at per. Id vis\n                                          quando docendi pertinacia.\n                                      button_click: [ ]\n                                      content_description: Share\n                                      enabled: [ ]\n                                      label:\n                                        type: label\n                                        content_description: Share\n                                        text: Share\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              hex: '#000000'\n                                              alpha: 1\n                                              type: hex\n                                            selectors:\n                                              - color:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                dark_mode: false\n                                              - color:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                                  type: hex\n                                                dark_mode: true\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 16\n                                          styles: [ ]\n                                      reporting_metadata:\n                                        trigger_link_id: 32235177-c70b-430f-bd2b-0c1bfecc80e7\n                                  - identifier: 2d0346d2-ecf6-451d-a48f-04f8202b9e64\n                                    margin:\n                                      bottom: 8\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      media_fit: fit_crop\n                                      border:\n                                        stroke_color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        stroke_width: 1\n                                      media_type: image\n                                      type: media\n                                      position:\n                                        horizontal: end\n                                        vertical: bottom\n                                      url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n                                  - identifier: 63356835-3d1d-4bcf-8bc7-08616769bab4\n                                    margin:\n                                      bottom: 36\n                                      end: 16\n                                      start: 16\n                                      top: 8\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      type: label_button\n                                      identifier: next--Next Screen\n                                      background_color:\n                                        default:\n                                          hex: '#63AFF1'\n                                          alpha: 1\n                                          type: hex\n                                      border:\n                                        radius: 0\n                                        stroke_color:\n                                          default:\n                                            hex: '#63AFF1'\n                                            alpha: 1\n                                            type: hex\n                                        stroke_width: 16\n                                      actions: { }\n                                      button_click:\n                                        - pager_next\n                                      enabled:\n                                        - pager_next\n                                      label:\n                                        type: label\n                                        content_description: Next Screen\n                                        text: Next Screen\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              hex: '#000000'\n                                              alpha: 1\n                                              type: hex\n                                            selectors:\n                                              - color:\n                                                  hex: '#000000'\n                                                  alpha: 1\n                                                  type: hex\n                                                dark_mode: false\n                                              - color:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 1\n                                                  type: hex\n                                                dark_mode: true\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 16\n                                          styles: [ ]\n                                      reporting_metadata:\n                                        trigger_link_id: 63356835-3d1d-4bcf-8bc7-08616769bab4\n\n            #\n            # PAGE 3\n            #\n            - type: pager_item\n              identifier: 6db51464-64bc-4437-845d-0e9be5267a18\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#DFEBEB'\n                    alpha: 1\n                    type: hex\n                items:\n                  - ignore_safe_area: false\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: container\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                    - identifier: 4d10fc1e-0df6-49a2-80ab-1d2f9514813d\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        type: button_layout\n                                        identifier: page_3_button_layout\n                                        content_description: Page 3 Button Layout\n                                        tap_effect:\n                                          type: none\n                                        actions:\n                                          show_toast: Page 3 tapped!\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                            - identifier: 9e4a96f2-2dcb-40df-96b5-4f53bcb60e23\n                                              margin:\n                                                bottom: 8\n                                                end: 16\n                                                start: 16\n                                                top: 8\n                                              size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                type: label\n                                                content_description: Page 3 (Button Layout wraps WebView)\n                                                text: Page 3\n                                                text_appearance:\n                                                  alignment: start\n                                                  color:\n                                                    default:\n                                                      hex: '#000000'\n                                                      alpha: 1\n                                                      type: hex\n                                                    selectors:\n                                                      - color:\n                                                          hex: '#000000'\n                                                          alpha: 1\n                                                          type: hex\n                                                        dark_mode: false\n                                                      - color:\n                                                          hex: '#FFFFFF'\n                                                          alpha: 1\n                                                          type: hex\n                                                        dark_mode: true\n                                                  font_families:\n                                                    - sans-serif\n                                                  font_size: 24\n                                                  styles:\n                                                    - bold\n                                            - identifier: page3-webview\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: web_view\n                                                url: \"https://docs.airship.com\"\n                                                background_color:\n                                                  default:\n                                                      hex: '#FFFFFF'\n                                                      alpha: 1\n                                                      type: hex\n                                            - identifier: 71f57cef-270d-4311-85a5-d2f9726d5cc1\n                                              margin:\n                                                bottom: 36\n                                                end: 16\n                                                start: 16\n                                                top: 8\n                                              size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                type: label_button\n                                                identifier: dismiss--Dismiss\n                                                background_color:\n                                                  default:\n                                                    hex: '#63AFF1'\n                                                    alpha: 1\n                                                    type: hex\n                                                border:\n                                                  stroke_color:\n                                                    default:\n                                                      hex: '#63AFF1'\n                                                      alpha: 1\n                                                      type: hex\n                                                  stroke_width: 16\n                                                button_click:\n                                                  - dismiss\n                                                label:\n                                                  type: label\n                                                  content_description: Dismiss\n                                                  text: Dismiss\n                                                  text_appearance:\n                                                    alignment: center\n                                                    color:\n                                                      default:\n                                                        hex: '#000000'\n                                                        alpha: 1\n                                                        type: hex\n                                                      selectors:\n                                                        - color:\n                                                            hex: '#000000'\n                                                            alpha: 1\n                                                            type: hex\n                                                          dark_mode: false\n                                                        - color:\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                            type: hex\n                                                          dark_mode: true\n                                                    font_families:\n                                                      - sans-serif\n                                                    font_size: 16\n                                                reporting_metadata:\n                                                  trigger_link_id: 71f57cef-270d-4311-85a5-d2f9726d5cc1\n      - ignore_safe_area: false\n        position:\n          horizontal: end\n          vertical: top\n        size:\n          height: 48\n          width: 48\n        view:\n          type: image_button\n          identifier: dismiss_button\n          button_click:\n            - dismiss\n          image:\n            type: icon\n            color:\n              default:\n                hex: '#000000'\n                alpha: 1\n                type: hex\n              selectors:\n                - color:\n                    hex: '#000000'\n                    alpha: 1\n                    type: hex\n                  dark_mode: false\n                - color:\n                    hex: '#FFFFFF'\n                    alpha: 1\n                    type: hex\n                  dark_mode: true\n            icon: close\n            scale: 0.4\n      - ignore_safe_area: false\n        margin:\n          bottom: 4\n          end: 4\n          start: 4\n          top: 4\n        position:\n          horizontal: center\n          vertical: bottom\n        size:\n          height: 7\n          width: 100%\n        view:\n          type: pager_indicator\n          bindings:\n            selected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  color:\n                    default:\n                      hex: '#7B7C84'\n                      alpha: 1\n                      type: hex\n                    selectors:\n                      - color:\n                          hex: '#7B7C84'\n                          alpha: 1\n                          type: hex\n                        dark_mode: false\n                      - color:\n                          hex: '#FFFFFF'\n                          alpha: 1\n                          type: hex\n                        dark_mode: true\n                  scale: 1\n            unselected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  color:\n                    default:\n                      hex: '#BCBDC2'\n                      alpha: 1\n                      type: hex\n                    selectors:\n                      - color:\n                          hex: '#BCBDC2'\n                          alpha: 1\n                          type: hex\n                        dark_mode: false\n                      - color:\n                          hex: '#FFFFFF'\n                          alpha: 1\n                          type: hex\n                        dark_mode: true\n                  scale: 1\n          spacing: 6\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/container.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 80%\n      height: auto\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    border:\n      stroke_color:\n        default:\n          hex: \"#00FF00\"\n          alpha: .3\n      stroke_width: 3\n      radius: 15\n    shadow:\n        selectors:\n          - shadow:\n              box_shadow:\n                color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: 1\n                blur_radius: 20\n                radius: 20\nview:\n  type: container\n  items:\n    - position:\n        horizontal: end\n        vertical: top\n      size:\n        height: auto\n        width: auto\n      margin:\n        top: 50\n        bottom: 50\n        start: 50\n        end: 50\n      view:\n        type: label\n        text: Sup Buddy\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#333333\"\n          alignment: start\n          styles:\n            - italic\n          font_families:\n            - permanent_marker\n            - casual\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: auto\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n          - size:\n              width: 100%\n              height: auto\n            weight: 1\n            view:\n              type: container\n              items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 50\n                    bottom: 50\n                    start: 50\n                    end: 50\n                  view:\n                    type: label\n                    text: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do\n                      eiusmod tempor incididunt ut labore et dolore magna aliqua. In arcu\n                      cursus euismod quis viverra nibh. Lobortis feugiat vivamus at augue\n                      eget arcu dictum. Imperdiet dui accumsan sit amet nulla. Ultrices\n                      neque ornare aenean euismod elementum. Tincidunt id aliquet risus\n                      feugiat in ante metus dictum.\n                    text_appearance:\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#333333\"\n                      alignment: start\n                      styles:\n                        - italic\n                      font_families:\n                        - permanent_marker\n                        - casual\n          - size:\n              width: 100%\n              height: auto\n            margin:\n              top: 50\n              bottom: 50\n              start: 50\n              end: 50\n            view:\n              type: label_button\n              identifier: BUTTON\n              background_color:\n                default:\n                  hex: \"#FF0000\"\n              label:\n                type: label\n                text_appearance:\n                  font_size: 24\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                  styles:\n                    - bold\n                  font_families:\n                    - casual\n                text: Push me!\n\n\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/containerception.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.5\n    ignore_safe_area: true\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#FF0000\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: false\n    view:\n      type: container\n      background_color:\n        default:\n          hex: \"#FFFFFF\"\n          alpha: 1\n      items:\n      - position:\n          horizontal: center\n          vertical: top\n        size:\n          width: auto\n          height: auto\n        ignore_safe_area: true\n        view:\n          type: label\n          text: \"DANGER AREA\"\n          text_appearance:\n            font_size: 12\n            color:\n              default:\n                type: hex\n                hex: \"#FF0000\"\n                alpha: 1\n            styles:\n            - bold\n            alignment: center\n      - position:\n          horizontal: center\n          vertical: bottom\n        size:\n          width: auto\n          height: auto\n        ignore_safe_area: false\n        view:\n          type: label\n          text: \"DANGER AREA\"\n          text_appearance:\n            font_size: 12\n            color:\n              default:\n                type: hex\n                hex: \"#FF0000\"\n                alpha: 1\n            styles:\n            - bold\n            alignment: center\n  - position:\n      horizontal: center\n      vertical: top\n    size:\n      width: auto\n      height: auto\n    ignore_safe_area: true\n    margin:\n      top: 4\n    view:\n      type: label\n      text: \"SAFE AREA\"\n      text_appearance:\n        font_size: 12\n        color:\n          default:\n            type: hex\n            hex: \"#FFFF00\"\n            alpha: 1\n        styles:\n        - bold\n        alignment: center\n  - position:\n      horizontal: start\n      vertical: top\n    size:\n      width: auto\n      height: auto\n    ignore_safe_area: true\n    margin:\n      top: 4\n      start: 16\n    view:\n      type: label\n      text: \"SAFE AREA\"\n      text_appearance:\n        font_size: 12\n        color:\n          default:\n            type: hex\n            hex: \"#FFFF00\"\n            alpha: 1\n        styles:\n        - bold\n        alignment: center\n  - position:\n      horizontal: end\n      vertical: top\n    size:\n      width: auto\n      height: auto\n    ignore_safe_area: true\n    margin:\n      top: 4\n      end: 16\n    view:\n      type: label\n      text: \"SAFE AREA\"\n      text_appearance:\n        font_size: 12\n        color:\n          default:\n            type: hex\n            hex: \"#FFFF00\"\n            alpha: 1\n        styles:\n        - bold\n        alignment: center\n  - position:\n      horizontal: center\n      vertical: bottom\n    size:\n      width: auto\n      height: auto\n    ignore_safe_area: true\n    margin:\n      bottom: 8\n    view:\n      type: label\n      text: \"SAFE AREA\"\n      text_appearance:\n        font_size: 12\n        color:\n          default:\n            type: hex\n            hex: \"#FFFF00\"\n            alpha: 1\n        styles:\n        - bold\n        alignment: center\n  - position:\n      horizontal: start\n      vertical: bottom\n    size:\n      width: auto\n      height: auto\n    ignore_safe_area: true\n    margin:\n      bottom: 8\n      start: 16\n    view:\n      type: label\n      text: \"SAFE AREA\"\n      text_appearance:\n        font_size: 12\n        color:\n          default:\n            type: hex\n            hex: \"#FFFF00\"\n            alpha: 1\n        styles:\n        - bold\n        alignment: center\n  - position:\n      horizontal: end\n      vertical: bottom\n    size:\n      width: auto\n      height: auto\n    ignore_safe_area: true\n    margin:\n      bottom: 8\n      end: 16\n    view:\n      type: label\n      text: \"SAFE AREA\"\n      text_appearance:\n        font_size: 12\n        color:\n          default:\n            type: hex\n            hex: \"#FFFF00\"\n            alpha: 1\n        styles:\n        - bold\n        alignment: center\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    view:\n      type: container\n      background_color:\n        default:\n          hex: \"#00FFFF\"\n          alpha: 0\n      items:\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 100\n          height: 100\n        ignore_safe_area: false\n        view:\n          type: label\n          background_color:\n            default:\n              hex: \"#00FF00\"\n              alpha: 1\n          text: \"ignore_safe_area: false\"\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n      - position:\n          horizontal: start\n          vertical: top\n        ignore_safe_area: true\n        size:\n          width: 100\n          height: 100\n        view:\n          type: label\n          background_color:\n            default:\n              hex: \"#be70ef\"\n              alpha: 1\n          text: \"ignore_safe_area: true\"\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n            alignment: center\n      - position:\n          horizontal: start\n          vertical: bottom\n        size:\n          width: 100\n          height: 100\n        ignore_safe_area: false\n        view:\n          type: label\n          background_color:\n            default:\n              hex: \"#0000FF\"\n              alpha: 1\n          text: \"ignore_safe_area: false\"\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n            alignment: center\n      - position:\n          horizontal: end\n          vertical: bottom\n        ignore_safe_area: true\n        size:\n          width: 100\n          height: 100\n        view:\n          type: label\n          background_color:\n            default:\n              hex: \"#FF00FF\"\n              alpha: 1\n          text: \"ignore_safe_area: true\"\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n            alignment: center\n      - position:\n          horizontal: center\n          vertical: center\n        ignore_safe_area: true\n        size:\n          width: auto\n          height: auto\n        view:\n          type: linear_layout\n          background_color:\n            default:\n              hex: \"#FF0000\"\n              alpha: 1\n          direction: vertical\n          items:\n          - size:\n              width: auto\n              height: 50\n            view:\n              type: label\n              text: \"container\"\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    type: hex\n                    hex: \"#333333\"\n                    alpha: 1\n                alignment: center\n          - size:\n              width: auto\n              height: 50\n            view:\n              type: label\n              text: \"ignore_safe_area: true\"\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    type: hex\n                    hex: \"#333333\"\n                    alpha: 1\n                alignment: center\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/custom-fonts.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web:\n      ignore_shade: true\nview:\n  type: pager_controller\n  identifier: 300229b5-c073-4ecb-8bc5-137054dd236b\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - size:\n        width: 100%\n        height: 100%\n      view:\n        type: container\n        items:\n        - identifier: e03cd022-5ea9-4a06-80c6-e85b59c1d8a0_pager_container_item\n          position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          view:\n            type: pager\n            disable_swipe: true\n            items:\n            - identifier: a0121f9b-4af6-425c-8ad0-ce74d7ee1b52\n              type: pager_item\n              view:\n                type: container\n                items:\n                - identifier: 470361c2-c195-49ff-80b9-fc248c00e83c_main_view_container_item\n                  size:\n                    width: 100%\n                    height: 100%\n                  position:\n                    horizontal: center\n                    vertical: center\n                  ignore_safe_area: false\n                  view:\n                    type: container\n                    items:\n                    - identifier: 7615e8fd-4256-4e37-9813-06579c0d7f5a_container_item\n                      margin:\n                        bottom: 0\n                        top: 0\n                        end: 0\n                        start: 0\n                      position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - identifier: scroll_container\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: scroll_layout\n                            direction: vertical\n                            view:\n                              type: linear_layout\n                              direction: vertical\n                              items:\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: ABCDEFGHIJKLMNOPQRSTUVWXYZ\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                    alignment: start\n                                    styles:\n                                    - bold\n                                    - italic\n                                    font_families:\n                                    - Merriweather\n                                    - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 100\tThin\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                    alignment: start\n                                    font_weight: 100\n                                    font_families:\n                                    - Merriweather\n                                    - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 200\tExtra Light\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 200\n                                    font_families:\n                                    - Merriweather\n                                    - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 300\tLight\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                    alignment: start\n                                    font_weight: 300\n                                    font_families:\n                                    - Merriweather\n                                    - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 400\tNormal\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 400\n                                    font_families:\n                                      - Merriweather\n                                      - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 500\tMedium\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 500\n                                    font_families:\n                                      - Merriweather\n                                      - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 600\tSemi Bold\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 600\n                                    font_families:\n                                      - Merriweather\n                                      - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 700\tBold\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 700\n                                    font_families:\n                                      - Merriweather\n                                      - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 800 Extra Bold\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 800\n                                    font_families:\n                                      - Merriweather\n                                      - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: c3bb182a-82c7-4dc6-b364-9aad64322a53\n                                size:\n                                  width: 100%\n                                  height: auto\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: 900 Black\n                                  content_description: test ulrich\n                                  text_appearance:\n                                    font_size: 32\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                      selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                    alignment: start\n                                    styles:\n                                    font_weight: 900\n                                    font_families:\n                                      - Merriweather\n                                      - merriweather\n                                  accessibility_role:\n                                    type: heading\n                                    level: 1\n                                  accessibility_hidden: false\n                              - identifier: '088310d4-f576-40bf-bad0-c1259da56abf_linear_layout_item'\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  direction: horizontal\n                                  items: []\n                background_color:\n                  default:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                  selectors:\n                  - platform: ios\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n              state_actions:\n              - type: set\n                key: a0121f9b-4af6-425c-8ad0-ce74d7ee1b52_next\n          ignore_safe_area: false\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            image:\n              scale: 0.4\n              type: icon\n              icon: close\n              color:\n                default:\n                  type: hex\n                  hex: \"#000000\"\n                  alpha: 1\n                selectors:\n                - platform: ios\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                - platform: ios\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                - platform: android\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                - platform: android\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                - platform: web\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                - platform: web\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n            identifier: dismiss_button\n            button_click:\n            - dismiss\n            localized_content_description:\n              ref: ua_dismiss\n              fallback: Dismiss\n            reporting_metadata:\n              button_id: dismiss_button\n              button_action: dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/form-input-branching-child-form.yaml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web: {}\nview:\n  type: state_controller\n  initial_state:\n    neat: dissatisfied\n  view:\n    type: pager_controller\n    identifier: 5ff76966-4e7e-4a5f-b3b4-3928cf7f4076\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          identifier: fa59a9dc-4f78-407e-bc89-4bb20c03c310\n          type: form_controller\n          validation_mode:\n              type: on_demand\n          form_enabled:\n          - form_submission\n          submit: submit_event\n          response_type: user_feedback\n          view:\n            type: container\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: false\n                items:\n                - identifier: b5628f32-3da1-42ae-b76e-c20800922d47\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: \"Favorite Animal\"\n                                          content_description: \"Favorite Animal\"\n                                          icon_start:\n                                            space: 8\n                                            type: floating\n                                            icon:\n                                              type: icon\n                                              icon: asterisk\n                                              color:\n                                                  default:\n                                                      hex: \"#000000\"\n                                                      alpha: 1.0\n                                              scale: .75\n                                          view_overrides:\n                                            icon_start:\n                                            - when_state_matches:\n                                                scope:\n                                                - edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value:\n                                                  equals: \"error\"\n                                              value:\n                                                space: 8\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: exclamationmark_circle_fill\n                                                  color:\n                                                      default:\n                                                          hex: \"#ff0000\"\n                                                          alpha: 1.0\n                                                  scale: .75\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: radio_input_controller\n                                          identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                          required: true\n                                          on_error:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"error\"\n                                          on_valid:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"valid\"\n                                          on_edit:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"editing\"\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: radio_input\n                                                    reporting_value: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7\n                                                    content_description: Cat\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                          - type: ellipse\n                                                            scale: 0.6\n                                                            aspect_ratio: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Cat\n                                                    content_description: Cat\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: radio_input\n                                                    reporting_value: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d\n                                                    content_description: Dog\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                          - type: ellipse\n                                                            scale: 0.6\n                                                            aspect_ratio: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Dog\n                                                    content_description: Dog\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            randomize_children: false\n                                          attribute_name: {}\n                                  - size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                  branching:\n                    next_page:\n                      selectors:\n                      - page_id: 24e09863-1dcf-40ea-8b81-b398d286d449\n                        when_state_matches:\n                          or:\n                          - scope:\n                            - \"$forms\"\n                            - current\n                            - data\n                            - children\n                            - edf9064c-54d5-4b29-9ac9-ad0997a62696 # favorite animal\n                            value:\n                              equals: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7 # cat\n                            key: value\n                      - page_id: d495142a-38d3-482c-9f14-203501b0518a\n                        when_state_matches:\n                          and:\n                          - scope:\n                            - \"$forms\"\n                            - current\n                            - data\n                            - children\n                            - edf9064c-54d5-4b29-9ac9-ad0997a62696 # favorite animal\n                            value:\n                              equals: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d # dog\n                            key: value\n                - identifier: d495142a-38d3-482c-9f14-203501b0518a\n                  type: pager_item\n                  branching:\n                    next_page:\n                      selectors:\n                      - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                  view:\n                      identifier: 98354588-45a2-4ca2-b9b6-e0b6e02a6e69\n#                      submit: submit_event\n                      type: form_controller\n                      validation_mode:\n                        type: immediate\n                      form_enabled:\n                        - form_submission\n                      view:\n                        items:\n                          - position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              height: 100%\n                              width: 100%\n                            view:\n                              background_color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#e8e4dc\"\n                                  type: hex\n                              direction: vertical\n                              type: scroll_layout\n                              view:\n                                direction: vertical\n                                items:\n                                  - margin:\n                                      bottom: 10\n                                      end: 0\n                                      start: 0\n                                      top: 0\n                                    size:\n                                      height: auto\n                                      width: 100%\n                                    view:\n                                      media_fit: center_crop\n                                      media_type: image\n                                      type: media\n                                      url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/bc692c5f-09ce-4ea4-bb55-108b1b5d28a8\n                                  - margin:\n                                      bottom: 10\n                                      end: 0\n                                      start: 0\n                                      top: 10\n                                    size:\n                                      height: auto\n                                      width: 92%\n                                    view:\n                                      direction: vertical\n                                      items:\n                                        - margin:\n                                            bottom: 10\n                                          size:\n                                            height: 100%\n                                            width: 100%\n                                          view:\n                                            text: How 🔥`satisfied`🔥 ~~is your friend~~ are **you** with *our* [product](https://www.airship.com)?\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#111111\"\n                                                  type: hex\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 18\n                                              styles:\n                                                - bold\n                                            type: label\n                                        - size:\n                                            height: 100%\n                                            width: 100%\n                                          view:\n                                            identifier: 52fd50d9-c899-4887-8210-9669cb27188c\n                                            type: radio_input_controller\n                                            required: true\n                                            attribute_name:\n                                                channel: HowSatisfiedAreYou\n                                            event_handlers:\n                                                - type: form_input\n                                                  state_actions:\n                                                    - type: set_form_value\n                                                      key: \"neat\"\n                                            view:\n                                              direction: vertical\n                                              items:\n                                                - size:\n                                                    height: 100%\n                                                    width: 100%\n                                                  view:\n                                                    direction: horizontal\n                                                    items:\n                                                      - size:\n                                                          height: 20\n                                                          width: auto\n                                                        view:\n                                                          reporting_value: very_satisfied\n                                                          attribute_value: VerySatisfied\n                                                          style:\n                                                            bindings:\n                                                              selected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#DDDDDD\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                              unselected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#FFFFFF\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                            type: checkbox\n                                                          type: radio_input\n                                                      - size:\n                                                          height: 100%\n                                                          width: 100%\n                                                        view:\n                                                          text: 🔥`satisfied`🔥\n                                                          text_appearance:\n                                                            alignment: start\n                                                            color:\n                                                              default:\n                                                                alpha: 1\n                                                                hex: \"#111111\"\n                                                                type: hex\n                                                            font_families:\n                                                              - sans-serif\n                                                            font_size: 18\n                                                            styles: []\n                                                          type: label\n                                                    type: linear_layout\n                                                - size:\n                                                    height: 100%\n                                                    width: 100%\n                                                  view:\n                                                    direction: horizontal\n                                                    items:\n                                                      - size:\n                                                          height: 20\n                                                          width: auto\n                                                        view:\n                                                          reporting_value: satisfied\n                                                          attribute_value: Satisfied\n                                                          style:\n                                                            bindings:\n                                                              selected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#DDDDDD\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                              unselected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#FFFFFF\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                            type: checkbox\n                                                          type: radio_input\n                                                      - size:\n                                                          height: 100%\n                                                          width: 100%\n                                                        view:\n                                                          text: Satisfied\n                                                          text_appearance:\n                                                            alignment: start\n                                                            color:\n                                                              default:\n                                                                alpha: 1\n                                                                hex: \"#111111\"\n                                                                type: hex\n                                                            font_families:\n                                                              - sans-serif\n                                                            font_size: 18\n                                                            styles: []\n                                                          type: label\n                                                    type: linear_layout\n                                                - size:\n                                                    height: 100%\n                                                    width: 100%\n                                                  view:\n                                                    direction: horizontal\n                                                    items:\n                                                      - size:\n                                                          height: 20\n                                                          width: auto\n                                                        view:\n                                                          reporting_value: eh\n                                                          attribute_value: Eh\n                                                          style:\n                                                            bindings:\n                                                              selected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#DDDDDD\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                              unselected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#FFFFFF\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                            type: checkbox\n                                                          type: radio_input\n                                                      - size:\n                                                          height: 100%\n                                                          width: 100%\n                                                        view:\n                                                          text: Eh\n                                                          text_appearance:\n                                                            alignment: start\n                                                            color:\n                                                              default:\n                                                                alpha: 1\n                                                                hex: \"#111111\"\n                                                                type: hex\n                                                            font_families:\n                                                              - sans-serif\n                                                            font_size: 18\n                                                            styles: []\n                                                          type: label\n                                                    type: linear_layout\n                                                - size:\n                                                    height: 100%\n                                                    width: 100%\n                                                  view:\n                                                    direction: horizontal\n                                                    items:\n                                                      - size:\n                                                          height: 20\n                                                          width: auto\n                                                        view:\n                                                          reporting_value: dissatisfied\n                                                          attribute_value: Dissatisfied\n                                                          style:\n                                                            bindings:\n                                                              selected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#DDDDDD\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                              unselected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#FFFFFF\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                            type: checkbox\n                                                          type: radio_input\n                                                      - size:\n                                                          height: 100%\n                                                          width: 100%\n                                                        view:\n                                                          text: Dissatisfied\n                                                          text_appearance:\n                                                            alignment: start\n                                                            color:\n                                                              default:\n                                                                alpha: 1\n                                                                hex: \"#111111\"\n                                                                type: hex\n                                                            font_families:\n                                                              - sans-serif\n                                                            font_size: 18\n                                                            styles: []\n                                                          type: label\n                                                    type: linear_layout\n                                                - size:\n                                                    height: 100%\n                                                    width: 100%\n                                                  view:\n                                                    direction: horizontal\n                                                    items:\n                                                      - size:\n                                                          height: 20\n                                                          width: auto\n                                                        view:\n                                                          reporting_value: very_dissatisfied\n                                                          attribute_value: VeryDissatisfied\n                                                          style:\n                                                            bindings:\n                                                              selected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#DDDDDD\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                              unselected:\n                                                                shapes:\n                                                                  - border:\n                                                                      radius: 2\n                                                                      stroke_color:\n                                                                        default:\n                                                                          alpha: 1\n                                                                          hex: \"#000000\"\n                                                                          type: hex\n                                                                      stroke_width: 1\n                                                                    color:\n                                                                      default:\n                                                                        alpha: 1\n                                                                        hex: \"#FFFFFF\"\n                                                                        type: hex\n                                                                    scale: 1\n                                                                    type: ellipse\n                                                            type: checkbox\n                                                          type: radio_input\n                                                      - size:\n                                                          height: 100%\n                                                          width: 100%\n                                                        view:\n                                                          text: Very Dissatisfied [(click here for more)](https://www.airship.com)\n                                                          text_appearance:\n                                                            alignment: start\n                                                            color:\n                                                              default:\n                                                                alpha: 1\n                                                                hex: \"#111111\"\n                                                                type: hex\n                                                            font_families:\n                                                              - sans-serif\n                                                            font_size: 18\n                                                            styles: []\n                                                          type: label\n                                                    type: linear_layout\n                                              type: linear_layout\n                                      type: linear_layout\n                                  - margin:\n                                      bottom: 10\n                                      end: 0\n                                      start: 0\n                                      top: 10\n                                    size:\n                                      height: auto\n                                      width: 92%\n                                    view:\n                                      direction: vertical\n                                      items:\n                                        - margin:\n                                            bottom: 10\n                                            end: 0\n                                            start: 0\n                                            top: 0\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            visibility:\n                                                default: false\n                                                invert_when_state_matches:\n                                                    or:\n                                                     - key: neat\n                                                       value:\n                                                            equals: dissatisfied\n                                                     - key: neat\n                                                       value:\n                                                            equals: very_dissatisfied\n                                            text: But why?\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#111111\"\n                                                  type: hex\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 18\n                                              styles:\n                                                - bold\n                                            type: label\n                                        - size:\n                                            height: 70\n                                            width: 100%\n                                          view:\n                                            visibility:\n                                                default: false\n                                                invert_when_state_matches:\n                                                     or:\n                                                     - key: neat\n                                                       value:\n                                                            equals: dissatisfied\n                                                     - key: neat\n                                                       value:\n                                                            equals: very_dissatisfied\n                                            background_color:\n                                              default:\n                                                alpha: 1\n                                                hex: \"#eae9e9\"\n                                                type: hex\n                                            border:\n                                              radius: 2\n                                              stroke_color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#63656b\"\n                                                  type: hex\n                                              stroke_width: 1\n                                            identifier: 7c7f0793-188f-4f60-aec2-0b35d9a4d005\n                                            input_type: text_multiline\n                                            required: false\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#000000\"\n                                                  type: hex\n                                              font_size: 14\n                                            type: text_input\n                                      type: linear_layout\n                                  - margin:\n                                      bottom: 10\n                                      end: 0\n                                      start: 0\n                                      top: 10\n                                    size:\n                                      height: auto\n                                      width: 92%\n                                    view:\n                                      direction: vertical\n                                      items:\n                                        - margin:\n                                            bottom: 10\n                                            end: 0\n                                            start: 0\n                                            top: 0\n                                          size:\n                                            height: auto\n                                            width: 100%\n                                          view:\n                                            text: What ~~areas~~ do we **need** to [*improve*](https://airship.com)?\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#111111\"\n                                                  type: hex\n                                              font_families:\n                                                - sans-serif\n                                              font_size: 18\n                                              styles:\n                                                - bold\n                                            type: label\n                                        - size:\n                                            height: 70\n                                            width: 100%\n                                          view:\n                                            background_color:\n                                              default:\n                                                alpha: 1\n                                                hex: \"#eae9e9\"\n                                                type: hex\n                                            border:\n                                              radius: 2\n                                              stroke_color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#63656b\"\n                                                  type: hex\n                                              stroke_width: 1\n                                            identifier: 9fb8ef64-2fbc-4439-b450-87b20fda5c43\n                                            input_type: text_multiline\n                                            required: false\n                                            text_appearance:\n                                              alignment: start\n                                              color:\n                                                default:\n                                                  alpha: 1\n                                                  hex: \"#000000\"\n                                                  type: hex\n                                              font_size: 14\n                                            type: text_input\n                                      type: linear_layout\n                                type: linear_layout\n                        type: container\n\n                - identifier: 24e09863-1dcf-40ea-8b81-b398d286d449\n                  type: pager_item\n                  branching:\n                    next_page:\n                      selectors:\n                      - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: \"What are your favorite Guy Fieri shows? (select at least 2)\"\n                                          content_description: \"What are your favorite Guy Fieri shows? (select at least 2)\"\n                                          icon_start:\n                                            space: 8\n                                            type: floating\n                                            icon:\n                                              type: icon\n                                              icon: asterisk\n                                              color:\n                                                  default:\n                                                      hex: \"#000000\"\n                                                      alpha: 1.0\n                                              scale: .75\n                                          view_overrides:\n                                            icon_start:\n                                            - when_state_matches:\n                                                scope:\n                                                - 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                                value:\n                                                  equals: \"error\"\n                                              value:\n                                                space: 8\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: exclamationmark_circle_fill\n                                                  color:\n                                                      default:\n                                                          hex: \"#ff0000\"\n                                                          alpha: 1.0\n                                                  scale: .75\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: checkbox_controller\n                                          identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                          on_error:\n                                            state_actions:\n                                              - type: set\n                                                key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                                value: \"error\"\n                                          on_valid:\n                                            state_actions:\n                                              - type: set\n                                                key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                                value: \"valid\"\n                                          on_edit:\n                                            state_actions:\n                                              - type: set\n                                                key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                                value: \"editing\"\n                                          required: true\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: checkbox\n                                                    reporting_value: 646d668e-5d50-44cf-8ddf-7fe4e2a404ab\n                                                    content_description: Diners,\n                                                      Drive-Ins, and Dives\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          icon:\n                                                            type: icon\n                                                            icon: checkmark\n                                                            scale: 0.8\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Diners, Drive-Ins,\n                                                      and Dives\n                                                    content_description: Diners,\n                                                      Drive-Ins, and Dives\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: checkbox\n                                                    reporting_value: 3a6f2143-968c-484e-ac2f-69efdbfaaabd\n                                                    content_description: Guy's\n                                                      Grocery Games\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          icon:\n                                                            type: icon\n                                                            icon: checkmark\n                                                            scale: 0.8\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Guy's Grocery Games\n                                                    content_description: Guy's\n                                                      Grocery Games\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: checkbox\n                                                    reporting_value: 86eaa518-2a24-4018-a303-4c9eb05ec2a3\n                                                    content_description: Guy's\n                                                      Big Bite\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          icon:\n                                                            type: icon\n                                                            icon: checkmark\n                                                            scale: 0.8\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Guy's Big Bite\n                                                    content_description: Guy's\n                                                      Big Bite\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: checkbox\n                                                    reporting_value: bbd19354-9728-4be6-baab-7d3779841eb2\n                                                    content_description: Guy's\n                                                      Family Road Trip\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          icon:\n                                                            type: icon\n                                                            icon: checkmark\n                                                            scale: 0.8\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Guy's Family Road\n                                                      Trip\n                                                    content_description: Guy's\n                                                      Family Road Trip\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: checkbox\n                                                    reporting_value: d27cc3e2-e1a9-4ed8-b013-a3b64017ee91\n                                                    content_description: Minute\n                                                      to Win It\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          icon:\n                                                            type: icon\n                                                            icon: checkmark\n                                                            scale: 0.8\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 4\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Minute to Win It\n                                                    content_description: Minute\n                                                      to Win It\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            randomize_children: true\n                                          min_selection: 2\n                                  - size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                - identifier: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: fe561774-93f5-4b09-bd0c-831b955042e6\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: \"Required Email\"\n                                      icon_start:\n                                        space: 8\n                                        type: floating\n                                        icon:\n                                          type: icon\n                                          icon: asterisk\n                                          color:\n                                              default:\n                                                  hex: \"#000000\"\n                                                  alpha: 1.0\n                                          scale: .75\n                                      view_overrides:\n                                        icon_start:\n                                        - when_state_matches:\n                                            scope:\n                                            - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                            value:\n                                              equals: \"error\"\n                                          value:\n                                            space: 8\n                                            type: floating\n                                            icon:\n                                              type: icon\n                                              icon: exclamationmark_circle_fill\n                                              color:\n                                                  default:\n                                                      hex: \"#ff0000\"\n                                                      alpha: 1.0\n                                              scale: .75\n                                      content_description: \"Required Email\"\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        alignment: start\n                                        styles: []\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                    size:\n                                      width: 100%\n                                      height: 50\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 4\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: 50\n                                        view:\n                                          border:\n                                            radius: 4\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                          type: text_input\n                                          text_appearance:\n                                            alignment: start\n                                            font_size: 16\n                                            font_families:\n                                            - sans-serif\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            place_holder_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                          background_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                          identifier: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                          input_type: email\n                                          required: true\n                                          content_description: ''\n                                          place_holder: \"email@email.com\"\n                                          view_overrides:\n                                            icon_end:\n                                            - when_state_matches:\n                                                scope:\n                                                - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                                value:\n                                                  equals: \"error\"\n                                              value:\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: exclamationmark_circle_fill\n                                                  scale: 1\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#ff0000\"\n                                                      alpha: 0.5\n                                            border:\n                                            - when_state_matches:\n                                                scope:\n                                                - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                                value:\n                                                  equals: \"error\"\n                                              value:\n                                                radius: 4\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#ff0000\"\n                                                    alpha: 1\n                                          on_error:\n                                            state_actions:\n                                            - type: set\n                                              key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                              value: \"error\"\n                                          on_edit:\n                                            state_actions:\n                                            - type: set\n                                              key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                              value: \"editing\"\n                                          on_valid:\n                                            state_actions:\n                                            - type: set\n                                              key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                              value: \"valid\"\n                                  - identifier: 26b7ddd7-f995-4a27-8fcb-e1b86d00d210\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Another Email\n                                      content_description: Another Email\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        alignment: start\n                                        styles: []\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5\n                                    size:\n                                      width: 100%\n                                      height: 50\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 4\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: 50\n                                        view:\n                                          border:\n                                            radius: 4\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                          type: text_input\n                                          text_appearance:\n                                            alignment: start\n                                            font_size: 16\n                                            font_families:\n                                            - sans-serif\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            place_holder_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                          background_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 0\n                                          identifier: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5\n                                          input_type: text\n                                          required: false\n                                          content_description: ''\n                                          place_holder: Optional\n                                          view_overrides:\n                                            icon_end:\n                                            - when_state_matches:\n                                                scope:\n                                                - c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                                value:\n                                                  equals: true\n                                              value:\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: exclamationmark_circle_fill\n                                                  scale: 1\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#ff0000\"\n                                                      alpha: 1\n                                            border:\n                                            - when_state_matches:\n                                                scope:\n                                                - c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                                value:\n                                                  equals: true\n                                              value:\n                                                radius: 4\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#ff0000\"\n                                                    alpha: 1\n                                          on_error:\n                                            state_actions:\n                                            - type: set\n                                              key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                              value: true\n                                          on_edit:\n                                            state_actions:\n                                            - type: set\n                                              key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                          on_valid:\n                                            state_actions:\n                                            - type: set\n                                              key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                  - identifier: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    view:\n                                      type: label_button\n                                      identifier: submit_feedback--Submit\n                                      reporting_metadata:\n                                        trigger_link_id: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                      label:\n                                        view_overrides:\n                                            icon_start:\n                                              - value:\n                                                  type: \"floating\"\n                                                  space: 8\n                                                  icon:\n                                                    type: icon\n                                                    icon: progress_spinner\n                                                    color:\n                                                        default:\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1.0\n                                                    scale: 1\n                                                when_state_matches:\n                                                  scope:\n                                                      - $forms\n                                                      - current\n                                                      - status\n                                                      - type\n                                                  value:\n                                                    equals: \"validating\"\n                                            text:\n                                              - value: \"Processing ...\"\n                                                when_state_matches:\n                                                  scope:\n                                                      - $forms\n                                                      - current\n                                                      - status\n                                                      - type\n                                                  value:\n                                                    equals: \"validating\"\n                                        type: label\n                                        text: Submit\n                                        content_description: Submit\n                                        text_appearance:\n                                          font_size: 16\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          alignment: center\n                                          styles: []\n                                          font_families:\n                                          - sans-serif\n                                      actions: {}\n                                      enabled:\n                                      - form_validation\n                                      button_click:\n                                      - form_submit\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                      border:\n                                        radius: 0\n                                        stroke_width: 16\n                                        stroke_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                      event_handlers:\n                                      - type: tap\n                                        state_actions:\n                                        - type: set\n                                          key: submitted\n                                          value: true\n                                  - size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                  branching:\n                    next_page:\n                      selectors: []\n              ignore_safe_area: false\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                identifier: dismiss_button\n                button_click:\n                - dismiss\n            - size:\n                height: auto\n                width: auto\n              margin:\n                start: 16\n                end: 16\n                top: 16\n                bottom: 16\n              position:\n                horizontal: end\n                vertical: bottom\n              view:\n                type: label_button\n                identifier: button1\n                background_color:\n                  default:\n                    hex: \"#FFD600\"\n                label:\n                  type: label\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        hex: \"#333333\"\n                    alignment: center\n                  text: 'Next'\n                button_click: [ \"pager_next\" ]\n                enabled: [ \"pager_next\" ]\n            - size:\n                height: auto\n                width: auto\n              margin:\n                start: 16\n                end: 16\n                top: 16\n                bottom: 16\n              position:\n                horizontal: start\n                vertical: bottom\n              view:\n                type: label_button\n                identifier: button1\n                background_color:\n                  default:\n                    hex: \"#FFD600\"\n                label:\n                  type: label\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        hex: \"#333333\"\n                    alignment: center\n                  text: 'Previous'\n                button_click: [ \"pager_previous\" ]\n                enabled: [ \"pager_previous\" ]\n            - margin:\n                top: 4\n                bottom: 4\n                end: 0\n                start: 0\n              position:\n                horizontal: center\n                vertical: bottom\n              size:\n                height: 7\n                width: 100%\n              view:\n                type: pager_indicator\n                spacing: 6\n                bindings:\n                  selected:\n                    shapes:\n                    - type: ellipse\n                      scale: 1\n                      aspect_ratio: 1\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#363636\"\n                          alpha: 1\n                        selectors:\n                        - platform: ios\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#363636\"\n                            alpha: 1\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#363636\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#363636\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                  unselected:\n                    shapes:\n                    - type: ellipse\n                      aspect_ratio: 1\n                      scale: 1\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#747474\"\n                          alpha: 1\n                        selectors:\n                        - platform: ios\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#747474\"\n                            alpha: 1\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#747474\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#747474\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n    branching:\n      pager_completions: []\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/form-input-branching-on-demand.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web: {}\nview:\n  type: pager_controller\n  identifier: 5ff76966-4e7e-4a5f-b3b4-3928cf7f4076\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - size:\n        width: 100%\n        height: 100%\n      view:\n        identifier: fa59a9dc-4f78-407e-bc89-4bb20c03c310\n        type: form_controller\n        validation_mode:\n            type: on_demand\n        form_enabled:\n        - form_submission\n        submit: submit_event\n        response_type: user_feedback\n        view:\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: 100%\n            view:\n              type: pager\n              disable_swipe: false\n              items:\n              - identifier: b5628f32-3da1-42ae-b76e-c20800922d47\n                type: pager_item\n                view:\n                  type: container\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: \"Favorite Animal\"\n                                        content_description: \"Favorite Animal\"\n                                        icon_start:\n                                          space: 8\n                                          type: floating\n                                          icon:\n                                            type: icon\n                                            icon: asterisk\n                                            color:\n                                                default:\n                                                    hex: \"#000000\"\n                                                    alpha: 1.0\n                                            scale: .75\n                                        view_overrides:\n                                          icon_start:\n                                          - when_state_matches:\n                                              scope:\n                                              - edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                              value:\n                                                equals: \"error\"\n                                            value:\n                                              space: 8\n                                              type: floating\n                                              icon:\n                                                type: icon\n                                                icon: exclamationmark_circle_fill\n                                                color:\n                                                    default:\n                                                        hex: \"#ff0000\"\n                                                        alpha: 1.0\n                                                scale: .75\n                                        text_appearance:\n                                          font_size: 20\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          alignment: start\n                                          styles: []\n                                          font_families:\n                                          - sans-serif\n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: radio_input_controller\n                                        identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                        required: true\n                                        on_error:\n                                          state_actions:\n                                            - type: set\n                                              key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                              value: \"error\"\n                                        on_valid:\n                                          state_actions:\n                                            - type: set\n                                              key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                              value: \"valid\"\n                                        on_edit:\n                                          state_actions:\n                                            - type: set\n                                              key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                              value: \"editing\"\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7\n                                                  content_description: Cat\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Cat\n                                                  content_description: Cat\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d\n                                                  content_description: Dog\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Dog\n                                                  content_description: Dog\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          randomize_children: false\n                                        attribute_name: {}\n                                - size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: horizontal\n                                    items: []\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                branching:\n                  next_page:\n                    selectors:\n                    - page_id: 24e09863-1dcf-40ea-8b81-b398d286d449\n                      when_state_matches:\n                        or:\n                        - scope:\n                          - \"$forms\"\n                          - current\n                          - data\n                          - children\n                          - edf9064c-54d5-4b29-9ac9-ad0997a62696 # favorite animal\n                          value:\n                            equals: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7 # cat \n                          key: value\n                    - page_id: d495142a-38d3-482c-9f14-203501b0518a\n                      when_state_matches:\n                        and:\n                        - scope:\n                          - \"$forms\"\n                          - current\n                          - data\n                          - children\n                          - edf9064c-54d5-4b29-9ac9-ad0997a62696 # favorite animal\n                          value:\n                            equals: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d # dog\n                          key: value\n                    # - page_id: d495142a-38d3-482c-9f14-203501b0518a\n                    # - page_id: 24e09863-1dcf-40ea-8b81-b398d286d449\n                    # - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n              - identifier: d495142a-38d3-482c-9f14-203501b0518a\n                type: pager_item\n                branching:\n                  next_page:\n                    selectors:\n                    - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                view:\n                  type: container\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: 7beb5f48-a5fe-4c56-ae31-994263ffcdb2\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: Favorite Color\n                                        content_description: Favorite Color\n                                        text_appearance:\n                                          font_size: 20\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          alignment: start\n                                          styles: []\n                                          font_families:\n                                          - sans-serif\n                                    - identifier: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                      margin:\n                                        top: 8\n                                        bottom: 8\n                                        start: 16\n                                        end: 16\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label_button\n                                        identifier: next\n                                        reporting_metadata:\n                                          trigger_link_id: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                        label:\n                                          view_overrides:\n                                              icon_start:\n                                                - value:\n                                                    type: \"floating\"\n                                                    space: 8\n                                                    icon:\n                                                      type: icon\n                                                      icon: progress_spinner\n                                                      color:\n                                                          default:\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1.0\n                                                      scale: 1\n                                                  when_state_matches:\n                                                    scope:\n                                                        - $forms\n                                                        - current\n                                                        - status\n                                                        - type\n                                                    value:\n                                                      equals: \"validating\"\n                                              text:\n                                                - value: \"Processing ...\"\n                                                  when_state_matches:\n                                                    scope:\n                                                        - $forms\n                                                        - current\n                                                        - status\n                                                        - type\n                                                    value:\n                                                      equals: \"validating\"\n                                          type: label\n                                          text: Next\n                                          content_description: Next\n                                          text_appearance:\n                                            font_size: 16\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            alignment: center\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                        actions: {}\n                                        button_click:\n                                        - pager_next\n                                        - form_validate\n                                        background_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                        border:\n                                          radius: 0\n                                          stroke_width: 16\n                                          stroke_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                        event_handlers:\n                                        - type: tap\n                                          state_actions:\n                                          - type: set\n                                            key: submitted\n                                            value: true\n                                            \n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: radio_input_controller\n                                        identifier: 7beb5f48-a5fe-4c56-ae31-994263ffcdb2\n                                        required: false\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: d0596a26-f197-48d7-96b3-c61ace2fb16b\n                                                  content_description: Blue\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Blue\n                                                  content_description: Blue\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: 55ffe2a4-d851-40a8-8046-28c8349e9ace\n                                                  content_description: Red\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Red\n                                                  content_description: Red\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          randomize_children: false\n                                        attribute_name: {}\n                                - size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: horizontal\n                                    items: []\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n              - identifier: 24e09863-1dcf-40ea-8b81-b398d286d449\n                type: pager_item\n                branching:\n                  next_page:\n                    selectors:\n                    - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                view:\n                  type: container\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: \"What are your favorite Guy Fieri shows? (select at least 2)\"\n                                        content_description: \"What are your favorite Guy Fieri shows? (select at least 2)\"\n                                        icon_start:\n                                          space: 8\n                                          type: floating\n                                          icon:\n                                            type: icon\n                                            icon: asterisk\n                                            color:\n                                                default:\n                                                    hex: \"#000000\"\n                                                    alpha: 1.0\n                                            scale: .75\n                                        view_overrides:\n                                          icon_start:\n                                          - when_state_matches:\n                                              scope:\n                                              - 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                              value:\n                                                equals: \"error\"\n                                            value:\n                                              space: 8\n                                              type: floating\n                                              icon:\n                                                type: icon\n                                                icon: exclamationmark_circle_fill\n                                                color:\n                                                    default:\n                                                        hex: \"#ff0000\"\n                                                        alpha: 1.0\n                                                scale: .75\n                                        text_appearance:\n                                          font_size: 20\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          alignment: start\n                                          styles: []\n                                          font_families:\n                                          - sans-serif\n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: checkbox_controller\n                                        identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                        on_error:\n                                          state_actions:\n                                            - type: set\n                                              key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                              value: \"error\"\n                                        on_valid:\n                                          state_actions:\n                                            - type: set\n                                              key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                              value: \"valid\"\n                                        on_edit:\n                                          state_actions:\n                                            - type: set\n                                              key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                              value: \"editing\"\n                                        required: true\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: 646d668e-5d50-44cf-8ddf-7fe4e2a404ab\n                                                  content_description: Diners,\n                                                    Drive-Ins, and Dives\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Diners, Drive-Ins,\n                                                    and Dives\n                                                  content_description: Diners,\n                                                    Drive-Ins, and Dives\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: 3a6f2143-968c-484e-ac2f-69efdbfaaabd\n                                                  content_description: Guy's\n                                                    Grocery Games\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Guy's Grocery Games\n                                                  content_description: Guy's\n                                                    Grocery Games\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: 86eaa518-2a24-4018-a303-4c9eb05ec2a3\n                                                  content_description: Guy's\n                                                    Big Bite\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Guy's Big Bite\n                                                  content_description: Guy's\n                                                    Big Bite\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: bbd19354-9728-4be6-baab-7d3779841eb2\n                                                  content_description: Guy's\n                                                    Family Road Trip\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Guy's Family Road\n                                                    Trip\n                                                  content_description: Guy's\n                                                    Family Road Trip\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 8\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 8\n                                                size:\n                                                  width: 20\n                                                  height: 20\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: d27cc3e2-e1a9-4ed8-b013-a3b64017ee91\n                                                  content_description: Minute\n                                                    to Win It\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Minute to Win It\n                                                  content_description: Minute\n                                                    to Win It\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          randomize_children: true\n                                        min_selection: 2\n                                - size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: horizontal\n                                    items: []\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n              - identifier: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                type: pager_item\n                view:\n                  type: container\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: fe561774-93f5-4b09-bd0c-831b955042e6\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: label\n                                    text: \"Required Email\"\n                                    icon_start:\n                                      space: 8\n                                      type: floating\n                                      icon:\n                                        type: icon\n                                        icon: asterisk\n                                        color:\n                                            default:\n                                                hex: \"#000000\"\n                                                alpha: 1.0\n                                        scale: .75\n                                    view_overrides:\n                                      icon_start:\n                                      - when_state_matches:\n                                          scope:\n                                          - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                          value:\n                                            equals: \"error\"\n                                        value:\n                                          space: 8\n                                          type: floating\n                                          icon:\n                                            type: icon\n                                            icon: exclamationmark_circle_fill\n                                            color:\n                                                default:\n                                                    hex: \"#ff0000\"\n                                                    alpha: 1.0\n                                            scale: .75\n                                    content_description: \"Required Email\"\n                                    text_appearance:\n                                      font_size: 20\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                      alignment: start\n                                      styles: []\n                                      font_families:\n                                      - sans-serif\n                                - identifier: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                  size:\n                                    width: 100%\n                                    height: 50\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 4\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: 50\n                                      view:\n                                        border:\n                                          radius: 4\n                                          stroke_width: 1\n                                          stroke_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                        type: text_input\n                                        text_appearance:\n                                          alignment: start\n                                          font_size: 16\n                                          font_families:\n                                          - sans-serif\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          place_holder_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                        background_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 0\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                        identifier: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                        input_type: email\n                                        required: true\n                                        content_description: ''\n                                        place_holder: \"email@email.com\"\n                                        view_overrides:\n                                          icon_end:\n                                          - when_state_matches:\n                                              scope:\n                                              - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                              value:\n                                                equals: \"error\"\n                                            value:\n                                              type: floating\n                                              icon:\n                                                type: icon\n                                                icon: exclamationmark_circle_fill\n                                                scale: 1\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#ff0000\"\n                                                    alpha: 0.5\n                                          border:\n                                          - when_state_matches:\n                                              scope:\n                                              - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                              value:\n                                                equals: \"error\"\n                                            value:\n                                              radius: 4\n                                              stroke_width: 1\n                                              stroke_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#ff0000\"\n                                                  alpha: 1\n                                        on_error:\n                                          state_actions:\n                                          - type: set\n                                            key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                            value: \"error\"\n                                        on_edit:\n                                          state_actions:\n                                          - type: set\n                                            key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                            value: \"editing\"\n                                        on_valid:\n                                          state_actions:\n                                          - type: set\n                                            key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                            value: \"valid\"\n                                - identifier: 26b7ddd7-f995-4a27-8fcb-e1b86d00d210\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: label\n                                    text: Another Email\n                                    content_description: Another Email\n                                    text_appearance:\n                                      font_size: 20\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                      alignment: start\n                                      styles: []\n                                      font_families:\n                                      - sans-serif\n                                - identifier: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5\n                                  size:\n                                    width: 100%\n                                    height: 50\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 4\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: 50\n                                      view:\n                                        border:\n                                          radius: 4\n                                          stroke_width: 1\n                                          stroke_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#63AFF1\"\n                                                alpha: 1\n                                        type: text_input\n                                        text_appearance:\n                                          alignment: start\n                                          font_size: 16\n                                          font_families:\n                                          - sans-serif\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          place_holder_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                        background_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 0\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 0\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                        identifier: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5\n                                        input_type: text\n                                        required: false\n                                        content_description: ''\n                                        place_holder: Optional\n                                        view_overrides:\n                                          icon_end:\n                                          - when_state_matches:\n                                              scope:\n                                              - c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                              value:\n                                                equals: true\n                                            value:\n                                              type: floating\n                                              icon:\n                                                type: icon\n                                                icon: exclamationmark_circle_fill\n                                                scale: 1\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#ff0000\"\n                                                    alpha: 1\n                                          border:\n                                          - when_state_matches:\n                                              scope:\n                                              - c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                              value:\n                                                equals: true\n                                            value:\n                                              radius: 4\n                                              stroke_width: 1\n                                              stroke_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#ff0000\"\n                                                  alpha: 1\n                                        on_error:\n                                          state_actions:\n                                          - type: set\n                                            key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                            value: true\n                                        on_edit:\n                                          state_actions:\n                                          - type: set\n                                            key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                        on_valid:\n                                          state_actions:\n                                          - type: set\n                                            key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                - identifier: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                  margin:\n                                    top: 8\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  view:\n                                    type: label_button\n                                    identifier: submit_feedback--Submit\n                                    reporting_metadata:\n                                      trigger_link_id: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                    label:\n                                      view_overrides:\n                                          icon_start:\n                                            - value:\n                                                type: \"floating\"\n                                                space: 8\n                                                icon:\n                                                  type: icon\n                                                  icon: progress_spinner\n                                                  color:\n                                                      default:\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1.0\n                                                  scale: 1\n                                              when_state_matches:\n                                                scope:\n                                                    - $forms\n                                                    - current\n                                                    - status\n                                                    - type\n                                                value:\n                                                  equals: \"validating\"\n                                          text:\n                                            - value: \"Processing ...\"\n                                              when_state_matches:\n                                                scope:\n                                                    - $forms\n                                                    - current\n                                                    - status\n                                                    - type\n                                                value:\n                                                  equals: \"validating\"\n                                      type: label\n                                      text: Submit\n                                      content_description: Submit\n                                      text_appearance:\n                                        font_size: 16\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        alignment: center\n                                        styles: []\n                                        font_families:\n                                        - sans-serif\n                                    actions: {}\n                                    enabled:\n                                    - form_validation\n                                    button_click:\n                                    - form_submit\n                                    - dismiss\n                                    background_color:\n                                      default:\n                                        type: hex\n                                        hex: \"#63AFF1\"\n                                        alpha: 1\n                                      selectors:\n                                      - platform: ios\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: false\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      - platform: web\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                    border:\n                                      radius: 0\n                                      stroke_width: 16\n                                      stroke_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                    event_handlers:\n                                    - type: tap\n                                      state_actions:\n                                      - type: set\n                                        key: submitted\n                                        value: true\n                                - size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: horizontal\n                                    items: []\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                branching:\n                  next_page:\n                    selectors: []\n            ignore_safe_area: false\n          - position:\n              horizontal: end\n              vertical: top\n            size:\n              width: 48\n              height: 48\n            view:\n              type: image_button\n              image:\n                scale: 0.4\n                type: icon\n                icon: close\n                color:\n                  default:\n                    type: hex\n                    hex: \"#000000\"\n                    alpha: 1\n                  selectors:\n                  - platform: ios\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: false\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: web\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n              identifier: dismiss_button\n              button_click:\n              - dismiss\n          - margin:\n              top: 4\n              bottom: 4\n              end: 0\n              start: 0\n            position:\n              horizontal: center\n              vertical: bottom\n            size:\n              height: 7\n              width: 100%\n            view:\n              type: pager_indicator\n              spacing: 6\n              bindings:\n                selected:\n                  shapes:\n                  - type: ellipse\n                    scale: 1\n                    aspect_ratio: 1\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#363636\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#363636\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#363636\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#363636\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                unselected:\n                  shapes:\n                  - type: ellipse\n                    aspect_ratio: 1\n                    scale: 1\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#747474\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#747474\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#747474\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#747474\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n  branching:\n    pager_completions: []\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/form_immediate_phone.yaml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    margin:\n      start: 16\n      end: 16\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\nview:\n  type: state_controller\n  view:\n    type: form_controller\n    validation_mode:\n        type: immediate\n    identifier: a_form\n    submit: submit_event\n    view:\n      type: scroll_layout\n      direction: vertical\n      view:\n        type: container\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            margin:\n              top: 24\n              start: 24\n              bottom: 24\n              end: 24\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n\n                #\n                # Text Input type: email\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Phone number with placeholder\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    bottom: 12\n                  view:\n                    type: text_input\n                    place_holder: Tap in here\n                    identifier: text_input_sms_1\n                    border:\n                      radius: 5\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#cccccc\"\n                          alpha: 1\n                    text_appearance:\n                      alignment: start\n                      font_size: 20\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#a8323a\"\n                          alpha: 1\n                    input_type: sms\n                    locales:\n                      - country_code: \"US\"\n                        prefix: \"+1\"\n                      - country_code: \"FR\"\n                        prefix: \"+33\"\n                      - country_code: \"UA\"\n                        prefix: \"+380\"\n                    required: true\n                    on_error:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"error\"\n                    on_valid:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"valid\"\n                    on_edit:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"editing\"\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Phone with no placeholder\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    bottom: 12\n                  view:\n                    type: text_input\n                    identifier: text_input_sms_2\n                    border:\n                      radius: 5\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#cccccc\"\n                          alpha: 1\n                    text_appearance:\n                      alignment: start\n                      font_size: 20\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#a8323a\"\n                          alpha: 1\n                    input_type: sms\n                    locales:\n                      - country_code: \"US\"\n                        prefix: \"+1\"\n                        registration:\n                          type: opt_in\n                          sender_id: \"123123123\"\n                      - country_code: \"FR\"\n                        prefix: \"+33\"\n                        registration:\n                          type: opt_in\n                          sender_id: \"123123123\"\n                      - country_code: \"UA\"\n                        prefix: \"+380\"\n                        registration:\n                          type: opt_in\n                          sender_id: \"123123123\"\n                    required: false\n                    on_error:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"error\"\n                    on_valid:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"valid\"\n                    on_edit:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"editing\"\n                - margin:\n                  size:\n                    height: auto\n                    width: 100%\n                  view:\n                    when_state_matches:\n                        key: is_valid_email\n                        value:\n                          equals: false\n                    background_color:\n                      default:\n                        alpha: 1\n                        hex: \"#222222\"\n                        type: hex\n                    border:\n                      radius: 0\n                      stroke_color:\n                        default:\n                          alpha: 1\n                          hex: \"#222222\"\n                          type: hex\n                      stroke_width: 1\n                    enabled:\n                        - form_validation\n                    button_click:\n                      - form_submit\n                      - dismiss\n                    identifier: e49c1d9a-1118-4a7b-8ae8-2e1ce42b0f1a\n                    label:\n                      text: Submit\n                      text_appearance:\n                        alignment: center\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFF\"\n                            type: hex\n                        font_families:\n                          - sans-serif\n                        font_size: 24\n                      type: label\n                    type: label_button\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/form_immediate_validation_email.yaml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    margin:\n      start: 16\n      end: 16\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\nview:\n  type: state_controller\n  view:\n    type: form_controller\n    validation_mode:\n        type: immediate\n    identifier: a_form\n    submit: submit_event\n    view:\n      type: scroll_layout\n      direction: vertical\n      view:\n        type: container\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            margin:\n              top: 24\n              start: 24\n              bottom: 24\n              end: 24\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n\n                #\n                # Text Input type: email\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Give me your email, it's required\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    bottom: 12\n                  view:\n                    type: text_input\n                    place_holder: Tap in here\n                    identifier: text_input_email\n                    icon_end:\n                      type: floating\n                      icon:\n                          type: icon\n                          icon: back_arrow\n                          color:\n                              default:\n                                  hex: \"#000000\"\n                                  alpha: 0.5\n                          scale: 1\n                    border:\n                      radius: 5\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#cccccc\"\n                          alpha: 1\n                    view_overrides:\n                      icon_end:\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: exclamationmark_circle_fill\n                              color:\n                                  default:\n                                      hex: \"#ff0000\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: \"error\"\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: progress_spinner\n                              color:\n                                  default:\n                                      hex: \"#000000\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: \"editing\"\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: checkmark\n                              color:\n                                  default:\n                                      hex: \"#00ff00\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: \"valid\"\n                      border:\n                        - value:\n                            radius: 5\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: \"#ff0000\"\n                                alpha: 0.5\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: false\n                        - value:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                hex: \"#cccccc\"\n                                alpha: 1\n                                stroke_width: 1\n                          when_state_matches:\n                              key: is_valid_email\n                              value:\n                                equals: true\n                    text_appearance:\n                      alignment: start\n                      font_size: 20\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#a8323a\"\n                          alpha: 1\n                    input_type: email\n                    required: true\n                    on_error:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"error\"\n                    on_valid:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"valid\"\n                    on_edit:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"editing\"\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Give me your email, it's required\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    bottom: 12\n                  view:\n                    type: text_input\n                    place_holder: Tap in here\n                    identifier: another_text_input_email\n                    icon_end:\n                      type: floating\n                      icon:\n                          type: icon\n                          icon: back_arrow\n                          color:\n                              default:\n                                  hex: \"#000000\"\n                                  alpha: 0.5\n                          scale: 1\n                    border:\n                      radius: 5\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#cccccc\"\n                          alpha: 1\n                    view_overrides:\n                      icon_end:\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: exclamationmark_circle_fill\n                              color:\n                                  default:\n                                      hex: \"#ff0000\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: another_is_valid_email\n                            value:\n                              equals: false\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: checkmark\n                              color:\n                                  default:\n                                      hex: \"#00ff00\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: another_is_valid_email\n                            value:\n                              equals: true\n                      border:\n                        - value:\n                            radius: 5\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: \"#ff0000\"\n                                alpha: 0.5\n                          when_state_matches:\n                            key: another_is_valid_email\n                            value:\n                              equals: false\n                        - value:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                hex: \"#cccccc\"\n                                alpha: 1\n                                stroke_width: 1\n                          when_state_matches:\n                              key: another_is_valid_email\n                              value:\n                                equals: true\n                    text_appearance:\n                      alignment: start\n                      font_size: 20\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#a8323a\"\n                          alpha: 1\n                    input_type: email\n                    required: true\n                    on_error:\n                      state_actions:\n                        - type: set\n                          key: another_is_valid_email\n                          value: false\n                    on_valid:\n                      state_actions:\n                        - type: set\n                          key: another_is_valid_email\n                          value: true\n                    on_edit:\n                      state_actions:\n                        - type: set\n                          key: another_is_valid_email\n                - margin:\n                  size:\n                    height: auto\n                    width: 100%\n                  view:\n                    when_state_matches:\n                        key: is_valid_email\n                        value:\n                          equals: false\n                    background_color:\n                      default:\n                        alpha: 1\n                        hex: \"#222222\"\n                        type: hex\n                    border:\n                      radius: 0\n                      stroke_color:\n                        default:\n                          alpha: 1\n                          hex: \"#222222\"\n                          type: hex\n                      stroke_width: 1\n                    enabled:\n                        - form_validation\n                    button_click:\n                      - form_submit\n                      - dismiss\n                    identifier: e49c1d9a-1118-4a7b-8ae8-2e1ce42b0f1a\n                    label:\n                      text: Submit\n                      text_appearance:\n                        alignment: center\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFF\"\n                            type: hex\n                        font_families:\n                          - sans-serif\n                        font_size: 24\n                      type: label\n                    type: label_button\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/form_on_demand_validation_email.yaml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    margin:\n      start: 16\n      end: 16\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\nview:\n  type: state_controller\n  view:\n    type: form_controller\n    validation_mode:\n        type: on_demand\n    identifier: a_form\n    submit: submit_event\n    state_triggers:\n        - identifier: toast_trigger\n          trigger_when_state_matches:\n            scope:\n            - $forms\n            - current\n            - status\n            - type\n            value:\n              equals: \"error\"\n          reset_when_state_matches:\n            not:\n              scope:\n              - $forms\n              - current\n              - status\n              - type\n              value:\n                equals: \"error\"\n          on_trigger:\n            state_actions:\n                - type: set\n                  key: show_toast\n                  ttl_seconds: 1.5\n                  value: true\n    view:\n      type: scroll_layout\n      direction: vertical\n      view:\n        type: container\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            margin:\n              top: 24\n              start: 24\n              bottom: 24\n              end: 24\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n                    \n                #\n                # Text Input type: email\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Give me your email, it's required.\n                    view_overrides:\n                        icon_start:\n                            - value:\n                                space: 8\n                                type: floating\n                                icon:\n                                  type: icon\n                                  icon: exclamationmark_circle_fill\n                                  color:\n                                      default:\n                                          hex: \"#ff0000\"\n                                          alpha: 0.5\n                                  scale: 1\n                              when_state_matches:\n                                key: is_valid_email\n                                value:\n                                  equals: \"error\"\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    bottom: 12\n                  view:\n                    type: text_input\n                    place_holder: Tap in here\n                    identifier: text_input_email\n                    icon_end:\n                      type: floating\n                      icon:\n                          type: icon\n                          icon: back_arrow\n                          color:\n                              default:\n                                  hex: \"#000000\"\n                                  alpha: 0.5\n                          scale: 1\n                    border:\n                      radius: 5\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#cccccc\"\n                          alpha: 1\n                    view_overrides:\n                      icon_end:\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: exclamationmark_circle_fill\n                              color:\n                                  default:\n                                      hex: \"#ff0000\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: \"error\"\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: checkmark\n                              color:\n                                  default:\n                                      hex: \"#00ff00\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: \"valid\"\n                      border:\n                        - value:\n                            radius: 5\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: \"#ff0000\"\n                                alpha: 0.5\n                          when_state_matches:\n                            key: is_valid_email\n                            value:\n                              equals: \"error\"\n                        - value:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                hex: \"#cccccc\"\n                                alpha: 1\n                                stroke_width: 1\n                          when_state_matches:\n                              key: is_valid_email\n                              value:\n                                equals: \"valid\"\n                    text_appearance:\n                      alignment: start\n                      font_size: 20\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#a8323a\"\n                          alpha: 1\n                    input_type: email\n                    required: true\n                    on_error:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"error\"\n                    on_valid:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"valid\"\n                    on_edit:\n                      state_actions:\n                        - type: set\n                          key: is_valid_email\n                          value: \"editing\"\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Give me your number, I require it.\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    bottom: 12\n                  view:\n                    type: text_input\n                    place_holder: Tap in here\n                    required: true\n                    identifier: another_text_input_email\n                    icon_end:\n                      type: floating\n                      icon:\n                          type: icon\n                          icon: back_arrow\n                          color:\n                              default:\n                                  hex: \"#000000\"\n                                  alpha: 0.5\n                          scale: 1\n                    border:\n                      radius: 5\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#cccccc\"\n                          alpha: 1\n                    view_overrides:\n                      icon_end:\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: exclamationmark_circle_fill\n                              color:\n                                  default:\n                                      hex: \"#ff0000\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: another_is_valid_email\n                            value:\n                              equals: \"error\"\n                        - value:\n                            type: floating\n                            icon:\n                              type: icon\n                              icon: checkmark\n                              color:\n                                  default:\n                                      hex: \"#00ff00\"\n                                      alpha: 0.5\n                              scale: 1\n                          when_state_matches:\n                            key: another_is_valid_email\n                            value:\n                              equals: \"valid\"\n                      border:\n                        - value:\n                            radius: 5\n                            stroke_width: 2\n                            stroke_color:\n                              default:\n                                hex: \"#ff0000\"\n                                alpha: 0.5\n                          when_state_matches:\n                            key: another_is_valid_email\n                            value:\n                              equals: \"error\"\n                        - value:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                hex: \"#cccccc\"\n                                alpha: 1\n                                stroke_width: 1\n                          when_state_matches:\n                              key: another_is_valid_email\n                              value:\n                                equals: \"valid\"\n                    text_appearance:\n                      alignment: start\n                      font_size: 20\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#a8323a\"\n                          alpha: 1\n                    input_type: sms\n                    required: true\n                    locales:\n                      - country_code: \"US\"\n                        prefix: \"+1\"\n                      - country_code: \"FR\"\n                        prefix: \"+33\"\n                      - country_code: \"UA\"\n                        prefix: \"+380\"\n                    on_error:\n                      state_actions:\n                        - type: set\n                          key: another_is_valid_email\n                          value: \"error\"\n                    on_valid:\n                      state_actions:\n                        - type: set\n                          key: another_is_valid_email\n                          value: \"valid\"\n                    on_edit:\n                      state_actions:\n                        - type: set\n                          key: another_is_valid_email\n                          value: \"editing\"\n                - margin:\n                  size:\n                    height: auto\n                    width: 100%\n                  view:\n                    when_state_matches:\n                        key: is_valid_email\n                        value:\n                          equals: \"error\"\n                    background_color:\n                      default:\n                        alpha: 1\n                        hex: \"#222222\"\n                        type: hex\n                    border:\n                      radius: 0\n                      stroke_color:\n                        default:\n                          alpha: 1\n                          hex: \"#222222\"\n                          type: hex\n                      stroke_width: 1\n                    enabled:\n                        - form_validation\n                    button_click:\n                      - form_submit\n                      - dismiss\n                    identifier: e49c1d9a-1118-4a7b-8ae8-2e1ce42b0f1a\n                    label:\n                      view_overrides:\n                          icon_start:\n                            - value:\n                                type: \"floating\"\n                                space: 8\n                                icon:\n                                  type: icon\n                                  icon: progress_spinner\n                                  color:\n                                      default:\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1.0\n                                  scale: 1\n                              when_state_matches:\n                                scope:\n                                    - $forms\n                                    - current\n                                    - status\n                                    - type\n                                value:\n                                  equals: \"validating\"\n                          text:\n                            - value: \"Processing ...\"\n                              when_state_matches:\n                                scope:\n                                    - $forms\n                                    - current\n                                    - status\n                                    - type\n                                value:\n                                  equals: \"validating\"\n                      text: Submit\n                      text_appearance:\n                        alignment: center\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFF\"\n                            type: hex\n                        font_families:\n                          - sans-serif\n                        font_size: 24\n                      type: label\n                    type: label_button\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: auto\n              height: auto\n            margin:\n              top: 24\n              start: 24\n              bottom: 24\n              end: 24\n            view:\n              type: container\n              background_color:\n                default:\n                  hex: \"#ff0000\"\n                  alpha: .8\n              border:\n                  radius: 5\n                  stroke_width: 1\n                  stroke_color:\n                    default:\n                      type: hex\n                      hex: \"#cccccc\"\n                      alpha: .8\n              items:\n                -   position:\n                        horizontal: center\n                        vertical: center\n                    size:\n                      width: auto\n                      height: auto\n                    margin:\n                      top: 24\n                      start: 24\n                      bottom: 24\n                      end: 24\n                    view:\n                      text: Error submitting form\n                      text_appearance:\n                        alignment: center\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFF\"\n                            type: hex\n                        font_families:\n                          - sans-serif\n                        font_size: 24\n                      type: label\n                      visibility:\n                        invert_when_state_matches:\n                          scope:\n                          - show_toast\n                          value:\n                            equals: true\n                        default: false\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/form_single_page_on_demand.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web:\n      ignore_shade: true\nview:\n  type: state_controller\n  view:\n    type: pager_controller\n    identifier: 6b72219d-15ef-4bcf-832b-70850b419687\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          identifier: 4a560c36-28fa-452d-a2e0-f07a9e3d7521\n          nps_identifier: b395410d-af24-4d9a-801e-c99e5833abf3\n          type: nps_form_controller\n          validation_mode:\n            type: on_demand\n          submit: submit_event\n          form_enabled:\n          - form_submission\n          response_type: nps\n          view:\n            type: container\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                - identifier: d2432b09-48d6-40a9-8753-72e372319121\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: 8ce7c2d5-23f6-4eda-b840-c76ba483fb68\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 48\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Single Choice\n                                      content_description: Single Choice\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: \"Favorite Animal\"\n                                          content_description: \"Favorite Animal\"\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: radio_input_controller\n                                          identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                          required: true\n                                          on_error:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"error\"\n                                          on_valid:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"valid\"\n                                          on_edit:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"editing\"\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: radio_input\n                                                    reporting_value: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7\n                                                    content_description: Cat\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                          - type: ellipse\n                                                            scale: 0.6\n                                                            aspect_ratio: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Cat\n                                                    content_description: Cat\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: radio_input\n                                                    reporting_value: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d\n                                                    content_description: Dog\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                          - type: ellipse\n                                                            scale: 0.6\n                                                            aspect_ratio: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Dog\n                                                    content_description: Dog\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 0\n                                                bottom: 8\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - margin:\n                                                    end: 8\n                                                  size:\n                                                    width: 20\n                                                    height: 20\n                                                  view:\n                                                    type: radio_input\n                                                    reporting_value: 8df9abde-f0fc-4b37-afe0-70ee8aff0c00\n                                                    content_description: Dragon\n                                                    style:\n                                                      type: checkbox\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                          - type: ellipse\n                                                            scale: 0.6\n                                                            aspect_ratio: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: ellipse\n                                                            scale: 1\n                                                            aspect_ratio: 1\n                                                            border:\n                                                              radius: 20\n                                                              stroke_width: 2\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                - margin:\n                                                    end: 16\n                                                  size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: label\n                                                    text: Dragon\n                                                    content_description: Dragon\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: false\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            randomize_children: true\n                                          attribute_name: {}\n                                  - identifier: \"some-required-label\"\n                                    size:\n                                        width: 100%\n                                        height: auto\n                                    margin:\n                                        top: 0\n                                        bottom: 0\n                                        start: 16\n                                        end: 0\n                                    view:\n                                      type: label\n                                      text: \"* Required\"\n                                      text_appearance:\n                                        font_size: 16\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        alignment: start\n                                        styles: []\n                                        font_families:\n                                            - sans-serif\n                                      view_overrides:\n                                        text_appearance:\n                                        - when_state_matches:\n                                            scope:\n                                            - edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                            value:\n                                              equals: \"error\"\n                                          value:\n                                            font_size: 16\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#ff0000\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                  - identifier: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    view:\n                                      type: label_button\n                                      identifier: submit_feedback--Submit\n                                      reporting_metadata:\n                                        trigger_link_id: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                      label:\n                                        view_overrides:\n                                            icon_start:\n                                              - value:\n                                                  type: \"floating\"\n                                                  space: 8\n                                                  icon:\n                                                    type: icon\n                                                    icon: progress_spinner\n                                                    color:\n                                                        default:\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1.0\n                                                    scale: 1\n                                                when_state_matches:\n                                                  scope:\n                                                      - $forms\n                                                      - current\n                                                      - status\n                                                      - type\n                                                  value:\n                                                    equals: \"validating\"\n                                            text:\n                                              - value: \"Processing ...\"\n                                                when_state_matches:\n                                                  scope:\n                                                      - $forms\n                                                      - current\n                                                      - status\n                                                      - type\n                                                  value:\n                                                    equals: \"validating\"\n                                        type: label\n                                        text: Submit\n                                        content_description: Submit\n                                        text_appearance:\n                                          font_size: 16\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          alignment: center\n                                          styles: []\n                                          font_families:\n                                          - sans-serif\n                                      actions: {}\n                                      enabled:\n                                      - form_validation\n                                      button_click:\n                                      - form_submit\n                                      - dismiss\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                      border:\n                                        radius: 0\n                                        stroke_width: 16\n                                        stroke_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#63AFF1\"\n                                              alpha: 1\n                                      event_handlers:\n                                      - type: tap\n                                        state_actions:\n                                        - type: set\n                                          key: submitted\n                                          value: true\n                                  - size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                        visibility:\n                          default: true\n                          invert_when_state_matches:\n                            key: submitted\n                            value:\n                              equals: true\n                    - ignore_safe_area: true\n                      size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            start: 0\n                            end: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: horizontal\n                            items: []\n                          ignore_safe_area: true\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: layout_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: 9c9b4565-c8e3-41bf-a211-90af3665b38b\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 48\n                                    bottom: 8\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: label\n                                    text: Nice\n                                    content_description: Nice\n                                    text_appearance:\n                                      font_size: 20\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                      alignment: start\n                                      styles: []\n                                      font_families:\n                                      - sans-serif\n                          ignore_safe_area: false\n                        background_color:\n                          default:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                          selectors:\n                          - platform: ios\n                            dark_mode: false\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: android\n                            dark_mode: false\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: android\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: web\n                            dark_mode: false\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          - platform: web\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                        visibility:\n                          default: false\n                          invert_when_state_matches:\n                            key: submitted\n                            value:\n                              equals: true\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                    visibility:\n                      default: true\n                      invert_when_state_matches:\n                        key: confirmation_screen_container\n                        value:\n                          equals: true\n              ignore_safe_area: false\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                identifier: dismiss_button\n                button_click:\n                - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/forms.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors:\n  - placement:\n      ignore_safe_area: false\n      size:\n        width: 60%\n        height: 40%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n        selectors:\n        - platform: ios\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: android\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: web\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n      web:\n        ignore_shade: false\n      border:\n        radius: 10\n        stroke_color:\n          default:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n          selectors:\n          - platform: ios\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: ios\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n    window_size: large\n    orientation: portrait\n  - placement:\n      ignore_safe_area: false\n      size:\n        width: 100%\n        height: 100%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n        selectors:\n        - platform: ios\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: android\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: web\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n      web:\n        ignore_shade: false\n      border:\n        radius: 0\n        stroke_color:\n          default:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n          selectors:\n          - platform: ios\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: ios\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n    window_size: small\n    orientation: landscape\n  - placement:\n      ignore_safe_area: false\n      size:\n        width: 100%\n        height: 100%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n        selectors:\n        - platform: ios\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: android\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: web\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n      web:\n        ignore_shade: false\n      border:\n        radius: 0\n        stroke_color:\n          default:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n          selectors:\n          - platform: ios\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: ios\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n    window_size: medium\n    orientation: landscape\n  - placement:\n      ignore_safe_area: false\n      size:\n        width: 50%\n        height: 50%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n        selectors:\n        - platform: ios\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: android\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n        - platform: web\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 0.2\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#868686\"\n            alpha: 1\n      web:\n        ignore_shade: false\n      border:\n        radius: 10\n        stroke_color:\n          default:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n          selectors:\n          - platform: ios\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: ios\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: android\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: false\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n          - platform: web\n            dark_mode: true\n            color:\n              type: hex\n              hex: \"#000000\"\n              alpha: 1\n    window_size: large\n    orientation: landscape\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 90%\n      height: 70%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#868686\"\n        alpha: 0.2\n      selectors:\n      - platform: ios\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n      - platform: ios\n        dark_mode: true\n        color:\n          type: hex\n          hex: \"#868686\"\n          alpha: 1\n      - platform: android\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n      - platform: android\n        dark_mode: true\n        color:\n          type: hex\n          hex: \"#868686\"\n          alpha: 1\n      - platform: web\n        dark_mode: false\n        color:\n          type: hex\n          hex: \"#868686\"\n          alpha: 0.2\n      - platform: web\n        dark_mode: true\n        color:\n          type: hex\n          hex: \"#868686\"\n          alpha: 1\n    web: {}\n    border:\n      radius: 14\n      stroke_color:\n        default:\n          type: hex\n          hex: \"#000000\"\n          alpha: 1\n        selectors:\n        - platform: ios\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n        - platform: ios\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n        - platform: android\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n        - platform: android\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n        - platform: web\n          dark_mode: false\n          color:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\n        - platform: web\n          dark_mode: true\n          color:\n            type: hex\n            hex: \"#000000\"\n            alpha: 1\nview:\n  type: state_controller\n  view:\n    type: pager_controller\n    identifier: e7d21eaa-315e-4f11-b22c-7f0e05d83463\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          identifier: '008fbf86-fefa-4470-83f7-eec1c724bc1f'\n          nps_identifier: 69eba561-4a43-420b-9aed-30d76f620262\n          type: nps_form_controller\n          submit: submit_event\n          form_enabled:\n          - form_submission\n          response_type: nps\n          view:\n            type: container\n            items:\n            - identifier: 3451f18c-1fb8-4862-a877-1db87df33b3e_pager_container_item\n              position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: false\n                items:\n                - identifier: c89ff0d7-47f5-40d0-bb4a-d390deb2ef28\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - identifier: 84e1dbaf-1d34-4b28-88d7-7c8893d0f457_main_view_container_item\n                      size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - identifier: 51c9228e-5b18-4a8c-9f1b-49dcf6296d75_container_item\n                          margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: 93ce1f2e-008f-46e0-85cd-4daa362bd92b\n                                    size:\n                                      width: 100%\n                                      height: 150\n                                    view:\n                                      type: container\n                                      items:\n                                      - identifier: 789fe754-fb89-4ce1-89bd-f60a79c0ef11\n                                        margin:\n                                          start: 0\n                                          end: 0\n                                        position:\n                                          horizontal: center\n                                          vertical: center\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: media\n                                          media_fit: center_inside\n                                          url: https://c00001-dl.asnapieu.com/binary/public/YtDP8p2vSbW-KQkjUVsBAw/e21a8d73-47c2-4c35-9166-421ad586e220\n                                          media_type: image\n                                      - identifier: 93ce1f2e-008f-46e0-85cd-4daa362bd92b_linear_container\n                                        margin:\n                                          top: 0\n                                          bottom: 0\n                                          start: 0\n                                          end: 0\n                                        position:\n                                          horizontal: center\n                                          vertical: center\n                                        size:\n                                          width: 100%\n                                          height: 100%\n                                        view:\n                                          type: linear_layout\n                                          direction: horizontal\n                                          items:\n                                          - view:\n                                              type: container\n                                              items:\n                                              - identifier: cc3834bb-3e1f-4afa-b8a5-9bac2abb91ec_container\n                                                size:\n                                                  width: 100%\n                                                  height: auto\n                                                view:\n                                                  type: media\n                                                  media_fit: center_inside\n                                                  position:\n                                                    horizontal: center\n                                                    vertical: center\n                                                  url: https://c00001-dl.asnapieu.com/binary/public/YtDP8p2vSbW-KQkjUVsBAw/134c0029-e8e2-4f23-b355-6c79024c2501\n                                                  media_type: image\n                                                position:\n                                                  vertical: center\n                                                  horizontal: center\n                                            identifier: cc3834bb-3e1f-4afa-b8a5-9bac2abb91ec_wrapper\n                                            size:\n                                              width: 90%\n                                              height: 100%\n                                            margin:\n                                              end: 0\n                                              top: 8\n                                              start: 0\n                                              bottom: 0\n                                          border:\n                                            radius: 0\n                                    margin:\n                                      top: 0\n                                      bottom: 0\n                                      start: 0\n                                      end: 0\n                                  - identifier: 69eba561-4a43-420b-9aed-30d76f620262\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      end: 16\n                                      top: 30\n                                      start: 16\n                                      bottom: 8\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - margin:\n                                              top: 4\n                                              bottom: 8\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: label\n                                              text: Hi Meghan. How likely are you\n                                                to recommend Airship to a friend or\n                                                a colleague\n                                              content_description: Hi Meghan. How\n                                                likely are you to recommend Airship\n                                                to a friend or a colleague\n                                              text_appearance:\n                                                font_size: 20\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#3A363F\"\n                                                    alpha: 1\n                                                  selectors:\n                                                  - platform: ios\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#3A363F\"\n                                                      alpha: 1\n                                                  - platform: ios\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#3A363F\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#3A363F\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#3A363F\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#3A363F\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#3A363F\"\n                                                      alpha: 1\n                                                alignment: center\n                                                styles:\n                                                - bold\n                                                font_families:\n                                                - sans-serif\n                                      - size:\n                                          width: 100%\n                                          height: auto\n                                        margin:\n                                          top: 0\n                                          bottom: 0\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - size:\n                                                  width: 50%\n                                                  height: auto\n                                                margin:\n                                                  end: 4\n                                                  bottom: 4\n                                                view:\n                                                  type: label\n                                                  text: \" Not at all likely\"\n                                                  content_description: \" Not at all\n                                                    likely\"\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#F8F9FA\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                              - size:\n                                                  width: 50%\n                                                  height: auto\n                                                margin:\n                                                  start: 4\n                                                  bottom: 4\n                                                view:\n                                                  type: label\n                                                  text: \" Very very likely \"\n                                                  content_description: \" Very very\n                                                    likely \"\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#F8F9FA\"\n                                                        alpha: 1\n                                                      selectors:\n                                                      - platform: ios\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: false\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                      - platform: web\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#F8F9FA\"\n                                                          alpha: 1\n                                                    alignment: end\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - size:\n                                                  height: 50\n                                                  width: 100%\n                                                view:\n                                                  type: score\n                                                  style:\n                                                    type: number_range\n                                                    start: 0\n                                                    end: 10\n                                                    spacing: 2\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          border:\n                                                            radius: 10\n                                                            stroke_width: 1\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                        text_appearance:\n                                                          alignment: center\n                                                          font_families:\n                                                          - sans-serif\n                                                          font_size: 24\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#3A363F\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          border:\n                                                            radius: 10\n                                                            stroke_width: 1\n                                                            stroke_color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#3A363F\"\n                                                                  alpha: 1\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#3A363F\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#3A363F\"\n                                                                alpha: 1\n                                                        text_appearance:\n                                                          font_size: 24\n                                                          font_families:\n                                                          - sans-serif\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                            selectors:\n                                                            - platform: ios\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: false\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                  identifier: 69eba561-4a43-420b-9aed-30d76f620262\n                                                  required: false\n                                  - identifier: 01577441-b8c8-4644-ae81-c078d47a21c1\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      end: 16\n                                      top: 8\n                                      start: 16\n                                      bottom: 8\n                                    view:\n                                      type: label\n                                      text: From 0 (not likely) to 10 (very likely)\n                                      content_description: From 0 (not likely) to\n                                        10 (very likely)\n                                      text_appearance:\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#3A363F\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#3A363F\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#3A363F\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#3A363F\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#3A363F\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#3A363F\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#3A363F\"\n                                              alpha: 1\n                                        alignment: center\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: 5d1b8a8d-40e4-4181-9435-910f8a742696_linear_layout_item\n                                    size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                  state_actions:\n                  - type: set\n                    key: c89ff0d7-47f5-40d0-bb4a-d390deb2ef28_next\n                - identifier: 29acc7f2-58de-4638-b374-a4730927658c\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - identifier: aa471d5d-6603-42db-b9d6-8ea176ae924a_main_view_container_item\n                      size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - identifier: 3808eeea-0318-4eba-8592-3f20e98ed4db_container_item\n                          margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: 4a9944b2-b2fe-443f-b70d-5b8f01d60c4e\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 30\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: First name\n                                      content_description: First name\n                                      text_appearance:\n                                        font_size: 22\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                      accessibility_role:\n                                        type: heading\n                                        level: 1\n                                      labels:\n                                        type: labels\n                                        view_type: text_input\n                                        view_id: 2ebac42b-c8d4-4da7-b39e-e77252483553\n                                  - identifier: 2ebac42b-c8d4-4da7-b39e-e77252483553\n                                    size:\n                                      width: 100%\n                                      height: 50\n                                    margin:\n                                      top: 16\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 4\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: 50\n                                        view:\n                                          border:\n                                            radius: 4\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                          type: text_input\n                                          text_appearance:\n                                            alignment: start\n                                            font_size: 22\n                                            font_families:\n                                            - sans-serif\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            place_holder_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                          background_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          identifier: 2ebac42b-c8d4-4da7-b39e-e77252483553\n                                          input_type: text\n                                          required: false\n                                          content_description: ''\n                                          place_holder: Enter first name here\n                                          attribute_name: {}\n                                          view_overrides:\n                                            icon_end:\n                                            - value:\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: exclamationmark_circle_fill\n                                                  color:\n                                                    default:\n                                                      hex: \"#ff0000\"\n                                                      alpha: 0.5\n                                                      type: hex\n                                                  scale: 1\n                                              when_state_matches:\n                                                scope:\n                                                - 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                                value:\n                                                  equals: false\n                                            - value:\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: checkmark\n                                                  color:\n                                                    default:\n                                                      hex: \"#00ff00\"\n                                                      alpha: 0.5\n                                                      type: hex\n                                                  scale: 1\n                                              when_state_matches:\n                                                scope:\n                                                - 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                                value:\n                                                  equals: true\n                                            border:\n                                            - value:\n                                                radius: 4\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    hex: \"#ff0000\"\n                                                    alpha: 0.5\n                                                    type: hex\n                                              when_state_matches:\n                                                scope:\n                                                - 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                                value:\n                                                  equals: false\n                                            - value:\n                                                radius: 4\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    hex: \"#cccccc\"\n                                                    alpha: 1\n                                                    type: hex\n                                              when_state_matches:\n                                                scope:\n                                                - 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                                value:\n                                                  equals: true\n                                          on_error:\n                                            state_actions:\n                                            - type: set\n                                              key: 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                              value: false\n                                          on_edit:\n                                            state_actions:\n                                            - type: set\n                                              key: 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                          on_valid:\n                                            state_actions:\n                                            - type: set\n                                              key: 2ebac42b-c8d4-4da7-b39e-e77252483553_short_text_is_valid\n                                              value: true\n                                  - identifier: e538ee92-2358-4774-96db-4b755e3dfcfb\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Email address\n                                      content_description: Email address\n                                      text_appearance:\n                                        font_size: 22\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                      accessibility_role:\n                                        type: heading\n                                        level: 1\n                                      labels:\n                                        type: labels\n                                        view_type: text_input\n                                        view_id: 4cd9d9e5-f057-4002-8950-7c87e92cc84c\n                                  - identifier: 4cd9d9e5-f057-4002-8950-7c87e92cc84c\n                                    size:\n                                      width: 100%\n                                      height: 50\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 4\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: 50\n                                        view:\n                                          border:\n                                            radius: 4\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                          type: text_input\n                                          text_appearance:\n                                            alignment: start\n                                            font_size: 22\n                                            font_families:\n                                            - sans-serif\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                            place_holder_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                              selectors:\n                                              - platform: ios\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: ios\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: android\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: false\n                                                color:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              - platform: web\n                                                dark_mode: true\n                                                color:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                          background_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          identifier: 4cd9d9e5-f057-4002-8950-7c87e92cc84c\n                                          input_type: email\n                                          required: false\n                                          content_description: ''\n                                          place_holder: Enter email address here\n                                          view_overrides:\n                                            icon_end:\n                                            - value:\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: exclamationmark_circle_fill\n                                                  color:\n                                                    default:\n                                                      hex: \"#ff0000\"\n                                                      alpha: 0.5\n                                                      type: hex\n                                                  scale: 1\n                                              when_state_matches:\n                                                scope:\n                                                - 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                                value:\n                                                  equals: false\n                                            - value:\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: checkmark\n                                                  color:\n                                                    default:\n                                                      hex: \"#00ff00\"\n                                                      alpha: 0.5\n                                                      type: hex\n                                                  scale: 1\n                                              when_state_matches:\n                                                scope:\n                                                - 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                                value:\n                                                  equals: true\n                                            border:\n                                            - value:\n                                                radius: 4\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    hex: \"#ff0000\"\n                                                    alpha: 0.5\n                                                    type: hex\n                                              when_state_matches:\n                                                scope:\n                                                - 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                                value:\n                                                  equals: false\n                                            - value:\n                                                radius: 4\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    hex: \"#cccccc\"\n                                                    alpha: 1\n                                                    type: hex\n                                              when_state_matches:\n                                                scope:\n                                                - 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                                value:\n                                                  equals: true\n                                          on_error:\n                                            state_actions:\n                                            - type: set\n                                              key: 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                              value: false\n                                          on_edit:\n                                            state_actions:\n                                            - type: set\n                                              key: 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                          on_valid:\n                                            state_actions:\n                                            - type: set\n                                              key: 4cd9d9e5-f057-4002-8950-7c87e92cc84c_email_is_valid\n                                              value: true\n                                  - identifier: 443d89b9-d4be-4230-846d-0f93e732da62_linear_layout_item\n                                    size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                  state_actions:\n                  - type: set\n                    key: 29acc7f2-58de-4638-b374-a4730927658c_next\n                - identifier: 806cc2a1-ea0f-4b22-b733-ea4382e3e439\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - identifier: 1987673b-4e8b-47ae-8f83-f412ca8c0d7e_main_view_container_item\n                      size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - identifier: b890fbf5-6e70-4270-b71e-c05a7308ea59_container_item\n                          margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: 7898f5c2-97c8-454f-a10a-3ebedee6736f\n                                    margin:\n                                      top: 48\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    view:\n                                      type: label_button\n                                      identifier: submit_feedback--Submit\n                                      reporting_metadata:\n                                        trigger_link_id: 7898f5c2-97c8-454f-a10a-3ebedee6736f\n                                      label:\n                                        type: label\n                                        text: Submit\n                                        content_description: Submit\n                                        text_appearance:\n                                          font_size: 16\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          alignment: center\n                                          styles:\n                                          - bold\n                                          font_families:\n                                          - sans-serif\n                                        view_overrides:\n                                          icon_start:\n                                          - value:\n                                              type: floating\n                                              space: 8\n                                              icon:\n                                                type: icon\n                                                icon: progress_spinner\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                  selectors:\n                                                  - platform: ios\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#FFFFFF\"\n                                                      alpha: 1\n                                                  - platform: ios\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#FFFFFF\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#FFFFFF\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                scale: 1\n                                            when_state_matches:\n                                              scope:\n                                              - \"$forms\"\n                                              - current\n                                              - status\n                                              - type\n                                              value:\n                                                equals: validating\n                                          text:\n                                          - value: Submit\n                                            when_state_matches:\n                                              scope:\n                                              - \"$forms\"\n                                              - current\n                                              - status\n                                              - type\n                                              value:\n                                                equals: validating\n                                      actions: {}\n                                      enabled:\n                                      - form_validation\n                                      button_click:\n                                      - form_submit\n                                      - dismiss\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#F0282D\"\n                                          alpha: 1\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#F0282D\"\n                                            alpha: 1\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFC7C9\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#F0282D\"\n                                            alpha: 1\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFC7C9\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: false\n                                          color:\n                                            type: hex\n                                            hex: \"#F0282D\"\n                                            alpha: 1\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#FFC7C9\"\n                                            alpha: 1\n                                      border:\n                                        radius: 4\n                                        stroke_width: 2\n                                        stroke_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                          selectors:\n                                          - platform: ios\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: ios\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: android\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: false\n                                            color:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                          - platform: web\n                                            dark_mode: true\n                                            color:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                      event_handlers:\n                                      - type: tap\n                                        state_actions:\n                                        - type: set\n                                          key: submitted\n                                          value: true\n                                      content_description: Submit\n                                  - identifier: 8efe9be2-32f3-449c-9868-9b48b44c9c2a_linear_layout_item\n                                    size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                    background_color:\n                      default:\n                        type: hex\n                        hex: \"#BCBDC2\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#BCBDC2\"\n                          alpha: 1\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 0.5\n                      - platform: android\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#BCBDC2\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 0.5\n                      - platform: web\n                        dark_mode: false\n                        color:\n                          type: hex\n                          hex: \"#BCBDC2\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 0.5\n                  state_actions:\n                  - type: set\n                    key: 806cc2a1-ea0f-4b22-b733-ea4382e3e439_next\n              ignore_safe_area: false\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                    selectors:\n                    - platform: ios\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: ios\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: android\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: false\n                      color:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    - platform: web\n                      dark_mode: true\n                      color:\n                        type: hex\n                        hex: \"#FFFFFF\"\n                        alpha: 1\n                identifier: dismiss_button\n                button_click:\n                - dismiss\n                localized_content_description:\n                  ref: ua_dismiss\n                  fallback: Dismiss\n            - margin:\n                top: 4\n                bottom: 4\n                end: 0\n                start: 0\n              position:\n                horizontal: center\n                vertical: bottom\n              size:\n                height: 7\n                width: 100%\n              view:\n                type: pager_indicator\n                spacing: 6\n                bindings:\n                  selected:\n                    shapes:\n                    - type: rectangle\n                      border:\n                        radius: 16\n                      scale: 1\n                      aspect_ratio: 2\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#F0282D\"\n                          alpha: 1\n                        selectors:\n                        - platform: ios\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#F0282D\"\n                            alpha: 1\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFC7C9\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#F0282D\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFC7C9\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#F0282D\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#FFC7C9\"\n                            alpha: 1\n                  unselected:\n                    shapes:\n                    - type: ellipse\n                      aspect_ratio: 1\n                      scale: 0.5\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#9966CC\"\n                          alpha: 0.88\n                        selectors:\n                        - platform: ios\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#9966CC\"\n                            alpha: 0.88\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#9966CC\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#9966CC\"\n                            alpha: 0.88\n                        - platform: android\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#9966CC\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: false\n                          color:\n                            type: hex\n                            hex: \"#9966CC\"\n                            alpha: 0.88\n                        - platform: web\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#9966CC\"\n                            alpha: 1\n                automated_accessibility_actions:\n                - type: announce\n            - position:\n                horizontal: center\n                vertical: bottom\n              size:\n                width: auto\n                height: auto\n              margin:\n                top: 20\n                start: 20\n                bottom: 20\n                end: 20\n              view:\n                visibility:\n                  invert_when_state_matches:\n                    scope:\n                    - ASYNC_VALIDATION_TOAST\n                    value:\n                      equals: true\n                  default: false\n                type: container\n                background_color:\n                  default:\n                    type: hex\n                    hex: \"#3d4047\"\n                    alpha: 0.8\n                border:\n                  radius: 20\n                  stroke_width: 1\n                  stroke_color:\n                    default:\n                      type: hex\n                      hex: \"#3d4047\"\n                      alpha: 0.8\n                items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: auto\n                    height: auto\n                  margin:\n                    top: 10\n                    start: 20\n                    bottom: 10\n                    end: 20\n                  view:\n                    type: label\n                    text: Error processing form. Please try again\n                    content_description: Error processing form. Please try again\n                    text_appearance:\n                      font_size: 16\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      alignment: center\n                      font_families:\n                      - sans-serif\n          validation_mode:\n            type: on_demand\n          state_triggers:\n          - identifier: SHOW_TOAST\n            trigger_when_state_matches:\n              scope:\n              - \"$forms\"\n              - current\n              - status\n              - type\n              value:\n                equals: error\n            reset_when_state_matches:\n              not:\n                scope:\n                - \"$forms\"\n                - current\n                - status\n                - type\n                value:\n                  equals: error\n            on_trigger:\n              state_actions:\n              - type: set\n                key: ASYNC_VALIDATION_TOAST\n                ttl_seconds: 1.5\n                value: true\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/image-pager-test.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.4\n  dismiss_on_touch_outside: false\nview:\n  type: pager_controller\n  identifier: image-pager-controller\n  view:\n    type: container\n    background_color:\n      default:\n        type: hex\n        hex: '#1A1A2E'\n        alpha: 1\n    items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: pager\n          gestures:\n            - type: swipe\n              identifier: swipe-up-dismiss\n              direction: up\n              behavior:\n                behaviors:\n                  - dismiss\n          items:\n            # PAGE 1 - Large landscape image\n            - identifier: page-1\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 100%\n                    ignore_safe_area: true\n                    view:\n                      type: media\n                      identifier: image-1\n                      media_type: image\n                      media_fit: center_crop\n                      url: \"https://picsum.photos/id/10/1200/800\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 80\n                      start: 16\n                      end: 16\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: label\n                            text: \"Page 1 — Landscape Photo\"\n                            text_appearance:\n                              font_size: 22\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              styles:\n                                - bold\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 6\n                          view:\n                            type: label\n                            text: \"Swipe left/right to change pages. Each page loads a different image.\"\n                            text_appearance:\n                              font_size: 14\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 0.8\n\n            # PAGE 2 - Portrait-style image\n            - identifier: page-2\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 100%\n                    ignore_safe_area: true\n                    view:\n                      type: media\n                      identifier: image-2\n                      media_type: image\n                      media_fit: center_crop\n                      url: \"https://picsum.photos/id/29/1200/800\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 80\n                      start: 16\n                      end: 16\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: label\n                            text: \"Page 2 — Nature Scene\"\n                            text_appearance:\n                              font_size: 22\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              styles:\n                                - bold\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 6\n                          view:\n                            type: label\n                            text: \"This image loads independently from the others.\"\n                            text_appearance:\n                              font_size: 14\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 0.8\n\n            # PAGE 3 - Another distinct image\n            - identifier: page-3\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 100%\n                    ignore_safe_area: true\n                    view:\n                      type: media\n                      identifier: image-3\n                      media_type: image\n                      media_fit: center_crop\n                      url: \"https://picsum.photos/id/47/1200/800\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 80\n                      start: 16\n                      end: 16\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: label\n                            text: \"Page 3 — Architecture\"\n                            text_appearance:\n                              font_size: 22\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              styles:\n                                - bold\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 6\n                          view:\n                            type: label\n                            text: \"Swiping back should show the cached image without re-fetching.\"\n                            text_appearance:\n                              font_size: 14\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 0.8\n\n            # PAGE 4 - GIF to test animated image loading\n            - identifier: page-4\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 100%\n                    ignore_safe_area: true\n                    view:\n                      type: media\n                      identifier: image-4\n                      media_type: image\n                      media_fit: center_inside\n                      url: \"https://media.giphy.com/media/v1.Y2lkPTc5MGI3NjExcDVwOWprNHRiYWxjdnQ3MjBtamc0YnI2cTNraTRlaGh6aGl4NXQ1aiZlcD12MV9pbnRlcm5hbF9naWZfYnlfaWQmY3Q9Zw/3o7aD2saalBwwftBIY/giphy.gif\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 80\n                      start: 16\n                      end: 16\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: label\n                            text: \"Page 4 — Animated GIF\"\n                            text_appearance:\n                              font_size: 22\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              styles:\n                                - bold\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 6\n                          view:\n                            type: label\n                            text: \"Tests animated image loading and frame playback.\"\n                            text_appearance:\n                              font_size: 14\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 0.8\n\n            # PAGE 5 - Large high-res image\n            - identifier: page-5\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 100%\n                    ignore_safe_area: true\n                    view:\n                      type: media\n                      identifier: image-5\n                      media_type: image\n                      media_fit: center_crop\n                      url: \"https://picsum.photos/id/65/2000/1400\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 80\n                      start: 16\n                      end: 16\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: label\n                            text: \"Page 5 — High Resolution\"\n                            text_appearance:\n                              font_size: 22\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              styles:\n                                - bold\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 6\n                          view:\n                            type: label\n                            text: \"2000x1400 image to test loading larger assets.\"\n                            text_appearance:\n                              font_size: 14\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 0.8\n\n      # Dismiss button\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 48\n          height: 48\n        margin:\n          top: 8\n          end: 8\n        view:\n          type: image_button\n          identifier: dismiss_button\n          button_click:\n            - dismiss\n          localized_content_description:\n            refs:\n              - ua_dismiss\n            fallback: Dismiss\n          image:\n            type: icon\n            icon: close\n            scale: 0.4\n            color:\n              default:\n                type: hex\n                hex: '#FFFFFF'\n                alpha: 1\n\n      # Pager indicator\n      - position:\n          horizontal: center\n          vertical: bottom\n        margin:\n          bottom: 24\n        size:\n          width: auto\n          height: 8\n        view:\n          type: pager_indicator\n          spacing: 8\n          bindings:\n            selected:\n              shapes:\n                - type: ellipse\n                  scale: 1\n                  color:\n                    default:\n                      type: hex\n                      hex: '#FFFFFF'\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: ellipse\n                  scale: 1\n                  color:\n                    default:\n                      type: hex\n                      hex: '#FFFFFF'\n                      alpha: 0.4\ndisplay_type: layout\nname: Image Pager Test\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/image_cropping.yml",
    "content": "---\npresentation:\n  dismiss_on_touch_outside: true\n  default_placement:\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.5\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n  type: modal\nversion: 1\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n      - position:\n          vertical: center\n          horizontal: center\n        size:\n          height: 100%\n          width: 100%\n        border:\n          radius: 25\n        margin:\n          top: 36\n        view:\n          type: pager\n          items:\n            - identifier: \"page-1\"\n              view:\n                type: container\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: scroll_layout\n                      direction: vertical\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Wide Image (100% x auto)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 24\n                              end: 24\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              media_fit: center_inside\n                              media_type: image\n                              type: media\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Adelie_penguins_in_the_South_Shetland_Islands.jpg/1024px-Adelie_penguins_in_the_South_Shetland_Islands.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Center (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: image\n                              type: media\n                              position:\n                                horizontal: center\n                                vertical: center\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Adelie_penguins_in_the_South_Shetland_Islands.jpg/1024px-Adelie_penguins_in_the_South_Shetland_Islands.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Top Start (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: image\n                              type: media\n                              position:\n                                horizontal: start\n                                vertical: top\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Adelie_penguins_in_the_South_Shetland_Islands.jpg/1024px-Adelie_penguins_in_the_South_Shetland_Islands.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Bottom End (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: image\n                              type: media\n                              position:\n                                horizontal: end\n                                vertical: bottom\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0e/Adelie_penguins_in_the_South_Shetland_Islands.jpg/1024px-Adelie_penguins_in_the_South_Shetland_Islands.jpg\n\n            - identifier: \"page-2\"\n              view:\n                type: container\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: scroll_layout\n                      direction: vertical\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Tall Image (100% x auto)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 48\n                              end: 48\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              media_fit: center_inside\n                              media_type: image\n                              type: media\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Center (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: image\n                              type: media\n                              position:\n                                horizontal: center\n                                vertical: center\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Top Start (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: image\n                              type: media\n                              position:\n                                horizontal: start\n                                vertical: top\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Bottom End (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: image\n                              type: media\n                              position:\n                                horizontal: end\n                                vertical: bottom\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n      - size:\n          height: 16\n          width: auto\n        position:\n          vertical: top\n          horizontal: center\n        margin:\n          top: 12\n        view:\n          type: pager_indicator\n          carousel_identifier: CAROUSEL_ID\n          border:\n            radius: 8\n          spacing: 4\n          bindings:\n            selected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  scale: 0.75\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  scale: 0.75\n                  border:\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#333333\"\n                        alpha: 1\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n      - position:\n          vertical: top\n          horizontal: end\n        size:\n          width: 36\n          height: 36\n        margin:\n          top: 0\n          end: 0\n        view:\n          type: image_button\n          identifier: x_button\n          button_click: [ dismiss ]\n          image:\n            type: icon\n            icon: close\n            scale: 0.5\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/image_resizing.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      max_width: 100%\n      max_height: 100%\n      width: 100%\n      min_width: 100%\n      height: 100%\n      min_height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: 80df39a5-774d-4bb5-9e35-c7a465189583\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n      - size:\n          height: 100%\n          width: 100%\n        view:\n          type: container\n          items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: false\n                items:\n                  - identifier: d07e16f6-f4c4-4acd-ad54-86996e6cf29a\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                      - size:\n                                          height: auto\n                                          width: 100%\n                                        view:\n                                          type: media\n                                          media_fit: fit_crop\n                                          position:\n                                            horizontal: center\n                                            vertical: center\n                                          url: https://hangar-dl.urbanairship.com/binary/public/Hx7SIqHqQDmFj6aruaAFcQ/34be6e8d-31d0-499b-886e-2b29459cb472\n                                          media_type: image\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                  - identifier: d07e16f6-f4c4-4acd-ad54-86996e6cf29b\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                      - size:\n                                          height: auto\n                                          width: 100%\n                                        view:\n                                          type: media\n                                          media_fit: center_inside\n                                          url: https://hangar-dl.urbanairship.com/binary/public/Hx7SIqHqQDmFj6aruaAFcQ/fbfe7dea-db33-4d6f-a1b4-84ae82908ec8\n                                          media_type: image\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                  - identifier: d07e16f6-f4c4-4acd-ad54-86996e6cf29c\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                      - size:\n                                          height: auto\n                                          width: 100%\n                                        view:\n                                          type: media\n                                          media_fit: center_inside\n                                          url: https://hangar-dl.urbanairship.com/binary/public/Hx7SIqHqQDmFj6aruaAFcQ/b9427b7f-d572-400d-b7e2-b8128babddd0\n                                          media_type: image\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#222222\"\n                      alpha: 1\n                identifier: dismiss_button\n                button_click:\n                  - dismiss\n            - margin:\n                top: 0\n                bottom: 8\n                end: 0\n                start: 0\n              position:\n                horizontal: center\n                vertical: bottom\n              size:\n                height: 12\n                width: 100%\n              view:\n                type: pager_indicator\n                spacing: 4\n                bindings:\n                  selected:\n                    shapes:\n                      - type: ellipse\n                        scale: 1\n                        aspect_ratio: 1\n                        color:\n                          default:\n                            type: hex\n                            hex: \"#AAAAAA\"\n                            alpha: 1\n                  unselected:\n                    shapes:\n                      - type: ellipse\n                        aspect_ratio: 1\n                        scale: 1\n                        color:\n                          default:\n                            type: hex\n                            hex: \"#CCCCCC\"\n                            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/image_scaling.yml",
    "content": "---\npresentation:\n  default_placement:\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.5\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n  type: modal\nversion: 1\nview:\n  type: scroll_layout\n  direction: vertical\n  view:\n    type: linear_layout\n    direction: vertical\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n    - size:\n        height: auto\n        width: 100%\n      view:\n        type: container\n        items:\n        - size:\n            height: 24\n            width: 24\n          margin:\n            end: 16\n            top: 16\n          position:\n            horizontal: end\n            vertical: center\n          view:\n            type: image_button\n            identifier: close_button\n            button_click: [ cancel ]\n            image:\n              type: icon\n              icon: close\n              scale: 0.75\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 1\n\n    #\n    # FIXED SIZES\n    #\n\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"Fixed (250 x 150)\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"center_crop\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n        bottom: 8\n        start: 8\n        end: 8\n      size:\n        width: 250\n        height: 150\n      view:\n        border:\n          stroke_width: 1\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n        media_fit: center_crop\n        media_type: image\n        type: media\n        url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Great_Wave_off_Kanagawa2.jpg/2560px-Great_Wave_off_Kanagawa2.jpg\n\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"center_inside\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n        bottom: 8\n        start: 8\n        end: 8\n      size:\n        width: 250\n        height: 150\n      view:\n        media_fit: center_inside\n        border:\n          stroke_width: 1\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n        media_type: image\n        type: media\n        url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Great_Wave_off_Kanagawa2.jpg/2560px-Great_Wave_off_Kanagawa2.jpg\n\n        #\n        # FIXED WIDTH x AUTO HEIGHT\n        #\n\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"Auto Height (150 x auto)\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"center_crop\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n        bottom: 8\n        start: 8\n        end: 8\n      size:\n        width: 150\n        height: auto\n      view:\n        border:\n          stroke_width: 1\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n        media_fit: center_crop\n        media_type: image\n        type: media\n        url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Great_Wave_off_Kanagawa2.jpg/2560px-Great_Wave_off_Kanagawa2.jpg\n\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"center_inside\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n        bottom: 8\n        start: 8\n        end: 8\n      size:\n        width: 150\n        height: auto\n      view:\n        media_fit: center_inside\n        border:\n          stroke_width: 1\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n        media_type: image\n        type: media\n        url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Great_Wave_off_Kanagawa2.jpg/2560px-Great_Wave_off_Kanagawa2.jpg\n\n        #\n        # AUTO WIDTH x FIXED HEIGHT\n        #\n\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"Auto Width (auto x 150)\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"center_crop\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n        bottom: 8\n        start: 8\n        end: 8\n      size:\n        width: auto\n        height: 150\n      view:\n        border:\n          stroke_width: 1\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n        media_fit: center_crop\n        media_type: image\n        type: media\n        url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Great_Wave_off_Kanagawa2.jpg/2560px-Great_Wave_off_Kanagawa2.jpg\n\n    - margin:\n        top: 8\n      size:\n        width: auto\n        height: auto\n      view:\n        type: label\n        text: \"center_inside\"\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n    - margin:\n        top: 8\n        bottom: 8\n        start: 8\n        end: 8\n      size:\n        width: auto\n        height: 150\n      view:\n        media_fit: center_inside\n        border:\n          stroke_width: 1\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n        media_type: image\n        type: media\n        url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/0d/Great_Wave_off_Kanagawa2.jpg/2560px-Great_Wave_off_Kanagawa2.jpg"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/is-accessibility-alert-form.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    web:\n      ignore_shade: true\nview:\n  type: state_controller\n  view:\n    type: pager_controller\n    identifier: 6b72219d-15ef-4bcf-832b-70850b419687\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n        - size:\n            width: 100%\n            height: 100%\n          view:\n            identifier: 4a560c36-28fa-452d-a2e0-f07a9e3d7521\n            nps_identifier: b395410d-af24-4d9a-801e-c99e5833abf3\n            type: nps_form_controller\n            validation_mode:\n              type: on_demand\n            submit: submit_event\n            form_enabled:\n              - form_submission\n            state_triggers:\n              - identifier: toast_trigger\n                trigger_when_state_matches:\n                  scope:\n                    - $forms\n                    - current\n                    - status\n                    - type\n                  value:\n                    equals: \"invalid\"\n                reset_when_state_matches:\n                  not:\n                    scope:\n                      - $forms\n                      - current\n                      - status\n                      - type\n                    value:\n                      equals: \"invalid\"\n                on_trigger:\n                  state_actions:\n                    - type: set\n                      key: show_toast\n                      ttl_seconds: 3.0\n                      value: true\n            response_type: nps\n            view:\n              type: container\n              items:\n                - position:\n                    horizontal: center\n                    vertical: center\n                  size:\n                    width: 100%\n                    height: 100%\n                  view:\n                    type: pager\n                    disable_swipe: true\n                    items:\n                      - identifier: d2432b09-48d6-40a9-8753-72e372319121\n                        type: pager_item\n                        view:\n                          type: container\n                          items:\n                            - size:\n                                width: 100%\n                                height: 100%\n                              position:\n                                horizontal: center\n                                vertical: center\n                              ignore_safe_area: false\n                              view:\n                                type: container\n                                items:\n                                  - margin:\n                                      bottom: 0\n                                      top: 0\n                                      end: 0\n                                      start: 0\n                                    position:\n                                      horizontal: center\n                                      vertical: center\n                                    size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                        - identifier: scroll_container\n                                          size:\n                                            width: 100%\n                                            height: 100%\n                                          view:\n                                            type: scroll_layout\n                                            direction: vertical\n                                            view:\n                                              type: linear_layout\n                                              direction: vertical\n                                              items:\n                                                - identifier: 8ce7c2d5-23f6-4eda-b840-c76ba483fb68\n                                                  size:\n                                                    width: 100%\n                                                    height: auto\n                                                  margin:\n                                                    top: 48\n                                                    bottom: 8\n                                                    start: 16\n                                                    end: 16\n                                                  view:\n                                                    type: label\n                                                    text: Single Choice\n                                                    content_description: Single Choice\n                                                    text_appearance:\n                                                      font_size: 20\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      alignment: start\n                                                      styles:\n                                                        - bold\n                                                      font_families:\n                                                        - sans-serif\n                                                - identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                  size:\n                                                    width: 100%\n                                                    height: auto\n                                                  margin:\n                                                    top: 8\n                                                    bottom: 0\n                                                    start: 16\n                                                    end: 16\n                                                  view:\n                                                    type: linear_layout\n                                                    direction: vertical\n                                                    items:\n                                                      - margin:\n                                                          top: 0\n                                                          bottom: 8\n                                                        size:\n                                                          width: 100%\n                                                          height: auto\n                                                        view:\n                                                          type: label\n                                                          text: \"Favorite Animal\"\n                                                          content_description: \"Favorite Animal\"\n                                                          text_appearance:\n                                                            font_size: 20\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                            alignment: start\n                                                            styles: []\n                                                            font_families:\n                                                              - sans-serif\n                                                      - margin:\n                                                          top: 0\n                                                          bottom: 8\n                                                        size:\n                                                          width: 100%\n                                                          height: auto\n                                                        view:\n                                                          type: radio_input_controller\n                                                          identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                          required: true\n                                                          on_error:\n                                                            state_actions:\n                                                              - type: set\n                                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                                value: \"error\"\n                                                          on_valid:\n                                                            state_actions:\n                                                              - type: set\n                                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                                value: \"valid\"\n                                                          on_edit:\n                                                            state_actions:\n                                                              - type: set\n                                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                                value: \"editing\"\n                                                          view:\n                                                            type: linear_layout\n                                                            direction: vertical\n                                                            items:\n                                                              - size:\n                                                                  width: 100%\n                                                                  height: 100%\n                                                                margin:\n                                                                  top: 0\n                                                                  bottom: 8\n                                                                view:\n                                                                  type: linear_layout\n                                                                  direction: horizontal\n                                                                  items:\n                                                                    - margin:\n                                                                        end: 8\n                                                                      size:\n                                                                        width: 20\n                                                                        height: 20\n                                                                      view:\n                                                                        type: radio_input\n                                                                        reporting_value: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7\n                                                                        content_description: Cat\n                                                                        style:\n                                                                          type: checkbox\n                                                                          bindings:\n                                                                            selected:\n                                                                              shapes:\n                                                                                - type: ellipse\n                                                                                  scale: 1\n                                                                                  aspect_ratio: 1\n                                                                                  border:\n                                                                                    radius: 20\n                                                                                    stroke_width: 2\n                                                                                    stroke_color:\n                                                                                      default:\n                                                                                        type: hex\n                                                                                        hex: \"#000000\"\n                                                                                        alpha: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#FFFFFF\"\n                                                                                      alpha: 1\n                                                                                - type: ellipse\n                                                                                  scale: 0.6\n                                                                                  aspect_ratio: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#000000\"\n                                                                                      alpha: 1\n                                                                            unselected:\n                                                                              shapes:\n                                                                                - type: ellipse\n                                                                                  scale: 1\n                                                                                  aspect_ratio: 1\n                                                                                  border:\n                                                                                    radius: 20\n                                                                                    stroke_width: 2\n                                                                                    stroke_color:\n                                                                                      default:\n                                                                                        type: hex\n                                                                                        hex: \"#000000\"\n                                                                                        alpha: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#FFFFFF\"\n                                                                                      alpha: 1\n                                                                    - margin:\n                                                                        end: 16\n                                                                      size:\n                                                                        height: auto\n                                                                        width: 100%\n                                                                      view:\n                                                                        type: label\n                                                                        text: Cat\n                                                                        content_description: Cat\n                                                                        text_appearance:\n                                                                          font_size: 16\n                                                                          color:\n                                                                            default:\n                                                                              type: hex\n                                                                              hex: \"#000000\"\n                                                                              alpha: 1\n                                                                          alignment: start\n                                                                          styles: []\n                                                                          font_families:\n                                                                            - sans-serif\n                                                              - size:\n                                                                  width: 100%\n                                                                  height: 100%\n                                                                margin:\n                                                                  top: 0\n                                                                  bottom: 8\n                                                                view:\n                                                                  type: linear_layout\n                                                                  direction: horizontal\n                                                                  items:\n                                                                    - margin:\n                                                                        end: 8\n                                                                      size:\n                                                                        width: 20\n                                                                        height: 20\n                                                                      view:\n                                                                        type: radio_input\n                                                                        reporting_value: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d\n                                                                        content_description: Dog\n                                                                        style:\n                                                                          type: checkbox\n                                                                          bindings:\n                                                                            selected:\n                                                                              shapes:\n                                                                                - type: ellipse\n                                                                                  scale: 1\n                                                                                  aspect_ratio: 1\n                                                                                  border:\n                                                                                    radius: 20\n                                                                                    stroke_width: 2\n                                                                                    stroke_color:\n                                                                                      default:\n                                                                                        type: hex\n                                                                                        hex: \"#000000\"\n                                                                                        alpha: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#FFFFFF\"\n                                                                                      alpha: 1\n                                                                                - type: ellipse\n                                                                                  scale: 0.6\n                                                                                  aspect_ratio: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#000000\"\n                                                                                      alpha: 1\n                                                                            unselected:\n                                                                              shapes:\n                                                                                - type: ellipse\n                                                                                  scale: 1\n                                                                                  aspect_ratio: 1\n                                                                                  border:\n                                                                                    radius: 20\n                                                                                    stroke_width: 2\n                                                                                    stroke_color:\n                                                                                      default:\n                                                                                        type: hex\n                                                                                        hex: \"#000000\"\n                                                                                        alpha: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#FFFFFF\"\n                                                                                      alpha: 1\n                                                                    - margin:\n                                                                        end: 16\n                                                                      size:\n                                                                        height: auto\n                                                                        width: 100%\n                                                                      view:\n                                                                        type: label\n                                                                        text: Dog\n                                                                        content_description: Dog\n                                                                        text_appearance:\n                                                                          font_size: 16\n                                                                          color:\n                                                                            default:\n                                                                              type: hex\n                                                                              hex: \"#000000\"\n                                                                              alpha: 1\n                                                                          alignment: start\n                                                                          styles: []\n                                                                          font_families:\n                                                                            - sans-serif\n                                                              - size:\n                                                                  width: 100%\n                                                                  height: 100%\n                                                                margin:\n                                                                  top: 0\n                                                                  bottom: 8\n                                                                view:\n                                                                  type: linear_layout\n                                                                  direction: horizontal\n                                                                  items:\n                                                                    - margin:\n                                                                        end: 8\n                                                                      size:\n                                                                        width: 20\n                                                                        height: 20\n                                                                      view:\n                                                                        type: radio_input\n                                                                        reporting_value: 8df9abde-f0fc-4b37-afe0-70ee8aff0c00\n                                                                        content_description: Dragon\n                                                                        style:\n                                                                          type: checkbox\n                                                                          bindings:\n                                                                            selected:\n                                                                              shapes:\n                                                                                - type: ellipse\n                                                                                  scale: 1\n                                                                                  aspect_ratio: 1\n                                                                                  border:\n                                                                                    radius: 20\n                                                                                    stroke_width: 2\n                                                                                    stroke_color:\n                                                                                      default:\n                                                                                        type: hex\n                                                                                        hex: \"#000000\"\n                                                                                        alpha: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#FFFFFF\"\n                                                                                      alpha: 1\n                                                                                - type: ellipse\n                                                                                  scale: 0.6\n                                                                                  aspect_ratio: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#000000\"\n                                                                                      alpha: 1\n                                                                            unselected:\n                                                                              shapes:\n                                                                                - type: ellipse\n                                                                                  scale: 1\n                                                                                  aspect_ratio: 1\n                                                                                  border:\n                                                                                    radius: 20\n                                                                                    stroke_width: 2\n                                                                                    stroke_color:\n                                                                                      default:\n                                                                                        type: hex\n                                                                                        hex: \"#000000\"\n                                                                                        alpha: 1\n                                                                                  color:\n                                                                                    default:\n                                                                                      type: hex\n                                                                                      hex: \"#FFFFFF\"\n                                                                                      alpha: 1\n                                                                    - margin:\n                                                                        end: 16\n                                                                      size:\n                                                                        height: auto\n                                                                        width: 100%\n                                                                      view:\n                                                                        type: label\n                                                                        text: Dragon\n                                                                        content_description: Dragon\n                                                                        text_appearance:\n                                                                          font_size: 16\n                                                                          color:\n                                                                            default:\n                                                                              type: hex\n                                                                              hex: \"#000000\"\n                                                                              alpha: 1\n                                                                          alignment: start\n                                                                          styles: []\n                                                                          font_families:\n                                                                            - sans-serif\n                                                            randomize_children: true\n                                                          attribute_name: {}\n                                                - identifier: \"some-required-label\"\n                                                  size:\n                                                    width: auto\n                                                    height: auto\n                                                  margin:\n                                                    top: 0\n                                                    bottom: 8\n                                                    start: 16\n                                                    end: 16\n                                                  position:\n                                                    vertical: center\n                                                    horizontal: start\n                                                  view:\n                                                    type: label\n                                                    refs:\n                                                      - ua_required_field\n                                                    text: \"* Required\"\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                        - sans-serif\n                                                    view_overrides:\n                                                      icon_end:\n                                                        - value:\n                                                            space: 8\n                                                            type: floating\n                                                            icon:\n                                                              type: icon\n                                                              icon: exclamationmark_circle_fill\n                                                              color:\n                                                                default:\n                                                                  hex: \"#ff0000\"\n                                                                  alpha: 1.0\n                                                              scale: 1\n                                                          when_state_matches:\n                                                            key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                            value:\n                                                              equals: \"error\"\n                                                      text_appearance:\n                                                        - when_state_matches:\n                                                            scope:\n                                                              - edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                            value:\n                                                              equals: \"error\"\n                                                          value:\n                                                            font_size: 16\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#ff0000\"\n                                                                alpha: 1\n                                                            alignment: start\n                                                            styles: []\n                                                            font_families:\n                                                              - sans-serif\n                                                - identifier: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                                  margin:\n                                                    top: 8\n                                                    bottom: 8\n                                                    start: 16\n                                                    end: 16\n                                                  size:\n                                                    width: 100%\n                                                    height: auto\n                                                  view:\n                                                    type: label_button\n                                                    identifier: submit_feedback--Submit\n                                                    reporting_metadata:\n                                                      trigger_link_id: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                                    label:\n                                                      view_overrides:\n                                                        text:\n                                                          - value: \"Processing ...\"\n                                                            when_state_matches:\n                                                              scope:\n                                                                - $forms\n                                                                - current\n                                                                - status\n                                                                - type\n                                                              value:\n                                                                equals: \"validating\"\n                                                        icon_start:\n                                                          - value:\n                                                            type: \"floating\"\n                                                            space: 8\n                                                            icon:\n                                                              type: icon\n                                                              icon: progress_spinner\n                                                              color:\n                                                                default:\n                                                                  hex: \"#00ff00\"\n                                                                  alpha: 1.0\n                                                              scale: 1\n                                                      type: label\n                                                      text: Submit\n                                                      content_description: Submit\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                                    actions: {}\n                                                    enabled:\n                                                      - form_validation\n                                                    button_click:\n                                                      - form_submit\n                                                      - dismiss\n                                                    background_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#63AFF1\"\n                                                        alpha: 1\n                                                    border:\n                                                      radius: 0\n                                                      stroke_width: 16\n                                                      stroke_color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#63AFF1\"\n                                                          alpha: 1\n                                                    event_handlers:\n                                                      - type: tap\n                                                        state_actions:\n                                                          - type: set\n                                                            key: submitted\n                                                            value: true\n                                                - size:\n                                                    width: 100%\n                                                    height: 100%\n                                                  view:\n                                                    type: linear_layout\n                                                    direction: horizontal\n                                                    items: []\n                                visibility:\n                                  default: true\n                                  invert_when_state_matches:\n                                    key: submitted\n                                    value:\n                                      equals: true\n                            - ignore_safe_area: true\n                              size:\n                                width: 100%\n                                height: 100%\n                              position:\n                                horizontal: center\n                                vertical: center\n                              view:\n                                type: container\n                                items:\n                                  - margin:\n                                      start: 0\n                                      end: 0\n                                    position:\n                                      horizontal: center\n                                      vertical: center\n                                    size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: horizontal\n                                      items: []\n                                    ignore_safe_area: true\n                                  - margin:\n                                      bottom: 0\n                                      top: 0\n                                      end: 0\n                                      start: 0\n                                    position:\n                                      horizontal: center\n                                      vertical: center\n                                    size:\n                                      width: 100%\n                                      height: 100%\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                        - identifier: layout_container\n                                          size:\n                                            width: 100%\n                                            height: 100%\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                              - identifier: 9c9b4565-c8e3-41bf-a211-90af3665b38b\n                                                size:\n                                                  width: 100%\n                                                  height: auto\n                                                margin:\n                                                  top: 48\n                                                  bottom: 8\n                                                  start: 16\n                                                  end: 16\n                                                view:\n                                                  type: label\n                                                  text: Nice\n                                                  content_description: Nice\n                                                  text_appearance:\n                                                    font_size: 20\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                      - sans-serif\n                                    ignore_safe_area: false\n                                background_color:\n                                  default:\n                                    type: hex\n                                    hex: \"#FFFFFF\"\n                                    alpha: 1\n                                visibility:\n                                  default: false\n                                  invert_when_state_matches:\n                                    key: submitted\n                                    value:\n                                      equals: true\n                          background_color:\n                            default:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          visibility:\n                            default: true\n                            invert_when_state_matches:\n                              key: confirmation_screen_container\n                              value:\n                                equals: true\n                  ignore_safe_area: false\n                - position:\n                    horizontal: end\n                    vertical: top\n                  size:\n                    width: 48\n                    height: 48\n                  view:\n                    type: image_button\n                    image:\n                      scale: 0.4\n                      type: icon\n                      icon: close\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#000000\"\n                          alpha: 1\n                    identifier: dismiss_button\n                    button_click:\n                      - dismiss\n                -   position:\n                      horizontal: center\n                      vertical: bottom\n                    size:\n                      width: auto\n                      height: auto\n                    margin:\n                      top: 20\n                      start: 20\n                      bottom: 20\n                      end: 20\n                    view:\n                      visibility:\n                        invert_when_state_matches:\n                          scope:\n                            - show_toast\n                          value:\n                            equals: true\n                        default: false\n                      type: container\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#3d4047\"\n                          alpha: 0.8\n                      border:\n                        radius: 20\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            type: hex\n                            hex: \"#3d4047\"\n                            alpha: 0.8\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: auto\n                            height: auto\n                          margin:\n                            top: 10\n                            start: 20\n                            bottom: 10\n                            end: 20\n                          view:\n                            type: label\n                            is_accessibility_alert: true\n                            refs:\n                              - ua_invalid_form_message\n                            text: Please fix the invalid fields to continue\n                            text_appearance:\n                              font_size: 16\n                              color:\n                                default:\n                                  type: hex\n                                  hex: \"#FFFFFF\"\n                                  alpha: 1\n                              alignment: center\n                              font_families:\n                                - sans-serif\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/linear_layout_scroll.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 80%\n      height: auto\n      max_height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\nview:\n  type: container\n  background_color:\n    hex: \"#FFFFFF\"\n  border:\n    stroke_color:\n      default:\n        hex: \"#00FF00\"\n    stroke_width: 3\n  background_color:\n    default:\n      hex: \"#000000\"\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: auto\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      # SCROLL LAYOUT\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: scroll_layout\n          direction: vertical\n          size:\n            width: 100%\n            height: auto\n          view:\n            # SCROLL CONTENT (CONTAINER)\n            type: container\n            background_color:\n              default:\n                hex: \"#ff0000\"\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: <h2>World Wide Web</h2>\n                  <p>The <b>World Wide Web (WWW)</b>, commonly known as the <b>Web</b>, is an information system where documents and other web resources are identified by Uniform Resource Locators (URLs), which may be interlinked by hyperlinks, and are accessible over the Internet. The resources of the Web are transferred via the Hypertext Transfer Protocol (HTTP), may be accessed by users by a software application called a <i>web browser</i>, and are published by a software application called a <i>web server</i>. The World Wide Web is not synonymous with the Internet, which pre-dated the Web in some form by over two decades and upon the technologies of which the Web is built.</p>\n                  <p>English scientist Tim Berners-Lee invented the World Wide Web in 1989. He wrote the first web browser in 1990 while employed at CERN near Geneva, Switzerland. The browser was released outside CERN to other research institutions starting in January 1991, and then to the general public in August 1991. The Web began to enter everyday use in 1993–4, when websites for general use started to become available. The World Wide Web has been central to the development of the Information Age, and is the primary tool billions of people use to interact on the Internet.</p>\n                text_appearance:\n                  color:\n                    default:\n                      hex: \"#00FF00\"\n                  alignment: start\n                  font_size: 16\n                  font_families:\n                  - geo\n                  - casual\n      # BOTTOM-PINNED BUTTON\n      - position:\n          horizontal: center\n          vertical: center\n        margin:\n          top: 4\n          bottom: 4\n          start: 4\n          end: 4\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label_button\n          identifier: BUTTON\n          behavior: dismiss\n          background_color:\n            default:\n              hex: \"#00FF00\"\n          label:\n            type: label\n            text_appearance:\n              font_size: 24\n              alignment: center\n              color:\n                default:\n                  hex: \"#000000\"\n              styles:\n                - bold\n              font_families:\n                - fake_font_that_doesnt_exist\n                - geo\n                - casual\n            text: 'Dial Modem'\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/markdown.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: 90%\n      max_height: 90%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\n  dismiss_on_touch_outside: true\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#FFFFFF\"\n      alpha: 1\n  items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: 100%\n      view:\n        type: scroll_layout\n        direction: vertical\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: linear_layout\n          direction: vertical\n          items:\n            #\n            # Title\n            #\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"==\\u00A0Some really long\\u00A0\\n\\u00A0TITLE!\\u00A0==\"\n                text_appearance:\n                  alignment: start\n                  styles:\n                    - bold\n                  font_size: 24\n                  line_height_multiplier: 1.4\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                markdown:\n                  appearance:\n                    highlight:\n                      corner_radius: 12\n                      color:\n                        default:\n                          hex: \"#FFFF00\"\n                          alpha: 1\n            #\n            # Superscripts & Subscripts\n            #\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Superscripts & Subscripts\n                text_appearance:\n                  alignment: start\n                  styles:\n                    - bold\n                  font_size: 16\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"H,{2},O  —  E = mc^^2^^\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"==H,{2},O is highlighted!==  and  ==E = mc^^2^^ too==\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"mc^^==2==^^ — only the superscript is highlighted\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"~~H,{2},O~~ and ~~mc^^2^^~~ — strikethrough composes\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"^^open only, ,{open only — treated as plain text\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            #\n            # Styles\n            #\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Styles\n                text_appearance:\n                  alignment: start\n                  styles:\n                    - bold\n                  font_size: 16\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Normal, **Bold**, *Italic*, ==Highlighted==, ==***Highlighted + Bold + Italic***==\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Normal, __Bold__, _Italic_, ___Bold + Italic___\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"__Label with line breaks:__==\\nNormal\\n__Bold__\\n_Italic_\\n___Bold + Italic___==\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"==Markdown formatting will *preserve* **symbols**, **punctuation**, & **newlines** in the original text:\\n\\n!@#$%^&*()_+-=[]{}|;':,.<>==\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: ~~Strikethrough~~\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: ==Disabled Normal, **Bold**, *Italic*, ***Bold + Italic*** [link](https://www.airship.com) https://www.airship.com==\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                markdown:\n                  disabled: true\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label_button\n                identifier: style_button\n                background_color:\n                  default:\n                    hex: \"#6699ff\"\n                    alpha: 1\n                border:\n                  radius: 5\n                  stroke_width: 1\n                  stroke_color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                label:\n                  type: label\n                  text: Styling works in *buttons*, too. **No way!**\n                  text_appearance:\n                    font_size: 14\n                    alignment: center\n                    color:\n                      default:\n                        hex: \"#000000\"\n                        alpha: 1\n            #\n            # HTML Links\n            #\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: HTML Links\n                text_appearance:\n                  alignment: start\n                  styles:\n                    - bold\n                  font_size: 16\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"Click [here](https://www.airship.com) to visit Airship's website, or [here](http://www.google.com) to visit Google.\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                markdown:\n                  appearance:\n                    anchor:\n                      color:\n                        default:\n                          hex: \"#00ff00\"\n                          alpha: 1\n                      styles:\n                        - underlined\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Email links work too! [test@example.com](mailto:test@example.com)\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Also phone numbers! [(503) 867-5309](tel:+1503-867-5309)\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Even stylish links! ~~***[(503) 867-5309](tel:+1503-867-5309)***~~ (but why?)\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label_button\n                identifier: link_button\n                border:\n                  stroke_width: 1\n                  radius: 5\n                  stroke_color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                background_color:\n                  default:\n                    hex: \"#ff99ff\"\n                    alpha: 1\n                label:\n                  type: label\n                  text: What about **[buttons](https://www.airship.com)**? ***Weird***, **but works!**\n                  text_appearance:\n                    font_size: 14\n                    alignment: center\n                    color:\n                      default:\n                        hex: \"#000000\"\n                        alpha: 1\n            #\n            # Text Links\n            #\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Text Links\n                text_appearance:\n                  alignment: start\n                  styles:\n                    - bold\n                  font_size: 16\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"*Some* bare links work, but this behavior will differ between platforms: https://www.airship.com, www.airship.com, www.airship.com/product, airship.com, airship.com/product\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 0\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"Bare emails work, but not bare phone numbers: test@example.com, +1 503-867-5309\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            - margin:\n                top: 24\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: \"All other markdown syntax will be ***ignored!!!***\"\n                text_appearance:\n                  alignment: start\n                  font_size: 14\n                  styles:\n                    - underlined\n                    - bold\n                  color:\n                    default:\n                      hex: \"#cc0000\"\n                      alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/martin.json",
    "content": "{\n  \"version\": 1,\n  \"presentation\": {\n    \"type\": \"modal\",\n    \"placement_selectors\": [],\n    \"android\": {\n      \"disable_back_button\": false\n    },\n    \"dismiss_on_touch_outside\": false,\n    \"default_placement\": {\n      \"ignore_safe_area\": false,\n      \"size\": {\n        \"width\": \"100%\",\n        \"height\": \"100%\"\n      },\n      \"position\": {\n        \"horizontal\": \"center\",\n        \"vertical\": \"top\"\n      },\n      \"shade_color\": {\n        \"default\": {\n          \"type\": \"hex\",\n          \"hex\": \"#000000\",\n          \"alpha\": 0.2\n        }\n      },\n      \"web\": {}\n    }\n  },\n  \"view\": {\n    \"type\": \"state_controller\",\n    \"view\": {\n      \"type\": \"pager_controller\",\n      \"identifier\": \"5d853b1f-0fc8-417d-b70e-1b73985e6c79\",\n      \"view\": {\n        \"type\": \"linear_layout\",\n        \"direction\": \"vertical\",\n        \"items\": [\n          {\n            \"size\": {\n              \"width\": \"100%\",\n              \"height\": \"100%\"\n            },\n            \"view\": {\n              \"type\": \"container\",\n              \"items\": [\n                {\n                  \"identifier\": \"ea24d15d-1bd1-4f1c-8026-6f9fba69351e_pager_container_item\",\n                  \"position\": {\n                    \"horizontal\": \"center\",\n                    \"vertical\": \"center\"\n                  },\n                  \"size\": {\n                    \"width\": \"100%\",\n                    \"height\": \"100%\"\n                  },\n                  \"view\": {\n                    \"type\": \"pager\",\n                    \"disable_swipe\": true,\n                    \"gestures\": [\n                      {\n                        \"identifier\": \"5d853b1f-0fc8-417d-b70e-1b73985e6c79_tap_start\",\n                        \"type\": \"tap\",\n                        \"location\": \"start\",\n                        \"behavior\": {\n                          \"behaviors\": [\"pager_previous\"]\n                        }\n                      },\n                      {\n                        \"identifier\": \"5d853b1f-0fc8-417d-b70e-1b73985e6c79_tap_end\",\n                        \"type\": \"tap\",\n                        \"location\": \"end\",\n                        \"behavior\": {\n                          \"behaviors\": [\"pager_next_or_dismiss\"]\n                        }\n                      },\n                      {\n                        \"identifier\": \"5d853b1f-0fc8-417d-b70e-1b73985e6c79_swipe_up\",\n                        \"type\": \"swipe\",\n                        \"direction\": \"up\",\n                        \"behavior\": {\n                          \"behaviors\": [\"dismiss\"]\n                        }\n                      },\n                      {\n                        \"identifier\": \"5d853b1f-0fc8-417d-b70e-1b73985e6c79_swipe_down\",\n                        \"type\": \"swipe\",\n                        \"direction\": \"down\",\n                        \"behavior\": {\n                          \"behaviors\": [\"dismiss\"]\n                        }\n                      },\n                      {\n                        \"identifier\": \"5d853b1f-0fc8-417d-b70e-1b73985e6c79_hold\",\n                        \"type\": \"hold\",\n                        \"press_behavior\": {\n                          \"behaviors\": [\"pager_pause\"]\n                        },\n                        \"release_behavior\": {\n                          \"behaviors\": [\"pager_resume\"]\n                        }\n                      }\n                    ],\n                    \"items\": [\n                      {\n                        \"identifier\": \"aff3e35f-6800-4e98-a720-194aac6488da\",\n                        \"type\": \"pager_item\",\n                        \"view\": {\n                          \"type\": \"container\",\n                          \"items\": [\n                            {\n                              \"identifier\": \"32309ee6-a12a-44e0-8bda-5b58a3e79c28_main_view_container_item\",\n                              \"size\": {\n                                \"width\": \"100%\",\n                                \"height\": \"100%\"\n                              },\n                              \"position\": {\n                                \"horizontal\": \"center\",\n                                \"vertical\": \"center\"\n                              },\n                              \"ignore_safe_area\": false,\n                              \"view\": {\n                                \"type\": \"container\",\n                                \"items\": [\n                                  {\n                                    \"identifier\": \"21ca1452-90b0-4f33-bea3-547ddbf9ffa8_container_item\",\n                                    \"margin\": {\n                                      \"bottom\": 0,\n                                      \"top\": 0,\n                                      \"end\": 0,\n                                      \"start\": 0\n                                    },\n                                    \"position\": {\n                                      \"horizontal\": \"center\",\n                                      \"vertical\": \"center\"\n                                    },\n                                    \"size\": {\n                                      \"width\": \"100%\",\n                                      \"height\": \"100%\"\n                                    },\n                                    \"view\": {\n                                      \"type\": \"linear_layout\",\n                                      \"direction\": \"vertical\",\n                                      \"items\": [\n                                        {\n                                          \"identifier\": \"layout_container\",\n                                          \"size\": {\n                                            \"width\": \"100%\",\n                                            \"height\": \"100%\"\n                                          },\n                                          \"view\": {\n                                            \"type\": \"linear_layout\",\n                                            \"direction\": \"vertical\",\n                                            \"items\": [\n                                              {\n                                                \"identifier\": \"eda7e75c-e87b-4e2b-9edf-9b27b8557b87\",\n                                                \"size\": {\n                                                  \"width\": \"100%\",\n                                                  \"height\": \"auto\"\n                                                },\n                                                \"margin\": {\n                                                  \"top\": 48,\n                                                  \"bottom\": 8,\n                                                  \"start\": 16,\n                                                  \"end\": 16\n                                                },\n                                                \"view\": {\n                                                  \"type\": \"label\",\n                                                  \"text\": \"screen 1\",\n                                                  \"content_description\": \"screen 1\",\n                                                  \"text_appearance\": {\n                                                    \"font_size\": 24,\n                                                    \"color\": {\n                                                      \"default\": {\n                                                        \"type\": \"hex\",\n                                                        \"hex\": \"#747474\",\n                                                        \"alpha\": 1\n                                                      },\n                                                      \"selectors\": []\n                                                    },\n                                                    \"alignment\": \"start\",\n                                                    \"styles\": [\"bold\"],\n                                                    \"font_families\": [\n                                                      \"sans-serif\"\n                                                    ]\n                                                  },\n                                                  \"accessibility_hidden\": false\n                                                }\n                                              }\n                                            ]\n                                          }\n                                        }\n                                      ]\n                                    }\n                                  }\n                                ]\n                              }\n                            }\n                          ],\n                          \"background_color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#FFFFFF\",\n                              \"alpha\": 1\n                            },\n                            \"selectors\": []\n                          }\n                        },\n                        \"automated_actions\": [\n                          {\n                            \"identifier\": \"[pager_next]_aff3e35f-6800-4e98-a720-194aac6488da\",\n                            \"delay\": 7,\n                            \"behaviors\": [\"pager_next\"]\n                          }\n                        ],\n                        \"state_actions\": [\n                          {\n                            \"type\": \"set\",\n                            \"key\": \"aff3e35f-6800-4e98-a720-194aac6488da_next\"\n                          }\n                        ]\n                      },\n                      {\n                        \"identifier\": \"9252241c-034f-42fe-b4e7-474aa6ca1d3d\",\n                        \"type\": \"pager_item\",\n                        \"view\": {\n                          \"type\": \"container\",\n                          \"items\": [\n                            {\n                              \"identifier\": \"d7a1f1b8-c0ed-454a-a193-5297b233d1d6_main_view_container_item\",\n                              \"size\": {\n                                \"width\": \"100%\",\n                                \"height\": \"100%\"\n                              },\n                              \"position\": {\n                                \"horizontal\": \"center\",\n                                \"vertical\": \"center\"\n                              },\n                              \"ignore_safe_area\": false,\n                              \"view\": {\n                                \"type\": \"container\",\n                                \"items\": [\n                                  {\n                                    \"identifier\": \"d3702bb5-0f90-448d-a82c-ef3912971630_container_item\",\n                                    \"margin\": {\n                                      \"bottom\": 0,\n                                      \"top\": 0,\n                                      \"end\": 0,\n                                      \"start\": 0\n                                    },\n                                    \"position\": {\n                                      \"horizontal\": \"center\",\n                                      \"vertical\": \"center\"\n                                    },\n                                    \"size\": {\n                                      \"width\": \"100%\",\n                                      \"height\": \"100%\"\n                                    },\n                                    \"view\": {\n                                      \"type\": \"linear_layout\",\n                                      \"direction\": \"vertical\",\n                                      \"items\": [\n                                        {\n                                          \"identifier\": \"layout_container\",\n                                          \"size\": {\n                                            \"width\": \"100%\",\n                                            \"height\": \"100%\"\n                                          },\n                                          \"view\": {\n                                            \"type\": \"linear_layout\",\n                                            \"direction\": \"vertical\",\n                                            \"items\": [\n                                              {\n                                                \"identifier\": \"8fb75c92-08b7-461e-9512-15d67915b077\",\n                                                \"size\": {\n                                                  \"width\": \"100%\",\n                                                  \"height\": \"auto\"\n                                                },\n                                                \"margin\": {\n                                                  \"top\": 48,\n                                                  \"bottom\": 8,\n                                                  \"start\": 16,\n                                                  \"end\": 16\n                                                },\n                                                \"view\": {\n                                                  \"type\": \"label\",\n                                                  \"text\": \"screen 2\",\n                                                  \"content_description\": \"screen 2\",\n                                                  \"text_appearance\": {\n                                                    \"font_size\": 24,\n                                                    \"color\": {\n                                                      \"default\": {\n                                                        \"type\": \"hex\",\n                                                        \"hex\": \"#747474\",\n                                                        \"alpha\": 1\n                                                      },\n                                                      \"selectors\": []\n                                                    },\n                                                    \"alignment\": \"start\",\n                                                    \"styles\": [\"bold\"],\n                                                    \"font_families\": [\n                                                      \"sans-serif\"\n                                                    ]\n                                                  },\n                                                  \"accessibility_hidden\": false\n                                                }\n                                              }\n                                            ]\n                                          }\n                                        }\n                                      ]\n                                    }\n                                  }\n                                ]\n                              }\n                            }\n                          ],\n                          \"background_color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#FFFFFF\",\n                              \"alpha\": 1\n                            },\n                            \"selectors\": []\n                          }\n                        },\n                        \"automated_actions\": [\n                          {\n                            \"identifier\": \"pager_next_or_dismiss_9252241c-034f-42fe-b4e7-474aa6ca1d3d\",\n                            \"delay\": 7,\n                            \"behaviors\": [\"pager_next_or_dismiss\"]\n                          }\n                        ],\n                        \"state_actions\": [\n                          {\n                            \"type\": \"set\",\n                            \"key\": \"9252241c-034f-42fe-b4e7-474aa6ca1d3d_next\"\n                          }\n                        ]\n                      }\n                    ]\n                  },\n                  \"ignore_safe_area\": false\n                },\n                {\n                  \"position\": {\n                    \"horizontal\": \"end\",\n                    \"vertical\": \"top\"\n                  },\n                  \"margin\": {\n                    \"top\": 8\n                  },\n                  \"size\": {\n                    \"width\": 48,\n                    \"height\": 48\n                  },\n                  \"view\": {\n                    \"type\": \"image_button\",\n                    \"image\": {\n                      \"scale\": 0.4,\n                      \"type\": \"icon\",\n                      \"icon\": \"close\",\n                      \"color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#747474\",\n                          \"alpha\": 1\n                        },\n                        \"selectors\": []\n                      }\n                    },\n                    \"identifier\": \"dismiss_button\",\n                    \"button_click\": [\"dismiss\"],\n                    \"localized_content_description\": {\n                      \"ref\": \"ua_dismiss\",\n                      \"fallback\": \"Dismiss\"\n                    },\n                    \"reporting_metadata\": {\n                      \"button_id\": \"dismiss_button\",\n                      \"button_action\": \"dismiss\"\n                    }\n                  }\n                },\n                {\n                  \"margin\": {\n                    \"top\": 8,\n                    \"bottom\": 0,\n                    \"end\": 16,\n                    \"start\": 16\n                  },\n                  \"position\": {\n                    \"horizontal\": \"center\",\n                    \"vertical\": \"top\"\n                  },\n                  \"size\": {\n                    \"height\": 2,\n                    \"width\": \"100%\"\n                  },\n                  \"view\": {\n                    \"type\": \"story_indicator\",\n                    \"source\": {\n                      \"type\": \"pager\"\n                    },\n                    \"style\": {\n                      \"type\": \"linear_progress\",\n                      \"direction\": \"horizontal\",\n                      \"sizing\": \"equal\",\n                      \"spacing\": 4,\n                      \"progress_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#363636\",\n                          \"alpha\": 1\n                        },\n                        \"selectors\": []\n                      },\n                      \"track_color\": {\n                        \"default\": {\n                          \"type\": \"hex\",\n                          \"hex\": \"#747474\",\n                          \"alpha\": 1\n                        },\n                        \"selectors\": []\n                      }\n                    },\n                    \"automated_accessibility_actions\": [\n                      {\n                        \"type\": \"announce\"\n                      }\n                    ]\n                  }\n                },\n                {\n                  \"ignore_safe_area\": false,\n                  \"margin\": {\n                    \"end\": 0,\n                    \"start\": 0,\n                    \"top\": 0,\n                    \"bottom\": 0\n                  },\n                  \"position\": {\n                    \"horizontal\": \"center\",\n                    \"vertical\": \"center\"\n                  },\n                  \"size\": {\n                    \"height\": 48,\n                    \"width\": 48\n                  },\n                  \"view\": {\n                    \"type\": \"stack_image_button\",\n                    \"identifier\": \"play_pause_button\",\n                    \"button_click\": [\"pager_toggle_pause\"],\n                    \"items\": [\n                      {\n                        \"type\": \"shape\",\n                        \"shape\": {\n                          \"type\": \"ellipse\",\n                          \"aspect_ratio\": 1,\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#FFFFFF\",\n                              \"alpha\": 1\n                            },\n                            \"selectors\": []\n                          },\n                          \"scale\": 0.7\n                        }\n                      },\n                      {\n                        \"type\": \"icon\",\n                        \"icon\": {\n                          \"type\": \"icon\",\n                          \"color\": {\n                            \"default\": {\n                              \"type\": \"hex\",\n                              \"hex\": \"#747474\",\n                              \"alpha\": 1\n                            },\n                            \"selectors\": []\n                          },\n                          \"icon\": \"pause\",\n                          \"scale\": 0.35\n                        }\n                      }\n                    ],\n                    \"localized_content_description\": {\n                      \"refs\": [\"ua_pause\"],\n                      \"fallback\": \"Pause\"\n                    },\n                    \"view_overrides\": {\n                      \"localized_content_description\": [\n                        {\n                          \"value\": {\n                            \"refs\": [\"ua_play\"],\n                            \"fallback\": \"Play\"\n                          },\n                          \"when_state_matches\": {\n                            \"scope\": [\"$pagers\", \"current\", \"paused\"],\n                            \"value\": {\n                              \"equals\": true\n                            }\n                          }\n                        }\n                      ],\n                      \"items\": [\n                        {\n                          \"value\": [\n                            {\n                              \"type\": \"shape\",\n                              \"shape\": {\n                                \"type\": \"ellipse\",\n                                \"aspect_ratio\": 1,\n                                \"color\": {\n                                  \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#FFFFFF\",\n                                    \"alpha\": 1\n                                  },\n                                  \"selectors\": []\n                                },\n                                \"scale\": 0.7\n                              }\n                            },\n                            {\n                              \"type\": \"icon\",\n                              \"icon\": {\n                                \"type\": \"icon\",\n                                \"color\": {\n                                  \"default\": {\n                                    \"type\": \"hex\",\n                                    \"hex\": \"#747474\",\n                                    \"alpha\": 1\n                                  },\n                                  \"selectors\": []\n                                },\n                                \"icon\": \"play\",\n                                \"scale\": 0.35\n                              }\n                            }\n                          ],\n                          \"when_state_matches\": {\n                            \"scope\": [\"$pagers\", \"current\", \"paused\"],\n                            \"value\": {\n                              \"equals\": false\n                            }\n                          }\n                        }\n                      ]\n                    }\n                  }\n                }\n              ]\n            }\n          }\n        ]\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/mobile-4599.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  placement_selectors:\n    - placement:\n        ignore_safe_area: true\n        size:\n          width: 70%\n          height: 60%\n        position:\n          horizontal: center\n          vertical: center\n        shade_color:\n          default:\n            type: hex\n            hex: '#868686'\n            alpha: 1\n        border:\n          radius: 15\n      window_size: large\n      orientation: portrait\n    - placement:\n        ignore_safe_area: true\n        size:\n          width: 80%\n          height: 80%\n        position:\n          horizontal: center\n          vertical: center\n        shade_color:\n          default:\n            type: hex\n            hex: '#868686'\n            alpha: 1\n        border:\n          radius: 15\n      window_size: large\n      orientation: landscape\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: true\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: '#000000'\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: dfbeb4be-acce-411e-b211-4fabefb4b1b8\n  view:\n    type: container\n    items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: pager\n          disable_swipe: true\n          gestures:\n            - identifier: dfbeb4be-acce-411e-b211-4fabefb4b1b8_tap_start\n              type: tap\n              location: start\n              behavior:\n                behaviors:\n                  - pager_previous\n            - identifier: dfbeb4be-acce-411e-b211-4fabefb4b1b8_tap_end\n              type: tap\n              location: end\n              behavior:\n                behaviors:\n                  - pager_next_or_first\n            - identifier: dfbeb4be-acce-411e-b211-4fabefb4b1b8_swipe_up\n              type: swipe\n              direction: up\n              behavior:\n                behaviors:\n                  - dismiss\n            - identifier: dfbeb4be-acce-411e-b211-4fabefb4b1b8_swipe_down\n              type: swipe\n              direction: down\n              behavior:\n                behaviors:\n                  - dismiss\n            - identifier: dfbeb4be-acce-411e-b211-4fabefb4b1b8_hold\n              type: hold\n              press_behavior:\n                behaviors:\n                  - pager_pause\n              release_behavior:\n                behaviors:\n                  - pager_resume\n          items:\n#            - identifier: 58bb45b8-de18-49a2-ba2c-2dab129233ac\n#              type: pager_item\n#              view:\n#                type: container\n#                items:\n#                  - margin:\n#                      start: 0\n#                      end: 0\n#                    position:\n#                      horizontal: center\n#                      vertical: center\n#                    size:\n#                      width: 100%\n#                      height: 100%\n#                    view:\n#                      type: media\n#                      media_fit: center_crop\n#                      url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/pexels-oliver-sjo%CC%88stro%CC%88m-1650732.jpg\n#                      media_type: image\n#                    ignore_safe_area: true\n#                  - size:\n#                      width: 100%\n#                      height: 100%\n#                    position:\n#                      horizontal: center\n#                      vertical: center\n#                    ignore_safe_area: true\n#                    view:\n#                      type: container\n#                      items:\n#                        - margin:\n#                            bottom: 0\n#                            top: 0\n#                            end: 0\n#                            start: 0\n#                          position:\n#                            horizontal: center\n#                            vertical: center\n#                          size:\n#                            width: 100%\n#                            height: 100%\n#                          view:\n#                            type: linear_layout\n#                            direction: vertical\n#                            items:\n#                              - identifier: layout_container\n#                                size:\n#                                  width: 100%\n#                                  height: 100%\n#                                view:\n#                                  type: linear_layout\n#                                  direction: vertical\n#                                  items:\n#                                    - identifier: c865e1c9-b03f-410a-be67-3679adf576b9\n#                                      size:\n#                                        width: 100%\n#                                        height: auto\n#                                      view:\n#                                        type: container\n#                                        items:\n#                                          - margin:\n#                                              top: 10\n#                                              bottom: 0\n#                                              start: 0\n#                                              end: 0\n#                                            position:\n#                                              horizontal: center\n#                                              vertical: center\n#                                            size:\n#                                              width: 100%\n#                                              height: auto\n#                                            view:\n#                                              type: linear_layout\n#                                              direction: horizontal\n#                                              items:\n#                                                - size:\n#                                                    width: 20%\n#                                                    height: auto\n#                                                  view:\n#                                                    type: media\n#                                                    media_fit: center_inside\n#                                                    url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/746a2309-146e-4ddd-87a8-e30650fb1f69\n#                                                    media_type: image\n#                                                  identifier: 7332c72c-3235-45b7-a868-14e3686db6ed\n#                                                  margin:\n#                                                    top: 0\n#                                                    bottom: 0\n#                                                    start: 0\n#                                                    end: 0\n#                                                - identifier: bef99142-1267-4caa-8851-6e1331b5b23b\n#                                                  size:\n#                                                    width: 100%\n#                                                    height: auto\n#                                                  margin:\n#                                                    top: 5\n#                                                    bottom: 8\n#                                                    start: 0\n#                                                    end: 16\n#                                                  view:\n#                                                    type: label\n#                                                    text: Surf Magazine\n#                                                    text_appearance:\n#                                                      font_size: 15\n#                                                      color:\n#                                                        default:\n#                                                          type: hex\n#                                                          hex: '#FFFFFF'\n#                                                          alpha: 1\n#                                                      alignment: start\n#                                                      styles:\n#                                                        - bold\n#                                                      font_families:\n#                                                        - sans-serif\n#                                      margin:\n#                                        top: 10\n#                                        bottom: 0\n#                                        start: 0\n#                                        end: 0\n#                              - identifier: f97c7145-2cce-4068-a7aa-2104c7a63d8d\n#                                size:\n#                                  width: 100%\n#                                  height: auto\n#                                view:\n#                                  type: container\n#                                  items:\n#                                    - margin:\n#                                        top: 8\n#                                        bottom: 8\n#                                        start: 0\n#                                        end: 0\n#                                      position:\n#                                        horizontal: center\n#                                        vertical: center\n#                                      size:\n#                                        width: 100%\n#                                        height: auto\n#                                      view:\n#                                        type: linear_layout\n#                                        direction: horizontal\n#                                        items:\n#                                          - identifier: 916018ab-4843-4e86-aabe-d572740b6219\n#                                            margin:\n#                                              top: 8\n#                                              bottom: 8\n#                                              start: 10\n#                                              end: 50\n#                                            size:\n#                                              width: 50%\n#                                              height: auto\n#                                            view:\n#                                              type: label_button\n#                                              identifier: dismiss--Watch video\n#                                              reporting_metadata:\n#                                                trigger_link_id: 916018ab-4843-4e86-aabe-d572740b6219\n#                                              label:\n#                                                type: label\n#                                                text: Watch video\n#                                                text_appearance:\n#                                                  font_size: 16\n#                                                  color:\n#                                                    default:\n#                                                      type: hex\n#                                                      hex: '#000000'\n#                                                      alpha: 1\n#                                                  alignment: center\n#                                                  styles:\n#                                                    - bold\n#                                                  font_families:\n#                                                    - sans-serif\n#                                              actions: {}\n#                                              enabled: []\n#                                              button_click:\n#                                                - dismiss\n#                                              background_color:\n#                                                default:\n#                                                  type: hex\n#                                                  hex: '#FFFFFF'\n#                                                  alpha: 1\n#                                              border:\n#                                                radius: 15\n#                                                stroke_width: 0\n#                                                stroke_color:\n#                                                  default:\n#                                                    type: hex\n#                                                    hex: '#FFFFFF'\n#                                                    alpha: 1\n#                                          - size:\n#                                              width: 18%\n#                                              height: auto\n#                                            view:\n#                                              type: media\n#                                              media_fit: center_inside\n#                                              url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/e0eb750c-5d97-4e3b-93be-26f5c6ebabe0\n#                                              media_type: image\n#                                            identifier: 4d6bb769-40b3-429f-aef8-bfdb5146db38\n#                                            margin:\n#                                              top: 0\n#                                              bottom: 0\n#                                              start: 0\n#                                              end: 60\n#                                          - size:\n#                                              width: 10%\n#                                              height: auto\n#                                            view:\n#                                              type: media\n#                                              media_fit: center_inside\n#                                              url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/552fef48-266f-49fe-982f-beda4107557f\n#                                              media_type: image\n#                                            identifier: 98c23a30-5f52-47eb-9838-16a11626eb23\n#                                            margin:\n#                                              top: 0\n#                                              bottom: 0\n#                                              start: 0\n#                                              end: 20\n#                                margin:\n#                                  top: 8\n#                                  bottom: 8\n#                                  start: 0\n#                                  end: 0\n#                background_color:\n#                  default:\n#                    type: hex\n#                    hex: '#BCBDC2'\n#                    alpha: 1\n#                  selectors:\n#                    - platform: ios\n#                      dark_mode: true\n#                      color:\n#                        type: hex\n#                        hex: '#FFFFFF'\n#                        alpha: 0.5\n#                    - platform: android\n#                      dark_mode: true\n#                      color:\n#                        type: hex\n#                        hex: '#FFFFFF'\n#                        alpha: 0.5\n#                    - platform: web\n#                      dark_mode: true\n#                      color:\n#                        type: hex\n#                        hex: '#FFFFFF'\n#                        alpha: 0.5\n#              automated_actions:\n#                - identifier: '[pager_next]_58bb45b8-de18-49a2-ba2c-2dab129233ac'\n#                  delay: 7\n#                  behaviors:\n#                    - pager_next\n            - identifier: e2fd43ff-4f4c-4f69-86dc-3762605752ba\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: true\n                    view:\n                      type: container\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: layout_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                    - identifier: d3c83a1a-3c17-4491-a646-9a6e2e0b360f\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: container\n                                        items:\n                                          - margin:\n                                              top: 20\n                                            position:\n                                              horizontal: center\n                                              vertical: center\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                                - size:\n                                                    width: 100%\n                                                    height: auto\n                                                  view:\n                                                    type: media\n                                                    media_fit: center_inside\n                                                    url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/746a2309-146e-4ddd-87a8-e30650fb1f69\n                                                    media_type: image\n                                                    content_description: clean SURF logo\n                                                  identifier: 91546819-ec1d-474d-bd7e-61b8dc14926f\n                                                - identifier: d8cf9f55-927c-4b4f-a225-7381a00f610e\n                                                  size:\n                                                    width: 100%\n                                                    height: auto\n                                                  margin:\n                                                    top: 8\n                                                    bottom: 8\n                                                    start: 16\n                                                    end: 16\n                                                  view:\n                                                    type: label\n                                                    text: Surf Magazine\n                                                    text_appearance:\n                                                      font_size: 24\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: '#FFFFFF'\n                                                          alpha: 1\n                                                      alignment: start\n                                                      styles:\n                                                        - bold\n                                                      font_families:\n                                                        - sans-serif\n                                      margin:\n                                        top: 20\n                                    - identifier: ac5948bf-e00f-42a9-927c-7d3d44f46ed7\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: container\n                                        items:\n                                          - position:\n                                              horizontal: center\n                                              vertical: center\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                                - identifier: 68913127-af13-472c-85c6-aea8ff421c35\n                                                  margin:\n                                                    top: 8\n                                                    bottom: 8\n                                                    start: 16\n                                                    end: 16\n                                                  size:\n                                                    width: 100%\n                                                    height: auto\n                                                  view:\n                                                    type: label_button\n                                                    identifier: dismiss--Watch full video\n                                                    reporting_metadata:\n                                                      trigger_link_id: 68913127-af13-472c-85c6-aea8ff421c35\n                                                    label:\n                                                      type: label\n                                                      text: Watch full video\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                          selectors:\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: '#000000'\n                                                                alpha: 1\n                                                            - platform: android\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: '#000000'\n                                                                alpha: 1\n                                                            - platform: web\n                                                              dark_mode: true\n                                                              color:\n                                                                type: hex\n                                                                hex: '#000000'\n                                                                alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                                    actions: {}\n                                                    enabled: []\n                                                    button_click:\n                                                      - dismiss\n                                                    background_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#63AFF1'\n                                                        alpha: 1\n                                                    border:\n                                                      radius: 3\n                                                      stroke_width: 0\n                                                      stroke_color:\n                                                        default:\n                                                          type: hex\n                                                          hex: '#63AFF1'\n                                                          alpha: 1\n                                                - size:\n                                                    width: 100%\n                                                    height: auto\n                                                  view:\n                                                    type: media\n                                                    media_fit: center_inside\n                                                    url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/e0eb750c-5d97-4e3b-93be-26f5c6ebabe0\n                                                    media_type: image\n                                                    content_description: share icon\n                                                  identifier: 3bdd060e-6f41-4899-9c8f-8dc0a4d97e3f\n                                                - size:\n                                                    width: 100%\n                                                    height: auto\n                                                  view:\n                                                    type: media\n                                                    media_fit: center_inside\n                                                    url: https://hangar-dl.urbanairship.com/binary/public/ISex_TTJRuarzs9-o_Gkhg/552fef48-266f-49fe-982f-beda4107557f\n                                                    media_type: image\n                                                    content_description: bookmark icon\n                                                  identifier: e5cc14e3-9c9c-4814-8b22-f3e93dd048a8\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/mobile-5409-2.json",
    "content": "{\n  \"in_app_message\": {\n    \"edit_grace_period\": 14,\n    \"end\": \"2025-12-20T06:59:48\",\n    \"features\": {\n      \"email_input\": \"1.0\"\n    },\n    \"forms\": [\n      {\n        \"id\": \"ab4f5d65-2c67-42df-9ba3-3f3f53f251ba\",\n        \"questions\": [\n          {\n            \"id\": \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3\",\n            \"label\": \"Email collection sweepstakes\",\n            \"type\": \"email\"\n          }\n        ],\n        \"response_type\": \"user_feedback\",\n        \"type\": \"form\"\n      }\n    ],\n    \"interval\": 60,\n    \"labels\": [\n      {\n        \"id\": \"bae41801-1be6-4251-ad93-bb911198c346\",\n        \"label\": \"Sweepstakes Intro\"\n      },\n      {\n        \"id\": \"df4b0790-09f8-40a2-bdbb-5c1307811a74\",\n        \"label\": \"Email\"\n      },\n      {\n        \"id\": \"5a4fa960-ab13-413d-b391-a397d51bea50\",\n        \"label\": \"You're Entered\"\n      }\n    ],\n    \"limit\": 1,\n    \"message\": {\n      \"audience\": {\n        \"miss_behavior\": \"skip\"\n      },\n      \"display\": {\n        \"layout\": {\n          \"presentation\": {\n            \"android\": {\n              \"disable_back_button\": false\n            },\n            \"default_placement\": {\n              \"ignore_safe_area\": true,\n              \"position\": {\n                \"horizontal\": \"center\",\n                \"vertical\": \"top\"\n              },\n              \"shade_color\": {\n                \"default\": {\n                  \"alpha\": 0.2,\n                  \"hex\": \"#000000\",\n                  \"type\": \"hex\"\n                }\n              },\n              \"size\": {\n                \"height\": \"100%\",\n                \"width\": \"100%\"\n              },\n              \"web\": {\n                \"ignore_shade\": true\n              }\n            },\n            \"dismiss_on_touch_outside\": false,\n            \"placement_selectors\": [],\n            \"type\": \"modal\"\n          },\n          \"version\": 1,\n          \"view\": {\n            \"type\": \"state_controller\",\n            \"view\": {\n              \"identifier\": \"5d2e18e6-d883-4176-b52d-0339d8351341\",\n              \"type\": \"pager_controller\",\n              \"view\": {\n                \"form_enabled\": [\n                  \"form_submission\"\n                ],\n                \"identifier\": \"ab4f5d65-2c67-42df-9ba3-3f3f53f251ba\",\n                \"response_type\": \"user_feedback\",\n                \"state_triggers\": [\n                  {\n                    \"identifier\": \"SHOW_TOAST\",\n                    \"on_trigger\": {\n                      \"state_actions\": [\n                        {\n                          \"key\": \"ASYNC_VALIDATION_TOAST\",\n                          \"ttl_seconds\": 1.5,\n                          \"type\": \"set\",\n                          \"value\": true\n                        }\n                      ]\n                    },\n                    \"reset_when_state_matches\": {\n                      \"not\": {\n                        \"scope\": [\n                          \"$forms\",\n                          \"current\",\n                          \"status\",\n                          \"type\"\n                        ],\n                        \"value\": {\n                          \"equals\": \"error\"\n                        }\n                      }\n                    },\n                    \"trigger_when_state_matches\": {\n                      \"scope\": [\n                        \"$forms\",\n                        \"current\",\n                        \"status\",\n                        \"type\"\n                      ],\n                      \"value\": {\n                        \"equals\": \"error\"\n                      }\n                    }\n                  }\n                ],\n                \"submit\": \"submit_event\",\n                \"type\": \"form_controller\",\n                \"validation_mode\": {\n                  \"type\": \"on_demand\"\n                },\n                \"view\": {\n                  \"items\": [\n                    {\n                      \"identifier\": \"b43c6c82-7783-4abf-b35b-76c9d6fab199_pager_container_item\",\n                      \"ignore_safe_area\": true,\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"center\"\n                      },\n                      \"size\": {\n                        \"height\": \"100%\",\n                        \"width\": \"100%\"\n                      },\n                      \"view\": {\n                        \"disable_swipe\": true,\n                        \"items\": [\n                          {\n                            \"identifier\": \"bae41801-1be6-4251-ad93-bb911198c346\",\n                            \"state_actions\": [\n                              {\n                                \"key\": \"bae41801-1be6-4251-ad93-bb911198c346_next\",\n                                \"type\": \"set\"\n                              }\n                            ],\n                            \"type\": \"pager_item\",\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#FFFFFF\",\n                                  \"type\": \"hex\"\n                                },\n                                \"selectors\": [\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"web\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"web\"\n                                  }\n                                ]\n                              },\n                              \"items\": [\n                                {\n                                  \"identifier\": \"698eef43-6f58-44ed-aa53-bb8250ab6e8a_background_image_container\",\n                                  \"ignore_safe_area\": true,\n                                  \"margin\": {\n                                    \"end\": 0,\n                                    \"start\": 0\n                                  },\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"media_fit\": \"fit_crop\",\n                                    \"media_type\": \"image\",\n                                    \"position\": {\n                                      \"horizontal\": \"center\",\n                                      \"vertical\": \"center\"\n                                    },\n                                    \"type\": \"media\",\n                                    \"url\": \"https://c00340-dl.urbanairship.com/binary/public/WjPzA418RrGPEdjgcfowqA/9ae597ce-1695-4b62-85b5-b43b48062574\"\n                                  }\n                                },\n                                {\n                                  \"identifier\": \"33bdb020-a3a8-48a9-8540-7d180c60920f_main_view_container_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"d9a91384-268e-40db-997f-733a6071c9fd_container_item\",\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"scroll_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"type\": \"scroll_layout\",\n                                                \"view\": {\n                                                  \"direction\": \"vertical\",\n                                                  \"items\": [\n                                                    {\n                                                      \"identifier\": \"df6c5a0e-f3b2-4574-ba73-f5bcfd358980\",\n                                                      \"margin\": {\n                                                        \"bottom\": 0,\n                                                        \"end\": 72,\n                                                        \"start\": 16,\n                                                        \"top\": 8\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"media_fit\": \"center_inside\",\n                                                        \"media_type\": \"image\",\n                                                        \"type\": \"media\",\n                                                        \"url\": \"https://c00340-dl.urbanairship.com/binary/public/WjPzA418RrGPEdjgcfowqA/a69428fb-2e6a-4d37-8761-805e975dc10d\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"2be8b119-1ffe-414a-b422-57781ffef579\",\n                                                      \"margin\": {\n                                                        \"bottom\": 0,\n                                                        \"end\": 16,\n                                                        \"start\": 16,\n                                                        \"top\": 16\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"accessibility_hidden\": false,\n                                                        \"accessibility_role\": {\n                                                          \"level\": 1,\n                                                          \"type\": \"heading\"\n                                                        },\n                                                        \"content_description\": \"Win a Ski Trip for 4 to Breck with Toyota!\",\n                                                        \"text\": \"Win a Ski Trip for 4 to Breck with Toyota!\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#10164C\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"Montserrat\"\n                                                          ],\n                                                          \"font_size\": 20,\n                                                          \"styles\": [\n                                                            \"bold\"\n                                                          ]\n                                                        },\n                                                        \"type\": \"label\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"ee31ab77-b878-4e8e-9b57-77e04daacb9a\",\n                                                      \"margin\": {\n                                                        \"bottom\": 0,\n                                                        \"end\": 24,\n                                                        \"start\": 24,\n                                                        \"top\": 8\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"accessibility_hidden\": false,\n                                                        \"content_description\": \"Sign up for email updates and mobile alerts and to enter for a chance to win the **Epic Winter Adventure Sweepstakes**. The Winner will receive a ski trip for 4 to The Village at Breckenridge 3/5/26-3/8/26, plus Helly Hansen and Oakley gear.\",\n                                                        \"text\": \"Sign up for email updates and mobile alerts and to enter for a chance to win the **Epic Winter Adventure Sweepstakes**. The Winner will receive a ski trip for 4 to The Village at Breckenridge 3/5/26-3/8/26, plus Helly Hansen and Oakley gear.\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#10164C\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"Montserrat\"\n                                                          ],\n                                                          \"font_size\": 14,\n                                                          \"styles\": []\n                                                        },\n                                                        \"type\": \"label\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"37236178-5434-4c52-bb63-ecfbd753a11a_linear_layout_item\",\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"direction\": \"horizontal\",\n                                                        \"items\": [],\n                                                        \"type\": \"linear_layout\"\n                                                      }\n                                                    }\n                                                  ],\n                                                  \"type\": \"linear_layout\"\n                                                }\n                                              }\n                                            },\n                                            {\n                                              \"identifier\": \"43b08423-f2c3-40c7-9aa6-d2fb1076ed28\",\n                                              \"margin\": {\n                                                \"bottom\": 16,\n                                                \"end\": 0,\n                                                \"start\": 0,\n                                                \"top\": 8\n                                              },\n                                              \"size\": {\n                                                \"height\": \"auto\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"43b08423-f2c3-40c7-9aa6-d2fb1076ed28_linear_container\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 0\n                                                    },\n                                                    \"position\": {\n                                                      \"horizontal\": \"center\",\n                                                      \"vertical\": \"center\"\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"border\": {\n                                                        \"radius\": 0\n                                                      },\n                                                      \"direction\": \"vertical\",\n                                                      \"items\": [\n                                                        {\n                                                          \"identifier\": \"66d48213-e944-4544-98df-7045d0a1fcdf\",\n                                                          \"margin\": {\n                                                            \"bottom\": 14,\n                                                            \"end\": 0,\n                                                            \"start\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"items\": [\n                                                              {\n                                                                \"identifier\": \"66d48213-e944-4544-98df-7045d0a1fcdf_linear_container\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 0,\n                                                                  \"end\": 0,\n                                                                  \"start\": 0,\n                                                                  \"top\": 0\n                                                                },\n                                                                \"position\": {\n                                                                  \"horizontal\": \"center\",\n                                                                  \"vertical\": \"center\"\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"border\": {\n                                                                    \"radius\": 0\n                                                                  },\n                                                                  \"direction\": \"vertical\",\n                                                                  \"items\": [\n                                                                    {\n                                                                      \"identifier\": \"5830a305-5557-49cc-b097-e4f630183153\",\n                                                                      \"margin\": {\n                                                                        \"bottom\": 0,\n                                                                        \"end\": 16,\n                                                                        \"start\": 16,\n                                                                        \"top\": 0\n                                                                      },\n                                                                      \"size\": {\n                                                                        \"height\": \"auto\",\n                                                                        \"width\": \"100%\"\n                                                                      },\n                                                                      \"view\": {\n                                                                        \"accessibility_hidden\": false,\n                                                                        \"content_description\": \"Stay in the know and crush the season!\",\n                                                                        \"text\": \"Stay in the know and crush the season!\",\n                                                                        \"text_appearance\": {\n                                                                          \"alignment\": \"center\",\n                                                                          \"color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#10164C\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"selectors\": [\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"web\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"web\"\n                                                                              }\n                                                                            ]\n                                                                          },\n                                                                          \"font_families\": [\n                                                                            \"Montserrat\"\n                                                                          ],\n                                                                          \"font_size\": 15,\n                                                                          \"styles\": []\n                                                                        },\n                                                                        \"type\": \"label\"\n                                                                      }\n                                                                    }\n                                                                  ],\n                                                                  \"type\": \"linear_layout\"\n                                                                }\n                                                              }\n                                                            ],\n                                                            \"type\": \"container\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"1821d34b-8ca6-4960-92ee-8e1a1c37c6b1\",\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"Please verify your age to enter.\",\n                                                            \"text\": \"Please verify your age to enter.\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"center\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 14,\n                                                              \"styles\": []\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"317d78f5-8c3a-4a30-a133-61773355c553\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 8\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"actions\": {\n                                                              \"add_tags_action\": [\n                                                                \"Sweepstakes Eligible 2025\"\n                                                              ]\n                                                            },\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#10164C\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 8,\n                                                              \"stroke_color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"stroke_width\": 8\n                                                            },\n                                                            \"button_click\": [\n                                                              \"pager_next\"\n                                                            ],\n                                                            \"content_description\": \"I AM OVER 18 — I AM OVER 18\",\n                                                            \"enabled\": [\n                                                              \"pager_next\"\n                                                            ],\n                                                            \"identifier\": \"next--Over 18\",\n                                                            \"label\": {\n                                                              \"content_description\": \"I AM OVER 18 — I AM OVER 18\",\n                                                              \"text\": \"I AM OVER 18\",\n                                                              \"text_appearance\": {\n                                                                \"alignment\": \"center\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"Montserrat\"\n                                                                ],\n                                                                \"font_size\": 14,\n                                                                \"styles\": [\n                                                                  \"bold\"\n                                                                ]\n                                                              },\n                                                              \"type\": \"label\"\n                                                            },\n                                                            \"localized_content_description\": {\n                                                              \"fallback\": \"Next\",\n                                                              \"ref\": \"ua_next\"\n                                                            },\n                                                            \"reporting_metadata\": {\n                                                              \"button_action\": \"next\",\n                                                              \"button_id\": \"317d78f5-8c3a-4a30-a133-61773355c553\",\n                                                              \"trigger_link_id\": \"317d78f5-8c3a-4a30-a133-61773355c553\"\n                                                            },\n                                                            \"type\": \"label_button\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"8de2aa00-81c1-42f7-ae3c-808c6b61335c\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 8\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"actions\": {\n                                                              \"add_tags_action\": [\n                                                                \"Sweepstakes Not Eligible 2025\"\n                                                              ]\n                                                            },\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#999999\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 8,\n                                                              \"stroke_color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#999999\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"stroke_width\": 8\n                                                            },\n                                                            \"button_click\": [\n                                                              \"dismiss\"\n                                                            ],\n                                                            \"content_description\": \"I AM UNDER 18\",\n                                                            \"enabled\": [],\n                                                            \"identifier\": \"dismiss--Under 18\",\n                                                            \"label\": {\n                                                              \"content_description\": \"I AM UNDER 18\",\n                                                              \"text\": \"I AM UNDER 18\",\n                                                              \"text_appearance\": {\n                                                                \"alignment\": \"center\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#000000\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"Montserrat\"\n                                                                ],\n                                                                \"font_size\": 14,\n                                                                \"styles\": [\n                                                                  \"bold\"\n                                                                ]\n                                                              },\n                                                              \"type\": \"label\"\n                                                            },\n                                                            \"localized_content_description\": {\n                                                              \"fallback\": \"Dismiss\",\n                                                              \"ref\": \"ua_dismiss\"\n                                                            },\n                                                            \"reporting_metadata\": {\n                                                              \"button_action\": \"dismiss\",\n                                                              \"button_id\": \"8de2aa00-81c1-42f7-ae3c-808c6b61335c\",\n                                                              \"trigger_link_id\": \"8de2aa00-81c1-42f7-ae3c-808c6b61335c\"\n                                                            },\n                                                            \"type\": \"label_button\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"dce3b36d-24c3-4074-84e2-d80bbaadccde\",\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 4\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"By entering this site you are agreeing to the Terms of Use and Privacy Policies for [Vail Resorts](https://www.epicpass.com/policies?tc_1=3), [Toyota](https://urldefense.com/v3/__https:/www.toyota.com/support/privacy-notice/__;!!FK2kAO7IF7m7Bw!tYDQ1he6xq5Qsr4LI6bSpBbVp442uZdypE-NNxTEHZXNZQqP4QuJmFejU4qsz-unDIo5vMMAC7A5V2NBeP4mT-ODfVh4quJkSE4S$), and [Helly Hansen](https://www.hellyhansen.com/privacy-policy).\\n\\n\",\n                                                            \"text\": \"By entering this site you are agreeing to the Terms of Use and Privacy Policies for [Vail Resorts](https://www.epicpass.com/policies?tc_1=3), [Toyota](https://urldefense.com/v3/__https:/www.toyota.com/support/privacy-notice/__;!!FK2kAO7IF7m7Bw!tYDQ1he6xq5Qsr4LI6bSpBbVp442uZdypE-NNxTEHZXNZQqP4QuJmFejU4qsz-unDIo5vMMAC7A5V2NBeP4mT-ODfVh4quJkSE4S$), and [Helly Hansen](https://www.hellyhansen.com/privacy-policy).\\n\\n\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"center\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 12,\n                                                              \"styles\": [\n                                                                \"italic\"\n                                                              ]\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"391c72d4-eb98-40fe-8d34-9bcf8e88442d\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"[Epic Winter Adventure Sweepstakes Terms and Conditions](https://www.epicpass.com/info/winter-adventure-sweepstakes-terms)\",\n                                                            \"text\": \"[Epic Winter Adventure Sweepstakes Terms and Conditions](https://www.epicpass.com/info/winter-adventure-sweepstakes-terms)\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"center\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 11,\n                                                              \"styles\": []\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"linear_layout\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"container\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\"\n                            }\n                          },\n                          {\n                            \"identifier\": \"df4b0790-09f8-40a2-bdbb-5c1307811a74\",\n                            \"state_actions\": [\n                              {\n                                \"key\": \"df4b0790-09f8-40a2-bdbb-5c1307811a74_next\",\n                                \"type\": \"set\"\n                              }\n                            ],\n                            \"type\": \"pager_item\",\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#FFFFFF\",\n                                  \"type\": \"hex\"\n                                },\n                                \"selectors\": [\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"web\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"web\"\n                                  }\n                                ]\n                              },\n                              \"items\": [\n                                {\n                                  \"identifier\": \"4144b3c5-a7bb-443b-bdf4-de61d4a86287_background_image_container\",\n                                  \"ignore_safe_area\": true,\n                                  \"margin\": {\n                                    \"end\": 0,\n                                    \"start\": 0\n                                  },\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"media_fit\": \"fit_crop\",\n                                    \"media_type\": \"image\",\n                                    \"position\": {\n                                      \"horizontal\": \"center\",\n                                      \"vertical\": \"center\"\n                                    },\n                                    \"type\": \"media\",\n                                    \"url\": \"https://c00340-dl.urbanairship.com/binary/public/WjPzA418RrGPEdjgcfowqA/9ae597ce-1695-4b62-85b5-b43b48062574\"\n                                  }\n                                },\n                                {\n                                  \"identifier\": \"c13fedfc-71ed-406e-9246-6a0f6f6cce3a_main_view_container_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"0815cef1-93a2-4bdd-8218-2a325aaf7bea_container_item\",\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"scroll_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"type\": \"scroll_layout\",\n                                                \"view\": {\n                                                  \"direction\": \"vertical\",\n                                                  \"items\": [\n                                                    {\n                                                      \"identifier\": \"9b69d5e1-c2cd-4fd8-a917-03d4b42ad756\",\n                                                      \"margin\": {\n                                                        \"bottom\": 0,\n                                                        \"end\": 72,\n                                                        \"start\": 16,\n                                                        \"top\": 8\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"media_fit\": \"center_inside\",\n                                                        \"media_type\": \"image\",\n                                                        \"type\": \"media\",\n                                                        \"url\": \"https://c00340-dl.urbanairship.com/binary/public/WjPzA418RrGPEdjgcfowqA/a69428fb-2e6a-4d37-8761-805e975dc10d\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"715ee0d8-b524-4203-b8f2-0f9316976922\",\n                                                      \"margin\": {\n                                                        \"bottom\": 8,\n                                                        \"end\": 16,\n                                                        \"start\": 16,\n                                                        \"top\": 24\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"accessibility_hidden\": false,\n                                                        \"accessibility_role\": {\n                                                          \"level\": 1,\n                                                          \"type\": \"heading\"\n                                                        },\n                                                        \"content_description\": \"Enter Your Email\",\n                                                        \"text\": \"Enter Your Email\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#10164C\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"Montserrat\"\n                                                          ],\n                                                          \"font_size\": 20,\n                                                          \"styles\": [\n                                                            \"bold\"\n                                                          ]\n                                                        },\n                                                        \"type\": \"label\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"5a8b2690-ca2b-4cff-8d48-3a6f60547e0b\",\n                                                      \"margin\": {\n                                                        \"bottom\": 8,\n                                                        \"end\": 24,\n                                                        \"start\": 24,\n                                                        \"top\": 8\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": 320\n                                                      },\n                                                      \"view\": {\n                                                        \"accessibility_hidden\": false,\n                                                        \"content_description\": \"Get resort updates, trip planning tips, and exclusive offers — plus the opportunity to enter for a chance to win!\",\n                                                        \"text\": \"Get resort updates, trip planning tips, and exclusive offers — plus the opportunity to enter for a chance to win!\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#10164C\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"Montserrat\"\n                                                          ],\n                                                          \"font_size\": 15,\n                                                          \"styles\": []\n                                                        },\n                                                        \"type\": \"label\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"70a5b1c1-967f-46e1-a48c-a63f96a4b068_linear_layout_item\",\n                                                      \"size\": {\n                                                        \"height\": \"100%\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"direction\": \"horizontal\",\n                                                        \"items\": [],\n                                                        \"type\": \"linear_layout\"\n                                                      }\n                                                    }\n                                                  ],\n                                                  \"type\": \"linear_layout\"\n                                                }\n                                              }\n                                            },\n                                            {\n                                              \"identifier\": \"cb6e47d3-0d1f-489b-a205-2fea6b32b2ae\",\n                                              \"margin\": {\n                                                \"bottom\": 16,\n                                                \"end\": 0,\n                                                \"start\": 0,\n                                                \"top\": 8\n                                              },\n                                              \"size\": {\n                                                \"height\": \"auto\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"cb6e47d3-0d1f-489b-a205-2fea6b32b2ae_linear_container\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 0\n                                                    },\n                                                    \"position\": {\n                                                      \"horizontal\": \"center\",\n                                                      \"vertical\": \"center\"\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"border\": {\n                                                        \"radius\": 0,\n                                                        \"stroke_color\": {\n                                                          \"default\": {\n                                                            \"alpha\": 0,\n                                                            \"hex\": \"#000000\",\n                                                            \"type\": \"hex\"\n                                                          },\n                                                          \"selectors\": [\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"web\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"web\"\n                                                            }\n                                                          ]\n                                                        },\n                                                        \"stroke_width\": 0\n                                                      },\n                                                      \"direction\": \"vertical\",\n                                                      \"items\": [\n                                                        {\n                                                          \"identifier\": \"51e543ba-5548-4108-a26b-c8ff2fab719c\",\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"end\": 0,\n                                                            \"start\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"items\": [\n                                                              {\n                                                                \"identifier\": \"51e543ba-5548-4108-a26b-c8ff2fab719c_linear_container\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 0,\n                                                                  \"end\": 0,\n                                                                  \"start\": 0,\n                                                                  \"top\": 0\n                                                                },\n                                                                \"position\": {\n                                                                  \"horizontal\": \"center\",\n                                                                  \"vertical\": \"center\"\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"border\": {\n                                                                    \"radius\": 0\n                                                                  },\n                                                                  \"direction\": \"horizontal\",\n                                                                  \"items\": [\n                                                                    {\n                                                                      \"identifier\": \"0f077f9e-897a-4082-a481-1266325a53ff\",\n                                                                      \"margin\": {\n                                                                        \"bottom\": 0,\n                                                                        \"end\": 4,\n                                                                        \"start\": 16,\n                                                                        \"top\": 0\n                                                                      },\n                                                                      \"size\": {\n                                                                        \"height\": \"auto\",\n                                                                        \"width\": \"auto\"\n                                                                      },\n                                                                      \"view\": {\n                                                                        \"accessibility_hidden\": false,\n                                                                        \"content_description\": \"Email Address\",\n                                                                        \"text\": \"Email Address\",\n                                                                        \"text_appearance\": {\n                                                                          \"alignment\": \"start\",\n                                                                          \"color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#10164C\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"selectors\": [\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"web\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#10164C\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"web\"\n                                                                              }\n                                                                            ]\n                                                                          },\n                                                                          \"font_families\": [\n                                                                            \"Montserrat\"\n                                                                          ],\n                                                                          \"font_size\": 12,\n                                                                          \"styles\": []\n                                                                        },\n                                                                        \"type\": \"label\"\n                                                                      }\n                                                                    },\n                                                                    {\n                                                                      \"identifier\": \"0c6d363d-3397-4833-9100-0301b672f45d\",\n                                                                      \"margin\": {\n                                                                        \"bottom\": 0,\n                                                                        \"end\": 0,\n                                                                        \"start\": 0,\n                                                                        \"top\": 0\n                                                                      },\n                                                                      \"size\": {\n                                                                        \"height\": \"auto\",\n                                                                        \"width\": \"auto\"\n                                                                      },\n                                                                      \"view\": {\n                                                                        \"accessibility_hidden\": false,\n                                                                        \"content_description\": \"*\",\n                                                                        \"text\": \"*\",\n                                                                        \"text_appearance\": {\n                                                                          \"alignment\": \"start\",\n                                                                          \"color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FF0000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"selectors\": [\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FF0000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FF0000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FF0000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FF0000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FF0000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"web\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FF0000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"web\"\n                                                                              }\n                                                                            ]\n                                                                          },\n                                                                          \"font_families\": [\n                                                                            \"Montserrat\"\n                                                                          ],\n                                                                          \"font_size\": 12,\n                                                                          \"styles\": []\n                                                                        },\n                                                                        \"type\": \"label\"\n                                                                      }\n                                                                    }\n                                                                  ],\n                                                                  \"type\": \"linear_layout\"\n                                                                }\n                                                              }\n                                                            ],\n                                                            \"type\": \"container\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3\",\n                                                          \"margin\": {\n                                                            \"bottom\": 16,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": 60,\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"direction\": \"vertical\",\n                                                            \"items\": [\n                                                              {\n                                                                \"margin\": {\n                                                                  \"bottom\": 8,\n                                                                  \"top\": 4\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": 60,\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"background_color\": {\n                                                                    \"default\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFFFFF\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"selectors\": [\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"web\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"web\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"border\": {\n                                                                    \"radius\": 4,\n                                                                    \"stroke_color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#7B7C84\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#7B7C84\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#7B7C84\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#7B7C84\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    },\n                                                                    \"stroke_width\": 1\n                                                                  },\n                                                                  \"content_description\": \"Email Address:\",\n                                                                  \"identifier\": \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3\",\n                                                                  \"input_type\": \"email\",\n                                                                  \"on_edit\": {\n                                                                    \"state_actions\": [\n                                                                      {\n                                                                        \"key\": \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\",\n                                                                        \"type\": \"set\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"on_error\": {\n                                                                    \"state_actions\": [\n                                                                      {\n                                                                        \"key\": \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\",\n                                                                        \"type\": \"set\",\n                                                                        \"value\": false\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"on_valid\": {\n                                                                    \"state_actions\": [\n                                                                      {\n                                                                        \"key\": \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\",\n                                                                        \"type\": \"set\",\n                                                                        \"value\": true\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"place_holder\": \"* \",\n                                                                  \"required\": true,\n                                                                  \"text_appearance\": {\n                                                                    \"alignment\": \"start\",\n                                                                    \"color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#696A6F\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    },\n                                                                    \"font_families\": [\n                                                                      \"sans-serif\"\n                                                                    ],\n                                                                    \"font_size\": 16,\n                                                                    \"place_holder_color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#696A6F\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#696A6F\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    }\n                                                                  },\n                                                                  \"type\": \"text_input\",\n                                                                  \"view_overrides\": {\n                                                                    \"border\": [\n                                                                      {\n                                                                        \"value\": {\n                                                                          \"radius\": 4,\n                                                                          \"stroke_color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 0.5,\n                                                                              \"hex\": \"#ff0000\",\n                                                                              \"type\": \"hex\"\n                                                                            }\n                                                                          },\n                                                                          \"stroke_width\": 1\n                                                                        },\n                                                                        \"when_state_matches\": {\n                                                                          \"scope\": [\n                                                                            \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\"\n                                                                          ],\n                                                                          \"value\": {\n                                                                            \"equals\": false\n                                                                          }\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"value\": {\n                                                                          \"radius\": 4,\n                                                                          \"stroke_color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#cccccc\",\n                                                                              \"type\": \"hex\"\n                                                                            }\n                                                                          },\n                                                                          \"stroke_width\": 1\n                                                                        },\n                                                                        \"when_state_matches\": {\n                                                                          \"scope\": [\n                                                                            \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\"\n                                                                          ],\n                                                                          \"value\": {\n                                                                            \"equals\": true\n                                                                          }\n                                                                        }\n                                                                      }\n                                                                    ],\n                                                                    \"icon_end\": [\n                                                                      {\n                                                                        \"value\": {\n                                                                          \"icon\": {\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 0.5,\n                                                                                \"hex\": \"#ff0000\",\n                                                                                \"type\": \"hex\"\n                                                                              }\n                                                                            },\n                                                                            \"icon\": \"exclamationmark_circle_fill\",\n                                                                            \"scale\": 1,\n                                                                            \"type\": \"icon\"\n                                                                          },\n                                                                          \"type\": \"floating\"\n                                                                        },\n                                                                        \"when_state_matches\": {\n                                                                          \"scope\": [\n                                                                            \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\"\n                                                                          ],\n                                                                          \"value\": {\n                                                                            \"equals\": false\n                                                                          }\n                                                                        }\n                                                                      },\n                                                                      {\n                                                                        \"value\": {\n                                                                          \"icon\": {\n                                                                            \"color\": {\n                                                                              \"default\": {\n                                                                                \"alpha\": 0.5,\n                                                                                \"hex\": \"#00ff00\",\n                                                                                \"type\": \"hex\"\n                                                                              }\n                                                                            },\n                                                                            \"icon\": \"checkmark\",\n                                                                            \"scale\": 1,\n                                                                            \"type\": \"icon\"\n                                                                          },\n                                                                          \"type\": \"floating\"\n                                                                        },\n                                                                        \"when_state_matches\": {\n                                                                          \"scope\": [\n                                                                            \"0c4eca52-ac0a-433a-8c9e-7b483e3927c3_email_is_valid\"\n                                                                          ],\n                                                                          \"value\": {\n                                                                            \"equals\": true\n                                                                          }\n                                                                        }\n                                                                      }\n                                                                    ]\n                                                                  }\n                                                                }\n                                                              }\n                                                            ],\n                                                            \"type\": \"linear_layout\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"3a10f821-b226-4c8b-8bbf-905fcfbe2318\",\n                                                          \"margin\": {\n                                                            \"bottom\": 4,\n                                                            \"end\": 20,\n                                                            \"start\": 20,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"By clicking submit, I agree to share my email address with Sponsor, Toyota and Helly Hansen for partner updates.\",\n                                                            \"text\": \"By clicking submit, I agree to share my email address with Sponsor, Toyota and Helly Hansen for partner updates.\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"start\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 14,\n                                                              \"styles\": []\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"a7ab3156-5930-4fef-a59b-b0d19720cc26\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 8\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"actions\": {\n                                                              \"add_tags_action\": [\n                                                                \"Sweeps Enable Email 2025\"\n                                                              ]\n                                                            },\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#10164C\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 8,\n                                                              \"stroke_color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"stroke_width\": 8\n                                                            },\n                                                            \"button_click\": [\n                                                              \"form_submit\",\n                                                              \"pager_next\"\n                                                            ],\n                                                            \"content_description\": \"SUBMIT — SUBMIT\",\n                                                            \"enabled\": [\n                                                              \"form_validation\"\n                                                            ],\n                                                            \"event_handlers\": [\n                                                              {\n                                                                \"state_actions\": [\n                                                                  {\n                                                                    \"key\": \"submitted\",\n                                                                    \"type\": \"set\",\n                                                                    \"value\": true\n                                                                  }\n                                                                ],\n                                                                \"type\": \"tap\"\n                                                              }\n                                                            ],\n                                                            \"identifier\": \"submit_feedback--SUBMIT\",\n                                                            \"label\": {\n                                                              \"content_description\": \"SUBMIT — SUBMIT\",\n                                                              \"text\": \"SUBMIT\",\n                                                              \"text_appearance\": {\n                                                                \"alignment\": \"center\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"Montserrat\"\n                                                                ],\n                                                                \"font_size\": 14,\n                                                                \"styles\": [\n                                                                  \"bold\"\n                                                                ]\n                                                              },\n                                                              \"type\": \"label\",\n                                                              \"view_overrides\": {\n                                                                \"icon_start\": [\n                                                                  {\n                                                                    \"value\": {\n                                                                      \"icon\": {\n                                                                        \"color\": {\n                                                                          \"default\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"selectors\": [\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": false,\n                                                                              \"platform\": \"ios\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": true,\n                                                                              \"platform\": \"ios\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": false,\n                                                                              \"platform\": \"android\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": true,\n                                                                              \"platform\": \"android\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": false,\n                                                                              \"platform\": \"web\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": true,\n                                                                              \"platform\": \"web\"\n                                                                            }\n                                                                          ]\n                                                                        },\n                                                                        \"icon\": \"progress_spinner\",\n                                                                        \"scale\": 1,\n                                                                        \"type\": \"icon\"\n                                                                      },\n                                                                      \"space\": 8,\n                                                                      \"type\": \"floating\"\n                                                                    },\n                                                                    \"when_state_matches\": {\n                                                                      \"scope\": [\n                                                                        \"$forms\",\n                                                                        \"current\",\n                                                                        \"status\",\n                                                                        \"type\"\n                                                                      ],\n                                                                      \"value\": {\n                                                                        \"equals\": \"validating\"\n                                                                      }\n                                                                    }\n                                                                  }\n                                                                ],\n                                                                \"text\": [\n                                                                  {\n                                                                    \"value\": \"SUBMIT\",\n                                                                    \"when_state_matches\": {\n                                                                      \"scope\": [\n                                                                        \"$forms\",\n                                                                        \"current\",\n                                                                        \"status\",\n                                                                        \"type\"\n                                                                      ],\n                                                                      \"value\": {\n                                                                        \"equals\": \"validating\"\n                                                                      }\n                                                                    }\n                                                                  }\n                                                                ],\n                                                                \"text_appearance\": [\n                                                                  {\n                                                                    \"value\": {\n                                                                      \"alignment\": \"center\",\n                                                                      \"color\": {\n                                                                        \"default\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"selectors\": [\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"web\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"web\"\n                                                                          }\n                                                                        ]\n                                                                      },\n                                                                      \"font_families\": [\n                                                                        \"Montserrat\"\n                                                                      ],\n                                                                      \"font_size\": 14,\n                                                                      \"styles\": [\n                                                                        \"bold\"\n                                                                      ]\n                                                                    }\n                                                                  }\n                                                                ]\n                                                              }\n                                                            },\n                                                            \"reporting_metadata\": {\n                                                              \"button_action\": \"submit_feedback\",\n                                                              \"button_id\": \"a7ab3156-5930-4fef-a59b-b0d19720cc26\",\n                                                              \"trigger_link_id\": \"a7ab3156-5930-4fef-a59b-b0d19720cc26\"\n                                                            },\n                                                            \"type\": \"label_button\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"6c850347-6abb-4a35-b595-9793bd2efcee\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 8\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"actions\": {\n                                                              \"add_tags_action\": [\n                                                                \"Sweeps Email Later 2025\"\n                                                              ]\n                                                            },\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#999999\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#999999\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 8,\n                                                              \"stroke_color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 0,\n                                                                  \"hex\": \"#999999\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 0,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 0,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 0,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#999999\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"stroke_width\": 8\n                                                            },\n                                                            \"button_click\": [\n                                                              \"dismiss\"\n                                                            ],\n                                                            \"content_description\": \"Maybe Later — Maybe Later\",\n                                                            \"enabled\": [],\n                                                            \"identifier\": \"dismiss--Maybe Later\",\n                                                            \"label\": {\n                                                              \"content_description\": \"Maybe Later — Maybe Later\",\n                                                              \"text\": \"Maybe Later\",\n                                                              \"text_appearance\": {\n                                                                \"alignment\": \"center\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#000000\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"Montserrat\"\n                                                                ],\n                                                                \"font_size\": 14,\n                                                                \"styles\": [\n                                                                  \"bold\"\n                                                                ]\n                                                              },\n                                                              \"type\": \"label\"\n                                                            },\n                                                            \"localized_content_description\": {\n                                                              \"fallback\": \"Dismiss\",\n                                                              \"ref\": \"ua_dismiss\"\n                                                            },\n                                                            \"reporting_metadata\": {\n                                                              \"button_action\": \"dismiss\",\n                                                              \"button_id\": \"6c850347-6abb-4a35-b595-9793bd2efcee\",\n                                                              \"trigger_link_id\": \"6c850347-6abb-4a35-b595-9793bd2efcee\"\n                                                            },\n                                                            \"type\": \"label_button\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"632fd4c7-235a-40d4-b267-1a14e3f470be\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"[Epic Winter Adventure Sweepstakes Terms and Conditions](https://www.epicpass.com/info/winter-adventure-sweepstakes-terms)\",\n                                                            \"text\": \"[Epic Winter Adventure Sweepstakes Terms and Conditions](https://www.epicpass.com/info/winter-adventure-sweepstakes-terms)\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"center\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 11,\n                                                              \"styles\": []\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"linear_layout\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"container\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\"\n                            }\n                          },\n                          {\n                            \"display_actions\": {\n                              \"add_tags_action\": [\n                                \"Sweeps Scene Completed 2025\"\n                              ]\n                            },\n                            \"identifier\": \"5a4fa960-ab13-413d-b391-a397d51bea50\",\n                            \"state_actions\": [\n                              {\n                                \"key\": \"5a4fa960-ab13-413d-b391-a397d51bea50_next\",\n                                \"type\": \"set\"\n                              }\n                            ],\n                            \"type\": \"pager_item\",\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#FFFFFF\",\n                                  \"type\": \"hex\"\n                                },\n                                \"selectors\": [\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"ios\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"android\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": false,\n                                    \"platform\": \"web\"\n                                  },\n                                  {\n                                    \"color\": {\n                                      \"alpha\": 1,\n                                      \"hex\": \"#FFFFFF\",\n                                      \"type\": \"hex\"\n                                    },\n                                    \"dark_mode\": true,\n                                    \"platform\": \"web\"\n                                  }\n                                ]\n                              },\n                              \"items\": [\n                                {\n                                  \"identifier\": \"d1eae0c5-fc84-4f18-8e07-dd348b70d810_background_image_container\",\n                                  \"ignore_safe_area\": true,\n                                  \"margin\": {\n                                    \"end\": 0,\n                                    \"start\": 0\n                                  },\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"media_fit\": \"fit_crop\",\n                                    \"media_type\": \"image\",\n                                    \"position\": {\n                                      \"horizontal\": \"center\",\n                                      \"vertical\": \"center\"\n                                    },\n                                    \"type\": \"media\",\n                                    \"url\": \"https://c00340-dl.urbanairship.com/binary/public/WjPzA418RrGPEdjgcfowqA/9ae597ce-1695-4b62-85b5-b43b48062574\"\n                                  }\n                                },\n                                {\n                                  \"identifier\": \"7f6fd84e-eedc-47ca-b975-a56fd7ad2fa4_main_view_container_item\",\n                                  \"ignore_safe_area\": true,\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"100%\",\n                                    \"width\": \"100%\"\n                                  },\n                                  \"view\": {\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"680f9160-f362-4819-b76e-ba3e1f2b4bb4_container_item\",\n                                        \"margin\": {\n                                          \"bottom\": 0,\n                                          \"end\": 0,\n                                          \"start\": 0,\n                                          \"top\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"direction\": \"vertical\",\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"scroll_container\",\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"type\": \"scroll_layout\",\n                                                \"view\": {\n                                                  \"direction\": \"vertical\",\n                                                  \"items\": [\n                                                    {\n                                                      \"identifier\": \"9a203dd2-4412-4b6a-a841-ca74f2482080\",\n                                                      \"margin\": {\n                                                        \"bottom\": 0,\n                                                        \"end\": 72,\n                                                        \"start\": 16,\n                                                        \"top\": 8\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"media_fit\": \"center_inside\",\n                                                        \"media_type\": \"image\",\n                                                        \"type\": \"media\",\n                                                        \"url\": \"https://c00340-dl.urbanairship.com/binary/public/WjPzA418RrGPEdjgcfowqA/a69428fb-2e6a-4d37-8761-805e975dc10d\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"31cde54f-4071-4d4c-a4c3-827abbb94531\",\n                                                      \"margin\": {\n                                                        \"bottom\": 8,\n                                                        \"end\": 16,\n                                                        \"start\": 16,\n                                                        \"top\": 24\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"accessibility_hidden\": false,\n                                                        \"accessibility_role\": {\n                                                          \"level\": 1,\n                                                          \"type\": \"heading\"\n                                                        },\n                                                        \"content_description\": \"You’re Entered!\",\n                                                        \"text\": \"You’re Entered!\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#10164C\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"Montserrat\"\n                                                          ],\n                                                          \"font_size\": 20,\n                                                          \"styles\": [\n                                                            \"bold\"\n                                                          ]\n                                                        },\n                                                        \"type\": \"label\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"76973380-871e-496e-a9bf-8133a2ca0243\",\n                                                      \"margin\": {\n                                                        \"bottom\": 8,\n                                                        \"end\": 24,\n                                                        \"start\": 24,\n                                                        \"top\": 8\n                                                      },\n                                                      \"size\": {\n                                                        \"height\": \"auto\",\n                                                        \"width\": 320\n                                                      },\n                                                      \"view\": {\n                                                        \"accessibility_hidden\": false,\n                                                        \"content_description\": \"You’ve entered for a chance to win the **Epic Winter Adventure Sweepstakes**— good luck!\",\n                                                        \"text\": \"You’ve entered for a chance to win the **Epic Winter Adventure Sweepstakes**— good luck!\",\n                                                        \"text_appearance\": {\n                                                          \"alignment\": \"center\",\n                                                          \"color\": {\n                                                            \"default\": {\n                                                              \"alpha\": 1,\n                                                              \"hex\": \"#10164C\",\n                                                              \"type\": \"hex\"\n                                                            },\n                                                            \"selectors\": [\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"ios\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"android\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": false,\n                                                                \"platform\": \"web\"\n                                                              },\n                                                              {\n                                                                \"color\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"dark_mode\": true,\n                                                                \"platform\": \"web\"\n                                                              }\n                                                            ]\n                                                          },\n                                                          \"font_families\": [\n                                                            \"Montserrat\"\n                                                          ],\n                                                          \"font_size\": 18,\n                                                          \"styles\": []\n                                                        },\n                                                        \"type\": \"label\"\n                                                      }\n                                                    },\n                                                    {\n                                                      \"identifier\": \"d1e70c3b-1c9f-4975-aa64-7b657f249520_linear_layout_item\",\n                                                      \"size\": {\n                                                        \"height\": \"100%\",\n                                                        \"width\": \"100%\"\n                                                      },\n                                                      \"view\": {\n                                                        \"direction\": \"horizontal\",\n                                                        \"items\": [],\n                                                        \"type\": \"linear_layout\"\n                                                      }\n                                                    }\n                                                  ],\n                                                  \"type\": \"linear_layout\"\n                                                }\n                                              }\n                                            },\n                                            {\n                                              \"identifier\": \"4b04475d-1cdf-4470-b138-ca8afca5ef41\",\n                                              \"margin\": {\n                                                \"bottom\": 80,\n                                                \"end\": 0,\n                                                \"start\": 0,\n                                                \"top\": 8\n                                              },\n                                              \"size\": {\n                                                \"height\": \"auto\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"4b04475d-1cdf-4470-b138-ca8afca5ef41_linear_container\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 0\n                                                    },\n                                                    \"position\": {\n                                                      \"horizontal\": \"center\",\n                                                      \"vertical\": \"center\"\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"border\": {\n                                                        \"radius\": 0,\n                                                        \"stroke_color\": {\n                                                          \"default\": {\n                                                            \"alpha\": 0,\n                                                            \"hex\": \"#000000\",\n                                                            \"type\": \"hex\"\n                                                          },\n                                                          \"selectors\": [\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"ios\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"android\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#000000\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": false,\n                                                              \"platform\": \"web\"\n                                                            },\n                                                            {\n                                                              \"color\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"dark_mode\": true,\n                                                              \"platform\": \"web\"\n                                                            }\n                                                          ]\n                                                        },\n                                                        \"stroke_width\": 0\n                                                      },\n                                                      \"direction\": \"vertical\",\n                                                      \"items\": [\n                                                        {\n                                                          \"identifier\": \"f6c45e0e-e092-43e7-8a85-60ce6f5bafc7\",\n                                                          \"margin\": {\n                                                            \"bottom\": 4,\n                                                            \"end\": 20,\n                                                            \"start\": 20,\n                                                            \"top\": 0\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"Keep an eye on your inbox and notifications for resort news and Winner in January.\",\n                                                            \"text\": \"Keep an eye on your inbox and notifications for resort news and Winner in January.\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"center\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 15,\n                                                              \"styles\": []\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"4b020117-cc07-4f4a-94f1-11ad6a294390\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 8\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"actions\": {},\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 1,\n                                                                \"hex\": \"#10164C\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#10164C\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 8,\n                                                              \"stroke_color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"stroke_width\": 8\n                                                            },\n                                                            \"button_click\": [\n                                                              \"dismiss\"\n                                                            ],\n                                                            \"content_description\": \"EXPLORE THE APP — EXPLORE THE APP\",\n                                                            \"enabled\": [],\n                                                            \"identifier\": \"dismiss--Explore App\",\n                                                            \"label\": {\n                                                              \"content_description\": \"EXPLORE THE APP — EXPLORE THE APP\",\n                                                              \"text\": \"EXPLORE THE APP\",\n                                                              \"text_appearance\": {\n                                                                \"alignment\": \"center\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"Montserrat\"\n                                                                ],\n                                                                \"font_size\": 14,\n                                                                \"styles\": [\n                                                                  \"bold\"\n                                                                ]\n                                                              },\n                                                              \"type\": \"label\"\n                                                            },\n                                                            \"localized_content_description\": {\n                                                              \"fallback\": \"Dismiss\",\n                                                              \"ref\": \"ua_dismiss\"\n                                                            },\n                                                            \"reporting_metadata\": {\n                                                              \"button_action\": \"dismiss\",\n                                                              \"button_id\": \"4b020117-cc07-4f4a-94f1-11ad6a294390\",\n                                                              \"trigger_link_id\": \"4b020117-cc07-4f4a-94f1-11ad6a294390\"\n                                                            },\n                                                            \"type\": \"label_button\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"0c047636-0e62-41b1-b15a-a021de4e88c4\",\n                                                          \"margin\": {\n                                                            \"bottom\": 8,\n                                                            \"end\": 16,\n                                                            \"start\": 16,\n                                                            \"top\": 20\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"accessibility_hidden\": false,\n                                                            \"content_description\": \"[Epic Winter Adventure Sweepstakes Terms and Conditions](https://www.epicpass.com/info/winter-adventure-sweepstakes-terms)\",\n                                                            \"text\": \"[Epic Winter Adventure Sweepstakes Terms and Conditions](https://www.epicpass.com/info/winter-adventure-sweepstakes-terms)\",\n                                                            \"text_appearance\": {\n                                                              \"alignment\": \"center\",\n                                                              \"color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#10164C\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#10164C\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"font_families\": [\n                                                                \"Montserrat\"\n                                                              ],\n                                                              \"font_size\": 11,\n                                                              \"styles\": []\n                                                            },\n                                                            \"type\": \"label\"\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"linear_layout\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"container\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"linear_layout\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\"\n                            }\n                          }\n                        ],\n                        \"type\": \"pager\"\n                      }\n                    },\n                    {\n                      \"position\": {\n                        \"horizontal\": \"end\",\n                        \"vertical\": \"top\"\n                      },\n                      \"size\": {\n                        \"height\": 48,\n                        \"width\": 48\n                      },\n                      \"view\": {\n                        \"button_click\": [\n                          \"dismiss\"\n                        ],\n                        \"identifier\": \"dismiss_button\",\n                        \"image\": {\n                          \"color\": {\n                            \"default\": {\n                              \"alpha\": 1,\n                              \"hex\": \"#10164C\",\n                              \"type\": \"hex\"\n                            },\n                            \"selectors\": [\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#10164C\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": false,\n                                \"platform\": \"ios\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#10164C\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": true,\n                                \"platform\": \"ios\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#10164C\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": false,\n                                \"platform\": \"android\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#10164C\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": true,\n                                \"platform\": \"android\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#10164C\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": false,\n                                \"platform\": \"web\"\n                              },\n                              {\n                                \"color\": {\n                                  \"alpha\": 1,\n                                  \"hex\": \"#10164C\",\n                                  \"type\": \"hex\"\n                                },\n                                \"dark_mode\": true,\n                                \"platform\": \"web\"\n                              }\n                            ]\n                          },\n                          \"icon\": \"close\",\n                          \"scale\": 0.4,\n                          \"type\": \"icon\"\n                        },\n                        \"localized_content_description\": {\n                          \"fallback\": \"Dismiss\",\n                          \"ref\": \"ua_dismiss\"\n                        },\n                        \"reporting_metadata\": {\n                          \"button_action\": \"dismiss\",\n                          \"button_id\": \"dismiss_button\"\n                        },\n                        \"type\": \"image_button\"\n                      }\n                    },\n                    {\n                      \"margin\": {\n                        \"bottom\": 4,\n                        \"end\": 0,\n                        \"start\": 0,\n                        \"top\": 4\n                      },\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"bottom\"\n                      },\n                      \"size\": {\n                        \"height\": 14,\n                        \"width\": \"100%\"\n                      },\n                      \"view\": {\n                        \"automated_accessibility_actions\": [\n                          {\n                            \"type\": \"announce\"\n                          }\n                        ],\n                        \"bindings\": {\n                          \"selected\": {\n                            \"shapes\": [\n                              {\n                                \"aspect_ratio\": 2,\n                                \"border\": {\n                                  \"radius\": 16\n                                },\n                                \"color\": {\n                                  \"default\": {\n                                    \"alpha\": 1,\n                                    \"hex\": \"#EF700C\",\n                                    \"type\": \"hex\"\n                                  },\n                                  \"selectors\": [\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#EF700C\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#EF700C\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#EF700C\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#EF700C\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#EF700C\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"web\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#EF700C\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"web\"\n                                    }\n                                  ]\n                                },\n                                \"scale\": 1,\n                                \"type\": \"rectangle\"\n                              }\n                            ]\n                          },\n                          \"unselected\": {\n                            \"shapes\": [\n                              {\n                                \"aspect_ratio\": 1,\n                                \"color\": {\n                                  \"default\": {\n                                    \"alpha\": 1,\n                                    \"hex\": \"#BCBDC2\",\n                                    \"type\": \"hex\"\n                                  },\n                                  \"selectors\": [\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#BCBDC2\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 0.5,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#BCBDC2\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 0.5,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#BCBDC2\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"web\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 0.5,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"web\"\n                                    }\n                                  ]\n                                },\n                                \"scale\": 0.5,\n                                \"type\": \"ellipse\"\n                              }\n                            ]\n                          }\n                        },\n                        \"spacing\": 0,\n                        \"type\": \"pager_indicator\"\n                      }\n                    },\n                    {\n                      \"margin\": {\n                        \"bottom\": 20,\n                        \"end\": 20,\n                        \"start\": 20,\n                        \"top\": 20\n                      },\n                      \"position\": {\n                        \"horizontal\": \"center\",\n                        \"vertical\": \"bottom\"\n                      },\n                      \"size\": {\n                        \"height\": \"auto\",\n                        \"width\": \"auto\"\n                      },\n                      \"view\": {\n                        \"background_color\": {\n                          \"default\": {\n                            \"alpha\": 0.8,\n                            \"hex\": \"#3d4047\",\n                            \"type\": \"hex\"\n                          }\n                        },\n                        \"border\": {\n                          \"radius\": 20,\n                          \"stroke_color\": {\n                            \"default\": {\n                              \"alpha\": 0.8,\n                              \"hex\": \"#3d4047\",\n                              \"type\": \"hex\"\n                            }\n                          },\n                          \"stroke_width\": 1\n                        },\n                        \"items\": [\n                          {\n                            \"margin\": {\n                              \"bottom\": 10,\n                              \"end\": 20,\n                              \"start\": 20,\n                              \"top\": 10\n                            },\n                            \"position\": {\n                              \"horizontal\": \"center\",\n                              \"vertical\": \"center\"\n                            },\n                            \"size\": {\n                              \"height\": \"auto\",\n                              \"width\": \"auto\"\n                            },\n                            \"view\": {\n                              \"content_description\": \"Error processing form. Please try again\",\n                              \"text\": \"Error processing form. Please try again\",\n                              \"text_appearance\": {\n                                \"alignment\": \"center\",\n                                \"color\": {\n                                  \"default\": {\n                                    \"alpha\": 1,\n                                    \"hex\": \"#FFFFFF\",\n                                    \"type\": \"hex\"\n                                  }\n                                },\n                                \"font_families\": [\n                                  \"sans-serif\"\n                                ],\n                                \"font_size\": 16\n                              },\n                              \"type\": \"label\"\n                            }\n                          }\n                        ],\n                        \"type\": \"container\",\n                        \"visibility\": {\n                          \"default\": false,\n                          \"invert_when_state_matches\": {\n                            \"scope\": [\n                              \"ASYNC_VALIDATION_TOAST\"\n                            ],\n                            \"value\": {\n                              \"equals\": true\n                            }\n                          }\n                        }\n                      }\n                    }\n                  ],\n                  \"type\": \"container\"\n                }\n              }\n            }\n          }\n        }\n      },\n      \"display_type\": \"layout\",\n      \"name\": \"20251209 - SA Sweepstakes Email Only Scene 12/16-12/19\"\n    },\n    \"reporting_context\": {\n      \"content_types\": [\n        \"scene\",\n        \"survey\"\n      ],\n      \"experiment_id\": \"\"\n    },\n    \"requires_eligibility\": false,\n    \"start\": \"2025-12-16T16:00:17\",\n    \"triggers\": [\n      {\n        \"goal\": 1,\n        \"type\": \"active_session\"\n      }\n    ]\n  },\n  \"notify\": false\n}"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/mobile-5409.json",
    "content": "{\n  \"in_app_message\": {\n    \"campaigns\": {\n      \"categories\": [\n        \"2025-01-28 - Promotional - SMS Acquisition - CB674705\",\n        \"2025-08-20 - Promotional - SMS Acquisition - CB674705\"\n      ]\n    },\n    \"edit_grace_period\": 14,\n    \"end\": \"2025-12-13T00:15:08\",\n    \"forms\": [\n      {\n        \"id\": \"5d9527b6-bfe4-4640-8770-4481aa0728de\",\n        \"questions\": [\n          {\n            \"id\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3\",\n            \"label\": \"Phone number\",\n            \"type\": \"sms\"\n          }\n        ],\n        \"response_type\": \"user_feedback\",\n        \"type\": \"form\"\n      }\n    ],\n    \"interval\": 10,\n    \"labels\": [\n      {\n        \"id\": \"92ea4467-5996-44a0-8aa7-4be1471568ec\",\n        \"label\": \"Screen 1\"\n      },\n      {\n        \"id\": \"5b8ead60-c42e-4944-8ad8-5e652b7713c6\",\n        \"label\": \"Screen 2\"\n      }\n    ],\n    \"limit\": 0,\n    \"message\": {\n      \"audience\": {\n        \"test_devices\": [\n          \"8f518776-d4d6-4703-aad7-5c45a1d19978\",\n          \"f9f28a89-6961-478e-bede-91fd61786657\"\n        ]\n      },\n      \"display\": {\n        \"layout\": {\n          \"presentation\": {\n            \"android\": {\n              \"disable_back_button\": false\n            },\n            \"default_placement\": {\n              \"border\": {\n                \"radius\": 20,\n                \"stroke_color\": {\n                  \"default\": {\n                    \"alpha\": 1,\n                    \"hex\": \"#000000\",\n                    \"type\": \"hex\"\n                  },\n                  \"selectors\": [\n                    {\n                      \"color\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                        \"type\": \"hex\"\n                      },\n                      \"dark_mode\": false,\n                      \"platform\": \"ios\"\n                    },\n                    {\n                      \"color\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#FFFFFF\",\n                        \"type\": \"hex\"\n                      },\n                      \"dark_mode\": true,\n                      \"platform\": \"ios\"\n                    },\n                    {\n                      \"color\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                        \"type\": \"hex\"\n                      },\n                      \"dark_mode\": false,\n                      \"platform\": \"android\"\n                    },\n                    {\n                      \"color\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#FFFFFF\",\n                        \"type\": \"hex\"\n                      },\n                      \"dark_mode\": true,\n                      \"platform\": \"android\"\n                    },\n                    {\n                      \"color\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                        \"type\": \"hex\"\n                      },\n                      \"dark_mode\": false,\n                      \"platform\": \"web\"\n                    },\n                    {\n                      \"color\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#FFFFFF\",\n                        \"type\": \"hex\"\n                      },\n                      \"dark_mode\": true,\n                      \"platform\": \"web\"\n                    }\n                  ]\n                },\n                \"stroke_width\": 0\n              },\n              \"ignore_safe_area\": false,\n              \"position\": {\n                \"horizontal\": \"center\",\n                \"vertical\": \"center\"\n              },\n              \"shade_color\": {\n                \"default\": {\n                  \"alpha\": 0.5,\n                  \"hex\": \"#111B40\",\n                  \"type\": \"hex\"\n                },\n                \"selectors\": [\n                  {\n                    \"color\": {\n                      \"alpha\": 0.5,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"dark_mode\": false,\n                    \"platform\": \"ios\"\n                  },\n                  {\n                    \"color\": {\n                      \"alpha\": 0.85,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"dark_mode\": true,\n                    \"platform\": \"ios\"\n                  },\n                  {\n                    \"color\": {\n                      \"alpha\": 0.5,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"dark_mode\": false,\n                    \"platform\": \"android\"\n                  },\n                  {\n                    \"color\": {\n                      \"alpha\": 0.85,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"dark_mode\": true,\n                    \"platform\": \"android\"\n                  },\n                  {\n                    \"color\": {\n                      \"alpha\": 0.5,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"dark_mode\": false,\n                    \"platform\": \"web\"\n                  },\n                  {\n                    \"color\": {\n                      \"alpha\": 0.85,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"dark_mode\": true,\n                    \"platform\": \"web\"\n                  }\n                ]\n              },\n              \"size\": {\n                \"height\": \"100%\",\n                \"width\": \"94%\"\n              },\n              \"web\": {}\n            },\n            \"dismiss_on_touch_outside\": false,\n            \"placement_selectors\": [\n              {\n                \"orientation\": \"portrait\",\n                \"placement\": {\n                  \"border\": {\n                    \"radius\": 4,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                        \"type\": \"hex\"\n                      },\n                      \"selectors\": [\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": false,\n                          \"platform\": \"ios\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": true,\n                          \"platform\": \"ios\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": false,\n                          \"platform\": \"android\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": true,\n                          \"platform\": \"android\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": false,\n                          \"platform\": \"web\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": true,\n                          \"platform\": \"web\"\n                        }\n                      ]\n                    }\n                  },\n                  \"ignore_safe_area\": false,\n                  \"position\": {\n                    \"horizontal\": \"center\",\n                    \"vertical\": \"center\"\n                  },\n                  \"shade_color\": {\n                    \"default\": {\n                      \"alpha\": 0.5,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"selectors\": [\n                      {\n                        \"color\": {\n                          \"alpha\": 0.5,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": false,\n                        \"platform\": \"ios\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.85,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": true,\n                        \"platform\": \"ios\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.5,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": false,\n                        \"platform\": \"android\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.85,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": true,\n                        \"platform\": \"android\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.5,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": false,\n                        \"platform\": \"web\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.85,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": true,\n                        \"platform\": \"web\"\n                      }\n                    ]\n                  },\n                  \"size\": {\n                    \"height\": 800,\n                    \"width\": 400\n                  },\n                  \"web\": {\n                    \"ignore_shade\": false\n                  }\n                },\n                \"window_size\": \"large\"\n              },\n              {\n                \"orientation\": \"landscape\",\n                \"placement\": {\n                  \"border\": {\n                    \"radius\": 4,\n                    \"stroke_color\": {\n                      \"default\": {\n                        \"alpha\": 1,\n                        \"hex\": \"#000000\",\n                        \"type\": \"hex\"\n                      },\n                      \"selectors\": [\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": false,\n                          \"platform\": \"ios\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": true,\n                          \"platform\": \"ios\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": false,\n                          \"platform\": \"android\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": true,\n                          \"platform\": \"android\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": false,\n                          \"platform\": \"web\"\n                        },\n                        {\n                          \"color\": {\n                            \"alpha\": 1,\n                            \"hex\": \"#000000\",\n                            \"type\": \"hex\"\n                          },\n                          \"dark_mode\": true,\n                          \"platform\": \"web\"\n                        }\n                      ]\n                    }\n                  },\n                  \"ignore_safe_area\": false,\n                  \"position\": {\n                    \"horizontal\": \"center\",\n                    \"vertical\": \"center\"\n                  },\n                  \"shade_color\": {\n                    \"default\": {\n                      \"alpha\": 0.5,\n                      \"hex\": \"#111B40\",\n                      \"type\": \"hex\"\n                    },\n                    \"selectors\": [\n                      {\n                        \"color\": {\n                          \"alpha\": 0.5,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": false,\n                        \"platform\": \"ios\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.85,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": true,\n                        \"platform\": \"ios\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.5,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": false,\n                        \"platform\": \"android\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.85,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": true,\n                        \"platform\": \"android\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.5,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": false,\n                        \"platform\": \"web\"\n                      },\n                      {\n                        \"color\": {\n                          \"alpha\": 0.85,\n                          \"hex\": \"#111B40\",\n                          \"type\": \"hex\"\n                        },\n                        \"dark_mode\": true,\n                        \"platform\": \"web\"\n                      }\n                    ]\n                  },\n                  \"size\": {\n                    \"height\": 800,\n                    \"width\": 400\n                  },\n                  \"web\": {\n                    \"ignore_shade\": false\n                  }\n                },\n                \"window_size\": \"large\"\n              }\n            ],\n            \"type\": \"modal\"\n          },\n          \"version\": 1,\n          \"view\": {\n            \"type\": \"state_controller\",\n            \"view\": {\n              \"identifier\": \"7c1c2a01-ea24-4bf9-bad8-f98dadb88778\",\n              \"type\": \"pager_controller\",\n              \"view\": {\n                \"direction\": \"vertical\",\n                \"items\": [\n                  {\n                    \"size\": {\n                      \"height\": \"100%\",\n                      \"width\": \"100%\"\n                    },\n                    \"view\": {\n                      \"form_enabled\": [\n                        \"form_submission\"\n                      ],\n                      \"identifier\": \"5d9527b6-bfe4-4640-8770-4481aa0728de\",\n                      \"response_type\": \"user_feedback\",\n                      \"state_triggers\": [\n                        {\n                          \"identifier\": \"SHOW_TOAST\",\n                          \"on_trigger\": {\n                            \"state_actions\": [\n                              {\n                                \"key\": \"ASYNC_VALIDATION_TOAST\",\n                                \"ttl_seconds\": 1.5,\n                                \"type\": \"set\",\n                                \"value\": true\n                              }\n                            ]\n                          },\n                          \"reset_when_state_matches\": {\n                            \"not\": {\n                              \"scope\": [\n                                \"$forms\",\n                                \"current\",\n                                \"status\",\n                                \"type\"\n                              ],\n                              \"value\": {\n                                \"equals\": \"error\"\n                              }\n                            }\n                          },\n                          \"trigger_when_state_matches\": {\n                            \"scope\": [\n                              \"$forms\",\n                              \"current\",\n                              \"status\",\n                              \"type\"\n                            ],\n                            \"value\": {\n                              \"equals\": \"error\"\n                            }\n                          }\n                        }\n                      ],\n                      \"submit\": \"submit_event\",\n                      \"type\": \"form_controller\",\n                      \"validation_mode\": {\n                        \"type\": \"on_demand\"\n                      },\n                      \"view\": {\n                        \"items\": [\n                          {\n                            \"identifier\": \"b1ce7969-7e6d-4732-bc6b-c8fc282bc93b_pager_container_item\",\n                            \"ignore_safe_area\": false,\n                            \"position\": {\n                              \"horizontal\": \"center\",\n                              \"vertical\": \"center\"\n                            },\n                            \"size\": {\n                              \"height\": \"100%\",\n                              \"width\": \"100%\"\n                            },\n                            \"view\": {\n                              \"disable_swipe\": true,\n                              \"items\": [\n                                {\n                                  \"identifier\": \"92ea4467-5996-44a0-8aa7-4be1471568ec\",\n                                  \"state_actions\": [\n                                    {\n                                      \"key\": \"92ea4467-5996-44a0-8aa7-4be1471568ec_next\",\n                                      \"type\": \"set\"\n                                    }\n                                  ],\n                                  \"type\": \"pager_item\",\n                                  \"view\": {\n                                    \"background_color\": {\n                                      \"default\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"selectors\": [\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"ios\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"ios\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"android\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"android\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"web\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 1,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"web\"\n                                        }\n                                      ]\n                                    },\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"73e8c2b9-a7f2-46b7-b108-55d2686bc136_background_image_container\",\n                                        \"ignore_safe_area\": false,\n                                        \"margin\": {\n                                          \"end\": 0,\n                                          \"start\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"media_fit\": \"fit_crop\",\n                                          \"media_type\": \"image\",\n                                          \"position\": {\n                                            \"horizontal\": \"center\",\n                                            \"vertical\": \"center\"\n                                          },\n                                          \"type\": \"media\",\n                                          \"url\": \"https://c00316-dl.urbanairship.com/binary/public/QpqQ9YfNQWut6CYpm0sQbw/dc668b35-bdd2-4b7f-b1b5-2562b3e128ff\"\n                                        }\n                                      },\n                                      {\n                                        \"identifier\": \"d2097722-3ae1-4c9b-9270-8dc63337b9f3_main_view_container_item\",\n                                        \"ignore_safe_area\": false,\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"63095da3-63dc-492d-ae17-340c8febd897_container_item\",\n                                              \"margin\": {\n                                                \"bottom\": 0,\n                                                \"end\": 0,\n                                                \"start\": 0,\n                                                \"top\": 0\n                                              },\n                                              \"position\": {\n                                                \"horizontal\": \"center\",\n                                                \"vertical\": \"center\"\n                                              },\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"scroll_container\",\n                                                    \"size\": {\n                                                      \"height\": \"100%\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"direction\": \"vertical\",\n                                                      \"type\": \"scroll_layout\",\n                                                      \"view\": {\n                                                        \"direction\": \"vertical\",\n                                                        \"items\": [\n                                                          {\n                                                            \"identifier\": \"48e7477f-dfdc-4ecc-ab25-f1bea4b86d03_wrapper\",\n                                                            \"margin\": {\n                                                              \"bottom\": 0,\n                                                              \"end\": 16,\n                                                              \"start\": 20,\n                                                              \"top\": 48\n                                                            },\n                                                            \"size\": {\n                                                              \"height\": \"auto\",\n                                                              \"width\": \"100%\"\n                                                            },\n                                                            \"view\": {\n                                                              \"items\": [\n                                                                {\n                                                                  \"identifier\": \"48e7477f-dfdc-4ecc-ab25-f1bea4b86d03_container\",\n                                                                  \"position\": {\n                                                                    \"horizontal\": \"start\",\n                                                                    \"vertical\": \"center\"\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"auto\",\n                                                                    \"width\": \"80%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"accessibility_hidden\": false,\n                                                                    \"content_description\": \"Wanna be in the know when our fares go low?\",\n                                                                    \"labels\": {\n                                                                      \"type\": \"labels\",\n                                                                      \"view_id\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3\",\n                                                                      \"view_type\": \"text_input\"\n                                                                    },\n                                                                    \"text\": \"Wanna be in the know when our fares go low?\",\n                                                                    \"text_appearance\": {\n                                                                      \"alignment\": \"start\",\n                                                                      \"color\": {\n                                                                        \"default\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"selectors\": [\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"web\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"web\"\n                                                                          }\n                                                                        ]\n                                                                      },\n                                                                      \"font_families\": [\n                                                                        \"sans-serif\"\n                                                                      ],\n                                                                      \"font_size\": 30,\n                                                                      \"styles\": [\n                                                                        \"bold\"\n                                                                      ]\n                                                                    },\n                                                                    \"type\": \"label\"\n                                                                  }\n                                                                }\n                                                              ],\n                                                              \"type\": \"container\"\n                                                            }\n                                                          },\n                                                          {\n                                                            \"identifier\": \"921bd845-482c-4b37-b6e0-abcb86b87883_wrapper\",\n                                                            \"margin\": {\n                                                              \"bottom\": 8,\n                                                              \"end\": 16,\n                                                              \"start\": 20,\n                                                              \"top\": 12\n                                                            },\n                                                            \"size\": {\n                                                              \"height\": \"auto\",\n                                                              \"width\": \"100%\"\n                                                            },\n                                                            \"view\": {\n                                                              \"items\": [\n                                                                {\n                                                                  \"identifier\": \"921bd845-482c-4b37-b6e0-abcb86b87883_container\",\n                                                                  \"position\": {\n                                                                    \"horizontal\": \"start\",\n                                                                    \"vertical\": \"center\"\n                                                                  },\n                                                                  \"size\": {\n                                                                    \"height\": \"auto\",\n                                                                    \"width\": \"80%\"\n                                                                  },\n                                                                  \"view\": {\n                                                                    \"accessibility_hidden\": false,\n                                                                    \"content_description\": \"Enter your phone number if you would LUV® to receive promotional texts* from Southwest®.\",\n                                                                    \"text\": \"Enter your phone number if you would LUV® to receive promotional texts* from Southwest®.\",\n                                                                    \"text_appearance\": {\n                                                                      \"alignment\": \"start\",\n                                                                      \"color\": {\n                                                                        \"default\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFFFFF\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"selectors\": [\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"web\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#FFFFFF\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"web\"\n                                                                          }\n                                                                        ]\n                                                                      },\n                                                                      \"font_families\": [\n                                                                        \"sans-serif\"\n                                                                      ],\n                                                                      \"font_size\": 16,\n                                                                      \"styles\": []\n                                                                    },\n                                                                    \"type\": \"label\"\n                                                                  }\n                                                                }\n                                                              ],\n                                                              \"type\": \"container\"\n                                                            }\n                                                          },\n                                                          {\n                                                            \"identifier\": \"03abf9bd-c4d4-4d8d-9026-65d6c0a77a0e_linear_layout_item\",\n                                                            \"size\": {\n                                                              \"height\": \"100%\",\n                                                              \"width\": \"100%\"\n                                                            },\n                                                            \"view\": {\n                                                              \"direction\": \"horizontal\",\n                                                              \"items\": [],\n                                                              \"type\": \"linear_layout\"\n                                                            }\n                                                          }\n                                                        ],\n                                                        \"type\": \"linear_layout\"\n                                                      }\n                                                    }\n                                                  },\n                                                  {\n                                                    \"identifier\": \"74c54b2f-fa5f-42c4-a3eb-4cb1e5014763\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 8\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"items\": [\n                                                        {\n                                                          \"identifier\": \"d2a22ce9-127d-45af-a16d-2ab682765163\",\n                                                          \"margin\": {\n                                                            \"end\": 0,\n                                                            \"start\": 0\n                                                          },\n                                                          \"position\": {\n                                                            \"horizontal\": \"center\",\n                                                            \"vertical\": \"center\"\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"media_fit\": \"center_crop\",\n                                                            \"media_type\": \"image\",\n                                                            \"type\": \"media\",\n                                                            \"url\": \"https://c00316-dl.urbanairship.com/binary/public/QpqQ9YfNQWut6CYpm0sQbw/63083f27-a09f-499c-b2bb-72c7028e6240\"\n                                                          }\n                                                        },\n                                                        {\n                                                          \"identifier\": \"74c54b2f-fa5f-42c4-a3eb-4cb1e5014763_linear_container\",\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"end\": 0,\n                                                            \"start\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"position\": {\n                                                            \"horizontal\": \"center\",\n                                                            \"vertical\": \"bottom\"\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 0\n                                                            },\n                                                            \"direction\": \"vertical\",\n                                                            \"items\": [\n                                                              {\n                                                                \"identifier\": \"1e3593cc-1870-4e96-8a32-c1b35d37b1dd\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 8,\n                                                                  \"end\": 25,\n                                                                  \"start\": 20,\n                                                                  \"top\": 15\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"accessibility_hidden\": false,\n                                                                  \"content_description\": \"*Msg & data rates may apply. Texts may be automated; msg frequency varies. Consent to texts is not a requirement or condition of purchase. Text STOP to 70139 to opt-out (a confirmation message may be sent). Subject to [Terms & Conditions](https://www.southwest.com/about-southwest/terms-and-conditions/?clk=CB674705) and [Privacy Policy](https://www.southwest.com/about-southwest/terms-and-conditions/privacy-policy/index.html?clk=CB674705).\",\n                                                                  \"text\": \"*Msg & data rates may apply. Texts may be automated; msg frequency varies. Consent to texts is not a requirement or condition of purchase. Text STOP to 70139 to opt-out (a confirmation message may be sent). Subject to [Terms & Conditions](https://www.southwest.com/about-southwest/terms-and-conditions/?clk=CB674705) and [Privacy Policy](https://www.southwest.com/about-southwest/terms-and-conditions/privacy-policy/index.html?clk=CB674705).\",\n                                                                  \"text_appearance\": {\n                                                                    \"alignment\": \"start\",\n                                                                    \"color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#000000\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#000000\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#000000\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#000000\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#000000\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#000000\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#000000\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    },\n                                                                    \"font_families\": [\n                                                                      \"sans-serif\"\n                                                                    ],\n                                                                    \"font_size\": 11,\n                                                                    \"styles\": []\n                                                                  },\n                                                                  \"type\": \"label\"\n                                                                }\n                                                              },\n                                                              {\n                                                                \"identifier\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 0,\n                                                                  \"end\": 20,\n                                                                  \"start\": 20,\n                                                                  \"top\": 0\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": 50,\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"direction\": \"vertical\",\n                                                                  \"items\": [\n                                                                    {\n                                                                      \"margin\": {\n                                                                        \"bottom\": 8,\n                                                                        \"top\": 4\n                                                                      },\n                                                                      \"size\": {\n                                                                        \"height\": 50,\n                                                                        \"width\": \"100%\"\n                                                                      },\n                                                                      \"view\": {\n                                                                        \"background_color\": {\n                                                                          \"default\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFFFFF\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"selectors\": [\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": false,\n                                                                              \"platform\": \"ios\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": true,\n                                                                              \"platform\": \"ios\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": false,\n                                                                              \"platform\": \"android\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": true,\n                                                                              \"platform\": \"android\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": false,\n                                                                              \"platform\": \"web\"\n                                                                            },\n                                                                            {\n                                                                              \"color\": {\n                                                                                \"alpha\": 1,\n                                                                                \"hex\": \"#FFFFFF\",\n                                                                                \"type\": \"hex\"\n                                                                              },\n                                                                              \"dark_mode\": true,\n                                                                              \"platform\": \"web\"\n                                                                            }\n                                                                          ]\n                                                                        },\n                                                                        \"border\": {\n                                                                          \"radius\": 4,\n                                                                          \"stroke_color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#7B7C84\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"selectors\": [\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#7B7C84\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FFFFFF\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#7B7C84\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FFFFFF\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#7B7C84\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"web\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FFFFFF\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"web\"\n                                                                              }\n                                                                            ]\n                                                                          },\n                                                                          \"stroke_width\": 1\n                                                                        },\n                                                                        \"content_description\": \"Phone number\",\n                                                                        \"identifier\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3\",\n                                                                        \"input_type\": \"sms\",\n                                                                        \"locales\": [\n                                                                          {\n                                                                            \"country_code\": \"US\",\n                                                                            \"prefix\": \"+1\",\n                                                                            \"registration\": {\n                                                                              \"sender_id\": \"70139\",\n                                                                              \"type\": \"opt_in\"\n                                                                            }\n                                                                          }\n                                                                        ],\n                                                                        \"on_edit\": {\n                                                                          \"state_actions\": [\n                                                                            {\n                                                                              \"key\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\",\n                                                                              \"type\": \"set\"\n                                                                            }\n                                                                          ]\n                                                                        },\n                                                                        \"on_error\": {\n                                                                          \"state_actions\": [\n                                                                            {\n                                                                              \"key\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\",\n                                                                              \"type\": \"set\",\n                                                                              \"value\": false\n                                                                            }\n                                                                          ]\n                                                                        },\n                                                                        \"on_valid\": {\n                                                                          \"state_actions\": [\n                                                                            {\n                                                                              \"key\": \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\",\n                                                                              \"type\": \"set\",\n                                                                              \"value\": true\n                                                                            }\n                                                                          ]\n                                                                        },\n                                                                        \"place_holder\": \"* Phone number\",\n                                                                        \"required\": true,\n                                                                        \"text_appearance\": {\n                                                                          \"alignment\": \"start\",\n                                                                          \"color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"selectors\": [\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"web\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"web\"\n                                                                              }\n                                                                            ]\n                                                                          },\n                                                                          \"font_families\": [\n                                                                            \"sans-serif\"\n                                                                          ],\n                                                                          \"font_size\": 16,\n                                                                          \"place_holder_color\": {\n                                                                            \"default\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#000000\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"selectors\": [\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#000000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FFFFFF\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"ios\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#000000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FFFFFF\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"android\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#000000\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": false,\n                                                                                \"platform\": \"web\"\n                                                                              },\n                                                                              {\n                                                                                \"color\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#FFFFFF\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"dark_mode\": true,\n                                                                                \"platform\": \"web\"\n                                                                              }\n                                                                            ]\n                                                                          }\n                                                                        },\n                                                                        \"type\": \"text_input\",\n                                                                        \"view_overrides\": {\n                                                                          \"border\": [\n                                                                            {\n                                                                              \"value\": {\n                                                                                \"radius\": 4,\n                                                                                \"stroke_color\": {\n                                                                                  \"default\": {\n                                                                                    \"alpha\": 0.5,\n                                                                                    \"hex\": \"#ff0000\",\n                                                                                    \"type\": \"hex\"\n                                                                                  }\n                                                                                },\n                                                                                \"stroke_width\": 1\n                                                                              },\n                                                                              \"when_state_matches\": {\n                                                                                \"scope\": [\n                                                                                  \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\"\n                                                                                ],\n                                                                                \"value\": {\n                                                                                  \"equals\": false\n                                                                                }\n                                                                              }\n                                                                            },\n                                                                            {\n                                                                              \"value\": {\n                                                                                \"radius\": 4,\n                                                                                \"stroke_color\": {\n                                                                                  \"default\": {\n                                                                                    \"alpha\": 1,\n                                                                                    \"hex\": \"#cccccc\",\n                                                                                    \"type\": \"hex\"\n                                                                                  }\n                                                                                },\n                                                                                \"stroke_width\": 1\n                                                                              },\n                                                                              \"when_state_matches\": {\n                                                                                \"scope\": [\n                                                                                  \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\"\n                                                                                ],\n                                                                                \"value\": {\n                                                                                  \"equals\": true\n                                                                                }\n                                                                              }\n                                                                            }\n                                                                          ],\n                                                                          \"icon_end\": [\n                                                                            {\n                                                                              \"value\": {\n                                                                                \"icon\": {\n                                                                                  \"color\": {\n                                                                                    \"default\": {\n                                                                                      \"alpha\": 0.5,\n                                                                                      \"hex\": \"#ff0000\",\n                                                                                      \"type\": \"hex\"\n                                                                                    }\n                                                                                  },\n                                                                                  \"icon\": \"exclamationmark_circle_fill\",\n                                                                                  \"scale\": 1,\n                                                                                  \"type\": \"icon\"\n                                                                                },\n                                                                                \"type\": \"floating\"\n                                                                              },\n                                                                              \"when_state_matches\": {\n                                                                                \"scope\": [\n                                                                                  \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\"\n                                                                                ],\n                                                                                \"value\": {\n                                                                                  \"equals\": false\n                                                                                }\n                                                                              }\n                                                                            },\n                                                                            {\n                                                                              \"value\": {\n                                                                                \"icon\": {\n                                                                                  \"color\": {\n                                                                                    \"default\": {\n                                                                                      \"alpha\": 0.5,\n                                                                                      \"hex\": \"#00ff00\",\n                                                                                      \"type\": \"hex\"\n                                                                                    }\n                                                                                  },\n                                                                                  \"icon\": \"checkmark\",\n                                                                                  \"scale\": 1,\n                                                                                  \"type\": \"icon\"\n                                                                                },\n                                                                                \"type\": \"floating\"\n                                                                              },\n                                                                              \"when_state_matches\": {\n                                                                                \"scope\": [\n                                                                                  \"88c75d1b-8392-4ca7-88d0-289d7bd48aa3_sms_is_valid\"\n                                                                                ],\n                                                                                \"value\": {\n                                                                                  \"equals\": true\n                                                                                }\n                                                                              }\n                                                                            }\n                                                                          ]\n                                                                        }\n                                                                      }\n                                                                    }\n                                                                  ],\n                                                                  \"type\": \"linear_layout\"\n                                                                }\n                                                              },\n                                                              {\n                                                                \"identifier\": \"2f86c07c-cb4d-4f21-a1fb-f237aa107a06\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 20,\n                                                                  \"end\": 20,\n                                                                  \"start\": 20,\n                                                                  \"top\": 8\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": 48,\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"actions\": {},\n                                                                  \"background_color\": {\n                                                                    \"default\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"selectors\": [\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFBF27\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFBF27\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFBF27\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFBF27\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFBF27\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"web\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#FFBF27\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"web\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"border\": {\n                                                                    \"radius\": 4,\n                                                                    \"stroke_color\": {\n                                                                      \"default\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"selectors\": [\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFBF27\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFBF27\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"ios\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFBF27\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFBF27\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"android\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFBF27\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": false,\n                                                                          \"platform\": \"web\"\n                                                                        },\n                                                                        {\n                                                                          \"color\": {\n                                                                            \"alpha\": 1,\n                                                                            \"hex\": \"#FFBF27\",\n                                                                            \"type\": \"hex\"\n                                                                          },\n                                                                          \"dark_mode\": true,\n                                                                          \"platform\": \"web\"\n                                                                        }\n                                                                      ]\n                                                                    },\n                                                                    \"stroke_width\": 0\n                                                                  },\n                                                                  \"button_click\": [\n                                                                    \"form_submit\",\n                                                                    \"pager_next\"\n                                                                  ],\n                                                                  \"content_description\": \"Sign up\",\n                                                                  \"enabled\": [\n                                                                    \"form_validation\"\n                                                                  ],\n                                                                  \"event_handlers\": [\n                                                                    {\n                                                                      \"state_actions\": [\n                                                                        {\n                                                                          \"key\": \"submitted\",\n                                                                          \"type\": \"set\",\n                                                                          \"value\": true\n                                                                        }\n                                                                      ],\n                                                                      \"type\": \"tap\"\n                                                                    }\n                                                                  ],\n                                                                  \"identifier\": \"submit_feedback--Sign up\",\n                                                                  \"label\": {\n                                                                    \"content_description\": \"Sign up\",\n                                                                    \"text\": \"Sign up\",\n                                                                    \"text_appearance\": {\n                                                                      \"alignment\": \"center\",\n                                                                      \"color\": {\n                                                                        \"default\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"selectors\": [\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"ios\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"android\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": false,\n                                                                            \"platform\": \"web\"\n                                                                          },\n                                                                          {\n                                                                            \"color\": {\n                                                                              \"alpha\": 1,\n                                                                              \"hex\": \"#111b40\",\n                                                                              \"type\": \"hex\"\n                                                                            },\n                                                                            \"dark_mode\": true,\n                                                                            \"platform\": \"web\"\n                                                                          }\n                                                                        ]\n                                                                      },\n                                                                      \"font_families\": [\n                                                                        \"Arial\"\n                                                                      ],\n                                                                      \"font_size\": 20,\n                                                                      \"styles\": [\n                                                                        \"bold\"\n                                                                      ]\n                                                                    },\n                                                                    \"type\": \"label\",\n                                                                    \"view_overrides\": {\n                                                                      \"icon_start\": [\n                                                                        {\n                                                                          \"value\": {\n                                                                            \"icon\": {\n                                                                              \"color\": {\n                                                                                \"default\": {\n                                                                                  \"alpha\": 1,\n                                                                                  \"hex\": \"#111b40\",\n                                                                                  \"type\": \"hex\"\n                                                                                },\n                                                                                \"selectors\": [\n                                                                                  {\n                                                                                    \"color\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#111b40\",\n                                                                                      \"type\": \"hex\"\n                                                                                    },\n                                                                                    \"dark_mode\": false,\n                                                                                    \"platform\": \"ios\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"color\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#111b40\",\n                                                                                      \"type\": \"hex\"\n                                                                                    },\n                                                                                    \"dark_mode\": true,\n                                                                                    \"platform\": \"ios\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"color\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#111b40\",\n                                                                                      \"type\": \"hex\"\n                                                                                    },\n                                                                                    \"dark_mode\": false,\n                                                                                    \"platform\": \"android\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"color\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#111b40\",\n                                                                                      \"type\": \"hex\"\n                                                                                    },\n                                                                                    \"dark_mode\": true,\n                                                                                    \"platform\": \"android\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"color\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#111b40\",\n                                                                                      \"type\": \"hex\"\n                                                                                    },\n                                                                                    \"dark_mode\": false,\n                                                                                    \"platform\": \"web\"\n                                                                                  },\n                                                                                  {\n                                                                                    \"color\": {\n                                                                                      \"alpha\": 1,\n                                                                                      \"hex\": \"#111b40\",\n                                                                                      \"type\": \"hex\"\n                                                                                    },\n                                                                                    \"dark_mode\": true,\n                                                                                    \"platform\": \"web\"\n                                                                                  }\n                                                                                ]\n                                                                              },\n                                                                              \"icon\": \"progress_spinner\",\n                                                                              \"scale\": 1,\n                                                                              \"type\": \"icon\"\n                                                                            },\n                                                                            \"space\": 8,\n                                                                            \"type\": \"floating\"\n                                                                          },\n                                                                          \"when_state_matches\": {\n                                                                            \"scope\": [\n                                                                              \"$forms\",\n                                                                              \"current\",\n                                                                              \"status\",\n                                                                              \"type\"\n                                                                            ],\n                                                                            \"value\": {\n                                                                              \"equals\": \"validating\"\n                                                                            }\n                                                                          }\n                                                                        }\n                                                                      ],\n                                                                      \"text\": [\n                                                                        {\n                                                                          \"value\": \"Sign up\",\n                                                                          \"when_state_matches\": {\n                                                                            \"scope\": [\n                                                                              \"$forms\",\n                                                                              \"current\",\n                                                                              \"status\",\n                                                                              \"type\"\n                                                                            ],\n                                                                            \"value\": {\n                                                                              \"equals\": \"validating\"\n                                                                            }\n                                                                          }\n                                                                        }\n                                                                      ]\n                                                                    }\n                                                                  },\n                                                                  \"reporting_metadata\": {\n                                                                    \"button_action\": \"submit_feedback\",\n                                                                    \"button_id\": \"2f86c07c-cb4d-4f21-a1fb-f237aa107a06\",\n                                                                    \"trigger_link_id\": \"2f86c07c-cb4d-4f21-a1fb-f237aa107a06\"\n                                                                  },\n                                                                  \"type\": \"label_button\"\n                                                                }\n                                                              },\n                                                              {\n                                                                \"identifier\": \"8ce302ea-275d-4135-a676-21bae5aca6a5_wrapper\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 0,\n                                                                  \"end\": 0,\n                                                                  \"start\": 0,\n                                                                  \"top\": 0\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"items\": [\n                                                                    {\n                                                                      \"identifier\": \"8ce302ea-275d-4135-a676-21bae5aca6a5_container\",\n                                                                      \"position\": {\n                                                                        \"horizontal\": \"center\",\n                                                                        \"vertical\": \"bottom\"\n                                                                      },\n                                                                      \"size\": {\n                                                                        \"height\": \"auto\",\n                                                                        \"width\": \"100%\"\n                                                                      },\n                                                                      \"view\": {\n                                                                        \"media_fit\": \"center_inside\",\n                                                                        \"media_type\": \"image\",\n                                                                        \"type\": \"media\",\n                                                                        \"url\": \"https://c00316-dl.urbanairship.com/binary/public/QpqQ9YfNQWut6CYpm0sQbw/da77da59-5f26-4bc0-9078-4500956ec83a\"\n                                                                      }\n                                                                    }\n                                                                  ],\n                                                                  \"type\": \"container\"\n                                                                }\n                                                              }\n                                                            ],\n                                                            \"type\": \"linear_layout\"\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"container\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"linear_layout\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"container\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                },\n                                {\n                                  \"identifier\": \"5b8ead60-c42e-4944-8ad8-5e652b7713c6\",\n                                  \"state_actions\": [\n                                    {\n                                      \"key\": \"5b8ead60-c42e-4944-8ad8-5e652b7713c6_next\",\n                                      \"type\": \"set\"\n                                    }\n                                  ],\n                                  \"type\": \"pager_item\",\n                                  \"view\": {\n                                    \"background_color\": {\n                                      \"default\": {\n                                        \"alpha\": 0,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"selectors\": [\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 0,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"ios\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 0,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"ios\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 0,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"android\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 0,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"android\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 0,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": false,\n                                          \"platform\": \"web\"\n                                        },\n                                        {\n                                          \"color\": {\n                                            \"alpha\": 0,\n                                            \"hex\": \"#FFFFFF\",\n                                            \"type\": \"hex\"\n                                          },\n                                          \"dark_mode\": true,\n                                          \"platform\": \"web\"\n                                        }\n                                      ]\n                                    },\n                                    \"items\": [\n                                      {\n                                        \"identifier\": \"cfea8c28-624d-486f-b7fe-bfc921b914a2_background_image_container\",\n                                        \"ignore_safe_area\": false,\n                                        \"margin\": {\n                                          \"end\": 0,\n                                          \"start\": 0\n                                        },\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"media_fit\": \"fit_crop\",\n                                          \"media_type\": \"image\",\n                                          \"position\": {\n                                            \"horizontal\": \"center\",\n                                            \"vertical\": \"center\"\n                                          },\n                                          \"type\": \"media\",\n                                          \"url\": \"https://c00316-dl.urbanairship.com/binary/public/QpqQ9YfNQWut6CYpm0sQbw/51f2ae07-3bc0-4313-802f-9018b0bf17bf\"\n                                        }\n                                      },\n                                      {\n                                        \"identifier\": \"99a24c81-46e4-4ff1-ae2a-a98f88c5baa7_main_view_container_item\",\n                                        \"ignore_safe_area\": false,\n                                        \"position\": {\n                                          \"horizontal\": \"center\",\n                                          \"vertical\": \"center\"\n                                        },\n                                        \"size\": {\n                                          \"height\": \"100%\",\n                                          \"width\": \"100%\"\n                                        },\n                                        \"view\": {\n                                          \"items\": [\n                                            {\n                                              \"identifier\": \"ab2d17a4-af95-42aa-a401-af8eb1ab6334_container_item\",\n                                              \"margin\": {\n                                                \"bottom\": 0,\n                                                \"end\": 0,\n                                                \"start\": 0,\n                                                \"top\": 0\n                                              },\n                                              \"position\": {\n                                                \"horizontal\": \"center\",\n                                                \"vertical\": \"center\"\n                                              },\n                                              \"size\": {\n                                                \"height\": \"100%\",\n                                                \"width\": \"100%\"\n                                              },\n                                              \"view\": {\n                                                \"direction\": \"vertical\",\n                                                \"items\": [\n                                                  {\n                                                    \"identifier\": \"scroll_container\",\n                                                    \"size\": {\n                                                      \"height\": \"100%\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"direction\": \"vertical\",\n                                                      \"type\": \"scroll_layout\",\n                                                      \"view\": {\n                                                        \"direction\": \"vertical\",\n                                                        \"items\": [\n                                                          {\n                                                            \"identifier\": \"be50dce5-422e-4824-9f51-96fe3ec93cd1\",\n                                                            \"margin\": {\n                                                              \"bottom\": 8,\n                                                              \"end\": 20,\n                                                              \"start\": 20,\n                                                              \"top\": 80\n                                                            },\n                                                            \"size\": {\n                                                              \"height\": \"auto\",\n                                                              \"width\": \"100%\"\n                                                            },\n                                                            \"view\": {\n                                                              \"accessibility_hidden\": false,\n                                                              \"content_description\": \"You're now prepared for takeoff. Look for a confirmation text from 70139 soon. You will need to reply \\\"Y\\\" to confirm your subscription.\",\n                                                              \"text\": \"You're now prepared for takeoff. Look for a confirmation text from 70139 soon. You will need to reply \\\"Y\\\" to confirm your subscription.\",\n                                                              \"text_appearance\": {\n                                                                \"alignment\": \"start\",\n                                                                \"color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFFFFF\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"font_families\": [\n                                                                  \"sans-serif\"\n                                                                ],\n                                                                \"font_size\": 23,\n                                                                \"styles\": []\n                                                              },\n                                                              \"type\": \"label\"\n                                                            }\n                                                          },\n                                                          {\n                                                            \"identifier\": \"bf5f0542-cfb8-4868-a6bd-13c1a0becb84\",\n                                                            \"margin\": {\n                                                              \"bottom\": 8,\n                                                              \"end\": 16,\n                                                              \"start\": 16,\n                                                              \"top\": 8\n                                                            },\n                                                            \"size\": {\n                                                              \"height\": 48,\n                                                              \"width\": \"100%\"\n                                                            },\n                                                            \"view\": {\n                                                              \"actions\": {},\n                                                              \"background_color\": {\n                                                                \"default\": {\n                                                                  \"alpha\": 1,\n                                                                  \"hex\": \"#FFBF27\",\n                                                                  \"type\": \"hex\"\n                                                                },\n                                                                \"selectors\": [\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"ios\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"android\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": false,\n                                                                    \"platform\": \"web\"\n                                                                  },\n                                                                  {\n                                                                    \"color\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#FFBF27\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"dark_mode\": true,\n                                                                    \"platform\": \"web\"\n                                                                  }\n                                                                ]\n                                                              },\n                                                              \"border\": {\n                                                                \"radius\": 4,\n                                                                \"stroke_color\": {\n                                                                  \"default\": {\n                                                                    \"alpha\": 1,\n                                                                    \"hex\": \"#FFBF27\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"selectors\": [\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"ios\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"android\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": false,\n                                                                      \"platform\": \"web\"\n                                                                    },\n                                                                    {\n                                                                      \"color\": {\n                                                                        \"alpha\": 1,\n                                                                        \"hex\": \"#FFBF27\",\n                                                                        \"type\": \"hex\"\n                                                                      },\n                                                                      \"dark_mode\": true,\n                                                                      \"platform\": \"web\"\n                                                                    }\n                                                                  ]\n                                                                },\n                                                                \"stroke_width\": 0\n                                                              },\n                                                              \"button_click\": [\n                                                                \"dismiss\"\n                                                              ],\n                                                              \"content_description\": \"Dismiss\",\n                                                              \"enabled\": [],\n                                                              \"identifier\": \"dismiss--Dismiss\",\n                                                              \"label\": {\n                                                                \"content_description\": \"Dismiss\",\n                                                                \"text\": \"Dismiss\",\n                                                                \"text_appearance\": {\n                                                                  \"alignment\": \"center\",\n                                                                  \"color\": {\n                                                                    \"default\": {\n                                                                      \"alpha\": 1,\n                                                                      \"hex\": \"#111b40\",\n                                                                      \"type\": \"hex\"\n                                                                    },\n                                                                    \"selectors\": [\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"ios\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"android\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": false,\n                                                                        \"platform\": \"web\"\n                                                                      },\n                                                                      {\n                                                                        \"color\": {\n                                                                          \"alpha\": 1,\n                                                                          \"hex\": \"#111b40\",\n                                                                          \"type\": \"hex\"\n                                                                        },\n                                                                        \"dark_mode\": true,\n                                                                        \"platform\": \"web\"\n                                                                      }\n                                                                    ]\n                                                                  },\n                                                                  \"font_families\": [\n                                                                    \"Arial\"\n                                                                  ],\n                                                                  \"font_size\": 20,\n                                                                  \"styles\": [\n                                                                    \"bold\"\n                                                                  ]\n                                                                },\n                                                                \"type\": \"label\"\n                                                              },\n                                                              \"localized_content_description\": {\n                                                                \"fallback\": \"Dismiss\",\n                                                                \"ref\": \"ua_dismiss\"\n                                                              },\n                                                              \"reporting_metadata\": {\n                                                                \"button_action\": \"dismiss\",\n                                                                \"button_id\": \"bf5f0542-cfb8-4868-a6bd-13c1a0becb84\",\n                                                                \"trigger_link_id\": \"bf5f0542-cfb8-4868-a6bd-13c1a0becb84\"\n                                                              },\n                                                              \"type\": \"label_button\"\n                                                            }\n                                                          },\n                                                          {\n                                                            \"identifier\": \"7fd4c4fc-7b29-410b-9f12-fedc1a6b1c19_linear_layout_item\",\n                                                            \"size\": {\n                                                              \"height\": \"100%\",\n                                                              \"width\": \"100%\"\n                                                            },\n                                                            \"view\": {\n                                                              \"direction\": \"horizontal\",\n                                                              \"items\": [],\n                                                              \"type\": \"linear_layout\"\n                                                            }\n                                                          }\n                                                        ],\n                                                        \"type\": \"linear_layout\"\n                                                      }\n                                                    }\n                                                  },\n                                                  {\n                                                    \"identifier\": \"059a1004-41ab-4c26-ba1f-3fea2b4951ea\",\n                                                    \"margin\": {\n                                                      \"bottom\": 0,\n                                                      \"end\": 0,\n                                                      \"start\": 0,\n                                                      \"top\": 28\n                                                    },\n                                                    \"size\": {\n                                                      \"height\": \"auto\",\n                                                      \"width\": \"100%\"\n                                                    },\n                                                    \"view\": {\n                                                      \"items\": [\n                                                        {\n                                                          \"identifier\": \"059a1004-41ab-4c26-ba1f-3fea2b4951ea_linear_container\",\n                                                          \"margin\": {\n                                                            \"bottom\": 0,\n                                                            \"end\": 0,\n                                                            \"start\": 0,\n                                                            \"top\": 0\n                                                          },\n                                                          \"position\": {\n                                                            \"horizontal\": \"center\",\n                                                            \"vertical\": \"center\"\n                                                          },\n                                                          \"size\": {\n                                                            \"height\": \"auto\",\n                                                            \"width\": \"100%\"\n                                                          },\n                                                          \"view\": {\n                                                            \"background_color\": {\n                                                              \"default\": {\n                                                                \"alpha\": 0,\n                                                                \"hex\": \"#FFFFFF\",\n                                                                \"type\": \"hex\"\n                                                              },\n                                                              \"selectors\": [\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"ios\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"android\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": false,\n                                                                  \"platform\": \"web\"\n                                                                },\n                                                                {\n                                                                  \"color\": {\n                                                                    \"alpha\": 0,\n                                                                    \"hex\": \"#FFFFFF\",\n                                                                    \"type\": \"hex\"\n                                                                  },\n                                                                  \"dark_mode\": true,\n                                                                  \"platform\": \"web\"\n                                                                }\n                                                              ]\n                                                            },\n                                                            \"border\": {\n                                                              \"radius\": 0\n                                                            },\n                                                            \"direction\": \"horizontal\",\n                                                            \"items\": [\n                                                              {\n                                                                \"identifier\": \"568ac3eb-d15e-44cd-a12f-d7d71c1d69cf\",\n                                                                \"margin\": {\n                                                                  \"bottom\": 0,\n                                                                  \"end\": 0,\n                                                                  \"start\": 0,\n                                                                  \"top\": 0\n                                                                },\n                                                                \"size\": {\n                                                                  \"height\": \"auto\",\n                                                                  \"width\": \"100%\"\n                                                                },\n                                                                \"view\": {\n                                                                  \"media_fit\": \"center_inside\",\n                                                                  \"media_type\": \"image\",\n                                                                  \"type\": \"media\",\n                                                                  \"url\": \"https://c00316-dl.urbanairship.com/binary/public/QpqQ9YfNQWut6CYpm0sQbw/da77da59-5f26-4bc0-9078-4500956ec83a\"\n                                                                }\n                                                              }\n                                                            ],\n                                                            \"type\": \"linear_layout\"\n                                                          }\n                                                        }\n                                                      ],\n                                                      \"type\": \"container\"\n                                                    }\n                                                  }\n                                                ],\n                                                \"type\": \"linear_layout\"\n                                              }\n                                            }\n                                          ],\n                                          \"type\": \"container\"\n                                        }\n                                      }\n                                    ],\n                                    \"type\": \"container\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"pager\"\n                            }\n                          },\n                          {\n                            \"position\": {\n                              \"horizontal\": \"end\",\n                              \"vertical\": \"top\"\n                            },\n                            \"size\": {\n                              \"height\": 48,\n                              \"width\": 48\n                            },\n                            \"view\": {\n                              \"button_click\": [\n                                \"dismiss\"\n                              ],\n                              \"identifier\": \"dismiss_button\",\n                              \"image\": {\n                                \"color\": {\n                                  \"default\": {\n                                    \"alpha\": 1,\n                                    \"hex\": \"#FFFFFF\",\n                                    \"type\": \"hex\"\n                                  },\n                                  \"selectors\": [\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"ios\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"android\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": false,\n                                      \"platform\": \"web\"\n                                    },\n                                    {\n                                      \"color\": {\n                                        \"alpha\": 1,\n                                        \"hex\": \"#FFFFFF\",\n                                        \"type\": \"hex\"\n                                      },\n                                      \"dark_mode\": true,\n                                      \"platform\": \"web\"\n                                    }\n                                  ]\n                                },\n                                \"icon\": \"close\",\n                                \"scale\": 0.4,\n                                \"type\": \"icon\"\n                              },\n                              \"localized_content_description\": {\n                                \"fallback\": \"Dismiss\",\n                                \"ref\": \"ua_dismiss\"\n                              },\n                              \"reporting_metadata\": {\n                                \"button_action\": \"dismiss\",\n                                \"button_id\": \"dismiss_button\"\n                              },\n                              \"type\": \"image_button\"\n                            }\n                          },\n                          {\n                            \"margin\": {\n                              \"bottom\": 4,\n                              \"end\": 0,\n                              \"start\": 0,\n                              \"top\": 4\n                            },\n                            \"position\": {\n                              \"horizontal\": \"center\",\n                              \"vertical\": \"bottom\"\n                            },\n                            \"size\": {\n                              \"height\": 14,\n                              \"width\": \"100%\"\n                            },\n                            \"view\": {\n                              \"automated_accessibility_actions\": [\n                                {\n                                  \"type\": \"announce\"\n                                }\n                              ],\n                              \"bindings\": {\n                                \"selected\": {\n                                  \"shapes\": [\n                                    {\n                                      \"aspect_ratio\": 2,\n                                      \"border\": {\n                                        \"radius\": 16\n                                      },\n                                      \"color\": {\n                                        \"default\": {\n                                          \"alpha\": 1,\n                                          \"hex\": \"#7B7C84\",\n                                          \"type\": \"hex\"\n                                        },\n                                        \"selectors\": [\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#7B7C84\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": false,\n                                            \"platform\": \"ios\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#FFFFFF\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": true,\n                                            \"platform\": \"ios\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#7B7C84\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": false,\n                                            \"platform\": \"android\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#FFFFFF\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": true,\n                                            \"platform\": \"android\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#7B7C84\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": false,\n                                            \"platform\": \"web\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#FFFFFF\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": true,\n                                            \"platform\": \"web\"\n                                          }\n                                        ]\n                                      },\n                                      \"scale\": 1,\n                                      \"type\": \"rectangle\"\n                                    }\n                                  ]\n                                },\n                                \"unselected\": {\n                                  \"shapes\": [\n                                    {\n                                      \"aspect_ratio\": 1,\n                                      \"color\": {\n                                        \"default\": {\n                                          \"alpha\": 1,\n                                          \"hex\": \"#7B7C84\",\n                                          \"type\": \"hex\"\n                                        },\n                                        \"selectors\": [\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#7B7C84\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": false,\n                                            \"platform\": \"ios\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#FFFFFF\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": true,\n                                            \"platform\": \"ios\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#7B7C84\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": false,\n                                            \"platform\": \"android\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#FFFFFF\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": true,\n                                            \"platform\": \"android\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#7B7C84\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": false,\n                                            \"platform\": \"web\"\n                                          },\n                                          {\n                                            \"color\": {\n                                              \"alpha\": 1,\n                                              \"hex\": \"#FFFFFF\",\n                                              \"type\": \"hex\"\n                                            },\n                                            \"dark_mode\": true,\n                                            \"platform\": \"web\"\n                                          }\n                                        ]\n                                      },\n                                      \"scale\": 0.5,\n                                      \"type\": \"ellipse\"\n                                    }\n                                  ]\n                                }\n                              },\n                              \"spacing\": 0,\n                              \"type\": \"pager_indicator\"\n                            }\n                          },\n                          {\n                            \"margin\": {\n                              \"bottom\": 20,\n                              \"end\": 20,\n                              \"start\": 20,\n                              \"top\": 20\n                            },\n                            \"position\": {\n                              \"horizontal\": \"center\",\n                              \"vertical\": \"bottom\"\n                            },\n                            \"size\": {\n                              \"height\": \"auto\",\n                              \"width\": \"auto\"\n                            },\n                            \"view\": {\n                              \"background_color\": {\n                                \"default\": {\n                                  \"alpha\": 0.8,\n                                  \"hex\": \"#3d4047\",\n                                  \"type\": \"hex\"\n                                }\n                              },\n                              \"border\": {\n                                \"radius\": 20,\n                                \"stroke_color\": {\n                                  \"default\": {\n                                    \"alpha\": 0.8,\n                                    \"hex\": \"#3d4047\",\n                                    \"type\": \"hex\"\n                                  }\n                                },\n                                \"stroke_width\": 1\n                              },\n                              \"items\": [\n                                {\n                                  \"margin\": {\n                                    \"bottom\": 10,\n                                    \"end\": 20,\n                                    \"start\": 20,\n                                    \"top\": 10\n                                  },\n                                  \"position\": {\n                                    \"horizontal\": \"center\",\n                                    \"vertical\": \"center\"\n                                  },\n                                  \"size\": {\n                                    \"height\": \"auto\",\n                                    \"width\": \"auto\"\n                                  },\n                                  \"view\": {\n                                    \"content_description\": \"Error processing form. Please try again\",\n                                    \"text\": \"Error processing form. Please try again\",\n                                    \"text_appearance\": {\n                                      \"alignment\": \"center\",\n                                      \"color\": {\n                                        \"default\": {\n                                          \"alpha\": 1,\n                                          \"hex\": \"#FFFFFF\",\n                                          \"type\": \"hex\"\n                                        }\n                                      },\n                                      \"font_families\": [\n                                        \"sans-serif\"\n                                      ],\n                                      \"font_size\": 16\n                                    },\n                                    \"type\": \"label\"\n                                  }\n                                }\n                              ],\n                              \"type\": \"container\",\n                              \"visibility\": {\n                                \"default\": false,\n                                \"invert_when_state_matches\": {\n                                  \"scope\": [\n                                    \"ASYNC_VALIDATION_TOAST\"\n                                  ],\n                                  \"value\": {\n                                    \"equals\": true\n                                  }\n                                }\n                              }\n                            }\n                          }\n                        ],\n                        \"type\": \"container\"\n                      }\n                    }\n                  }\n                ],\n                \"type\": \"linear_layout\"\n              }\n            }\n          }\n        }\n      },\n      \"display_type\": \"layout\",\n      \"name\": \"TEST 2025-12-11 - Promotional - SMS Acquisition\"\n    },\n    \"reporting_context\": {\n      \"content_types\": [\n        \"scene\",\n        \"survey\"\n      ],\n      \"experiment_id\": \"\",\n      \"is_test\": true\n    },\n    \"requires_eligibility\": false,\n    \"scopes\": [\n      \"app\"\n    ],\n    \"triggers\": [\n      {\n        \"goal\": 1,\n        \"predicate\": {\n          \"or\": [\n            {\n              \"and\": [\n                {\n                  \"ignore_case\": true,\n                  \"key\": \"event_name\",\n                  \"value\": {\n                    \"equals\": \"swa:offers:southwest homepage\"\n                  }\n                },\n                {\n                  \"and\": [\n                    {\n                      \"key\": \"launchedFromDeepLink\",\n                      \"scope\": [\n                        \"properties\"\n                      ],\n                      \"value\": {\n                        \"equals\": \"0\"\n                      }\n                    }\n                  ]\n                }\n              ]\n            }\n          ]\n        },\n        \"type\": \"custom_event_count\"\n      }\n    ]\n  },\n  \"notify\": false\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/mobile-5553-shape-corners.yml",
    "content": "---\n# MOBILE-5553: Verify per-corner radii on rectangle shapes\n# Shapes render inside stack_image_button items.\n# Left column = uniform radius | Right column = per-corner equivalent\n# With the fix both columns should look identical (or correctly asymmetric in rows 2/4).\n# Without the fix the right column would render as plain rectangles.\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 95%\n      height: 90%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: scroll_layout\n  direction: vertical\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    border:\n      radius: 12\n    items:\n    - position:\n        horizontal: center\n        vertical: top\n      size:\n        height: auto\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n\n        # Title\n        - margin:\n            top: 16\n            bottom: 4\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"MOBILE-5553: Shape Corner Radii\"\n            text_appearance:\n              alignment: center\n              font_size: 16\n              styles: [bold]\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 16\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"Left = uniform radius  |  Right = per-corner\\nBoth columns should look identical per row\"\n            text_appearance:\n              alignment: center\n              font_size: 11\n              color:\n                default:\n                  hex: \"#666666\"\n                  alpha: 1\n\n        # ── Row 1: fill-only, all corners equal ──────────────────────\n        - margin:\n            top: 0\n            bottom: 6\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"1. Fill only — all corners = 24\"\n            text_appearance:\n              alignment: start\n              font_size: 13\n              styles: [bold]\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 20\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: 64\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                end: 6\n              view:\n                type: stack_image_button\n                identifier: row1_left\n                items:\n                  - type: shape\n                    shape:\n                      type: rectangle\n                      color:\n                        default:\n                          hex: \"#4A90D9\"\n                          alpha: 1\n                      border:\n                        radius: 24\n\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                start: 6\n              view:\n                type: stack_image_button\n                identifier: row1_right\n                items:\n                  - type: shape\n                    shape:\n                      type: rectangle\n                      color:\n                        default:\n                          hex: \"#E8734A\"\n                          alpha: 1\n                      border:\n                        corner_radius:\n                          top_left: 24\n                          top_right: 24\n                          bottom_left: 24\n                          bottom_right: 24\n\n        # ── Row 2: fill-only, top corners only ───────────────────────\n        - margin:\n            top: 0\n            bottom: 6\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"2. Fill only — top corners only (TL=24 TR=24 BL=0 BR=0)\"\n            text_appearance:\n              alignment: start\n              font_size: 13\n              styles: [bold]\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 4\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"Right: should be rounded top, square bottom\"\n            text_appearance:\n              alignment: start\n              font_size: 11\n              color:\n                default:\n                  hex: \"#888888\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 20\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: 64\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                end: 6\n              view:\n                type: label\n                text: \"N/A\\n(uniform can't\\ndo this)\"\n                text_appearance:\n                  alignment: center\n                  font_size: 11\n                  color:\n                    default:\n                      hex: \"#999999\"\n                      alpha: 1\n\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                start: 6\n              view:\n                type: stack_image_button\n                identifier: row2_right\n                items:\n                  - type: shape\n                    shape:\n                      type: rectangle\n                      color:\n                        default:\n                          hex: \"#E8734A\"\n                          alpha: 1\n                      border:\n                        corner_radius:\n                          top_left: 24\n                          top_right: 24\n                          bottom_left: 0\n                          bottom_right: 0\n\n        # ── Row 3: stroke + all corners ──────────────────────────────\n        - margin:\n            top: 0\n            bottom: 6\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"3. Stroke (3dp) — all corners = 16\"\n            text_appearance:\n              alignment: start\n              font_size: 13\n              styles: [bold]\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 20\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: 64\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                end: 6\n              view:\n                type: stack_image_button\n                identifier: row3_left\n                items:\n                  - type: shape\n                    shape:\n                      type: rectangle\n                      border:\n                        radius: 16\n                        stroke_width: 3\n                        stroke_color:\n                          default:\n                            hex: \"#4A90D9\"\n                            alpha: 1\n\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                start: 6\n              view:\n                type: stack_image_button\n                identifier: row3_right\n                items:\n                  - type: shape\n                    shape:\n                      type: rectangle\n                      border:\n                        corner_radius:\n                          top_left: 16\n                          top_right: 16\n                          bottom_left: 16\n                          bottom_right: 16\n                        stroke_width: 3\n                        stroke_color:\n                          default:\n                            hex: \"#E8734A\"\n                            alpha: 1\n\n        # ── Row 4: stroke + diagonal corners (ticket use case) ───────\n        - margin:\n            top: 0\n            bottom: 6\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"4. Stroke (3dp) — diagonal corners TL=24 BR=24\"\n            text_appearance:\n              alignment: start\n              font_size: 13\n              styles: [bold]\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 4\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label\n            text: \"Right: matches ticket use case (outside-edge rounded buttons)\"\n            text_appearance:\n              alignment: start\n              font_size: 11\n              color:\n                default:\n                  hex: \"#888888\"\n                  alpha: 1\n\n        - margin:\n            top: 0\n            bottom: 24\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: 64\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                end: 6\n              view:\n                type: label\n                text: \"N/A\\n(uniform can't\\ndo this)\"\n                text_appearance:\n                  alignment: center\n                  font_size: 11\n                  color:\n                    default:\n                      hex: \"#999999\"\n                      alpha: 1\n\n            - size:\n                width: 50%\n                height: 100%\n              margin:\n                start: 6\n              view:\n                type: stack_image_button\n                identifier: row4_right\n                items:\n                  - type: shape\n                    shape:\n                      type: rectangle\n                      border:\n                        corner_radius:\n                          top_left: 24\n                          top_right: 0\n                          bottom_left: 0\n                          bottom_right: 24\n                        stroke_width: 3\n                        stroke_color:\n                          default:\n                            hex: \"#E8734A\"\n                            alpha: 1\n\n        # Dismiss\n        - margin:\n            top: 0\n            bottom: 20\n            start: 16\n            end: 16\n          size:\n            width: 100%\n            height: auto\n          view:\n            type: label_button\n            identifier: dismiss\n            background_color:\n              default:\n                hex: \"#333333\"\n                alpha: 1\n            border:\n              radius: 8\n            label:\n              type: label\n              text: \"Dismiss\"\n              text_appearance:\n                alignment: center\n                font_size: 14\n                color:\n                  default:\n                    hex: \"#ffffff\"\n                    alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-buttons.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 95%\n      height: 85%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF00FF\"\n        alpha: 1\n    stroke_width: 1\n    radius: 10\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n        #\n        # Positions\n        #\n      - margin:\n          top: 8\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"Position\"\n          text_appearance:\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            styles:\n            - bold\n            - underlined\n            font_size: 16\n      # Button: start|top\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: container\n          position:\n            horizontal: center\n            vertical: center\n          items:\n          - position:\n              horizontal: start\n              vertical: top\n            margin:\n              top: 0\n              bottom: 0\n              start: 8\n              end: 8\n            size:\n              width: auto\n              height: auto\n            view:\n              type: label_button\n              identifier: button1\n              background_color:\n                default:\n                  hex: \"#D32F2F\" # red\n                  alpha: 1\n              label:\n                type: label\n                text: 'start|top'\n                text_appearance:\n                  font_size: 10\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n      # Button: center|center\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: container\n          position:\n            horizontal: center\n            vertical: center\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            margin:\n              top: 0\n              bottom: 0\n              start: 8\n              end: 8\n            size:\n              width: auto\n              height: auto\n            view:\n              type: label_button\n              identifier: button2\n              background_color:\n                default:\n                  hex: \"#E65100\" # orange\n                  alpha: 1\n              label:\n                type: label\n                text: 'center|center'\n                text_appearance:\n                  font_size: 10\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n      # Button: end|bottom\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: container\n          position:\n            horizontal: center\n            vertical: center\n          items:\n          - position:\n              horizontal: end\n              vertical: bottom\n            margin:\n              top: 0\n              bottom: 0\n              start: 8\n              end: 8\n            size:\n              width: auto\n              height: auto\n            view:\n              type: label_button\n              identifier: button3\n              background_color:\n                default:\n                  hex: \"#FFD600\" # yellow\n                  alpha: 1\n              label:\n                type: label\n                text: 'end|bottom'\n                text_appearance:\n                  font_size: 10\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n        #\n        # Borders\n        #\n      - margin:\n          top: 8\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"Border\"\n          text_appearance:\n            alignment: start\n            styles:\n            - bold\n            - underlined\n            font_size: 16\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Button w/ 2dp border and no radius\n      - margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label_button\n          identifier: button4\n          background_color:\n            default:\n              hex: \"#558B2F\" # green\n              alpha: 1\n          border:\n            radius: 0\n            stroke_width: 3\n            stroke_color:\n              default:\n                hex: \"#AED581\" # light green\n                alpha: 1\n          label:\n            type: label\n            text: '3dp stroke'\n            text_appearance:\n              font_size: 12\n              alignment: center\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 1\n\n        # Button w/ 5dp border and 10dp radius\n      - margin:\n          top: 4\n          bottom: 4\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label_button\n          identifier: button5\n          background_color:\n            default:\n              hex: \"#1976D2\" # blue\n              alpha: 1\n          border:\n            radius: 15\n            stroke_width: 5\n            stroke_color:\n              default:\n                hex: \"#64B5F6\" # light blue\n                alpha: 1\n          label:\n            type: label\n            text: '5dp stroke, 15dp radius'\n            text_appearance:\n              font_size: 12\n              alignment: center\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 1\n\n        # Button w/ no border and 20dp radius\n      - margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label_button\n          identifier: button6\n          background_color:\n            default:\n              hex: \"#283593\" # purple\n              alpha: 1\n          border:\n            radius: 25\n            stroke_width: 0\n          label:\n            type: label\n            text: 'no stroke, 20dp radius'\n            text_appearance:\n              font_size: 12\n              alignment: center\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n\n        #\n        # Sizes\n        #\n      - margin:\n          top: 4\n          bottom: 4\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"Size\"\n          text_appearance:\n            alignment: start\n            styles:\n            - bold\n            - underlined\n            font_size: 16\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Button: auto x auto\n      - margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: auto\n          height: auto\n        view:\n          type: label_button\n          identifier: button7\n          background_color:\n            default:\n              hex: \"#AED581\" # light green\n              alpha: 1\n          label:\n            type: label\n            text: 'auto x auto'\n            text_appearance:\n              font_size: 12\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n              alignment: center\n\n        # auto x 56dp\n      - margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: auto\n          height: 64\n        view:\n          type: label_button\n          identifier: button9\n          background_color:\n            default:\n              hex: \"#283593\" # purple\n              alpha: 1\n          label:\n            type: label\n            text: 'auto x 64dp'\n            text_appearance:\n              font_size: 12\n              alignment: center\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n\n        # 66% x auto\n      - margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: 66%\n          height: auto\n        view:\n          type: label_button\n          identifier: button8\n          background_color:\n            default:\n              hex: \"#64B5F6\" # light blue\n              alpha: 1\n          label:\n            type: label\n            text: '66% x auto'\n            text_appearance:\n              font_size: 12\n              alignment: center\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-checkboxes-radios.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.75\nview:\n    type: state_controller\n    view:\n      type: form_controller\n      identifier: neat_form\n      submit: submit_event\n      view:\n        type: container\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        border:\n          stroke_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n          stroke_width: 2\n          radius: 0\n        items:\n        # TOP-LEVEL LINEAR LAYOUT\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            height: auto\n            width: 100%\n          view:\n            type: linear_layout\n            direction: vertical\n            items:\n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: toggle\n                    identifier: hide\n                    event_handlers:\n                        - type: form_input\n                          state_actions:\n                            - type: set_form_value\n                              key: hide\n                    style:\n                      type: switch\n                      toggle_colors:\n                        on:\n                          default:\n                            hex: \"#00FF00\"\n                            alpha: 1\n                        off:\n                          default:\n                            hex: \"#FF0000\"\n                            alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    visibility:\n                        default: true\n                        invert_when_state_matches:\n                            key: hide\n                            value:\n                                equals: true\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                    - size:\n                        width: 100%\n                        height: 48\n                      margin:\n                        start: 16\n                        end: 16\n                      view:\n                        type: label\n                        text: Check some boxes!\n                        text_appearance:\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 1\n                          alignment: start\n                          font_size: 18\n                    - size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: checkbox_controller\n                        identifier: box_types\n                        required: true\n                        min_selection: 1\n                        max_selection: 2\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items:\n                                - size:\n                                    width: 100%\n                                    height: 100%\n                                  margin:\n                                    start: 16\n                                    end: 16\n                                  view:\n                                    type: label\n                                    text: Moving boxes\n                                    text_appearance:\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      font_size: 14\n                                      alignment: start\n                                - size:\n                                    width: 48\n                                    height: 48\n                                  view:\n                                    type: checkbox\n                                    reporting_value: moving boxes\n                                    style:\n                                      type: checkbox\n                                      bindings:\n                                        selected:\n                                          shapes:\n                                          - type: rectangle\n                                            scale: .5\n                                            aspect_ratio: 1\n                                            color:\n                                              default:\n                                                hex: \"#66FF66\"\n                                                alpha: 1\n                                            border:\n                                              stroke_width: 2\n                                              radius: 5\n                                              stroke_color:\n                                                default:\n                                                  hex: \"#333333\"\n                                                  alpha: 1\n                                          icon:\n                                            icon: checkmark\n                                            color:\n                                              default:\n                                                hex: \"#333333\"\n                                                alpha: 1\n                                            scale: .4\n                                        unselected:\n                                          shapes:\n                                          - type: rectangle\n                                            scale: .5\n                                            aspect_ratio: 1\n                                            color:\n                                              default:\n                                                hex: \"#FF6666\"\n                                                alpha: 1\n                                            border:\n                                              stroke_width: 2\n                                              radius: 5\n                                              stroke_color:\n                                                default:\n                                                  hex: \"#333333\"\n                                                  alpha: 1\n                                          icon:\n                                            icon: close\n                                            color:\n                                              default:\n                                                hex: \"#333333\"\n                                                alpha: 1\n                                            scale: .4\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                margin:\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  text: Bread boxes\n                              - size:\n                                  width: 48\n                                  height: 48\n                                view:\n                                  type: checkbox\n                                  reporting_value: bread boxes\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        shapes:\n                                        - type: rectangle\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              hex: \"#66FF66\"\n                                              alpha: 1\n                                          border:\n                                            stroke_width: 2\n                                            radius: 5\n                                            stroke_color:\n                                              default:\n                                                hex: \"#333333\"\n                                                alpha: 1\n                                        icon:\n                                          icon: checkmark\n                                          color:\n                                            default:\n                                              hex: \"#333333\"\n                                              alpha: 1\n                                          scale: .4\n                                      unselected:\n                                        shapes:\n                                        - type: rectangle\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              hex: \"#FF6666\"\n                                              alpha: 1\n                                          border:\n                                            stroke_width: 2\n                                            radius: 5\n                                            stroke_color:\n                                              default:\n                                                hex: \"#333333\"\n                                                alpha: 1\n                                        icon:\n                                          icon: close\n                                          color:\n                                            default:\n                                              hex: \"#333333\"\n                                              alpha: 1\n                                          scale: .4\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                margin:\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  text: Hat boxes\n                              - size:\n                                  width: 48\n                                  height: 48\n                                view:\n                                  type: checkbox\n                                  reporting_value: hat boxes\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        shapes:\n                                        - type: rectangle\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              hex: \"#66FF66\"\n                                              alpha: 1\n                                          border:\n                                            stroke_width: 2\n                                            radius: 5\n                                            stroke_color:\n                                              default:\n                                                hex: \"#333333\"\n                                                alpha: 1\n                                        icon:\n                                          icon: checkmark\n                                          color:\n                                            default:\n                                              hex: \"#333333\"\n                                              alpha: 1\n                                          scale: .4\n                                      unselected:\n                                        shapes:\n                                        - type: rectangle\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              hex: \"#FF6666\"\n                                              alpha: 1\n                                          border:\n                                            stroke_width: 2\n                                            radius: 5\n                                            stroke_color:\n                                              default:\n                                                hex: \"#333333\"\n                                                alpha: 1\n                                        icon:\n                                          icon: close\n                                          color:\n                                            default:\n                                              hex: \"#333333\"\n                                              alpha: 1\n                                          scale: .4\n                    - size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: radio_input_controller\n                        identifier: radio_types\n                        required: true\n                        attribute_name:\n                          channel: \"radio-attribute-name-channel\"\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label\n                              text: Choose a radio!\n                              text_appearance:\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                alignment: start\n                                font_size: 18\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                margin:\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  text: AM Radio\n                              - size:\n                                  width: 48\n                                  height: 48\n                                view:\n                                  type: radio_input\n                                  reporting_value: am\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        shapes:\n                                        - type: ellipse\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          border:\n                                            stroke_width: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        - type: ellipse\n                                          scale: .3\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                      unselected:\n                                        shapes:\n                                        - type: ellipse\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          border:\n                                            stroke_width: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                margin:\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: FM Radio\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                              - size:\n                                  width: 48\n                                  height: 48\n                                view:\n                                  type: radio_input\n                                  reporting_value: fm\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        shapes:\n                                        - type: ellipse\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          border:\n                                            stroke_width: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        - type: ellipse\n                                          scale: .3\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                      unselected:\n                                        shapes:\n                                        - type: ellipse\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          border:\n                                            stroke_width: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: linear_layout\n                              direction: horizontal\n                              items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                margin:\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: HAM Radio\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                              - size:\n                                  width: 48\n                                  height: 48\n                                view:\n                                  type: radio_input\n                                  reporting_value: ham\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        shapes:\n                                        - type: ellipse\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          border:\n                                            stroke_width: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                        - type: ellipse\n                                          scale: .3\n                                          aspect_ratio: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                      unselected:\n                                        shapes:\n                                        - type: ellipse\n                                          scale: .5\n                                          aspect_ratio: 1\n                                          border:\n                                            stroke_width: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n\n                    - size:\n                        width: 100%\n                        height: 48\n                      margin:\n                        start: 16\n                        end: 16\n                      view:\n                        type: linear_layout\n                        direction: horizontal\n                        items:\n                        - size:\n                            width: 100%\n                            height: 100%\n                          margin:\n                            end: 16\n                          view:\n                            type: label\n                            text: Toggle the switch!\n                            text_appearance:\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                              font_size: 18\n                              alignment: start\n                        - size:\n                            width: auto\n                            height: auto\n                          view:\n                            type: toggle\n                            identifier: toggle-switch\n                            required: true\n                            attribute_name:\n                              contact: \"toggle-attribute-name-contact\"\n                            attribute_value: \"attribute-value-toggle-switch\"\n                            style:\n                              type: switch\n                              toggle_colors:\n                                on:\n                                  default:\n                                    hex: \"#00FF00\"\n                                    alpha: 1\n                                off:\n                                  default:\n                                    hex: \"#FF0000\"\n                                    alpha: 1\n                    # Nested NPS form\n                    - size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: nps_form_controller\n                        identifier: cool_nps_form\n                        nps_identifier: \"nps_rating\"\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label\n                              text: Choose a score!\n                              text_appearance:\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                alignment: start\n                                font_size: 18\n                          - size:\n                              width: 100%\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: score\n                              identifier: \"nps_rating\"\n                              required: true\n                              attribute_name:\n                                channel: \"nps-feedback-attribute-name-channel\"\n                              style:\n                                type: number_range\n                                start: 0\n                                end: 10\n                                spacing: 4\n                                bindings:\n                                  selected:\n                                    shapes:\n                                    - type: rectangle\n                                      aspect_ratio: 1\n                                      scale: 1\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                    - type: ellipse\n                                      aspect_ratio: 1.5\n                                      scale: 1\n                                      border:\n                                        stroke_width: 2\n                                        stroke_color:\n                                          default:\n                                            hex: \"#999999\"\n                                            alpha: 1\n                                      color:\n                                        default:\n                                          hex: \"#FFFFFF\"\n                                          alpha: 0\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                      font_families:\n                                      - permanent_marker\n                                  unselected:\n                                    shapes:\n                                    - type: ellipse\n                                      aspect_ratio: 1.5\n                                      scale: 1\n                                      border:\n                                        stroke_width: 2\n                                        stroke_color:\n                                          default:\n                                            hex: \"#999999\"\n                                            alpha: 1\n                                      color:\n                                        default:\n                                          hex: \"#FFFFFF\"\n                                          alpha: 1\n                                    text_appearance:\n                                      font_size: 14\n                                      styles:\n                                      - bold\n                                      color:\n                                        default:\n                                          hex: \"#333333\"\n                                          alpha: 1\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label\n                              text: Type stuff!\n                              text_appearance:\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                alignment: start\n                                font_size: 18\n                          - size:\n                              width: 100%\n                              height: 48\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: text_input\n                              identifier: nps_feedback\n                              input_type: text_multiline\n                              place_holder: blah blah blah...\n                              required: true\n                              border:\n                                stroke_width: 2\n                                stroke_color:\n                                  default:\n                                    hex: \"#666666\"\n                                    alpha: 1\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              text_appearance:\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                alignment: start\n                                font_size: 12\n                                place_holder_color:\n                                    default:\n                                      hex: \"#ff0000\"\n                                      alpha: 1\n\n                    # BOTTOM-PINNED BUTTON\n                    - size:\n                        width: 100%\n                        height: auto\n                      margin:\n                        top: 16\n                        bottom: 16\n                        start: 16\n                        end: 16\n                      view:\n                        type: label_button\n                        identifier: SUBMIT_BUTTON\n                        background_color:\n                          default:\n                            hex: \"#000000\"\n                            alpha: 1\n                        button_click: [\"form_submit\", \"cancel\"]\n                        enabled: [\"form_validation\"]\n                        label:\n                          type: label\n                          text: 'SEND IT!'\n                          text_appearance:\n                            font_size: 14\n                            alignment: center\n                            color:\n                              default:\n                                hex: \"#ffffff\"\n                                alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-constrained.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    device:\n      lock_orientation: portrait\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: bottom\n    margin:\n      top: 99\n      bottom: 25\n      start: 25\n      end: 25\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: 6ab1531a-fcb3-44b4-91d7-52db73ae7cd9\n  view:\n    type: linear_layout\n    direction: vertical\n    border:\n      stroke_color:\n        default:\n          hex: \"#ff0000\"\n          alpha: 1.0\n      stroke_width: 2\n      radius: 0\n    items:\n      - size:\n          height: 100%\n          width: 100%\n        view:\n          type: container\n          items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                  - identifier: c36a5103-0a8d-4e34-b7b7-331ec1cbc87e\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - size:\n                            width: 100%\n                            height: 100%\n                          position:\n                            horizontal: center\n                            vertical: center\n                          view:\n                            type: container\n                            items:\n                              - margin:\n                                  bottom: 16\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        direction: vertical\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              margin:\n                                                top: 48\n                                                bottom: 8\n                                                start: 16\n                                                end: 16\n                                              view:\n                                                type: label\n                                                text: This is test\n                                                text_appearance:\n                                                  font_size: 30\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                  alignment: center\n                                                  styles: []\n                                                  font_families:\n                                                    - serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items: []\n                                  background_color:\n                                    default:\n                                      type: hex\n                                      hex: \"#FFFFFF\"\n                                      alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n              ignore_safe_area: false\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                    selectors:\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                identifier: dismiss_button\n                button_click:\n                  - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-custom-biometric-login.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.6\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#000000\"\n      alpha: 1\n  border:\n    stroke_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\n    stroke_width: 1\n    radius: 0\n  items:\n    # TOP-LEVEL LINEAR LAYOUT\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: 100%\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n          # SCROLL LAYOUT\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: 100%\n            view:\n              type: scroll_layout\n              direction: vertical\n              view:\n                # SCROLL CONTENT (LINEAR LAYOUT)\n                type: linear_layout\n                direction: vertical\n                size:\n                  width: 100%\n                  height: 100%\n                items:\n                  # Camera View\n                  - size:\n                      width: 100%\n                      height: 100%\n                    view:\n                      type: custom_view\n                      name: biometric_login_custom_view\n                      properties:\n                        login_description: \"I'm a biometric login view in a glassmorphic style\"\n                      background_color:\n                      selectors:\n                        - platform: ios\n                          dark_mode: false\n                          color:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            hex: \"#000000\"\n                            alpha: 1\n                      default:\n                        hex: \"#FF00FF\"\n                        alpha: 1\n                  # BODY\n                  - size:\n                      width: 100%\n                      height: auto\n                    margin:\n                      top: 8\n                      bottom: 8\n                      start: 8\n                      end: 8\n                    view:\n                      type: label\n                      text: Camera permissions will be requested automatically if we're not already authorized. We inherit our authorization from the containing application.\n                      text_appearance:\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        alignment: start\n                        styles: [italic]\n                        font_families: [permanent_marker]\n                        font_size: 14\n    # TOP-RIGHT ICON BUTTON\n    - position:\n        horizontal: end\n        vertical: top\n      size:\n        width: 24\n        height: 24\n      view:\n        type: image_button\n        identifier: close_button\n        button_click: [dismiss]\n        image:\n          type: icon\n          icon: close\n          color:\n            default:\n              hex: \"#FFFFFF\"\n              alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-custom-interstitial-banner-ad.yaml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.6\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#000000\"\n      alpha: 1\n  border:\n    stroke_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\n    stroke_width: 1\n    radius: 0\n  items:\n    # TOP-LEVEL LINEAR LAYOUT\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: 100%\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n          # SCROLL LAYOUT\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: 100%\n            view:\n              type: scroll_layout\n              direction: vertical\n              view:\n                # SCROLL CONTENT (LINEAR LAYOUT)\n                type: linear_layout\n                direction: vertical\n                size:\n                  width: 100%\n                  height: 100%\n                items:\n                  # Camera View\n                  - size:\n                      width: 100%\n                      height: 100%\n                    view:\n                      type: custom_view\n                      name: ad\n                      properties:\n                        login_description: \"I'm a biometric login view in a glassmorphic style\"\n                      background_color:\n                      selectors:\n                        - platform: ios\n                          dark_mode: false\n                          color:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            hex: \"#000000\"\n                            alpha: 1\n                      default:\n                        hex: \"#FF00FF\"\n                        alpha: 1\n                  # BODY\n                  - size:\n                      width: 100%\n                      height: auto\n                    margin:\n                      top: 8\n                      bottom: 8\n                      start: 8\n                      end: 8\n                    view:\n                      type: label\n                      text: Camera permissions will be requested automatically if we're not already authorized. We inherit our authorization from the containing application.\n                      text_appearance:\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        alignment: start\n                        styles: [italic]\n                        font_families: [permanent_marker]\n                        font_size: 14\n    # TOP-RIGHT ICON BUTTON\n    - position:\n        horizontal: end\n        vertical: top\n      size:\n        width: 24\n        height: 24\n      view:\n        type: image_button\n        identifier: close_button\n        button_click: [dismiss]\n        image:\n          type: icon\n          icon: close\n          color:\n            default:\n              hex: \"#FFFFFF\"\n              alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-custom-weather-and-map.yaml",
    "content": "---\nversion: 1\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    size:\n      min_width: 100%\n      min_height: 100%\n      max_width: 100%\n      width: 100%\n      height: 100%\n      max_height: 100%\n    device:\n      lock_orientation: portrait\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: top\n  type: modal\n  dismiss_on_touch_outside: false\nview:\n  type: pager_controller\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n      - view:\n          items:\n            - size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                items:\n                  - type: pager_item\n                    view:\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          view:\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  items:\n                                    - size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        direction: vertical\n                                        view:\n                                          items:\n                                            - margin:\n                                                bottom: 0\n                                                top: 0\n                                                end: 0\n                                                start: 0\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                type: custom_view\n                                                name: weather_custom_view\n                                                properties:\n                                                  weather_type: \"storm\"\n                                                background_color:\n                                                default:\n                                                  hex: \"#FF00FF\"\n                                                  alpha: 1\n                                            - margin:\n                                                bottom: 8\n                                                end: 16\n                                                top: 8\n                                                start: 16\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                text_appearance:\n                                                  alignment: start\n                                                  styles:\n                                                    - bold\n                                                  font_size: 24\n                                                  font_families:\n                                                    - sans-serif\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#487399\"\n                                                      alpha: 1\n                                                    selectors:\n                                                      - color:\n                                                          hex: \"#487399\"\n                                                          alpha: 1\n                                                          type: hex\n                                                        dark_mode: true\n                                                        platform: ios\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#487399\"\n                                                type: label\n                                                text: Portland Metro Storm Advisory\n                                            - margin:\n                                                start: 16\n                                                end: 16\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                type: label\n                                                text:\n                                                  \"A Flood warning is in effect from \\n4:45\n                                                  PM PST\"\n                                                text_appearance:\n                                                  alignment: start\n                                                  styles: []\n                                                  color:\n                                                    default:\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                      type: hex\n                                                    selectors:\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                          type: hex\n                                                      - color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                        platform: android\n                                                        dark_mode: true\n                                                  font_families:\n                                                    - sans-serif\n                                                  font_size: 20\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                            - margin:\n                                                start: 16\n                                                end: 16\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                text_appearance:\n                                                  alignment: start\n                                                  styles:\n                                                    - bold\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                          type: hex\n                                                  font_families:\n                                                    - sans-serif\n                                                  font_size: 20\n                                                type: label\n                                                text:\n                                                  Do not attempt to travel unless you are fleeing\n                                                  an area subject to flooding or under an evacuation\n                                                  order\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                            - margin:\n                                                start: 16\n                                                end: 16\n                                                top: 8\n                                                bottom: 0\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                background_color:\n                                                  default:\n                                                    alpha: 1\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                  selectors:\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      color:\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                        type: hex\n                                                      dark_mode: true\n                                                type: linear_layout\n                                                items:\n                                                  - margin:\n                                                      start: 0\n                                                      end: 0\n                                                      top: 4\n                                                      bottom: 16\n                                                    view:\n                                                      border:\n                                                        stroke_width: 1\n                                                        stroke_color:\n                                                          default:\n                                                            hex: \"#63AFF1\"\n                                                            type: hex\n                                                            alpha: 1\n                                                          selectors:\n                                                            - color:\n                                                                hex: \"#63AFF1\"\n                                                                type: hex\n                                                                alpha: 1\n                                                              platform: ios\n                                                              dark_mode: true\n                                                            - color:\n                                                                type: hex\n                                                                hex: \"#63AFF1\"\n                                                                alpha: 1\n                                                              dark_mode: true\n                                                              platform: android\n                                                        radius: 0\n                                                      button_click:\n                                                        - pager_next\n                                                      enabled:\n                                                        - pager_next\n                                                      reporting_metadata:\n                                                        trigger_link_id: e1842a1c-1183-45da-90fc-c3c343a4a28f\n                                                      identifier: next--Show My Flood Risk\n                                                      background_color:\n                                                        default:\n                                                          hex: \"#8d5797\"\n                                                          type: hex\n                                                          alpha: 1\n                                                        selectors:\n                                                          - color:\n                                                              type: hex\n                                                              alpha: 1\n                                                              hex: \"#8d5797\"\n                                                            platform: ios\n                                                            dark_mode: true\n                                                          - platform: android\n                                                            color:\n                                                              alpha: 1\n                                                              type: hex\n                                                              hex: \"#8d5797\"\n                                                            dark_mode: true\n                                                      type: label_button\n                                                      label:\n                                                        type: label\n                                                        text: Show My Flood Risk\n                                                        text_appearance:\n                                                          alignment: center\n                                                          styles: []\n                                                          font_size: 16\n                                                          font_families:\n                                                            - sans-serif\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              alpha: 1\n                                                              hex: \"#000000\"\n                                                            selectors:\n                                                              - platform: ios\n                                                                color:\n                                                                  type: hex\n                                                                  alpha: 1\n                                                                  hex: \"#FFFFFF\"\n                                                                dark_mode: true\n                                                              - platform: android\n                                                                color:\n                                                                  hex: \"#FFFFFF\"\n                                                                  type: hex\n                                                                  alpha: 1\n                                                                dark_mode: true\n                                                      actions: {}\n                                                    size:\n                                                      width: 100%\n                                                      height: 48\n                                                direction: horizontal\n                                            - view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items: []\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                          direction: vertical\n                                          type: linear_layout\n                                      identifier: scroll_container\n                                  background_color:\n                                    default:\n                                      hex: \"#FFFFFF\"\n                                      alpha: 1\n                                      type: hex\n                                    selectors:\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                      - color:\n                                          hex: \"#000000\"\n                                          type: hex\n                                          alpha: 1\n                                        dark_mode: true\n                                        platform: android\n                                  type: linear_layout\n                                  direction: vertical\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                margin:\n                                  bottom: 16\n                            type: container\n                          size:\n                            width: 100%\n                            height: 100%\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                        selectors:\n                          - color:\n                              hex: \"#000000\"\n                              type: hex\n                              alpha: 1\n                            dark_mode: true\n                            platform: ios\n                          - color:\n                              type: hex\n                              alpha: 1\n                              hex: \"#000000\"\n                            platform: android\n                            dark_mode: true\n                      type: container\n                    identifier: 2eb347b6-84d9-4761-9a27-1cdc6719a394\n                  - type: pager_item\n                    view:\n                      background_color:\n                        default:\n                          hex: \"#FFFFFF\"\n                          type: hex\n                          alpha: 1\n                        selectors:\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              type: hex\n                              alpha: 1\n                              hex: \"#000000\"\n                          - color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                            dark_mode: true\n                            platform: android\n                      items:\n                        - size:\n                            width: 100%\n                            height: 100%\n                          position:\n                            horizontal: center\n                            vertical: center\n                          view:\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        view:\n                                          items:\n                                            - margin:\n                                                bottom: 0\n                                                top: 42\n                                                end: 0\n                                                start: 0\n                                              size:\n                                                width: 100%\n                                                height: 40\n                                              view:\n                                                type: custom_view\n                                                name: ad_custom_view\n                                                properties:\n                                                  ad_type: \"survival\"\n                                                background_color:\n                                                default:\n                                                  hex: \"#FF00FF\"\n                                                  alpha: 1\n                                            - margin:\n                                                start: 0\n                                                end: 0\n                                                top: 0\n                                                bottom: 0\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                media_type: image\n                                                media_fit: center_inside\n                                                url: https://ehs.stanford.edu/wp-content/uploads/Risk-Assessment-Tool.png\n                                                type: media\n                                            - margin:\n                                                start: 16\n                                                end: 16\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                text_appearance:\n                                                  alignment: center\n                                                  styles:\n                                                    - bold\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                      - color:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                        platform: ios\n                                                        dark_mode: true\n                                                      - platform: android\n                                                        color:\n                                                          alpha: 1\n                                                          hex: \"#FFFFFF\"\n                                                          type: hex\n                                                        dark_mode: true\n                                                  font_families:\n                                                    - sans-serif\n                                                  font_size: 20\n                                                type: label\n                                                text: Your Location's Flood Risk is\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                            - margin:\n                                                bottom: 8\n                                                top: 8\n                                                end: 16\n                                                start: 16\n                                              view:\n                                                type: label\n                                                text: High\n                                                text_appearance:\n                                                  alignment: center\n                                                  styles:\n                                                    - bold\n                                                  color:\n                                                    default:\n                                                      hex: \"#FF0000\"\n                                                      alpha: 1\n                                                      type: hex\n                                                  font_families:\n                                                    - sans-serif\n                                                  font_size: 55\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                            - margin:\n                                                bottom: 8\n                                                top: 8\n                                                end: 16\n                                                start: 16\n                                              view:\n                                                text_appearance:\n                                                  alignment: center\n                                                  styles:\n                                                    - bold\n                                                  font_size: 24\n                                                  font_families:\n                                                    - sans-serif\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      alpha: 1\n                                                      hex: \"#000000\"\n                                                    selectors:\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          alpha: 1\n                                                          hex: \"#FFFFFF\"\n                                                      - color:\n                                                          hex: \"#FFFFFF\"\n                                                          type: hex\n                                                          alpha: 1\n                                                        dark_mode: true\n                                                        platform: android\n                                                text: Please proceed to your evacutation site immediately\n                                                type: label\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                            - margin:\n                                                bottom: 0\n                                                end: 16\n                                                top: 8\n                                                start: 16\n                                              view:\n                                                background_color:\n                                                  default:\n                                                    type: hex\n                                                    alpha: 1\n                                                    hex: \"#FFFFFF\"\n                                                  selectors:\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        alpha: 1\n                                                        hex: \"#000000\"\n                                                    - platform: android\n                                                      color:\n                                                        type: hex\n                                                        alpha: 1\n                                                        hex: \"#000000\"\n                                                      dark_mode: true\n                                                type: linear_layout\n                                                items:\n                                                  - margin:\n                                                      bottom: 16\n                                                      end: 0\n                                                      top: 4\n                                                      start: 0\n                                                    size:\n                                                      width: 100%\n                                                      height: 48\n                                                    view:\n                                                      button_click:\n                                                        - pager_next\n                                                      border:\n                                                        stroke_width: 1\n                                                        stroke_color:\n                                                          default:\n                                                            hex: \"#63AFF1\"\n                                                            type: hex\n                                                            alpha: 1\n                                                          selectors:\n                                                            - platform: ios\n                                                              dark_mode: true\n                                                              color:\n                                                                hex: \"#63AFF1\"\n                                                                type: hex\n                                                                alpha: 1\n                                                            - color:\n                                                                alpha: 1\n                                                                type: hex\n                                                                hex: \"#63AFF1\"\n                                                              dark_mode: true\n                                                              platform: android\n                                                        radius: 0\n                                                      reporting_metadata:\n                                                        trigger_link_id: 3ea917f7-f52d-4a7f-bb42-71d1761e9165\n                                                      enabled:\n                                                        - pager_next\n                                                      background_color:\n                                                        default:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#63AFF1\"\n                                                        selectors:\n                                                          - platform: ios\n                                                            color:\n                                                              alpha: 1\n                                                              hex: \"#63AFF1\"\n                                                              type: hex\n                                                            dark_mode: true\n                                                          - color:\n                                                              type: hex\n                                                              hex: \"#63AFF1\"\n                                                              alpha: 1\n                                                            dark_mode: true\n                                                            platform: android\n                                                      identifier: next--Show Evacuation Route\n                                                      type: label_button\n                                                      label:\n                                                        text_appearance:\n                                                          alignment: center\n                                                          styles: []\n                                                          font_size: 16\n                                                          font_families:\n                                                            - sans-serif\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              alpha: 1\n                                                              hex: \"#000000\"\n                                                            selectors:\n                                                              - platform: ios\n                                                                color:\n                                                                  alpha: 1\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                dark_mode: true\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                                  type: hex\n                                                        type: label\n                                                        text: Show Evacuation Route\n                                                      actions: {}\n                                                direction: horizontal\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                            - view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items: []\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                          direction: vertical\n                                          type: linear_layout\n                                        direction: vertical\n                                  type: linear_layout\n                                  background_color:\n                                    default:\n                                      type: hex\n                                      alpha: 1\n                                      hex: \"#FFFFFF\"\n                                    selectors:\n                                      - platform: ios\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          hex: \"#000000\"\n                                          type: hex\n                                          alpha: 1\n                                  direction: vertical\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                margin:\n                                  bottom: 16\n                            type: container\n                      type: container\n                    identifier: 8be0627e-274f-49d7-a116-f2e087c922b3\n                  - type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - size:\n                            width: 100%\n                            height: 100%\n                          position:\n                            horizontal: center\n                            vertical: center\n                          view:\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  background_color:\n                                    default:\n                                      hex: \"#FFFFFF\"\n                                      type: hex\n                                      alpha: 1\n                                    selectors:\n                                      - platform: ios\n                                        color:\n                                          alpha: 1\n                                          type: hex\n                                          hex: \"#000000\"\n                                        dark_mode: true\n                                      - platform: android\n                                        dark_mode: true\n                                        color:\n                                          type: hex\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                  type: linear_layout\n                                  items:\n                                    - size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        direction: vertical\n                                        view:\n                                          items:\n                                            - margin:\n                                                bottom: 0\n                                                top: 42\n                                                end: 0\n                                                start: 0\n                                              size:\n                                                width: 100%\n                                                height: 420\n                                              view:\n                                                type: custom_view\n                                                name: map_custom_view\n                                                properties:\n                                                  map_type: \"route\"\n                                                background_color:\n                                                default:\n                                                  hex: \"#FF00FF\"\n                                                  alpha: 1\n                                            - margin:\n                                                start: 16\n                                                end: 16\n                                                top: 8\n                                                bottom: 8\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                text_appearance:\n                                                  alignment: start\n                                                  styles:\n                                                    - bold\n                                                  font_size: 18\n                                                  font_families:\n                                                    - sans-serif\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      alpha: 1\n                                                      hex: \"#000000\"\n                                                    selectors:\n                                                      - color:\n                                                          hex: \"#FFFFFF\"\n                                                          type: hex\n                                                          alpha: 1\n                                                        platform: ios\n                                                        dark_mode: true\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          alpha: 1\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                text: Please proceed to your evacutation site\n                                                type: label\n                                            - margin:\n                                                bottom: 0\n                                                top: 0\n                                                end: 16\n                                                start: 16\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                type: linear_layout\n                                                items:\n                                                  - margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                    view:\n                                                      items:\n                                                        - margin:\n                                                            end: 16\n                                                            bottom: 0\n                                                            top: 0\n                                                          view:\n                                                            type: media\n                                                            media_fit: center_inside\n                                                            url: https://cdn-icons-png.flaticon.com/512/9494/9494565.png\n                                                            media_type: image\n                                                          size:\n                                                            width: 60\n                                                            height: 60\n                                                        - view:\n                                                            text_appearance:\n                                                              alignment: start\n                                                              styles: []\n                                                              color:\n                                                                default:\n                                                                  alpha: 1\n                                                                  hex: \"#000000\"\n                                                                  type: hex\n                                                                selectors:\n                                                                  - color:\n                                                                      type: hex\n                                                                      alpha: 1\n                                                                      hex: \"#FFFFFF\"\n                                                                    dark_mode: true\n                                                                    platform: ios\n                                                                  - platform: android\n                                                                    dark_mode: true\n                                                                    color:\n                                                                      alpha: 1\n                                                                      type: hex\n                                                                      hex: \"#FFFFFF\"\n                                                              font_families:\n                                                                - sans-serif\n                                                              font_size: 16\n                                                            text:\n                                                              Follow your evacuation route to higher\n                                                              ground now\n                                                            type: label\n                                                          size:\n                                                            width: 100%\n                                                            height: 100%\n                                                      type: linear_layout\n                                                      direction: horizontal\n                                                    size:\n                                                      width: 100%\n                                                      height: auto\n                                                  - margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                    view:\n                                                      items:\n                                                        - margin:\n                                                            end: 16\n                                                            bottom: 0\n                                                            top: 0\n                                                          size:\n                                                            width: 60\n                                                            height: 60\n                                                          view:\n                                                            url: https://cdn-icons-png.flaticon.com/512/9494/9494599.png\n                                                            type: media\n                                                            media_fit: center_inside\n                                                            media_type: image\n                                                        - size:\n                                                            width: 100%\n                                                            height: 100%\n                                                          view:\n                                                            type: label\n                                                            text:\n                                                              'Do not drive or walk into flooded\n                                                              areas: it only takes 6\" of water to knock\n                                                              you off your feet'\n                                                            text_appearance:\n                                                              alignment: start\n                                                              styles: []\n                                                              font_size: 16\n                                                              font_families:\n                                                                - sans-serif\n                                                              color:\n                                                                default:\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                                  type: hex\n                                                                selectors:\n                                                                  - color:\n                                                                      type: hex\n                                                                      hex: \"#FFFFFF\"\n                                                                      alpha: 1\n                                                                    platform: ios\n                                                                    dark_mode: true\n                                                                  - platform: android\n                                                                    color:\n                                                                      type: hex\n                                                                      hex: \"#FFFFFF\"\n                                                                      alpha: 1\n                                                                    dark_mode: true\n                                                      direction: horizontal\n                                                      type: linear_layout\n                                                    size:\n                                                      width: 100%\n                                                      height: auto\n                                                direction: vertical\n                                            - margin:\n                                                start: 16\n                                                top: 8\n                                                end: 16\n                                                bottom: 0\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                items:\n                                                  - margin:\n                                                      bottom: 16\n                                                      top: 4\n                                                      end: 0\n                                                      start: 0\n                                                    view:\n                                                      button_click:\n                                                        - dismiss\n                                                      border:\n                                                        stroke_width: 1\n                                                        stroke_color:\n                                                          default:\n                                                            hex: \"#63AFF1\"\n                                                            alpha: 1\n                                                            type: hex\n                                                          selectors:\n                                                            - color:\n                                                                alpha: 1\n                                                                type: hex\n                                                                hex: \"#63AFF1\"\n                                                              dark_mode: true\n                                                              platform: ios\n                                                            - color:\n                                                                type: hex\n                                                                alpha: 1\n                                                                hex: \"#63AFF1\"\n                                                              platform: android\n                                                              dark_mode: true\n                                                        radius: 0\n                                                      enabled: []\n                                                      reporting_metadata:\n                                                        trigger_link_id: 13c8900d-3131-424a-9478-b756fa310f41\n                                                      background_color:\n                                                        default:\n                                                          hex: \"#63AFF1\"\n                                                          type: hex\n                                                          alpha: 1\n                                                        selectors:\n                                                          - color:\n                                                              hex: \"#63AFF1\"\n                                                              alpha: 1\n                                                              type: hex\n                                                            platform: ios\n                                                            dark_mode: true\n                                                          - color:\n                                                              alpha: 1\n                                                              hex: \"#63AFF1\"\n                                                              type: hex\n                                                            dark_mode: true\n                                                            platform: android\n                                                      label:\n                                                        type: label\n                                                        text: Close\n                                                        text_appearance:\n                                                          alignment: center\n                                                          styles: []\n                                                          font_size: 16\n                                                          font_families:\n                                                            - sans-serif\n                                                          color:\n                                                            default:\n                                                              alpha: 1\n                                                              hex: \"#000000\"\n                                                              type: hex\n                                                            selectors:\n                                                              - color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                                dark_mode: true\n                                                                platform: ios\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  alpha: 1\n                                                                  hex: \"#FFFFFF\"\n                                                                  type: hex\n                                                      identifier: dismiss--Close\n                                                      type: label_button\n                                                      actions: {}\n                                                    size:\n                                                      width: 100%\n                                                      height: 48\n                                                background_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                  selectors:\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        hex: \"#000000\"\n                                                        type: hex\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: true\n                                                      color:\n                                                        alpha: 1\n                                                        hex: \"#000000\"\n                                                        type: hex\n                                                type: linear_layout\n                                                direction: horizontal\n                                            - view:\n                                                type: linear_layout\n                                                items: []\n                                                direction: horizontal\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                          direction: vertical\n                                          type: linear_layout\n                                      identifier: scroll_container\n                                  direction: vertical\n                                margin:\n                                  bottom: 16\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                            type: container\n                      background_color:\n                        default:\n                          hex: \"#FFFFFF\"\n                          type: hex\n                          alpha: 1\n                        selectors:\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                          - platform: android\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                    identifier: 89095bb5-eeec-43ee-861d-6ca45f392540\n                disable_swipe: false\n              ignore_safe_area: false\n              position:\n                horizontal: center\n                vertical: center\n            - position:\n                horizontal: end\n                vertical: top\n              view:\n                identifier: dismiss_button\n                button_click:\n                  - dismiss\n                image:\n                  scale: 0.4\n                  icon: close\n                  type: icon\n                  color:\n                    default:\n                      alpha: 1\n                      hex: \"#63AFF1\"\n                      type: hex\n                    selectors:\n                      - color:\n                          type: hex\n                          alpha: 1\n                          hex: \"#63AFF1\"\n                        dark_mode: true\n                        platform: ios\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          alpha: 1\n                          hex: \"#63AFF1\"\n                type: image_button\n              size:\n                width: 48\n                height: 48\n            - size:\n                width: 100%\n                height: 7\n              view:\n                type: pager_indicator\n                spacing: 6\n                bindings:\n                  selected:\n                    shapes:\n                      - scale: 1\n                        aspect_ratio: 1\n                        type: ellipse\n                        color:\n                          default:\n                            type: hex\n                            hex: \"#63AFF1\"\n                            alpha: 1\n                          selectors:\n                            - platform: ios\n                              color:\n                                type: hex\n                                alpha: 1\n                                hex: \"#63AFF1\"\n                              dark_mode: true\n                            - platform: android\n                              dark_mode: true\n                              color:\n                                hex: \"#63AFF1\"\n                                alpha: 1\n                                type: hex\n                  unselected:\n                    shapes:\n                      - color:\n                          default:\n                            hex: \"#BCBDC2\"\n                            alpha: 1\n                            type: hex\n                          selectors:\n                            - platform: ios\n                              dark_mode: true\n                              color:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 0.5\n                            - platform: android\n                              dark_mode: true\n                              color:\n                                hex: \"#FFFFFF\"\n                                type: hex\n                                alpha: 0.5\n                        aspect_ratio: 1\n                        type: ellipse\n                        scale: 1\n              margin:\n                bottom: 8\n                top: 0\n                end: 0\n                start: 0\n              position:\n                horizontal: center\n                vertical: bottom\n          type: container\n        size:\n          width: 100%\n          height: 100%\n  identifier: 3451d5ae-31ea-4bca-81f9-84846274b683\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-day-night-colors.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 80%\n      height: 30%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      selectors:\n      - platform: ios\n        dark_mode: true\n        color:\n          hex: \"#FFFFFF\"\n          alpha: 0.5\n      - platform: ios\n        dark_mode: false\n        color:\n          hex: \"#000000\"\n          alpha: 0.5\n      default:\n        hex: \"#FF00FF\"\n        alpha: 1\nview:\n  type: container\n  border:\n    stroke_color:\n      selectors:\n      - platform: ios\n        dark_mode: false\n        color:\n          hex: \"#f5e253\"\n          alpha: 1\n      - platform: ios\n        dark_mode: true\n        color:\n          hex: \"#7329c2\"\n          alpha: 1\n      default:\n        hex: \"#FF00FF\"\n        alpha: 1\n    stroke_width: 2\n    radius: 15\n  background_color:\n    selectors:\n    - platform: ios\n      dark_mode: false\n      color:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    - platform: ios\n      dark_mode: true\n      color:\n        hex: \"#000000\"\n        alpha: 1\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: top\n    margin:\n      top: 64\n      bottom: 16\n      start: 16\n      end: 16\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: \"The Earth orbits the sun once every 365 days and rotates about its axis once every 24 hours. Day and night are due to the Earth rotating on its axis, not its orbiting around the sun.\"\n      text_appearance:\n        font_size: 14\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#FFFFFF\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n\n  - position:\n      horizontal: center\n      vertical: bottom\n    margin:\n      bottom: 16\n    size:\n      width: auto\n      height: auto\n    view:\n      type: label_button\n      identifier: cool_button\n      button_click: [ cancel ]\n      border:\n        stroke_width: 1\n        radius: 3\n        stroke_color:\n          selectors:\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#7329c2\"\n              alpha: 1\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#f5e253\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n      background_color:\n        selectors:\n        - platform: ios\n          dark_mode: true\n          color:\n            hex: \"#7329c2\"\n            alpha: 1\n        - platform: ios\n          dark_mode: false\n          color:\n            hex: \"#f5e253\"\n            alpha: 1\n        default:\n          hex: \"#FF00FF\"\n          alpha: 1\n      label:\n        type: label\n        text: Cool story, bro!\n        text_appearance:\n          font_size: 14\n          color:\n            selectors:\n            - platform: ios\n              dark_mode: true\n              color:\n                hex: \"#FFFFFF\"\n                alpha: 1\n            - platform: ios\n              dark_mode: false\n              color:\n                hex: \"#000000\"\n                alpha: 1\n            default:\n              hex: \"#FF00FF\"\n              alpha: 1\n\n  - position:\n      horizontal: end\n      vertical: top\n    margin:\n      top: 8\n      end: 8\n    size:\n      width: 24\n      height: 24\n    view:\n      type: image_button\n      identifier: close_button\n      button_click: [ cancel ]\n      image:\n        type: icon\n        icon: close\n        color:\n          selectors:\n          - platform: ios\n            dark_mode: false\n            color:\n              hex: \"#000000\"\n              alpha: 1\n          - platform: ios\n            dark_mode: true\n            color:\n              hex: \"#ffffff\"\n              alpha: 1\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-disable-back-button.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  android:\n    disable_back_button: true\n  ignore_safe_area: true\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    margin:\n      top: 16\n      bottom: 16\n      start: 16\n      end: 16\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - margin:\n          bottom: 16\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"Playground App needs an update\"\n          text_appearance:\n            font_size: 24\n            styles:\n            - bold\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      - margin:\n          bottom: 32\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"To continue using this app, download the latest version.\"\n          text_appearance:\n            font_size: 14\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      - position:\n          horizontal: center\n          vertical: bottom\n        margin:\n          bottom: 16\n        size:\n          width: auto\n          height: auto\n        view:\n          type: label_button\n          identifier: update_button\n          actions:\n            deep_link_action: \"uairship://app_store\"\n          background_color:\n            default:\n              hex: \"#33dd33\"\n              alpha: 1\n          label:\n            type: label\n            text: UPDATE\n            text_appearance:\n              font_size: 14\n              styles:\n              - bold\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-labels.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: auto\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\n  dismiss_on_touch_outside: true\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      #\n      # Size\n      #\n      - margin:\n          top: 8\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Size\n          text_appearance:\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            styles:\n            - bold\n            - underlined\n            font_size: 16\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Tiny (8sp)\n          text_appearance:\n            alignment: start\n            font_size: 8\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Small (10sp)\n          text_appearance:\n            alignment: start\n            font_size: 10\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Normal (12sp)\n          text_appearance:\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Larger (16sp)\n          text_appearance:\n            alignment: start\n            font_size: 16\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Huge (24sp)\n          text_appearance:\n            alignment: start\n            font_size: 24\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      #\n      # Alignment\n      #\n      - margin:\n          top: 8\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Alignment\n          text_appearance:\n            alignment: start\n            styles:\n            - bold\n            - underlined\n            font_size: 16\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Start\n          text_appearance:\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Center\n          text_appearance:\n            alignment: center\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: End\n          text_appearance:\n            alignment: end\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      #\n      # Style\n      #\n      - margin:\n          top: 8\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Style\n          text_appearance:\n            alignment: start\n            styles:\n            - bold\n            - underlined\n            font_size: 16\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Normal\n          text_appearance:\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Bold\n          text_appearance:\n            styles:\n            - bold\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Underline\n          text_appearance:\n            styles:\n            - underlined\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Italic\n          text_appearance:\n            styles:\n            - italic\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n      - margin:\n          top: 0\n          bottom: 8\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Bold + Italic + Underline\n          text_appearance:\n            styles:\n            - bold\n            - underlined\n            - italic\n            alignment: start\n            font_size: 12\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-linear-layout-horizontal.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 364\n      height: 30%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.7\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  border:\n    stroke_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\n    stroke_width: 3\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: horizontal\n      background_color:\n        default:\n          hex: \"#ffffff\"\n          alpha: 0.35\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: auto\n          height: 100%\n        view:\n          type: label\n          text: auto\n          background_color:\n            default:\n              hex: \"#FF0000\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 75%\n          height: 100%\n        view:\n          type: label\n          text: 75%\n          background_color:\n            default:\n              hex: \"#00FF00\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 20%\n          height: 100%\n        view:\n          type: label\n          text: 20%\n          background_color:\n            default:\n              hex: \"#0000FF\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 40\n          height: 100%\n        view:\n          type: label\n          text: 40dp\n          background_color:\n            default:\n              hex: \"#FF00FF\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-linear-layout-vertical.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 30%\n      height: 364\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.7\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  border:\n    stroke_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\n    stroke_width: 3\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#ffffff\"\n          alpha: 0.35\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: auto\n          background_color:\n            default:\n              hex: \"#FF0000\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 75%\n        view:\n          type: label\n          text: 75%\n          background_color:\n            default:\n              hex: \"#00FF00\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 20%\n        view:\n          type: label\n          text: 20%\n          background_color:\n            default:\n              hex: \"#0000FF\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 40\n        view:\n          type: label\n          text: 40dp\n          background_color:\n            default:\n              hex: \"#FF00FF\"\n              alpha: 0.35\n          text_appearance:\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n            font_size: 12\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-linear-layout-webview-emoji.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 95%\n      height: 90%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.7\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#000000\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: auto\n        margin:\n          top: 24\n          bottom: 24\n          start: 16\n          end: 16\n        view:\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            view:\n              type: label\n              text: \"Wikipedia <i>Push technology</i>\"\n              text_appearance:\n                color:\n                  default:\n                    hex: \"#ffffff\"\n                    alpha: 1\n                alignment: center\n                font_size: 18\n                styles:\n                - bold\n                font_families:\n                - monospace\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        margin:\n          top: 16\n          bottom: 16\n          start: 16\n          end: 16\n        view:\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: 100%\n            view:\n              type: web_view\n              url: \"https://en.m.wikipedia.org/wiki/Push_technology\"\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: auto\n        margin:\n          top: 24\n          bottom: 24\n          start: 16\n          end: 16\n        view:\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: auto\n              height: auto\n            view:\n              type: label\n              text: \"🆒 🆒 🆒\"\n              text_appearance:\n                font_size: 24\n                color:\n                  default:\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-linear-layout.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 75%\n      height: 60%\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  border:\n    stroke_color:\n      default:\n        hex: \"#00FF00\"\n        alpha: 1\n    stroke_width: 5\n    radius: 15\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          # SCROLL CONTENT (CONTAINER)\n          type: container\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            margin:\n              top: 16\n              bottom: 16\n              start: 16\n              end: 16\n            size:\n              width: 100%\n              height: 100%\n            view:\n              type: label\n              text: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed\n                do eiusmod tempor incididunt ut labore et dolore magna aliqua. In\n                arcu cursus euismod quis viverra nibh. Lobortis feugiat vivamus\n                at augue eget arcu dictum. Imperdiet dui accumsan sit amet nulla.\n                Ultrices neque ornare aenean euismod elementum. Tincidunt id aliquet\n                risus feugiat in ante metus dictum\n              text_appearance:\n                color:\n                  default:\n                    hex: \"#333333\"\n                    alpha: 1\n                alignment: start\n                styles:\n                - italic\n                font_families:\n                - permanent_marker\n                - casual\n                font_size: 14\n      # BOTTOM-PINNED BUTTON #1\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 16\n          bottom: 16\n          start: 16\n          end: 16\n        view:\n          type: label_button\n          identifier: BUTTON\n          background_color:\n            default:\n              hex: \"#FF0000\"\n              alpha: 1\n          button_click:\n          - cancel\n          label:\n            type: label\n            text: 'Push me!'\n            text_appearance:\n              font_size: 24\n              alignment: center\n              color:\n                default:\n                  hex: \"#333333\"\n                  alpha: 1\n              styles:\n              - bold\n              font_families:\n              - casual\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-media-header-body-stacked-buttons.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: 75%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      # SCROLL LAYOUT\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: scroll_layout\n          direction: vertical\n          view:\n            # SCROLL CONTENT (LINEAR LAYOUT)\n            type: linear_layout\n            direction: vertical\n            size:\n              width: 100%\n              height: 100%\n            items:\n            # MEDIA\n            - size:\n                width: 100%\n                height: auto\n              view:\n                type: media\n                url: https://media.giphy.com/media/6y70TnZ4ug9zetb5Oq/giphy.gif\n                media_type: image\n                media_fit: center_inside\n            # HEADER\n            - margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Lorem ipsum dolor sit amet\n                text_appearance:\n                  color:\n                    default:\n                      hex: \"#FF00FF\"\n                      alpha: 1\n                  alignment: start\n                  styles:\n                  - bold\n                  - underlined\n                  - italic\n                  font_families:\n                  - permanent_marker\n                  - casual\n                  font_size: 24\n            # BODY\n            - size:\n                width: 100%\n                height: auto\n              margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              view:\n                type: label\n                text: Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed\n                  do eiusmod tempor incididunt ut labore et dolore magna aliqua. In\n                  arcu cursus euismod quis viverra nibh. Lobortis feugiat vivamus\n                  at augue eget arcu dictum. Imperdiet dui accumsan sit amet nulla.\n                  Ultrices neque ornare aenean euismod elementum. Tincidunt id aliquet\n                  risus feugiat in ante metus dictum.\n                text_appearance:\n                  color:\n                    default:\n                      hex: \"#333333\"\n                      alpha: 1\n                  alignment: start\n                  styles: [ italic ]\n                  font_families: [ permanent_marker ]\n                  font_size: 14\n      # BOTTOM-PINNED BUTTON #1\n      - position:\n          horizontal: center\n          vertical: center\n        margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label_button\n          identifier: BUTTON\n          background_color:\n            default:\n              hex: \"#FF0000\"\n              alpha: 1\n          label:\n            type: label\n            text_appearance:\n              font_size: 24\n              alignment: center\n              styles:\n              - bold\n              - italic\n              - underlined\n              font_families:\n              - permanent_marker\n              color:\n                default:\n                  hex: \"#00FF00\"\n                  alpha: 1\n            text: 'NO'\n      # BOTTOM-PINNED BUTTON #2\n      - position:\n          horizontal: center\n          vertical: center\n        margin:\n          top: 0\n          bottom: 0\n          start: 8\n          end: 8\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: label_button\n          identifier: BUTTON\n          background_color:\n            default:\n              hex: \"#00FF00\"\n              alpha: 1\n          label:\n            type: label\n            text_appearance:\n              font_size: 24\n              alignment: center\n              styles:\n              - bold\n              - italic\n              - underlined\n              font_families:\n              - permanent_marker\n              color:\n                default:\n                  hex: \"#FF0000\"\n                  alpha: 1\n            text: 'YES'\n  # TOP-LEFT IMAGE BUTTON\n  - position:\n      horizontal: start\n      vertical: top\n    size:\n      width: 48\n      height: 48\n    view:\n      type: image_button\n      identifier: octopus_button\n      button_click: [ cancel ]\n      image:\n        type: url\n        url: https://testing-library.com/img/octopus-64x64.png\n  # TOP-RIGHT ICON BUTTON\n  - position:\n      horizontal: end\n      vertical: top\n    size:\n      width: 48\n      height: 48\n    view:\n      type: image_button\n      identifier: close_button\n      button_click: [ dismiss ]\n      image:\n        type: icon\n        icon: close\n        color:\n          default:\n            hex: \"#FF00FF\"\n            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-pager-fullsize.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: 80%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    border:\n      radius: 30\n      stroke_width: 8\n      stroke_color:\n        default:\n          hex: \"#333333\"\n          alpha: 0.8\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n    - position:\n        vertical: center\n        horizontal: center\n      size:\n        height: 100%\n        width: 100%\n      view:\n        type: pager\n        items:\n        - identifier: \"pager-page-1-id\"\n          display_actions:\n            add_tags_action: 'pager-page-1x'\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#FF0000\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: label\n                text: This is the first page about stuff.\n                text_appearance:\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                  font_size: 14\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_1\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n        - identifier: \"pager-page-2-id\"\n          display_actions:\n            add_tags_action: 'pager-page-2x'\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#00FF00\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: label\n                text: More stuff is here on the second page.\n                text_appearance:\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                  font_size: 14\n\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_2\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n        - identifier: \"pager-page-3-id\"\n          display_actions:\n            add_tags_action: 'pager-page-3x'\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#0000FF\"\n                alpha: 0.5\n            items:\n            - position:\n                vertical: center\n                horizontal: center\n              size:\n                height: auto\n                width: auto\n              view:\n                type: nps_form_controller\n                identifier: page_3_nps_form\n                nps_identifier: score_identifier\n                submit: submit_event\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - size:\n                      width: 100%\n                      height: auto\n                    margin:\n                      start: 8\n                      end: 8\n                    view:\n                      type: score\n                      identifier: score_identifier\n                      required: true\n                      style:\n                        type: number_range\n                        start: 0\n                        end: 10\n                        spacing: 2\n                        bindings:\n                          selected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#FFFFFF\"\n                                  alpha: 0\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                          unselected:\n                            shapes:\n                            - type: ellipse\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                            text_appearance:\n                              font_size: 14\n                              color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                  - size:\n                      width: auto\n                      height: auto\n                    margin:\n                      start: 16\n                      end: 16\n                    view:\n                      type: label_button\n                      identifier: submit_button\n                      background_color:\n                        default:\n                          hex: \"#ffffff\"\n                          alpha: 1\n                      button_click: [\"form_submit\", \"cancel\"]\n                      enabled: [\"form_validation\"]\n                      display_actions:\n                        add_tags_action: 'pager-page-3-form-submit'\n                      label:\n                        type: label\n                        text: SuBmIt!1!1@\n                        text_appearance:\n                          font_size: 14\n                          alignment: center\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 1\n\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 24\n                width: 24\n              margin:\n                top: 8\n                end: 8\n              view:\n                type: image_button\n                identifier: close_button_3\n                button_click: [ dismiss ]\n                image:\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n\n    - size:\n        height: 16\n        width: auto\n      position:\n        vertical: bottom\n        horizontal: center\n      margin:\n        bottom: 8\n      view:\n        type: pager_indicator\n        carousel_identifier: CAROUSEL_ID\n        background_color:\n          default:\n            hex: \"#333333\"\n            alpha: 0.7\n        border:\n          radius: 8\n        spacing: 4\n        bindings:\n          selected:\n            shapes:\n            - type: rectangle\n              aspect_ratio: 2.25\n              scale: 0.9\n              border:\n                radius: 3\n                stroke_width: 1\n                stroke_color:\n                  default:\n                    hex: \"#ffffff\"\n                    alpha: 0.7\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n          unselected:\n            shapes:\n            - type: rectangle\n              aspect_ratio: 2.25\n              scale: .9\n              border:\n                radius: 3\n                stroke_width: 1\n                stroke_color:\n                  default:\n                    hex: \"#ffffff\"\n                    alpha: 0.7\n              color:\n                default:\n                  hex: \"#000000\"\n                  alpha: 0\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-pager-with-title-and-button.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: 95%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#FFFFFF\"\n        alpha: 1\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: auto\n        width: 100%\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n        - size:\n            height: auto\n            width: 100%\n          margin:\n            top: 16\n            start: 16\n            end: 16\n          view:\n            type: label\n            text: Take a spin\n            text_appearance:\n              alignment: center\n              styles:\n              - bold\n              font_size: 18\n              color:\n                default:\n                  hex: \"#000000\"\n        - size:\n            height: 250\n            width: 100%\n          margin:\n            start: 64\n            end: 64\n            top: 16\n            bottom: 16\n          view:\n            type: pager\n            disable_swipe: false\n            items:\n            - identifier: \"page-1\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#88FF0000\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: This is the first page about stuff.\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n            - identifier: \"page-2\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: More stuff is here on the second page.\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n            - identifier: \"page-3\"\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: .8\n                items:\n                - position:\n                    vertical: center\n                    horizontal: center\n                  size:\n                    height: auto\n                    width: auto\n                  view:\n                    type: label\n                    text: That's not all! There's a third page, too!\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          hex: \"#000000\"\n                      font_size: 14\n        - margin:\n            bottom: 10\n            end: 0\n            start: 0\n            top: 10\n          size:\n            height: 100%\n            width: 90%\n          view:\n            type: custom_view\n            name: scene_controller_test\n            properties:\n                cool: story\n            border:\n                stroke_color:\n                  default:\n                    hex: \"#000000\"\n                stroke_width: 3\n                radius: 4\n        - size:\n            height: 30\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: pager_indicator\n            spacing: 16\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  color:\n                    default:\n                      hex: \"#0000FF\"\n                icon:\n                  icon: checkmark\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n                  scale: .8\n              unselected:\n                shapes:\n                - type: rectangle\n                  aspect_ratio: 1\n                  border:\n                    stroke_color:\n                      default:\n                        hex: \"#000000\"\n                    stroke_width: 3\n                    radius: 4\n                  color:\n                    default:\n                      hex: \"#FF0000\"\n                icon:\n                  icon: close\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n                  scale: .8\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-placement-selectors.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  placement_selectors:\n  - orientation: portrait\n    placement:\n      size:\n        width: 66%\n        height: auto\n        max_height: 10%\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          hex: \"#000000\"\n          alpha: 0.6\n  - orientation: landscape\n    placement:\n      size:\n        width: 33%\n        height: auto\n      position:\n        horizontal: center\n        vertical: center\n      shade_color:\n        default:\n          hex: \"#000000\"\n          alpha: 0.6\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\nview:\n  type: container\n  border:\n    stroke_color:\n      default:\n        hex: \"#CED8F7\"\n        alpha: 1\n    stroke_width: 2\n    radius: 15\n  background_color:\n    default:\n      hex: \"#002082\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    margin:\n      top: 16\n      bottom: 16\n      start: 16\n      end: 16\n    size:\n      width: 100%\n      height: auto\n    view:\n      type: label\n      text: If the Placement Selectors for this modal are working correctly, the container should fill ~2/3 of the screen width in portrait orientation or ~1/3 of the width in landscape orientation.\\n\\nIf this modal is touching any edges of the screen, something ain't right and it was rendered with default placement!.\n      text_appearance:\n        color:\n          default:\n            hex: \"#CED8F7\"\n            alpha: 1\n        alignment: center\n        font_families:\n        - walter_turncoat\n        font_size: 14\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-responsive.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  placement_selectors:\n    - orientation: landscape\n      placement:\n        ignore_safe_area: false\n        size:\n          width: 100%\n          height: 100%\n        position:\n          horizontal: center\n          vertical: bottom\n        margin:\n          top: 25\n          bottom: 25\n          start: 25\n          end: 25\n        shade_color:\n          default:\n            type: hex\n            hex: \"#000000\"\n            alpha: 0.2\n        background_color:\n          default:\n            type: hex\n            hex: \"#FF0000\"\n            alpha: 1\n        border:\n          radius: 20\n\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\n    background_color:\n      default:\n        type: hex\n        hex: \"#FFFFFF\"\n        alpha: 1\nview:\n  type: pager_controller\n  identifier: 6ab1531a-fcb3-44b4-91d7-52db73ae7cd9\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n      - size:\n          height: 100%\n          width: 100%\n        view:\n          type: container\n          items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                  - identifier: c36a5103-0a8d-4e34-b7b7-331ec1cbc87e\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - size:\n                            width: 100%\n                            height: 100%\n                          position:\n                            horizontal: center\n                            vertical: center\n                          view:\n                            type: container\n                            items:\n                              - margin:\n                                  bottom: 16\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        direction: vertical\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              margin:\n                                                top: 48\n                                                bottom: 8\n                                                start: 16\n                                                end: 16\n                                              view:\n                                                type: label\n                                                text: This is test\n                                                text_appearance:\n                                                  font_size: 30\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                      - platform: ios\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                      - platform: android\n                                                        dark_mode: true\n                                                        color:\n                                                          type: hex\n                                                          hex: \"#FFFFFF\"\n                                                          alpha: 1\n                                                  alignment: center\n                                                  styles: []\n                                                  font_families:\n                                                    - serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items: []\n              ignore_safe_area: false\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                    selectors:\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                identifier: dismiss_button\n                button_click:\n                  - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-score.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 90%\n      height: auto\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.75\nview:\n  type: form_controller\n  identifier: parent_form\n  submit: submit_event\n  view:\n    type: linear_layout\n    direction: vertical\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n    # Score 1 (0 - 10)\n    - size:\n        width: auto\n        height: 40\n      margin:\n        top: 8\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: nps_form_controller\n        identifier: nps_zero_to_ten_form\n        nps_identifier: nps_zero_to_ten\n        view:\n          type: score\n          identifier: \"nps_zero_to_ten\"\n          required: true\n          style:\n            type: number_range\n            spacing: 2\n            start: 0\n            end: 10\n            bindings:\n              selected:\n                shapes:\n                - type: rectangle\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 12\n                  styles:\n                  - bold\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n              unselected:\n                shapes:\n                - type: rectangle\n                  border:\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#999999\"\n                        alpha: 1\n                  color:\n                    default:\n                      hex: \"#dedede\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 12\n                  color:\n                    default:\n                      hex: \"#666666\"\n                      alpha: 1\n    # Score 2 (1 - 5)\n    - size:\n        width: auto\n        height: 24\n      margin:\n        top: 8\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: nps_form_controller\n        identifier: nps_zero_to_ten_form\n        nps_identifier: nps_zero_to_ten\n        view:\n          type: score\n          identifier: \"nps_zero_to_ten\"\n          required: true\n          style:\n            type: number_range\n            spacing: 8\n            start: 1\n            end: 5\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#FFDD33\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              unselected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#3333ff\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n\n    # Score 3 (97 - 105)\n    - size:\n        width: auto\n        height: 32\n      margin:\n        top: 8\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: nps_form_controller\n        identifier: nps_zero_to_ten_form\n        nps_identifier: nps_zero_to_ten\n        view:\n          type: score\n          identifier: \"nps_zero_to_ten\"\n          required: true\n          style:\n            type: number_range\n            spacing: 8\n            start: 97\n            end: 105\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#FF0000\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              unselected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#0000FF\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 14\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n\n    # BOTTOM-PINNED BUTTON\n    - size:\n        width: 100%\n        height: auto\n      margin:\n        top: 16\n        bottom: 16\n        start: 16\n        end: 16\n      view:\n        type: label_button\n        identifier: SUBMIT_BUTTON\n        background_color:\n          default:\n            hex: \"#000000\"\n            alpha: 1\n        button_click: [\"form_submit\", \"cancel\"]\n        enabled: [\"form_validation\"]\n        label:\n          type: label\n          text: 'SEND IT!'\n          text_appearance:\n            font_size: 14\n            alignment: center\n            color:\n              default:\n                hex: \"#ffffff\"\n                alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-transparancy.yml",
    "content": "#100% — FF\n#95% — F2\n#90% — E6\n#85% — D9\n#80% — CC\n#75% — BF\n#70% — B3\n#65% — A6\n#60% — 99\n#55% — 8C\n#50% — 80\n#45% — 73\n#40% — 66\n#35% — 59\n#30% — 4D\n#25% — 40\n#20% — 33\n#15% — 26\n#10% — 1A\n#5% — 0D\n#0% — 00\n---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 75%\n      height: 50%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#FFFFFF\"\n      alpha: .25\n  border:\n    stroke_color:\n      default:\n        hex: \"#FF0000\"\n        alpha: 0.5\n    stroke_width: 2\n    radius: 0\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    margin:\n      top: 16\n      bottom: 16\n      start: 16\n      end: 16\n    view:\n      type: container\n      background_color:\n        default:\n          hex: \"#FFFFFF\"\n          alpha: 0.25\n      border:\n        stroke_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 0.5\n        stroke_width: 2\n        radius: 0\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        margin:\n          top: 16\n          bottom: 16\n          start: 16\n          end: 16\n        size:\n          width: auto\n          height: auto\n        view:\n          type: label\n          text: Lorem ipsum dolor sit amet\n          text_appearance:\n            color:\n              default:\n                hex: \"#333333\"\n                alpha: 1\n            alignment: center\n            font_size: 14\n            font_families:\n            - permanent_marker\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-video-cropping.yml",
    "content": "---\npresentation:\n  dismiss_on_touch_outside: true\n  default_placement:\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.5\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n  type: modal\nversion: 1\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n      - position:\n          vertical: center\n          horizontal: center\n        size:\n          height: 100%\n          width: 100%\n        border:\n          radius: 25\n        margin:\n          top: 36\n        view:\n          type: pager\n          items:\n            - identifier: \"page-1\"\n              view:\n                type: container\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: scroll_layout\n                      direction: vertical\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Wide Image (100% x auto)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 24\n                              end: 24\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              media_fit: center_inside\n                              media_type: video\n                              type: media\n                              url: https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_10MB.mp4\n                              video:\n                                aspect_ratio: 1.7777\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Center (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              url: https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_10MB.mp4\n                              media_type: video\n                              video:\n                                aspect_ratio: 1.7777\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n                              type: media\n                              position:\n                                horizontal: center\n                                vertical: center\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Top Start (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: start\n                                vertical: top\n                              url: https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_10MB.mp4\n                              video:\n                                aspect_ratio: 1.7777\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Bottom End (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: end\n                                vertical: bottom\n                              url: https://test-videos.co.uk/vids/bigbuckbunny/mp4/h264/720/Big_Buck_Bunny_720_10s_10MB.mp4\n                              video:\n                                aspect_ratio: 1.7777\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n            - identifier: \"page-2\"\n              view:\n                type: container\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: scroll_layout\n                      direction: vertical\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Tall Image (100% x auto)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 48\n                              end: 48\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              media_fit: center_inside\n                              media_type: video\n                              type: media\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                aspect_ratio: 0.5625\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Center (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              position:\n                                horizontal: center\n                                vertical: center\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                aspect_ratio: 0.5625\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Top Start (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              position:\n                                horizontal: start\n                                vertical: top\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                aspect_ratio: 0.5625\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Bottom End (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              position:\n                                horizontal: end\n                                vertical: bottom\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                aspect_ratio: 0.5625\n                                show_controls: false\n                                autoplay: true\n                                muted: true\n                                loop: true\n      - size:\n          height: 16\n          width: auto\n        position:\n          vertical: top\n          horizontal: center\n        margin:\n          top: 12\n        view:\n          type: pager_indicator\n          carousel_identifier: CAROUSEL_ID\n          border:\n            radius: 8\n          spacing: 4\n          bindings:\n            selected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  scale: 0.75\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  scale: 0.75\n                  border:\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#333333\"\n                        alpha: 1\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n      - position:\n          vertical: top\n          horizontal: end\n        size:\n          width: 36\n          height: 36\n        margin:\n          top: 0\n          end: 0\n        view:\n          type: image_button\n          identifier: x_button\n          button_click: [ dismiss ]\n          image:\n            type: icon\n            icon: close\n            scale: 0.5\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-webview-full-size.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 95%\n      height: 90%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#ffffff\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: web_view\n      url: \"https://docs.airship.com\"\n      background_color:\n        default:\n          hex: \"#000000\"\n          alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/modal-webview-with-buttons.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 95%\n      height: 90%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#000000\"\n      alpha: 1\n  items:\n  - position:\n      horizontal: center\n      vertical: top\n    size:\n      height: 90%\n      width: 100%\n    view:\n      type: web_view\n      url: \"https://docs.airship.com\"\n      background_color:\n        default:\n          hex: \"#000000\"\n          alpha: 1\n  - position:\n      horizontal: start\n      vertical: bottom\n    size:\n      height: 10%\n      width: 50%\n    view:\n      type: label_button\n      identifier: button1\n      button_click: [ dismiss ]\n      label:\n        type: label\n        text: cool\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n      background_color:\n        default:\n          hex: \"#AED581\" # green\n          alpha: 1\n  - position:\n      horizontal: end\n      vertical: bottom\n    size:\n      height: 10%\n      width: 50%\n    view:\n      type: label_button\n      button_click: [ cancel ]\n      identifier: button2\n      background_color:\n        default:\n          hex: \"#D32F2F\" # red\n          alpha: 1\n      label:\n        type: label\n        text: beans\n        text_appearance:\n          font_size: 14\n          color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/model-custom-camera-view.yml",
    "content": "---\nversion: 1\npresentation:\n  dismiss_on_touch_outside: true\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#000000\"\n      alpha: 1\n  border:\n    stroke_color:\n      default:\n        hex: \"#000000\"\n        alpha: 1\n    stroke_width: 1\n    radius: 0\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      # SCROLL LAYOUT\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: scroll_layout\n          direction: vertical\n          view:\n            # SCROLL CONTENT (LINEAR LAYOUT)\n            type: linear_layout\n            direction: vertical\n            size:\n              width: 100%\n              height: 100%\n            items:\n            # Camera View\n            - size:\n                width: 100%\n                height: 100%\n              view:\n                type: custom_view\n                name: camera_custom_view\n                properties:\n                    camera: \"I'm a camera\"\n                background_color:\n                selectors:\n                - platform: ios\n                  dark_mode: false\n                  color:\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                - platform: ios\n                  dark_mode: true\n                  color:\n                    hex: \"#000000\"\n                    alpha: 1\n                default:\n                  hex: \"#FF00FF\"\n                  alpha: 1\n            # BODY\n            - size:\n                width: 100%\n                height: auto\n              margin:\n                top: 8\n                bottom: 8\n                start: 8\n                end: 8\n              view:\n                type: label\n                text: Camera permissions will be requested automatically if we're not already authorized.\\n\\n We inherit our authorization from the containing application.\n                text_appearance:\n                  color:\n                    default:\n                      hex: \"#333333\"\n                      alpha: 1\n                  alignment: start\n                  styles: [ italic ]\n                  font_families: [ permanent_marker ]\n                  font_size: 14\n  # TOP-RIGHT ICON BUTTON\n  - position:\n      horizontal: end\n      vertical: top\n    size:\n      width: 24\n      height: 24\n    view:\n      type: image_button\n      identifier: close_button\n      button_click: [ dismiss ]\n      image:\n        type: icon\n        icon: close\n        color:\n          default:\n            hex: \"#000000\"\n            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/multi-video-players.yml",
    "content": "---\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    device:\n      lock_orientation: portrait\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      max_height: 100%\n      max_width: 100%\n      min_height: 100%\n      min_width: 100%\n      width: 100%\n  dismiss_on_touch_outside: false\n  type: modal\nversion: 1\nview:\n  identifier: cd53eb72-6983-43b7-a7c6-084067b3e431\n  type: pager_controller\n  view:\n    direction: vertical\n    items:\n      - size:\n          height: 100%\n          width: 100%\n        view:\n          items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                height: 100%\n                width: 100%\n              view:\n                disable_swipe: false\n                items:\n                  - identifier: d3fc5b7e-1a6b-4dc9-aacc-75f1a4b0f8be\n                    type: pager_item\n                    view:\n                      background_color:\n                        default:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                        selectors:\n                          - color:\n                              alpha: 1\n                              hex: \"#000000\"\n                              type: hex\n                            dark_mode: true\n                            platform: ios\n                          - color:\n                              alpha: 1\n                              hex: \"#000000\"\n                              type: hex\n                            dark_mode: true\n                            platform: android\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            items:\n                              - margin:\n                                  bottom: 16\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#FFFFFF\"\n                                      type: hex\n                                    selectors:\n                                      - color:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                        dark_mode: true\n                                        platform: ios\n                                      - color:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                        dark_mode: true\n                                        platform: android\n                                  direction: vertical\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        direction: vertical\n                                        type: scroll_layout\n                                        view:\n                                          direction: vertical\n                                          items:\n                                            - size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: youtube\n                                                type: media\n                                                url: https://www.youtube.com/embed/xUOQZeN8A7o\n                                                video:\n                                                  aspect_ratio: 1.77777777777778\n                                                  autoplay: true\n                                                  loop: true\n                                                  muted: true\n                                                  show_controls: true\n                                            - size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: vimeo\n                                                type: media\n                                                url: https://player.vimeo.com/video/714680147?autoplay=0&loop=1&controls=1&muted=1&unmute_button=0\n                                                video:\n                                                  aspect_ratio: 1.77777777777778\n                                                  autoplay: false\n                                                  loop: true\n                                                  muted: true\n                                                  show_controls: true\n                                            - size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: image\n                                                type: media\n                                                url: https://hangar-dl.urbanairship.com/binary/public/VWDwdOFjRTKLRxCeXTVP6g/25b3bea5-e233-4d25-8d96-c65e6a859cee\n                                            - size:\n                                                height: 100%\n                                                width: 100%\n                                              view:\n                                                direction: horizontal\n                                                items: []\n                                                type: linear_layout\n                                          type: linear_layout\n                                  type: linear_layout\n                            type: container\n                      type: container\n                  - identifier: d3fc5b7e-1a6b-4dc9-aacc-75f1a4b0f8be\n                    type: pager_item\n                    view:\n                      background_color:\n                        default:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                        selectors:\n                          - color:\n                              alpha: 1\n                              hex: \"#000000\"\n                              type: hex\n                            dark_mode: true\n                            platform: ios\n                          - color:\n                              alpha: 1\n                              hex: \"#000000\"\n                              type: hex\n                            dark_mode: true\n                            platform: android\n                      items:\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            items:\n                              - margin:\n                                  bottom: 16\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#FFFFFF\"\n                                      type: hex\n                                    selectors:\n                                      - color:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                        dark_mode: true\n                                        platform: ios\n                                      - color:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                        dark_mode: true\n                                        platform: android\n                                  direction: vertical\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        direction: vertical\n                                        type: scroll_layout\n                                        view:\n                                          direction: vertical\n                                          items:\n                                            - size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: video\n                                                type: media\n                                                url: https://dsqqu7oxq6o1v.cloudfront.net/preview-992269-Avy3e9Poxf-high.mp4\n                                                video:\n                                                  aspect_ratio: 1.77777777777778\n                                                  autoplay: false\n                                                  loop: true\n                                                  muted: true\n                                                  show_controls: true\n                                            - size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: video\n                                                type: media\n                                                url: https://dsqqu7oxq6o1v.cloudfront.net/preview-992269-Avy3e9Poxf-high.mp4\n                                                video:\n                                                  aspect_ratio: 1.77777777777778\n                                                  autoplay: true\n                                                  loop: true\n                                                  muted: true\n                                                  show_controls: true\n                                            - size:\n                                                height: 100%\n                                                width: 100%\n                                              view:\n                                                direction: horizontal\n                                                items: []\n                                                type: linear_layout\n                                          type: linear_layout\n                                  type: linear_layout\n                            type: container\n                      type: container\n                  - identifier: a2f25df7-fdc2-4cbd-8f7e-80f2eba335d3\n                    type: pager_item\n                    view:\n                      background_color:\n                        default:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                        selectors:\n                          - color:\n                              alpha: 1\n                              hex: \"#000000\"\n                              type: hex\n                            dark_mode: true\n                            platform: ios\n                          - color:\n                              alpha: 1\n                              hex: \"#000000\"\n                              type: hex\n                            dark_mode: true\n                            platform: android\n                      items:\n                        - margin:\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            media_fit: center_crop\n                            media_type: vimeo\n                            type: media\n                            url: https://player.vimeo.com/video/714680147?autoplay=1&loop=1&controls=1&muted=1&unmute_button=0\n                            video:\n                              aspect_ratio: 1.7777777777777777\n                              autoplay: true\n                              loop: true\n                              muted: true\n                              show_controls: true\n                        - position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            items:\n                              - margin:\n                                  bottom: 16\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: vertical\n                                  items:\n                                    - identifier: scroll_container\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        direction: vertical\n                                        type: scroll_layout\n                                        view:\n                                          direction: vertical\n                                          items:\n                                            - size:\n                                                height: 100%\n                                                width: 100%\n                                              view:\n                                                direction: horizontal\n                                                items: []\n                                                type: linear_layout\n                                          type: linear_layout\n                                  type: linear_layout\n                            type: container\n                      type: container\n                type: pager\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                height: 48\n                width: 48\n              view:\n                button_click:\n                  - dismiss\n                identifier: dismiss_button\n                image:\n                  color:\n                    default:\n                      alpha: 1\n                      hex: \"#000000\"\n                      type: hex\n                    selectors:\n                      - color:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                        dark_mode: true\n                        platform: ios\n                      - color:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                        dark_mode: true\n                        platform: android\n                  icon: close\n                  scale: 0.4\n                  type: icon\n                type: image_button\n            - margin:\n                bottom: 8\n                end: 0\n                start: 0\n                top: 0\n              position:\n                horizontal: center\n                vertical: bottom\n              size:\n                height: 12\n                width: 100%\n              view:\n                bindings:\n                  selected:\n                    shapes:\n                      - aspect_ratio: 1\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#AAAAAA\"\n                            type: hex\n                        scale: 1\n                        type: ellipse\n                  unselected:\n                    shapes:\n                      - aspect_ratio: 1\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#CCCCCC\"\n                            type: hex\n                        scale: 1\n                        type: ellipse\n                spacing: 4\n                type: pager_indicator\n          type: container\n    type: linear_layout\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/nps.yml",
    "content": "  version: 1\n  presentation:\n    type: modal\n    placement_selectors: []\n    android:\n      disable_back_button: false\n    dismiss_on_touch_outside: false\n    default_placement:\n      ignore_safe_area: false\n      size:\n        width: 100%\n        height: 100%\n      position:\n        horizontal: center\n        vertical: top\n      shade_color:\n        default:\n          type: hex\n          hex: \"#000000\"\n          alpha: 0.2\n      web:\n        ignore_shade: true\n  view:\n    type: state_controller\n    view:\n      type: pager_controller\n      identifier: 50674629-f8fe-4d98-8ff8-7c727db42d1c\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n        - size:\n            width: 100%\n            height: 100%\n          view:\n            identifier: ef6ed88c-2329-4fa4-a6ed-6141051febf0\n            nps_identifier: 221acead-707d-4927-90ae-cffc09636871\n            type: nps_form_controller\n            submit: submit_event\n            form_enabled:\n            - form_submission\n            response_type: nps\n            view:\n              type: container\n              items:\n              - position:\n                  horizontal: center\n                  vertical: center\n                size:\n                  width: 100%\n                  height: 100%\n                view:\n                  type: pager\n                  disable_swipe: true\n                  items:\n                  - identifier: 760793cc-460f-4e68-a035-8db5b4bed25e\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                      - size:\n                          width: 100%\n                          height: 100%\n                        position:\n                          horizontal: center\n                          vertical: center\n                        ignore_safe_area: false\n                        view:\n                          type: container\n                          items:\n                          - margin:\n                              bottom: 0\n                              top: 0\n                              end: 0\n                              start: 0\n                            position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: linear_layout\n                              direction: vertical\n                              items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - identifier: 221acead-707d-4927-90ae-cffc09636871\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      margin:\n                                        top: 48\n                                        bottom: 8\n                                        start: 16\n                                        end: 16\n                                      view:\n                                        type: linear_layout\n                                        direction: vertical\n                                        items:\n                                        - size:\n                                            width: 100%\n                                            height: auto\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - margin:\n                                                top: 4\n                                                bottom: 8\n                                              size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                type: label\n                                                text: \"How likely is it that you\n                                                  would recommend [your company, product,\n                                                  etc.] to a friend or colleague?\"\n                                                labels:\n                                                    type: labels\n                                                    view_type: \"score\"\n                                                    view_id: \"221acead-707d-4927-90ae-cffc09636871\"\n                                                text_appearance:\n                                                  font_size: 20\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                  alignment: start\n                                                  styles: []\n                                                  font_families:\n                                                  - sans-serif\n                                        - size:\n                                            width: 100%\n                                            height: auto\n                                          margin:\n                                            top: 0\n                                            bottom: 0\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - size:\n                                                    width: 50%\n                                                    height: auto\n                                                  margin:\n                                                    end: 4\n                                                    bottom: 4\n                                                  view:\n                                                    type: label\n                                                    text: Not Likely\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: start\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                                - size:\n                                                    width: 50%\n                                                    height: auto\n                                                  margin:\n                                                    start: 4\n                                                    bottom: 4\n                                                  view:\n                                                    type: label\n                                                    text: Very Likely\n                                                    text_appearance:\n                                                      font_size: 16\n                                                      color:\n                                                        default:\n                                                          type: hex\n                                                          hex: \"#000000\"\n                                                          alpha: 1\n                                                        selectors:\n                                                        - platform: ios\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: android\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                        - platform: web\n                                                          dark_mode: true\n                                                          color:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                      alignment: end\n                                                      styles: []\n                                                      font_families:\n                                                      - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              view:\n                                                type: linear_layout\n                                                direction: horizontal\n                                                items:\n                                                - size:\n                                                    height: auto\n                                                    width: 100%\n                                                  view:\n                                                    type: score\n                                                    content_description: \"0 means not likely and 10 means very likely\"\n                                                    style:\n                                                      type: number_range\n                                                      start: 0\n                                                      end: 10\n                                                      spacing: 2\n                                                      wrapping:\n                                                        line_spacing: 10\n                                                        max_items_per_line: 6a\n                                                      bindings:\n                                                        selected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            border:\n                                                              radius: 2\n                                                              stroke_width: 1\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                                selectors:\n                                                                - platform: ios\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                                - platform: android\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                                - platform: web\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                          text_appearance:\n                                                            alignment: center\n                                                            font_families:\n                                                            - sans-serif\n                                                            font_size: 24\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                        unselected:\n                                                          shapes:\n                                                          - type: rectangle\n                                                            scale: 1\n                                                            border:\n                                                              radius: 2\n                                                              stroke_width: 1\n                                                              stroke_color:\n                                                                default:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                                selectors:\n                                                                - platform: ios\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                                - platform: android\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                                - platform: web\n                                                                  dark_mode: true\n                                                                  color:\n                                                                    type: hex\n                                                                    hex: \"#FFFFFF\"\n                                                                    alpha: 1\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                          text_appearance:\n                                                            font_size: 24\n                                                            font_families:\n                                                            - sans-serif\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#000000\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                    identifier: 221acead-707d-4927-90ae-cffc09636871\n                                                    required: true\n                                    - identifier: f6987b3a-8531-4568-9dfd-aaf6a20fffb3\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      margin:\n                                        top: 8\n                                        bottom: 8\n                                        start: 16\n                                        end: 16\n                                      view:\n                                        type: linear_layout\n                                        direction: vertical\n                                        items:\n                                        - margin:\n                                            top: 0\n                                            bottom: 8\n                                          size:\n                                            width: 100%\n                                            height: auto\n                                          view:\n                                            type: label\n                                            text: What is the primary reason for your\n                                              score?\n                                            text_appearance:\n                                              font_size: 20\n                                              color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                                selectors:\n                                                - platform: ios\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: android\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: web\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                              alignment: start\n                                              styles: []\n                                              font_families:\n                                              - sans-serif\n                                        - margin:\n                                            top: 0\n                                            bottom: 8\n                                          size:\n                                            width: 100%\n                                            height: 75\n                                          view:\n                                            background_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#eae9e9\"\n                                                alpha: 1\n                                            border:\n                                              radius: 2\n                                              stroke_width: 1\n                                              stroke_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#63656b\"\n                                                  alpha: 1\n                                            type: text_input\n                                            text_appearance:\n                                              alignment: start\n                                              font_size: 14\n                                              color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                            identifier: f6987b3a-8531-4568-9dfd-aaf6a20fffb3\n                                            input_type: text_multiline\n                                            required: false\n                                    - size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: linear_layout\n                                        direction: horizontal\n                                        items: []\n                              - identifier: e8c5c4ae-c909-4cfd-b4a1-a9d37790b630\n                                size:\n                                  width: 100%\n                                  height: auto\n                                view:\n                                  type: container\n                                  items:\n                                  - margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 0\n                                      end: 0\n                                    position:\n                                      horizontal: center\n                                      vertical: center\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - identifier: fd5e54ff-5564-4cf4-9ff6-d44d52bfb09e\n                                        margin:\n                                          top: 8\n                                          end: 16\n                                          bottom: 0\n                                          start: 16\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - margin:\n                                              top: 4\n                                              bottom: 16\n                                              start: 0\n                                              end: 0\n                                            size:\n                                              width: 100%\n                                              height: 48\n                                            view:\n                                              type: label_button\n                                              identifier: submit_feedback--Submit\n                                              reporting_metadata:\n                                                trigger_link_id: c26d8bb3-9989-4ea0-b02b-91a0585f8dae\n                                              label:\n                                                type: label\n                                                text: Submit\n                                                text_appearance:\n                                                  font_size: 16\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#000000\"\n                                                      alpha: 1\n                                                    selectors:\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#FFFFFF\"\n                                                        alpha: 1\n                                                  alignment: center\n                                                  styles: []\n                                                  font_families:\n                                                  - sans-serif\n                                              actions: {}\n                                              enabled:\n                                              - form_validation\n                                              button_click:\n                                              - form_submit\n                                              - dismiss\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#63AFF1\"\n                                                  alpha: 1\n                                                selectors:\n                                                - platform: ios\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#63AFF1\"\n                                                    alpha: 1\n                                                - platform: android\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#63AFF1\"\n                                                    alpha: 1\n                                                - platform: web\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#63AFF1\"\n                                                    alpha: 1\n                                              border:\n                                                radius: 0\n                                                stroke_width: 16\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#63AFF1\"\n                                                    alpha: 1\n                                                  selectors:\n                                                  - platform: ios\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                              event_handlers:\n                                              - type: tap\n                                                state_actions:\n                                                - type: set\n                                                  key: submitted\n                                                  value: true\n                                          background_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 0\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: android\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                            - platform: web\n                                              dark_mode: true\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 0\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#FFFFFF\"\n                                          alpha: 0\n                                        selectors:\n                                        - platform: ios\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 0\n                                        - platform: android\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 0\n                                        - platform: web\n                                          dark_mode: true\n                                          color:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 0\n                                margin:\n                                  top: 8\n                                  bottom: 8\n                                  start: 0\n                                  end: 0\n                              background_color:\n                                default:\n                                  type: hex\n                                  hex: \"#FFFFFF\"\n                                  alpha: 1\n                                selectors:\n                                - platform: ios\n                                  dark_mode: true\n                                  color:\n                                    type: hex\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                - platform: android\n                                  dark_mode: true\n                                  color:\n                                    type: hex\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                - platform: web\n                                  dark_mode: true\n                                  color:\n                                    type: hex\n                                    hex: \"#000000\"\n                                    alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                        selectors:\n                        - platform: ios\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#000000\"\n                            alpha: 1\n                        - platform: android\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#000000\"\n                            alpha: 1\n                        - platform: web\n                          dark_mode: true\n                          color:\n                            type: hex\n                            hex: \"#000000\"\n                            alpha: 1\n                ignore_safe_area: false\n              - position:\n                  horizontal: end\n                  vertical: top\n                size:\n                  width: 48\n                  height: 48\n                view:\n                  type: image_button\n                  image:\n                    scale: 0.4\n                    type: icon\n                    icon: close\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                      selectors:\n                      - platform: ios\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: android\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      - platform: web\n                        dark_mode: true\n                        color:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                  identifier: dismiss_button\n                  button_click:\n                  - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/pager-behaviors.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 90%\n      height: 90%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        hex: \"#444444\"\n        alpha: .3\nview:\n  type: linear_layout\n  direction: vertical\n  background_color:\n    default:\n      hex: \"#FFFFFF\"\n      alpha: 1\n  items:\n  - size:\n      height: auto\n      width: auto\n    margin:\n      top: 16\n      start: 16\n      end: 16\n      bottom: 16\n    view:\n      type: label\n      text: Pager Behaviors\n      text_appearance:\n        alignment: center\n        font_size: 14\n        color:\n          default:\n            hex: \"#000000\"\n  - size:\n      width: 100%\n      height: auto\n    view:\n      type: pager_controller\n      identifier: pager_controller\n      view:\n        type: linear_layout\n        direction: vertical\n        items:\n        - size:\n            width: 100%\n            height: auto\n          view:\n            type: container\n            items:\n              - position:\n                  vertical: center\n                  horizontal: center\n                size:\n                  height: 75\n                  width: 50%\n                view:\n                  type: pager\n                  border:\n                    radius: 2\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                  items:\n                    - identifier: \"page1\"\n                      view:\n                        type: empty_view\n                        background_color:\n                          default:\n                            hex: \"#00FF00\"\n                            alpha: 0.5\n                    - identifier: \"page2\"\n                      view:\n                        type: empty_view\n                        background_color:\n                          default:\n                            hex: \"#FFFF00\"\n                            alpha: 0.5\n                    - identifier: \"page3\"\n                      view:\n                        type: empty_view\n                        background_color:\n                          default:\n                            hex: \"#FF00FF\"\n                            alpha: 0.5\n\n              - position:\n                  horizontal: center\n                  vertical: bottom\n                size:\n                  height: 24\n                  width: auto\n                margin:\n                  bottom: 8\n                view:\n                  type: pager_indicator\n                  background_color:\n                    default:\n                      hex: \"#333333\"\n                      alpha: 0.7\n                  border:\n                    radius: 4\n                  spacing: 4\n                  bindings:\n                    selected:\n                      shapes:\n                        - type: rectangle\n                          aspect_ratio: 2.25\n                          scale: 0.9\n                          border:\n                            radius: 4\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                hex: \"#ffffff\"\n                                alpha: 0.7\n                          color:\n                            default:\n                              hex: \"#ffffff\"\n                              alpha: 1\n                    unselected:\n                      shapes:\n                        - type: rectangle\n                          aspect_ratio: 2.25\n                          scale: .9\n                          border:\n                            radius: 4\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                hex: \"#ffffff\"\n                                alpha: 0.7\n                          color:\n                            default:\n                              hex: \"#000000\"\n                              alpha: 0\n        - size:\n            height: auto\n            width: auto\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n            - size:\n                height: auto\n                width: auto\n              margin:\n                start: 16\n                end: 16\n                top: 16\n                bottom: 16\n              view:\n                type: label_button\n                identifier: button1\n                background_color:\n                  default:\n                    hex: \"#FFD600\"\n                label:\n                  type: label\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        hex: \"#333333\"\n                    alignment: center\n                  text: 'Previous'\n                button_click: [ \"pager_previous\" ]\n                enabled: [ \"pager_previous\" ]\n            - size:\n                height: auto\n                width: auto\n              margin:\n                start: 16\n                end: 16\n                top: 16\n                bottom: 16\n              view:\n                type: label_button\n                identifier: button1\n                background_color:\n                  default:\n                    hex: \"#FFD600\"\n                label:\n                  type: label\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        hex: \"#333333\"\n                    alignment: center\n                  text: 'Next'\n                button_click: [ \"pager_next\" ]\n                enabled: [ \"pager_next\" ]\n\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Next or dismiss'\n            button_click: [ \"pager_next_or_dismiss\" ]\n\n        - size:\n            height: auto\n            width: auto\n          margin:\n            start: 16\n            end: 16\n            top: 16\n            bottom: 16\n          view:\n            type: label_button\n            identifier: button1\n            background_color:\n              default:\n                hex: \"#FFD600\"\n            label:\n              type: label\n              text_appearance:\n                font_size: 10\n                color:\n                  default:\n                    hex: \"#333333\"\n                alignment: center\n              text: 'Next or first'\n            button_click: [ \"pager_next_or_first\" ]\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/portrait-video.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      max_width: 100%\n      max_height: 100%\n      width: 100%\n      min_width: 100%\n      height: 100%\n      min_height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: 80df39a5-774d-4bb5-9e35-c7a465189583\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n      - size:\n          height: 100%\n          width: 100%\n        view:\n          type: container\n          items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: false\n                items:\n                  - identifier: d07e16f6-f4c4-4acd-ad54-86996e6cf29c\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                      - size:\n                                          height: auto\n                                          width: 100%\n                                        view:\n                                          type: media\n                                          video:\n                                              aspect_ratio: 0.56\n                                              show_controls: true\n                                              autoplay: true\n                                              muted: true\n                                              loop: true\n                                          media_fit: center_inside\n                                          url: https://hangar-dl.urbanairship.com/binary/public/Hx7SIqHqQDmFj6aruaAFcQ/611c0ad9-73b6-4a66-8010-8d1ea1be961a\n                                          #url: https://dsqqu7oxq6o1v.cloudfront.net/preview-992269-Avy3e9Poxf-high.mp4\n                                          #url: https:\\/\\/hangar-dl.urbanairship.com\\/binary\\/public\\/ISex_TTJRuarzs9-o_Gkhg\\/1dc4d48b-63ba-4dd4-8cdc-8ba63613780f\n                                          media_type: video\n                                      - size:\n                                          width: 100%\n                                          height: auto\n                                        margin:\n                                          top: 48\n                                          bottom: 8\n                                          start: 16\n                                          end: 16\n                                        view:\n                                          type: label\n                                          text: test\n                                          text_appearance:\n                                            font_size: 16\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#222222\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: [ ]\n                                            font_families:\n                                              - sans-serif\n                                      - size:\n                                          width: 100%\n                                          height: auto\n                                        margin:\n                                          top: 48\n                                          bottom: 8\n                                          start: 16\n                                          end: 16\n                                        view:\n                                          type: label\n                                          text: test\n                                          text_appearance:\n                                            font_size: 16\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#222222\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: [ ]\n                                            font_families:\n                                              - sans-serif\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                  - identifier: 1cd9d07b-32ce-40bb-9526-412cce473b38\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                      - size:\n                                          width: 100%\n                                          height: auto\n                                        margin:\n                                          top: 48\n                                          bottom: 8\n                                          start: 16\n                                          end: 16\n                                        view:\n                                          type: label\n                                          text: test\n                                          text_appearance:\n                                            font_size: 16\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#222222\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                              - sans-serif\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                  - identifier: 1c798221-acf9-4031-a633-22e9708990fb\n                    type: pager_item\n                    view:\n                      type: container\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                      - size:\n                                          width: 100%\n                                          height: auto\n                                        margin:\n                                          top: 48\n                                          bottom: 8\n                                          start: 16\n                                          end: 16\n                                        view:\n                                          type: label\n                                          text: blabla\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#222222\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                              - sans-serif\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#222222\"\n                      alpha: 1\n                identifier: dismiss_button\n                button_click:\n                  - dismiss\n            - margin:\n                top: 0\n                bottom: 8\n                end: 0\n                start: 0\n              position:\n                horizontal: center\n                vertical: bottom\n              size:\n                height: 12\n                width: 100%\n              view:\n                type: pager_indicator\n                spacing: 4\n                bindings:\n                  selected:\n                    shapes:\n                      - type: ellipse\n                        scale: 1\n                        aspect_ratio: 1\n                        color:\n                          default:\n                            type: hex\n                            hex: \"#AAAAAA\"\n                            alpha: 1\n                  unselected:\n                    shapes:\n                      - type: ellipse\n                        aspect_ratio: 1\n                        scale: 1\n                        color:\n                          default:\n                            type: hex\n                            hex: \"#CCCCCC\"\n                            alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/qa-advseg-2998.yml",
    "content": "---\npresentation:\n  default_placement:\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      max_height: 100%\n      max_width: 100%\n      min_height: 100%\n      min_width: 100%\n      width: 100%\n  disable_back_button: false\n  dismiss_on_touch_outside: false\n  type: modal\nversion: 1\nview:\n  identifier: 962920d0-ee64-48ad-baec-16777779f1a9\n  submit: submit_event\n  type: form_controller\n  view:\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: 100%\n        width: 100%\n      view:\n        background_color:\n          default:\n            alpha: 1\n            hex: \"#e8e4dc\"\n            type: hex\n        direction: vertical\n        type: scroll_layout\n        view:\n          direction: vertical\n          items:\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 0\n            size:\n              height: auto\n              width: 100%\n            view:\n              media_fit: center_crop\n              media_type: image\n              type: media\n              url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/bc692c5f-09ce-4ea4-bb55-108b1b5d28a8\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 10\n            size:\n              height: auto\n              width: 92%\n            view:\n              direction: vertical\n              items:\n              - margin:\n                  bottom: 10\n                size:\n                  height: 100%\n                  width: 100%\n                view:\n                  text: How satisfied are you with our product?\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#111111\"\n                        type: hex\n                    font_families:\n                    - sans-serif\n                    font_size: 18\n                    styles:\n                    - bold\n                  type: label\n              - size:\n                  height: 100%\n                  width: 100%\n                view:\n                  identifier: bdd3abc0-c6ae-40a9-8962-135ee9e822c0\n                  type: radio_input_controller\n                  view:\n                    direction: vertical\n                    items:\n                    - size:\n                        height: 100%\n                        width: 100%\n                      view:\n                        direction: horizontal\n                        items:\n                        - size:\n                            height: 20\n                            width: 20\n                          view:\n                            reporting_value: da0695bd-d2c9-412a-aeb7-9f8ac2ff820b\n                            style:\n                              bindings:\n                                selected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#DDDDDD\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                                unselected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#FFFFFF\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                              type: checkbox\n                            type: radio_input\n                        - size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            text: Very satisfied\n                            text_appearance:\n                              alignment: start\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#111111\"\n                                  type: hex\n                              font_families:\n                              - sans-serif\n                              font_size: 18\n                              styles: []\n                            type: label\n                        type: linear_layout\n                    - size:\n                        height: 100%\n                        width: 100%\n                      view:\n                        direction: horizontal\n                        items:\n                        - size:\n                            height: 20\n                            width: 20\n                          view:\n                            reporting_value: 941e41b1-aa1a-477d-abfd-3866e84c8732\n                            style:\n                              bindings:\n                                selected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#DDDDDD\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                                unselected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#FFFFFF\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                              type: checkbox\n                            type: radio_input\n                        - size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            text: Dissatisfied\n                            text_appearance:\n                              alignment: start\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#111111\"\n                                  type: hex\n                              font_families:\n                              - sans-serif\n                              font_size: 18\n                              styles: []\n                            type: label\n                        type: linear_layout\n                    - size:\n                        height: 100%\n                        width: 100%\n                      view:\n                        direction: horizontal\n                        items:\n                        - size:\n                            height: 20\n                            width: 20\n                          view:\n                            reporting_value: 3365a895-22d5-4a6a-bd20-1b4374cf6146\n                            style:\n                              bindings:\n                                selected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#DDDDDD\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                                unselected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#FFFFFF\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                              type: checkbox\n                            type: radio_input\n                        - size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            text: Satisfied\n                            text_appearance:\n                              alignment: start\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#111111\"\n                                  type: hex\n                              font_families:\n                              - sans-serif\n                              font_size: 18\n                              styles: []\n                            type: label\n                        type: linear_layout\n                    - size:\n                        height: 100%\n                        width: 100%\n                      view:\n                        direction: horizontal\n                        items:\n                        - size:\n                            height: 20\n                            width: 20\n                          view:\n                            reporting_value: aac3415a-43f7-44a8-9c1d-ac9d482dc72d\n                            style:\n                              bindings:\n                                selected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#DDDDDD\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                                unselected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#FFFFFF\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                              type: checkbox\n                            type: radio_input\n                        - size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            text: Very dissatisfied\n                            text_appearance:\n                              alignment: start\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#111111\"\n                                  type: hex\n                              font_families:\n                              - sans-serif\n                              font_size: 18\n                              styles: []\n                            type: label\n                        type: linear_layout\n                    - size:\n                        height: 100%\n                        width: 100%\n                      view:\n                        direction: horizontal\n                        items:\n                        - size:\n                            height: 20\n                            width: 20\n                          view:\n                            reporting_value: 30a71f98-d60d-4e38-bdbc-047d6ef1b9f2\n                            style:\n                              bindings:\n                                selected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#DDDDDD\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                                unselected:\n                                  shapes:\n                                  - border:\n                                      radius: 20\n                                      stroke_color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#000000\"\n                                          type: hex\n                                      stroke_width: 1\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#FFFFFF\"\n                                        type: hex\n                                    scale: 1\n                                    type: ellipse\n                              type: checkbox\n                            type: radio_input\n                        - size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            text: Neither satisfied nor dissatisfied\n                            text_appearance:\n                              alignment: start\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#111111\"\n                                  type: hex\n                              font_families:\n                              - sans-serif\n                              font_size: 18\n                              styles: []\n                            type: label\n                        type: linear_layout\n                    type: linear_layout\n              type: linear_layout\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 10\n            size:\n              height: auto\n              width: 92%\n            view:\n              direction: vertical\n              items:\n              - margin:\n                  bottom: 10\n                  end: 0\n                  start: 0\n                  top: 0\n                size:\n                  height: auto\n                  width: 100%\n                view:\n                  text: What did you like most about your experience?\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#111111\"\n                        type: hex\n                    font_families:\n                    - sans-serif\n                    font_size: 18\n                    styles:\n                    - bold\n                  type: label\n              - size:\n                  height: 70\n                  width: 100%\n                view:\n                  background_color:\n                    default:\n                      alpha: 1\n                      hex: \"#eae9e9\"\n                      type: hex\n                  border:\n                    radius: 2\n                    stroke_color:\n                      default:\n                        alpha: 1\n                        hex: \"#63656b\"\n                        type: hex\n                    stroke_width: 1\n                  identifier: 7c7f0793-188f-4f60-aec2-0b35d9a4d005\n                  input_type: text\n                  required: false\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#000000\"\n                        type: hex\n                    font_size: 14\n                  type: text_input\n              type: linear_layout\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 10\n            size:\n              height: auto\n              width: 92%\n            view:\n              direction: vertical\n              items:\n              - margin:\n                  bottom: 10\n                  end: 0\n                  start: 0\n                  top: 0\n                size:\n                  height: auto\n                  width: 100%\n                view:\n                  text: What areas do we need to improve?\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#111111\"\n                        type: hex\n                    font_families:\n                    - sans-serif\n                    font_size: 18\n                    styles:\n                    - bold\n                  type: label\n              - size:\n                  height: 70\n                  width: 100%\n                view:\n                  background_color:\n                    default:\n                      alpha: 1\n                      hex: \"#eae9e9\"\n                      type: hex\n                  border:\n                    radius: 2\n                    stroke_color:\n                      default:\n                        alpha: 1\n                        hex: \"#63656b\"\n                        type: hex\n                    stroke_width: 1\n                  identifier: 9fb8ef64-2fbc-4439-b450-87b20fda5c43\n                  input_type: text\n                  required: false\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#000000\"\n                        type: hex\n                    font_size: 14\n                  type: text_input\n              type: linear_layout\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 10\n            size:\n              height: auto\n              width: 92%\n            view:\n              direction: vertical\n              items:\n              - margin:\n                  bottom: 10\n                  end: 0\n                  start: 0\n                  top: 0\n                size:\n                  height: auto\n                  width: 100%\n                view:\n                  text: Do you have any other feedback, concern or question?\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#111111\"\n                        type: hex\n                    font_families:\n                    - sans-serif\n                    font_size: 18\n                    styles:\n                    - bold\n                  type: label\n              - size:\n                  height: 70\n                  width: 100%\n                view:\n                  background_color:\n                    default:\n                      alpha: 1\n                      hex: \"#eae9e9\"\n                      type: hex\n                  border:\n                    radius: 2\n                    stroke_color:\n                      default:\n                        alpha: 1\n                        hex: \"#63656b\"\n                        type: hex\n                    stroke_width: 1\n                  identifier: 4a2e9284-9321-4176-9173-7f10b648e2e3\n                  input_type: text\n                  required: false\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#000000\"\n                        type: hex\n                    font_size: 14\n                  type: text_input\n              type: linear_layout\n          - margin:\n              bottom: 30\n              top: 10\n            size:\n              height: 100%\n              width: 100%\n            view:\n              direction: horizontal\n              items:\n              - margin:\n                  bottom: 30\n                  end: 20\n                  start: 20\n                  top: 4\n                size:\n                  height: 40\n                  width: 85%\n                view:\n                  actions: {}\n                  background_color:\n                    default:\n                      alpha: 1\n                      hex: \"#222222\"\n                      type: hex\n                  border:\n                    radius: 0\n                    stroke_color:\n                      default:\n                        alpha: 1\n                        hex: \"#222222\"\n                        type: hex\n                    stroke_width: 1\n                  button_click:\n                  - form_submit\n                  - dismiss\n                  enabled:\n                  - form_validation\n                  identifier: e49c1d9a-1118-4a7b-8ae8-2e1ce42b0f1a\n                  label:\n                    text: Submit\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                      font_families:\n                      - sans-serif\n                      font_size: 24\n                    type: label\n                  type: label_button\n              type: linear_layout\n          type: linear_layout\n    - margin:\n        bottom: 0\n        end: 10\n        start: 0\n        top: 10\n      position:\n        horizontal: end\n        vertical: top\n      size:\n        height: 48\n        width: 48\n      view:\n        type: image_button\n        button_click:\n        - dismiss\n        identifier: dismiss_button\n        image:\n          color:\n            default:\n              alpha: 1\n              hex: \"#000000\"\n              type: hex\n          icon: close\n          scale: 0.4\n          type: icon\n    type: container\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/qa-advseg-2999.yml",
    "content": "---\npresentation:\n  default_placement:\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      max_height: 100%\n      max_width: 100%\n      min_height: 100%\n      min_width: 100%\n      width: 100%\n  disable_back_button: false\n  dismiss_on_touch_outside: false\n  type: modal\nversion: 1\nview:\n  identifier: fa241604-ce7c-45a3-94a9-1951caec74a4\n  nps_identifier: 24574490-64c5-476d-8ecb-7390ea42fe02\n  submit: submit_event\n  type: nps_form_controller\n  view:\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        height: 100%\n        width: 100%\n      view:\n        background_color:\n          default:\n            alpha: 1\n            hex: \"#FEF5E7\"\n            type: hex\n        direction: vertical\n        type: scroll_layout\n        view:\n          direction: vertical\n          items:\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 0\n            size:\n              height: 100%\n              width: 100%\n            view:\n              media_fit: center_crop\n              media_type: image\n              type: media\n              url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/bc692c5f-09ce-4ea4-bb55-108b1b5d28a8\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 10\n            size:\n              height: auto\n              width: 92%\n            view:\n              direction: vertical\n              items:\n              - size:\n                  height: 140\n                  width: 100%\n                view:\n                  direction: vertical\n                  items:\n                  - margin:\n                      bottom: 4\n                      end: 0\n                      start: 0\n                      top: 20\n                    size:\n                      height: 120\n                      width: 100%\n                    view:\n                      text: How likely is it that you would recommend 23 Grande to\n                        a friend or colleague?\n                      text_appearance:\n                        alignment: end\n                        color:\n                          default:\n                            alpha: 1\n                            hex: \"#111111\"\n                            type: hex\n                        font_families:\n                        - sans-serif\n                        font_size: 18\n                        styles:\n                        - bold\n                      type: label\n                  type: linear_layout\n              - margin:\n                  bottom: 0\n                  end: 10\n                  start: 10\n                  top: 0\n                size:\n                  height: 90\n                  width: 100%\n                view:\n                  direction: vertical\n                  items:\n                  - margin:\n                      bottom: 8\n                      top: 10\n                    size:\n                      height: 25\n                      width: 100%\n                    view:\n                      direction: horizontal\n                      items:\n                      - size:\n                          height: 25\n                          width: 50%\n                        view:\n                          text: Not Likely\n                          text_appearance:\n                            alignment: start\n                            color:\n                              default:\n                                alpha: 1\n                                hex: \"#111111\"\n                                type: hex\n                            font_families:\n                            - sans-serif\n                            font_size: 18\n                            styles: []\n                          type: label\n                      - size:\n                          height: 25\n                          width: 50%\n                        view:\n                          text: Very Likely\n                          text_appearance:\n                            alignment: end\n                            color:\n                              default:\n                                alpha: 1\n                                hex: \"#111111\"\n                                type: hex\n                            font_families:\n                            - sans-serif\n                            font_size: 18\n                            styles: []\n                          type: label\n                      type: linear_layout\n                  - size:\n                      height: 40\n                      width: 100%\n                    view:\n                      direction: horizontal\n                      items:\n                      - margin:\n                          bottom: 0\n                          end: 2\n                          start: 2\n                          top: 0\n                        size:\n                          height: 40\n                          width: 100%\n                        view:\n                          identifier: 24574490-64c5-476d-8ecb-7390ea42fe02\n                          required: true\n                          style:\n                            bindings:\n                              selected:\n                                shapes:\n                                - border:\n                                    radius: 2\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#000000\"\n                                        type: hex\n                                    stroke_width: 1\n                                  color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#DDDDDD\"\n                                      type: hex\n                                  scale: 1\n                                  type: rectangle\n                                text_appearance:\n                                  alignment: center\n                                  color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#000000\"\n                                      type: hex\n                                  font_size: 12\n                              unselected:\n                                shapes:\n                                - border:\n                                    radius: 2\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#000000\"\n                                        type: hex\n                                    stroke_width: 1\n                                  color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#FFFFFF\"\n                                      type: hex\n                                  scale: 1\n                                  type: rectangle\n                                text_appearance:\n                                  color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#000000\"\n                                      type: hex\n                                  font_size: 12\n                            end: 10\n                            spacing: 2\n                            start: 0\n                            type: number_range\n                          type: score\n                      type: linear_layout\n                  type: linear_layout\n              type: linear_layout\n          - margin:\n              bottom: 10\n              end: 0\n              start: 0\n              top: 10\n            size:\n              height: auto\n              width: 92%\n            view:\n              direction: vertical\n              items:\n              - margin:\n                  bottom: 10\n                  end: 0\n                  start: 0\n                  top: 0\n                size:\n                  height: auto\n                  width: 100%\n                view:\n                  text: What is the primary reason for your score?\n                  text_appearance:\n                    alignment: end\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#111111\"\n                        type: hex\n                    font_families:\n                    - sans-serif\n                    font_size: 18\n                    styles:\n                    - bold\n                  type: label\n              - size:\n                  height: 70\n                  width: 100%\n                view:\n                  background_color:\n                    default:\n                      alpha: 1\n                      hex: \"#eae9e9\"\n                      type: hex\n                  border:\n                    radius: 2\n                    stroke_color:\n                      default:\n                        alpha: 1\n                        hex: \"#63656b\"\n                        type: hex\n                    stroke_width: 1\n                  identifier: e7427c26-64c2-48c6-99b6-13de49a83c7f\n                  input_type: text\n                  required: false\n                  text_appearance:\n                    alignment: start\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#000000\"\n                        type: hex\n                    font_size: 14\n                  type: text_input\n              type: linear_layout\n          - margin:\n              bottom: 30\n              top: 10\n            size:\n              height: 100%\n              width: 100%\n            view:\n              direction: horizontal\n              items:\n              - margin:\n                  bottom: 30\n                  end: 20\n                  start: 20\n                  top: 4\n                size:\n                  height: 40\n                  width: 85%\n                view:\n                  actions: {}\n                  background_color:\n                    default:\n                      alpha: 1\n                      hex: \"#222222\"\n                      type: hex\n                  border:\n                    radius: 0\n                    stroke_color:\n                      default:\n                        alpha: 1\n                        hex: \"#222222\"\n                        type: hex\n                    stroke_width: 1\n                  button_click:\n                  - form_submit\n                  - dismiss\n                  enabled:\n                  - form_validation\n                  identifier: 9e1d0085-ed68-41f9-bd4e-2741d7d79b66\n                  label:\n                    text: Submit\n                    text_appearance:\n                      alignment: center\n                      color:\n                        default:\n                          alpha: 1\n                          hex: \"#FFFFFF\"\n                          type: hex\n                      font_families:\n                      - sans-serif\n                      font_size: 24\n                    type: label\n                  type: label_button\n              type: linear_layout\n          type: linear_layout\n    - margin:\n        bottom: 0\n        end: 10\n        start: 0\n        top: 10\n      position:\n        horizontal: end\n        vertical: top\n      size:\n        height: 48\n        width: 48\n      view:\n        button_click:\n        - dismiss\n        identifier: dismiss_button\n        image:\n          color:\n            default:\n              alpha: 1\n              hex: \"#000000\"\n              type: hex\n          icon: close\n          scale: 0.4\n          type: icon\n        type: image_button\n    type: container\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/safe-areas-linear-layout.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0 # no shade\n    ignore_safe_area: true\nview:\n  type: container\n  background_color:\n    default:\n      hex: \"#FF00FF\"\n      alpha: 1\n  items:\n  # TOP-LEVEL LINEAR LAYOUT\n  - position:\n      horizontal: center\n      vertical: center\n    size:\n      height: 100%\n      width: 100%\n    view:\n      type: linear_layout\n      direction: vertical\n      border:\n        stroke_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 1\n        stroke_width: 5\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FF6666\"\n              alpha: 1\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FFFFFF\"\n              alpha: 1\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FF6666\"\n              alpha: 1\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FFFFFF\"\n              alpha: 1\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FF6666\"\n              alpha: 1\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FFFFFF\"\n              alpha: 1\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: empty_view\n          background_color:\n            default:\n              hex: \"#FF6666\"\n              alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/safe-areas-pager.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    ignore_safe_area: true\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0 # no shade\nview:\n  type: pager_controller\n  identifier: pager-id\n  view:\n    type: container\n    items:\n    - position:\n        horizontal: center\n        vertical: center\n      size:\n        width: 100%\n        height: 100%\n      ignore_safe_area: true\n      view:\n        type: pager\n        disable_swipe: false\n        items:\n        - identifier: \"page-1\"\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#FF0000\"\n                alpha: 1\n            items:\n            - size:\n                width: 100%\n                height: 100%\n              position:\n                horizontal: center\n                vertical: center\n              margin:\n                top: 36\n                bottom: 36\n                start: 8\n                end: 8\n              view:\n                type: empty_view\n                border:\n                  stroke_width: 2\n                  stroke_color:\n                    default:\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  radius: 5\n        - idenfier: \"page-2\"\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#00FF00\"\n                alpha: 1\n            items:\n            - size:\n                width: 100%\n                height: 100%\n              position:\n                horizontal: center\n                vertical: center\n              margin:\n                top: 36\n                bottom: 36\n                start: 8\n                end: 8\n              view:\n                type: empty_view\n                border:\n                  stroke_width: 2\n                  stroke_color:\n                    default:\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  radius: 5\n        - identifier: \"page-3\"\n          view:\n            type: container\n            background_color:\n              default:\n                hex: \"#0000FF\"\n                alpha: 1\n            items:\n            - size:\n                width: 100%\n                height: 100%\n              position:\n                horizontal: center\n                vertical: center\n              margin:\n                top: 36\n                bottom: 36\n                start: 8\n                end: 8\n              view:\n                type: empty_view\n                border:\n                  stroke_width: 2\n                  stroke_color:\n                    default:\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n                  radius: 5\n    - position:\n        horizontal: end\n        vertical: top\n      ignore_safe_area: false\n      margin:\n        top: 8\n        end: 8\n      size:\n        width: 24\n        height: 24\n      view:\n        type: image_button\n        image:\n          type: icon\n          icon: close\n          color:\n            default:\n              type: hex\n              alpha: 1\n              hex: \"#FFFFFF\"\n        identifier: dismiss_button\n        button_click:\n        - dismiss\n    - margin:\n        bottom: 8\n      position:\n        horizontal: center\n        vertical: bottom\n      size:\n        height: 20\n        width: 100%\n      ignore_safe_area: false\n      view:\n        type: pager_indicator\n        spacing: 4\n        bindings:\n          selected:\n            shapes:\n            - type: ellipse\n              scale: 1\n              aspect_ratio: 1\n              color:\n                default:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n          unselected:\n            shapes:\n            - type: ellipse\n              aspect_ratio: 1\n              scale: 1\n              color:\n                default:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 0.5\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/scene-scroll.yml",
    "content": "---\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    device:\n      lock_orientation: portrait\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      max_height: 100%\n      max_width: 100%\n      min_height: 100%\n      min_width: 100%\n      width: 100%\n  dismiss_on_touch_outside: false\n  type: modal\nversion: 1\nview:\n  identifier: d565411e-b6ac-46be-9b12-9160ca354cb2\n  type: pager_controller\n  view:\n    items:\n      - ignore_safe_area: false\n        position:\n          horizontal: center\n          vertical: center\n        size:\n          height: 100%\n          width: 100%\n        view:\n          disable_swipe: false\n          items:\n            - identifier: d86b50db-5ef4-44dd-a0a8-fba5bee5a00f\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            top: 0\n                          size:\n                            height: auto\n                            width: 100%\n                          view:\n                            media_fit: center_crop\n                            media_type: image\n                            type: media\n                            url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/56956abe-2bf2-416c-8f7e-f25d65ab6fc0\n                        - margin:\n                            bottom: 10\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: auto\n                            width: 90%\n                          view:\n                            text: Your free trial has ended\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#111111\"\n                                  type: hex\n                              font_families:\n                                - sans-serif\n                              font_size: 20\n                              styles:\n                                - underlined\n                                - bold\n                                - italic\n                            type: label\n                        - margin:\n                            bottom: 10\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 100%\n                            width: 90%\n                          view:\n                            direction: vertical\n                            type: scroll_layout\n                            view:\n                              text: 'Are you enjoying your experience? To continue enjoying\n                        your benefits, select a plan to upgrade to today. '\n                              text_appearance:\n                                alignment: center\n                                color:\n                                  default:\n                                    alpha: 1\n                                    hex: \"#111111\"\n                                    type: hex\n                                font_families:\n                                  - sans-serif\n                                font_size: 34\n                                styles:\n                                  - underlined\n                                  - bold\n                                  - italic\n                              type: label\n                        - size:\n                            height: 0\n                            width: 0\n                          view:\n                            actions: {}\n                            identifier: home_page--footer_button\n                            label:\n                              text: ''\n                              text_appearance:\n                                alignment: center\n                                color:\n                                  default:\n                                    alpha: 1\n                                    hex: \"#37FF00\"\n                                    type: hex\n                                font_families:\n                                  - Test limit\n                                font_size: 34\n                                styles:\n                                  - bold\n                                  - italic\n                                  - underlined\n                              type: label\n                            type: label_button\n                        - margin:\n                            bottom: 0\n                            top: 10\n                          size:\n                            height: auto\n                            width: 92%\n                          view:\n                            direction: vertical\n                            items:\n                              - margin:\n                                  bottom: 4\n                                  end: 0\n                                  start: 0\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 100%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#222222\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click:\n                                    - pager_next\n                                  enabled:\n                                    - pager_next\n                                  identifier: next--See All Plans\n                                  label:\n                                    text: See All Plans\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#FFFFFF\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 14\n                                      styles: []\n                                    type: label\n                                  type: label_button\n                              - margin:\n                                  bottom: 20\n                                  end: 0\n                                  start: 0\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 100%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#222222\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click:\n                                    - dismiss\n                                  enabled: []\n                                  identifier: 'home_page--Go back to basic account '\n                                  label:\n                                    text: 'Go back to basic account '\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#FFFFF0\"\n                                          type: hex\n                                      font_families:\n                                        - SF Pro\n                                      font_size: 14\n                                      styles: []\n                                    type: label\n                                  type: label_button\n                            type: linear_layout\n                      type: linear_layout\n                type: container\n            - identifier: ed3ff5ff-f633-4dd0-869b-32b285c3e9d9\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            bottom: 0\n                            end: 20\n                            start: 20\n                            top: 0\n                          size:\n                            height: auto\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 35\n                                size:\n                                  height: auto\n                                  width: 90%\n                                view:\n                                  text: Why upgrade?\n                                  text_appearance:\n                                    alignment: center\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#111111\"\n                                        type: hex\n                                    font_families:\n                                      - sans-serif\n                                    font_size: 20\n                                    styles:\n                                      - underlined\n                                      - bold\n                                      - italic\n                                  type: label\n                              - size:\n                                  height: 0\n                                  width: 0\n                                view:\n                                  text: ''\n                                  text_appearance:\n                                    alignment: center\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#111111\"\n                                        type: hex\n                                    font_families:\n                                      - sans-serif\n                                    font_size: 34\n                                    styles:\n                                      - underlined\n                                      - bold\n                                      - italic\n                                  type: label\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 60\n                                  width: 100%\n                                view:\n                                  direction: horizontal\n                                  items:\n                                    - margin:\n                                        bottom: 0\n                                        end: 20\n                                        start: 20\n                                        top: 0\n                                      size:\n                                        height: 60\n                                        width: 60\n                                      view:\n                                        media_fit: center_inside\n                                        media_type: image\n                                        type: media\n                                        url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                    - margin:\n                                        bottom: 0\n                                        end: 0\n                                        start: 0\n                                        top: 0\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        text: 'Plan #1 - Value prop - Price'\n                                        text_appearance:\n                                          alignment: start\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#111111\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 34\n                                          styles:\n                                            - underlined\n                                            - bold\n                                            - italic\n                                        type: label\n                                  type: linear_layout\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 60\n                                  width: 100%\n                                view:\n                                  direction: horizontal\n                                  items:\n                                    - margin:\n                                        bottom: 0\n                                        end: 20\n                                        start: 20\n                                        top: 0\n                                      size:\n                                        height: 60\n                                        width: 60\n                                      view:\n                                        media_fit: center_inside\n                                        media_type: image\n                                        type: media\n                                        url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                    - margin:\n                                        bottom: 0\n                                        end: 0\n                                        start: 0\n                                        top: 0\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        text: 'Plan #2 - Value prop - Price'\n                                        text_appearance:\n                                          alignment: start\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#111111\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 34\n                                          styles:\n                                            - underlined\n                                            - bold\n                                            - italic\n                                        type: label\n                                  type: linear_layout\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 60\n                                  width: 100%\n                                view:\n                                  direction: horizontal\n                                  items:\n                                    - margin:\n                                        bottom: 0\n                                        end: 20\n                                        start: 20\n                                        top: 0\n                                      size:\n                                        height: 60\n                                        width: 60\n                                      view:\n                                        media_fit: center_inside\n                                        media_type: image\n                                        type: media\n                                        url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                    - margin:\n                                        bottom: 0\n                                        end: 0\n                                        start: 0\n                                        top: 0\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        text: 'Plan #3 - Value prop - Price'\n                                        text_appearance:\n                                          alignment: start\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#111111\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 34\n                                          styles:\n                                            - underlined\n                                            - bold\n                                            - italic\n                                        type: label\n                                  type: linear_layout\n                            type: linear_layout\n                        - margin:\n                            bottom: 0\n                            end: 0\n                            start: 0\n                            top: 0\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            type: empty_view\n                        - margin:\n                            bottom: 0\n                            top: 10\n                          size:\n                            height: auto\n                            width: 92%\n                          view:\n                            direction: vertical\n                            items:\n                              - margin:\n                                  bottom: 4\n                                  end: 0\n                                  start: 0\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 100%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#222222\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click:\n                                    - pager_next\n                                  enabled:\n                                    - pager_next\n                                  identifier: 'next--Plan #1'\n                                  label:\n                                    text: 'Plan #1'\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#FFFFFF\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 14\n                                      styles: []\n                                    type: label\n                                  type: label_button\n                              - margin:\n                                  bottom: 4\n                                  end: 0\n                                  start: 0\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 100%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#222222\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click:\n                                    - dismiss\n                                  enabled: []\n                                  identifier: 'home_page--Plan #2'\n                                  label:\n                                    text: 'Plan #2'\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#FFFFF0\"\n                                          type: hex\n                                      font_families:\n                                        - SF Pro\n                                      font_size: 14\n                                      styles: []\n                                    type: label\n                                  type: label_button\n                              - margin:\n                                  bottom: 20\n                                  end: 0\n                                  start: 0\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 100%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#222222\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click:\n                                    - dismiss\n                                  enabled: []\n                                  identifier: 'home_page--Plan #3'\n                                  label:\n                                    text: 'Plan #3'\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#FFFFFF\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 14\n                                      styles: []\n                                    type: label\n                                  type: label_button\n                            type: linear_layout\n                      type: linear_layout\n                type: container\n          type: pager\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          height: 48\n          width: 48\n        view:\n          button_click:\n            - dismiss\n          identifier: dismiss_button\n          image:\n            color:\n              default:\n                alpha: 1\n                hex: \"#000000\"\n                type: hex\n            icon: close\n            scale: 0.4\n            type: icon\n          type: image_button\n      - margin:\n          bottom: 4\n          end: 0\n          start: 0\n          top: 0\n        position:\n          horizontal: center\n          vertical: bottom\n        size:\n          height: 12\n          width: 100%\n        view:\n          bindings:\n            selected:\n              shapes:\n                - aspect_ratio: 1\n                  color:\n                    default:\n                      alpha: 1\n                      hex: \"#AAAAAA\"\n                      type: hex\n                  scale: 1\n                  type: ellipse\n            unselected:\n              shapes:\n                - aspect_ratio: 1\n                  color:\n                    default:\n                      alpha: 1\n                      hex: \"#CCCCCC\"\n                      type: hex\n                  scale: 1\n                  type: ellipse\n          spacing: 4\n          type: pager_indicator\n    type: container\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/story-one-screen.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    device:\n      lock_orientation: portrait\n    size:\n      max_width: 100%\n      max_height: 100%\n      width: 100%\n      min_width: 100%\n      height: 100%\n      min_height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: 63a41161-9322-4425-a940-fa928665459e\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - size:\n        height: 100%\n        width: 100%\n      view:\n        type: container\n        items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          view:\n            type: pager\n            disable_swipe: true\n            gestures:\n            - identifier: 63a41161-9322-4425-a940-fa928665459e_tap_start\n              type: tap\n              location: start\n              behavior:\n                behaviors:\n                - pager_previous\n            - identifier: 63a41161-9322-4425-a940-fa928665459e_tap_end\n              type: tap\n              location: end\n              behavior:\n                behaviors:\n                - pager_next\n            - identifier: 63a41161-9322-4425-a940-fa928665459e_swipe_up\n              type: swipe\n              direction: up\n              behavior:\n                behaviors:\n                - dismiss\n            - identifier: 63a41161-9322-4425-a940-fa928665459e_swipe_down\n              type: swipe\n              direction: down\n              behavior:\n                behaviors:\n                - dismiss\n            - identifier: 63a41161-9322-4425-a940-fa928665459e_hold\n              type: hold\n              press_behavior:\n                behaviors:\n                - pager_pause\n              release_behavior:\n                behaviors:\n                - pager_resume\n            items:\n            - identifier: a648eca0-6f68-49fc-971e-6de4cfdf5af3\n              type: pager_item\n              view:\n                type: container\n                items:\n                - size:\n                    width: 100%\n                    height: 100%\n                  position:\n                    horizontal: center\n                    vertical: center\n                  view:\n                    type: container\n                    items:\n                    - margin:\n                        bottom: 16\n                      position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - identifier: layout_container\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - size:\n                                width: 100%\n                                height: auto\n                              margin:\n                                top: 48\n                                bottom: 8\n                                start: 16\n                                end: 16\n                              view:\n                                type: label\n                                text: It’s time to update\n                                text_appearance:\n                                  font_size: 12\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#000000\"\n                                      alpha: 1\n                                    selectors:\n                                    - platform: ios\n                                      dark_mode: true\n                                      color:\n                                        type: hex\n                                        hex: \"#FFFFFF\"\n                                        alpha: 1\n                                    - platform: android\n                                      dark_mode: true\n                                      color:\n                                        type: hex\n                                        hex: \"#FFFFFF\"\n                                        alpha: 1\n                                  alignment: center\n                                  styles:\n                                  - bold\n                                  - italic\n                                  - underlined\n                                  font_families:\n                                  - serif\n                            - size:\n                                height: auto\n                                width: 100%\n                              view:\n                                type: media\n                                media_fit: center_inside\n                                url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                media_type: image\n                            - size:\n                                width: 100%\n                                height: auto\n                              margin:\n                                top: 8\n                                bottom: 8\n                                start: 16\n                                end: 16\n                              view:\n                                type: label\n                                text: The newest version of our app is now available.\n                                  We’re excited to tell you what’s new!\n                                text_appearance:\n                                  font_size: 14\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#000000\"\n                                      alpha: 1\n                                    selectors:\n                                    - platform: ios\n                                      dark_mode: true\n                                      color:\n                                        type: hex\n                                        hex: \"#FFFFFF\"\n                                        alpha: 1\n                                    - platform: android\n                                      dark_mode: true\n                                      color:\n                                        type: hex\n                                        hex: \"#FFFFFF\"\n                                        alpha: 1\n                                  alignment: center\n                                  styles:\n                                  - italic\n                                  font_families:\n                                  - fancy fonts\n                            - size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items: []\n                        background_color:\n                          default:\n                            type: hex\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                          selectors:\n                          - platform: ios\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                          - platform: android\n                            dark_mode: true\n                            color:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                background_color:\n                  default:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                  selectors:\n                  - platform: ios\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                  - platform: android\n                    dark_mode: true\n                    color:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n              automated_actions:\n              - identifier: pager_next_or_first_a648eca0-6f68-49fc-971e-6de4cfdf5af3\n                delay: 4\n                behaviors:\n                - pager_next_or_first\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            image:\n              scale: 0.4\n              type: icon\n              icon: close\n              color:\n                default:\n                  type: hex\n                  hex: \"#000000\"\n                  alpha: 1\n                selectors:\n                - platform: ios\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n                - platform: android\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#FFFFFF\"\n                    alpha: 1\n            identifier: dismiss_button\n            button_click:\n            - dismiss\n        - margin:\n            top: 8\n            bottom: 0\n            end: 16\n            start: 16\n          position:\n            horizontal: center\n            vertical: top\n          size:\n            height: 1.5\n            width: 100%\n          view:\n            type: story_indicator\n            source:\n              type: pager\n            style:\n              type: linear_progress\n              direction: horizontal\n              sizing: equal\n              spacing: 4\n              progress_color:\n                default:\n                  type: hex\n                  hex: \"#AAAAAA\"\n                  alpha: 1\n              track_color:\n                default:\n                  type: hex\n                  hex: \"#AAAAAA\"\n                  alpha: 0.5\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/story-video-and-gif.yml",
    "content": "---\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    device:\n      lock_orientation: portrait\n    ignore_safe_area: true\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      max_height: 100%\n      max_width: 100%\n      min_height: 100%\n      min_width: 100%\n      width: 100%\n  dismiss_on_touch_outside: false\n  type: modal\nversion: 1\nview:\n  identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a\n  type: pager_controller\n  view:\n    items:\n      - ignore_safe_area: true\n        position:\n          horizontal: center\n          vertical: center\n        size:\n          height: 100%\n          width: 100%\n        view:\n          disable_swipe: true\n          gestures:\n            - behavior:\n                behaviors:\n                  - pager_previous\n              identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a_tap_start\n              location: start\n              type: tap\n            - behavior:\n                behaviors:\n                  - pager_next\n              identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a_tap_end\n              location: end\n              type: tap\n            - behavior:\n                behaviors:\n                  - dismiss\n              direction: up\n              identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a_swipe_up\n              type: swipe\n            - behavior:\n                behaviors:\n                  - dismiss\n              direction: down\n              identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a_swipe_down\n              type: swipe\n            - identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a_hold\n              press_behavior:\n                behaviors:\n                  - pager_pause\n              release_behavior:\n                behaviors:\n                  - pager_resume\n              type: hold\n          items:\n            - automated_actions:\n                - behaviors:\n                    - pager_next\n                  delay: 10\n                  identifier: \"[pager_next]_e09ba183-67e5-422c-acb8-da868e392a7c\"\n              identifier: e09ba183-67e5-422c-acb8-da868e392a7c\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#969111\"\n                    type: hex\n                items:\n                  - ignore_safe_area: true\n                    margin:\n                      end: 0\n                      start: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      media_fit: center_crop\n                      media_type: video\n                      type: media\n                      url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/18e56538-eb2b-4d7a-a69f-cd107d8a72d9\n                      video:\n                        aspect_ratio: 0.5625\n                        autoplay: true\n                        loop: true\n                        muted: true\n                        show_controls: false\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - identifier: layout_container\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: vertical\n                                  items:\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 48\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: VIDEO longer than Story\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 8\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: '22 seconds video '\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 8\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: 10 seconds story\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        direction: horizontal\n                                        items: []\n                                        type: linear_layout\n                                  type: linear_layout\n                            type: linear_layout\n                      type: container\n                type: container\n            - automated_actions:\n                - behaviors:\n                    - pager_next\n                  delay: 10\n                  identifier: \"[pager_next]_afb9eb28-a67f-40c2-a4fe-7b28fd3707e0\"\n              identifier: afb9eb28-a67f-40c2-a4fe-7b28fd3707e0\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#969111\"\n                    type: hex\n                items:\n                  - ignore_safe_area: true\n                    margin:\n                      end: 0\n                      start: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      media_fit: center_crop\n                      media_type: video\n                      type: media\n                      url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/e17527d2-3471-47b2-8733-7afbe3272b50\n                      video:\n                        aspect_ratio: 0.5625\n                        autoplay: true\n                        loop: true\n                        muted: true\n                        show_controls: false\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - identifier: layout_container\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: vertical\n                                  items:\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 48\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: Video shorter than Story\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                            selectors:\n                                              - color:\n                                                  alpha: 1\n                                                  hex: \"#FFFFFF\"\n                                                  type: hex\n                                                dark_mode: true\n                                                platform: ios\n                                              - color:\n                                                  alpha: 1\n                                                  hex: \"#FFFFFF\"\n                                                  type: hex\n                                                dark_mode: true\n                                                platform: android\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 8\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: 6 seconds video\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                            selectors:\n                                              - color:\n                                                  alpha: 1\n                                                  hex: \"#FFFFFF\"\n                                                  type: hex\n                                                dark_mode: true\n                                                platform: ios\n                                              - color:\n                                                  alpha: 1\n                                                  hex: \"#FFFFFF\"\n                                                  type: hex\n                                                dark_mode: true\n                                                platform: android\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 8\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: 10 seconds story\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                            selectors:\n                                              - color:\n                                                  alpha: 1\n                                                  hex: \"#FFFFFF\"\n                                                  type: hex\n                                                dark_mode: true\n                                                platform: ios\n                                              - color:\n                                                  alpha: 1\n                                                  hex: \"#FFFFFF\"\n                                                  type: hex\n                                                dark_mode: true\n                                                platform: android\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        direction: horizontal\n                                        items: []\n                                        type: linear_layout\n                                  type: linear_layout\n                            type: linear_layout\n                      type: container\n                type: container\n            - automated_actions:\n                - behaviors:\n                    - pager_next\n                  delay: 10\n                  identifier: \"[pager_next]_e09ba183-67e5-422c-acb8-da868e392a7c\"\n              identifier: e09ba183-67e5-422c-acb8-da868e392a7c\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#969111\"\n                    type: hex\n                items:\n                  - ignore_safe_area: true\n                    margin:\n                      end: 0\n                      start: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      media_fit: center_crop\n                      media_type: youtube\n                      type: media\n                      url: https://www.youtube.com/embed/xUOQZeN8A7o\n                      video:\n                        aspect_ratio: 0.5625\n                        autoplay: true\n                        loop: true\n                        muted: true\n                        show_controls: false\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      items:\n                        - margin:\n                            bottom: 16\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - identifier: layout_container\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: vertical\n                                  items:\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 48\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: Youtube VIDEO\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 8\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: 'youtube video '\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - margin:\n                                        bottom: 8\n                                        end: 16\n                                        start: 16\n                                        top: 8\n                                      size:\n                                        height: auto\n                                        width: 100%\n                                      view:\n                                        text: 10 seconds story\n                                        text_appearance:\n                                          alignment: center\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#000000\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 40\n                                          styles:\n                                            - bold\n                                        type: label\n                                    - size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        direction: horizontal\n                                        items: [ ]\n                                        type: linear_layout\n                                  type: linear_layout\n                            type: linear_layout\n                      type: container\n                type: container\n            - automated_actions:\n                - behaviors:\n                    - pager_next\n                  delay: 10\n                  identifier: pager_next_627ad448-a18c-432e-976e-d0ab10a67fdb\n              identifier: 627ad448-a18c-432e-976e-d0ab10a67fdb\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#969111\"\n                    type: hex\n                items:\n                  - ignore_safe_area: true\n                    margin:\n                      end: 0\n                      start: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      media_fit: center_inside\n                      media_type: image\n                      type: media\n                      url: https://hangar-dl.urbanairship.com/binary/public/VWDwdOFjRTKLRxCeXTVP6g/e9a5493a-c5bb-48ab-9557-48cee046ea74\n                type: container\n            - automated_actions:\n                - behaviors:\n                    - pager_next\n                  delay: 10\n                  identifier: pager_next_627ad448-a18c-432e-976e-d0ab10a67fdb\n              identifier: 627ad448-a18c-432e-976e-d0ab10a67fdb\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#969111\"\n                    type: hex\n                items:\n                  - ignore_safe_area: true\n                    margin:\n                      end: 0\n                      start: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      media_fit: center_inside\n                      media_type: image\n                      type: media\n                      url: https://hangar-dl.urbanairship.com/binary/public/VWDwdOFjRTKLRxCeXTVP6g/25b3bea5-e233-4d25-8d96-c65e6a859cee\n                type: container\n          type: pager\n      - margin:\n          bottom: 0\n          end: 16\n          start: 16\n          top: 8\n        position:\n          horizontal: center\n          vertical: top\n        size:\n          height: 2\n          width: 100%\n        view:\n          source:\n            type: pager\n          style:\n            direction: horizontal\n            progress_color:\n              default:\n                alpha: 1\n                hex: \"#AAAAAA\"\n                type: hex\n            sizing: equal\n            spacing: 4\n            track_color:\n              default:\n                alpha: 0.5\n                hex: \"#AAAAAA\"\n                type: hex\n            type: linear_progress\n          type: story_indicator\n    type: container\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/story.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\n    ignore_safe_area: true\nview:\n  type: pager_controller\n  identifier: pager-controller-id\n  view:\n    type: container\n    border:\n      radius: 30\n      stroke_width: 2\n      stroke_color:\n        default:\n          hex: '#333333'\n          alpha: 0.8\n    background_color:\n      default:\n        hex: '#ffffff'\n        alpha: 1\n    items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        margin:\n          top: 0\n          bottom: 0\n          start: 0\n          end: 0\n        view:\n          border:\n            stroke_width: 1\n          media_fit: center_crop\n          media_type: image\n          type: media\n          url: >-\n            https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/bc692c5f-09ce-4ea4-bb55-108b1b5d28a8\n      - position:\n          vertical: center\n          horizontal: center\n        size:\n          height: 100%\n          width: 100%\n        border:\n          radius: 25\n        view:\n          type: pager\n          gestures:\n            - behavior:\n                behaviors:\n                  - dismiss\n              direction: up\n              identifier: 7f67898a-5576-4f9d-af73-ff4d9568117a_swipe_up\n              type: swipe\n            - type: tap\n              identifier: tap-gesture-start-id\n              location: start\n              behavior:\n                behaviors:\n                  - pager_previous\n            - type: tap\n              identifier: tap-gesture-end-id\n              location: end\n              behavior:\n                behaviors:\n                  - pager_next\n            - type: hold\n              identifier: hold-gesture-any-id\n              press_behavior:\n                behaviors:\n                  - pager_pause\n              release_behavior:\n                behaviors:\n                  - pager_resume\n          items:\n            - identifier: pager-page-1-id\n              automated_actions:\n                - delay: 0\n                  identifier: automated-action-1-delay0-id\n                  actions:\n                    - add_tags_action: pager-page-1x-automated\n                    - add_tags_action: pager-page-1y-automated\n                - delay: 4\n                  identifier: automated-action-1-delay4-id\n                  reporting_metadata:\n                    key1: value1\n                    key2: value2\n                  behaviors:\n                    - pager_next\n              display_actions:\n                add_tags_action: pager-page-1x\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#FF0000'\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: label\n                      text: This is the first page about stuff.\n                      text_appearance:\n                        alignment: center\n                        color:\n                          default:\n                            hex: '#000000'\n                            alpha: 1\n                        font_size: 14\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_1\n                      button_click:\n                        - dismiss\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: '#000000'\n                            alpha: 1\n            - identifier: pager-page-2-id\n              automated_actions:\n                - delay: 1.03\n                  identifier: automated-action-2-delay1-id\n                  actions:\n                    - add_tags_action: pager-page-2x-automated\n                - delay: 6\n                  identifier: automated-action-2-delay6-id\n                  behaviors:\n                    - pager_next\n              display_actions:\n                add_tags_action: pager-page-2x\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#00FF00'\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: label\n                      text: More stuff is here on the second page.\n                      text_appearance:\n                        alignment: center\n                        color:\n                          default:\n                            hex: '#000000'\n                            alpha: 1\n                        font_size: 14\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_2\n                      button_click:\n                        - dismiss\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: '#000000'\n                            alpha: 1\n            - identifier: pager-page-3-id\n              automated_actions:\n                - delay: 4.01\n                  identifier: automated-action-3-delay4-id\n                  actions:\n                    - add_tags_action: pager-page-3x-automated\n                - delay: 8\n                  identifier: automated-action-3-delay8-id\n                  behaviors:\n                    - pager_next_or_first\n                    - form_submit\n              display_actions:\n                add_tags_action: pager-page-3x\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: '#0000FF'\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_3_nps_form\n                      nps_identifier: score_identifier\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 100%\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier\n                              required: true\n                              style:\n                                type: number_range\n                                start: 0\n                                end: 10\n                                spacing: 2\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: '#FFFFFF'\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: '#000000'\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: '#000000'\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: '#ffffff'\n                                          alpha: 1\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: '#ffffff'\n                                  alpha: 1\n                              button_click:\n                                - form_submit\n                                - cancel\n                              enabled:\n                                - form_validation\n                              display_actions:\n                                add_tags_action: pager-page-3-form-submit\n                              label:\n                                type: label\n                                text: SuBmIt!1!1@\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: '#000000'\n                                      alpha: 1\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_3\n                      button_click:\n                        - dismiss\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: '#000000'\n                            alpha: 1\n      - size:\n          height: 5\n          width: 80%\n        position:\n          vertical: top\n          horizontal: center\n        margin:\n          bottom: 8\n        view:\n          type: story_indicator\n          source:\n            type: pager\n          style:\n            type: linear_progress\n            direction: horizontal\n            sizing: equal\n            spacing: 4\n            progress_color:\n              default:\n                hex: '#E6E6FA'\n                alpha: 1\n            track_color:\n              default:\n                hex: '#E6E6FA'\n                alpha: 0.7\n      - ignore_safe_area: false\n        position:\n          horizontal: start\n          vertical: center\n        size:\n          height: 48\n          width: 48\n        margin:\n          bottom: 0\n          end: 4\n          start: 4\n          top: 0\n        view:\n          type: stack_image_button\n          identifier: back button\n          button_click:\n            - pager_previous\n          enabled:\n            - pager_previous\n          items:\n            - type: shape\n              shape:\n                type: ellipse\n                aspect_ratio: 1\n                color:\n                  default:\n                    alpha: 1\n                    type: hex\n                    hex: '#FFFFFF'\n                  selectors:\n                    - color:\n                        hex: '#000000'\n                        alpha: 1\n                        type: hex\n                      dark_mode: true\n                scale: 0.7\n            - type: icon\n              icon:\n                type: icon\n                color:\n                  default:\n                    hex: '#000000'\n                    alpha: 1\n                    type: hex\n                  selectors:\n                    - color:\n                        hex: '#000000'\n                        alpha: 1\n                        type: hex\n                      dark_mode: false\n                    - color:\n                        hex: '#FFFFFF'\n                        alpha: 1\n                        type: hex\n                      dark_mode: true\n                icon: chevron_backward\n                scale: 0.35\n      - ignore_safe_area: false\n        position:\n          horizontal: end\n          vertical: center\n        size:\n          height: 48\n          width: 48\n        margin:\n          bottom: 0\n          end: 4\n          start: 4\n          top: 0\n        view:\n          type: stack_image_button\n          identifier: forward button\n          button_click:\n            - pager_next\n          enabled:\n            - pager_next\n          items:\n            - type: shape\n              shape:\n                type: ellipse\n                aspect_ratio: 1\n                color:\n                  default:\n                    alpha: 1\n                    type: hex\n                    hex: '#FFFFFF'\n                  selectors:\n                    - color:\n                        hex: '#000000'\n                        alpha: 1\n                        type: hex\n                      dark_mode: true\n                scale: 0.7\n            - type: icon\n              icon:\n                type: icon\n                color:\n                  default:\n                    hex: '#000000'\n                    alpha: 1\n                    type: hex\n                  selectors:\n                    - color:\n                        hex: '#000000'\n                        alpha: 1\n                        type: hex\n                      dark_mode: false\n                    - color:\n                        hex: '#FFFFFF'\n                        alpha: 1\n                        type: hex\n                      dark_mode: true\n                icon: chevron_forward\n                scale: 0.35\n      - ignore_safe_area: false\n        position:\n          horizontal: center\n          vertical: bottom\n        size:\n          height: 48\n          width: 48\n        margin:\n          bottom: 0\n          end: 4\n          start: 4\n          top: 0\n        view:\n          type: stack_image_button\n          identifier: play/pause button\n          button_click:\n            - pager_toggle_pause\n          items:\n            - type: shape\n              shape:\n                type: ellipse\n                aspect_ratio: 1\n                color:\n                  default:\n                    alpha: 1\n                    type: hex\n                    hex: '#FFFFFF'\n                  selectors:\n                    - color:\n                        hex: '#000000'\n                        alpha: 1\n                        type: hex\n                      dark_mode: true\n                scale: 0.7\n            - type: icon\n              icon:\n                type: icon\n                color:\n                  default:\n                    hex: '#000000'\n                    alpha: 1\n                    type: hex\n                  selectors:\n                    - color:\n                        hex: '#000000'\n                        alpha: 1\n                        type: hex\n                      dark_mode: false\n                    - color:\n                        hex: '#FFFFFF'\n                        alpha: 1\n                        type: hex\n                      dark_mode: true\n                icon: pause\n                scale: 0.35\n          view_overrides:\n            items:\n              - value:\n                  - type: shape\n                    shape:\n                      type: ellipse\n                      aspect_ratio: 1\n                      color:\n                        default:\n                          alpha: 1\n                          type: hex\n                          hex: '#FFFFFF'\n                        selectors:\n                          - color:\n                              hex: '#000000'\n                              alpha: 1\n                              type: hex\n                            dark_mode: true\n                      scale: 0.7\n                  - type: icon\n                    icon:\n                      type: icon\n                      color:\n                        default:\n                          hex: '#000000'\n                          alpha: 1\n                          type: hex\n                        selectors:\n                          - color:\n                              hex: '#000000'\n                              alpha: 1\n                              type: hex\n                            dark_mode: false\n                          - color:\n                              hex: '#FFFFFF'\n                              alpha: 1\n                              type: hex\n                            dark_mode: true\n                      icon: play\n                      scale: 0.35\n                when_state_matches:\n                  scope:\n                    - $pagers\n                    - current\n                    - paused\n                  value:\n                    equals: true\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/tap-handler-visibility.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: auto\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\n  dismiss_on_touch_outside: false\nview:\n  type: state_controller\n  view:\n    type: form_controller\n    identifier: a_form\n    submit: submit_event\n    view:\n      type: scroll_layout\n      direction: vertical\n      view:\n        type: container\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            margin:\n              top: 32\n              bottom: 32\n              start: 16\n              end: 16\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n\n               #\n               # Label\n               #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 0\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Label\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Tap me\n                    text_appearance:\n                      alignment: start\n                      font_size: 16\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                    event_handlers:\n                      - type: tap\n                        state_actions:\n                          - type: set\n                            key: label_tapped\n                            value: true\n                    visibility:\n                      default: true\n                      invert_when_state_matches:\n                        key: label_tapped\n                        value:\n                          equals: true\n\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: label\n                    text: Tap me again?\n                    text_appearance:\n                      alignment: end\n                      font_size: 16\n                      color:\n                        default:\n                          hex: \"#FF00FF\"\n                          alpha: 1\n                    visibility:\n                      default: false\n                      invert_when_state_matches:\n                        key: label_tapped\n                        value:\n                          equals: true\n                    event_handlers:\n                      - type: tap\n                        state_actions:\n                          - type: set\n                            key: label_tapped\n                            value: false\n\n                #\n                # Label Button\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Label Button\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n\n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: label_button\n                    identifier: boop_button\n                    background_color:\n                      default:\n                        hex: \"#000000\"\n                        alpha: 1\n                    label:\n                      type: label\n                      text: Click to close\n                      text_appearance:\n                        alignment: start\n                        font_size: 12\n                        color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                    event_handlers:\n                      - type: tap\n                        state_actions:\n                          - type: set\n                            key: button_tapped\n                            value: true\n                    visibility:\n                      default: true\n                      invert_when_state_matches:\n                        key: button_tapped\n                        value:\n                          equals: true\n\n                - size:\n                    width: auto\n                    height: auto\n                  view:\n                    type: label_button\n                    background_color:\n                      default:\n                        hex: \"#000000\"\n                        alpha: 1\n                    identifier: close_confirm_button\n                    button_click:\n                      - dismiss\n                    label:\n                      type: label\n                      text: Are you sure?\n                      text_appearance:\n                        alignment: end\n                        font_size: 12\n                        color:\n                          default:\n                            hex: \"#FF0000\"\n                            alpha: 1\n                    visibility:\n                      default: false\n                      invert_when_state_matches:\n                        key: button_tapped\n                        value:\n                          equals: true\n\n                #\n                # Image Buttons (url & icon)\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 4\n                  view:\n                    type: label\n                    text: Image Button\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    start: 12\n                    end: 12\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 50%\n                          height: 100\n                        view:\n                          type: image_button\n                          identifier: image_button_airship\n                          image:\n                            type: url\n                            url: https://upload.wikimedia.org/wikipedia/en/thumb/8/8b/Airship_2019_logo.png/220px-Airship_2019_logo.png\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: airship_tapped\n                                  value: true\n                          visibility:\n                            default: true\n                            invert_when_state_matches:\n                              key: airship_tapped\n                              value:\n                                equals: true\n\n                      - size:\n                          width: 50%\n                          height: 100\n                        view:\n                          type: image_button\n                          identifier: image_button_airship_old\n                          image:\n                            type: url\n                            url: https://upload.wikimedia.org/wikipedia/commons/thumb/2/2b/Urban_Airship_Logo.jpg/640px-Urban_Airship_Logo.jpg\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: airship_tapped\n                                  value: false\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: airship_tapped\n                              value:\n                                equals: true\n\n                      - size:\n                          width: 50%\n                          height: 100\n                        view:\n                          type: image_button\n                          identifier: image_button_forward\n                          image:\n                            type: icon\n                            icon: forward_arrow\n                            color:\n                              default:\n                                hex: \"#00ff00\"\n                                alpha: 1\n                            scale: 0.5\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: icon_tapped\n                                  value: true\n                          visibility:\n                            default: true\n                            invert_when_state_matches:\n                              key: icon_tapped\n                              value:\n                                equals: true\n\n                      - size:\n                          width: 50%\n                          height: 100\n                        view:\n                          type: image_button\n                          identifier: image_button_back\n                          image:\n                            type: icon\n                            icon: back_arrow\n                            color:\n                              default:\n                                hex: \"#ff0000\"\n                                alpha: 1\n                            scale: 0.5\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: icon_tapped\n                                  value: false\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: icon_tapped\n                              value:\n                                equals: true\n\n                #\n                # Toggle\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 4\n                  view:\n                    type: label\n                    text: Toggle\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 16\n                    start: 24\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: toggle\n                          identifier: toggle\n                          style:\n                            type: switch\n                            toggle_colors:\n                              on:\n                                default:\n                                  hex: \"#00FF00\"\n                                  alpha: 1\n                              off:\n                                default:\n                                  hex: \"#FF0000\"\n                                  alpha: 1\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: toggled\n                                  value: true\n                      - size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: label\n                          text: \"<-- You tapped!\"\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: toggled\n                              value:\n                                equals: true\n                      - size:\n                          width: auto\n                          height: 24\n                        view:\n                          type: label\n                          text: \"<-- Tap there\"\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: true\n                            invert_when_state_matches:\n                              key: toggled\n                              value:\n                                equals: true\n                #\n                # Radios\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                  view:\n                    type: label\n                    text: Radio Inputs\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    start: 24\n                  view:\n                    type: radio_input_controller\n                    identifier: radios\n                    view:\n                      type: linear_layout\n                      direction: horizontal\n                      items:\n                        - size:\n                            width: auto\n                            height: auto\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - size:\n                                width: auto\n                                height: auto\n                              margin:\n                                top: 4\n                              view:\n                                type: radio_input\n                                reporting_value: radio_red\n                                event_handlers:\n                                  - type: tap\n                                    state_actions:\n                                      - type: set\n                                        key: color\n                                        value: red\n                                style:\n                                  type: checkbox\n                                  bindings:\n                                    selected:\n                                      shapes:\n                                        - border:\n                                            radius: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            stroke_width: 2\n                                          color:\n                                            default:\n                                              hex: \"#ff0000\"\n                                              alpha: 1\n                                          scale: 1\n                                          type: ellipse\n                                    unselected:\n                                      shapes:\n                                        - border:\n                                            radius: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 0.5\n                                            stroke_width: 1\n                                          color:\n                                            default:\n                                              hex: \"#ff0000\"\n                                              alpha: 0.5\n                                          scale: 1\n                                          type: ellipse\n                            - size:\n                                width: auto\n                                height: auto\n                              margin:\n                                top: 4\n                              view:\n                                type: radio_input\n                                reporting_value: radio_yellow\n                                event_handlers:\n                                  - type: tap\n                                    state_actions:\n                                      - type: set\n                                        key: color\n                                        value: yellow\n                                style:\n                                  type: checkbox\n                                  bindings:\n                                    selected:\n                                      shapes:\n                                        - border:\n                                            radius: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            stroke_width: 2\n                                          color:\n                                            default:\n                                              hex: \"#ffff00\"\n                                              alpha: 1\n                                          scale: 1\n                                          type: ellipse\n                                    unselected:\n                                      shapes:\n                                        - border:\n                                            radius: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 0.5\n                                            stroke_width: 1\n                                          color:\n                                            default:\n                                              hex: \"#ffff00\"\n                                              alpha: 0.5\n                                          scale: 1\n                                          type: ellipse\n                            - size:\n                                width: auto\n                                height: auto\n                              margin:\n                                top: 4\n                              view:\n                                type: radio_input\n                                reporting_value: radio_green\n                                event_handlers:\n                                  - type: tap\n                                    state_actions:\n                                      - type: set\n                                        key: color\n                                        value: green\n                                style:\n                                  type: checkbox\n                                  bindings:\n                                    selected:\n                                      shapes:\n                                        - border:\n                                            radius: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            stroke_width: 2\n                                          color:\n                                            default:\n                                              hex: \"#00ff00\"\n                                              alpha: 1\n                                          scale: 1\n                                          type: ellipse\n                                    unselected:\n                                      shapes:\n                                        - border:\n                                            radius: 2\n                                            stroke_color:\n                                              default:\n                                                hex: \"#000000\"\n                                                alpha: 0.5\n                                            stroke_width: 1\n                                          color:\n                                            default:\n                                              hex: \"#00ff00\"\n                                              alpha: 0.5\n                                          scale: 1\n                                          type: ellipse\n                        - size:\n                            width: auto\n                            height: auto\n                          margin:\n                            top: 4\n                            start: 8\n                          view:\n                            type: label\n                            text: \"<-- Pick a color\"\n                            text_appearance:\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                              font_size: 14\n                              alignment: start\n                            visibility:\n                              default: true\n                              invert_when_state_matches:\n                                key: color\n                                value:\n                                  is_present: true\n                        - size:\n                            width: auto\n                            height: auto\n                          margin:\n                            top: 4\n                            start: 8\n                          view:\n                            type: label\n                            text: \"<-- You picked RED\"\n                            text_appearance:\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                              font_size: 14\n                              alignment: start\n                            visibility:\n                              default: false\n                              invert_when_state_matches:\n                                key: color\n                                value:\n                                  equals: red\n                        - size:\n                            width: auto\n                            height: auto\n                          margin:\n                            top: 4\n                            start: 8\n                          view:\n                            type: label\n                            text: \"<-- You picked YELLOW\"\n                            text_appearance:\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                              font_size: 14\n                              alignment: start\n                            visibility:\n                              default: false\n                              invert_when_state_matches:\n                                key: color\n                                value:\n                                  equals: yellow\n                        - size:\n                            width: auto\n                            height: auto\n                          margin:\n                            top: 4\n                            start: 8\n                          view:\n                            type: label\n                            text: \"<-- You picked GREEN\"\n                            text_appearance:\n                              color:\n                                default:\n                                  hex: \"#000000\"\n                                  alpha: 1\n                              font_size: 14\n                              alignment: start\n                            visibility:\n                              default: false\n                              invert_when_state_matches:\n                                key: color\n                                value:\n                                  equals: green\n\n\n                #\n                # Checkboxes\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                  view:\n                    type: label\n                    text: Checkboxes\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 12\n                    start: 24\n                  view:\n                    type: checkbox_controller\n                    identifier: checkboxes\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: linear_layout\n                            direction: horizontal\n                            items:\n                              - size:\n                                  width: auto\n                                  height: auto\n                                margin:\n                                  top: 0\n                                view:\n                                  type: checkbox\n                                  reporting_value: check_cyan\n                                  event_handlers:\n                                    - type: tap\n                                      state_actions:\n                                        - type: set\n                                          key: last_check\n                                          value: cyan\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        icon:\n                                          icon: checkmark\n                                          color:\n                                            default:\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          scale: 0.5\n                                        shapes:\n                                          - border:\n                                              radius: 5\n                                              stroke_color:\n                                                default:\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              stroke_width: 2\n                                            color:\n                                              default:\n                                                hex: \"#00ffff\"\n                                                alpha: 1\n                                            scale: 1\n                                            type: rectangle\n                                      unselected:\n                                        shapes:\n                                          - border:\n                                              radius: 5\n                                              stroke_color:\n                                                default:\n                                                  hex: \"#000000\"\n                                                  alpha: 0.5\n                                              stroke_width: 1\n                                            color:\n                                              default:\n                                                hex: \"#00ffff\"\n                                                alpha: 0.5\n                                            scale: 1\n                                            type: rectangle\n                              - size:\n                                  width: auto\n                                  height: auto\n                                margin:\n                                  start: 8\n                                view:\n                                  type: label\n                                  text: \"<-- Check it\"\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  visibility:\n                                    default: true\n                                    invert_when_state_matches:\n                                      key: last_check\n                                      value:\n                                        is_present: true\n\n                              - size:\n                                  width: auto\n                                  height: auto\n                                margin:\n                                  start: 8\n                                view:\n                                  type: label\n                                  text: \"<-- Tapped last\"\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  visibility:\n                                    default: false\n                                    invert_when_state_matches:\n                                      key: last_check\n                                      value:\n                                        equals: cyan\n\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 4\n                          view:\n                            type: linear_layout\n                            direction: horizontal\n                            items:\n                              - size:\n                                  width: auto\n                                  height: auto\n                                view:\n                                  type: checkbox\n                                  reporting_value: check_magenta\n                                  event_handlers:\n                                    - type: tap\n                                      state_actions:\n                                        - type: set\n                                          key: last_check\n                                          value: magenta\n                                  style:\n                                    type: checkbox\n                                    bindings:\n                                      selected:\n                                        icon:\n                                          icon: checkmark\n                                          color:\n                                            default:\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                          scale: 0.5\n                                        shapes:\n                                          - border:\n                                              radius: 5\n                                              stroke_color:\n                                                default:\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                              stroke_width: 2\n                                            color:\n                                              default:\n                                                hex: \"#ff00ff\"\n                                                alpha: 1\n                                            scale: 1\n                                            type: rectangle\n                                      unselected:\n                                        shapes:\n                                          - border:\n                                              radius: 5\n                                              stroke_color:\n                                                default:\n                                                  hex: \"#000000\"\n                                                  alpha: 0.5\n                                              stroke_width: 1\n                                            color:\n                                              default:\n                                                hex: \"#ff00ff\"\n                                                alpha: 0.5\n                                            scale: 1\n                                            type: rectangle\n                              - size:\n                                  width: auto\n                                  height: auto\n                                margin:\n                                  start: 8\n                                view:\n                                  type: label\n                                  text: \"<-- Check it\"\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  visibility:\n                                    default: true\n                                    invert_when_state_matches:\n                                      key: last_check\n                                      value:\n                                        is_present: true\n\n                              - size:\n                                  width: auto\n                                  height: auto\n                                margin:\n                                  start: 8\n                                view:\n                                  type: label\n                                  text: \"<-- Tapped last\"\n                                  text_appearance:\n                                    color:\n                                      default:\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    font_size: 14\n                                    alignment: start\n                                  visibility:\n                                    default: false\n                                    invert_when_state_matches:\n                                      key: last_check\n                                      value:\n                                        equals: magenta\n                #\n                # Text Input\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Text Input\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 125\n                          height: auto\n                        margin:\n                          start: 24\n                        view:\n                          type: text_input\n                          place_holder: Tap in here\n                          identifier: text_input\n                          border:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#cccccc\"\n                                alpha: 1\n                          text_appearance:\n                            alignment: start\n                            font_size: 14\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                          input_type: text\n                          required: false\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: text_input_tapped\n                                  value: true\n\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: text_input_tapped\n                              value:\n                                equals: true\n\n\n                #\n                # Score\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Score\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 125\n                          height: 32\n                        margin:\n                          start: 24\n                        view:\n                          type: score\n                          identifier: score_input\n                          border:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#cccccc\"\n                                alpha: 1\n                          required: false\n                          style:\n                            type: number_range\n                            start: 1\n                            end: 5\n                            spacing: 1\n                            bindings:\n                              selected:\n                                shapes:\n                                  - type: rectangle\n                                    scale: 0.75\n                                    border:\n                                      radius: 2\n                                      stroke_width: 1\n                                      stroke_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: .5\n                                text_appearance:\n                                  alignment: center\n                                  font_size: 12\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#ffffff\"\n                                      alpha: 1\n                              unselected:\n                                shapes:\n                                  - type: rectangle\n                                    scale: 0.75\n                                    border:\n                                      radius: 2\n                                      stroke_width: 1\n                                      stroke_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#ffffff\"\n                                        alpha: 1\n                                text_appearance:\n                                  font_size: 12\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#000000\"\n                                      alpha: 1\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: score_tapped\n                                  value: true\n\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: score_tapped\n                              value:\n                                equals: true\n\n\n                #\n                # Media Image\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Media Image\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 150\n                          height: 100\n                        view:\n                          type: media\n                          media_fit: center_inside\n                          url: https://upload.wikimedia.org/wikipedia/en/thumb/8/8b/Airship_2019_logo.png/220px-Airship_2019_logo.png\n                          media_type: image\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: media_image_tapped\n                                  value: true\n                          visibility:\n                            default: true\n                            invert_when_state_matches:\n                              key: media_image_tapped\n                              value:\n                                equals: true\n                      - size:\n                          width: 150\n                          height: 100\n                        view:\n                          type: media\n                          media_fit: center_inside\n                          url: https://upload.wikimedia.org/wikipedia/commons/thumb/2/2b/Urban_Airship_Logo.jpg/640px-Urban_Airship_Logo.jpg\n                          media_type: image\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: media_image_tapped\n                                  value: false\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: media_image_tapped\n                              value:\n                                equals: true\n\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: media_image_tapped\n                              value:\n                                is_present: true\n\n                #\n                # Media Video\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Media Video\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 175\n                          height: auto\n                        view:\n                          type: media\n                          media_fit: center_inside\n                          url: https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\n                          media_type: video\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: media_video_tapped\n                                  value: true\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: media_video_tapped\n                              value:\n                                equals: true\n\n                #\n                # Media SVG\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Media SVG\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    start: 16\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 75\n                          height: 75\n                        view:\n                          type: media\n                          media_fit: center_inside\n                          url: https://www.airship.com/wp-content/themes/airship/images/logo-mark.svg\n                          media_type: image\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: media_svg_tapped\n                                  value: true\n                          visibility:\n                            default: true\n                            invert_when_state_matches:\n                              key: media_svg_tapped\n                              value:\n                                equals: true\n                      - size:\n                          width: 75\n                          height: 75\n                        view:\n                          type: media\n                          media_fit: center_inside\n                          url: https://upload.wikimedia.org/wikipedia/commons/thumb/0/04/Zeppelin_%28example%29.svg/512px-Zeppelin_%28example%29.svg.png?20180723125412\n                          media_type: image\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: media_svg_tapped\n                                  value: false\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: media_svg_tapped\n                              value:\n                                equals: true\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: media_svg_tapped\n                              value:\n                                is_present: true\n\n                #\n                # WebView\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: WebView\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 50%\n                          height: 150\n                        view:\n                          type: web_view\n                          url: \"https://example.com\"\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: web_view_tapped\n                                  value: true\n\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: web_view_tapped\n                              value:\n                                equals: true\n\n                #\n                # Pager Indicator\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Pager Indicator\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 50%\n                          height: 75\n                        view:\n                          type: pager_controller\n                          identifier: pager_controller\n                          view:\n                            type: container\n                            border:\n                              radius: 2\n                              stroke_width: 1\n                              stroke_color:\n                                default:\n                                  type: hex\n                                  hex: \"#000000\"\n                                  alpha: 1\n                            items:\n                              - position:\n                                  vertical: center\n                                  horizontal: center\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  type: pager\n                                  items:\n                                    - identifier: \"page1\"\n                                      view:\n                                        type: empty_view\n                                        background_color:\n                                          default:\n                                            hex: \"#00FF00\"\n                                            alpha: 0.5\n                                    - identifier: \"page2\"\n                                      view:\n                                        type: empty_view\n                                        background_color:\n                                          default:\n                                            hex: \"#FFFF00\"\n                                            alpha: 0.5\n                                    - identifier: \"page2\"\n                                      view:\n                                        type: empty_view\n                                        background_color:\n                                          default:\n                                            hex: \"#FF00FF\"\n                                            alpha: 0.5\n\n                              - position:\n                                  horizontal: center\n                                  vertical: bottom\n                                size:\n                                  height: 24\n                                  width: auto\n                                margin:\n                                  bottom: 8\n                                view:\n                                  type: pager_indicator\n                                  background_color:\n                                    default:\n                                      hex: \"#333333\"\n                                      alpha: 0.7\n                                  border:\n                                    radius: 4\n                                  spacing: 4\n                                  event_handlers:\n                                    - type: tap\n                                      state_actions:\n                                        - type: set\n                                          key: pager_indicator_tapped\n                                          value: true\n                                  bindings:\n                                    selected:\n                                      shapes:\n                                        - type: rectangle\n                                          aspect_ratio: 2.25\n                                          scale: 0.9\n                                          border:\n                                            radius: 4\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                hex: \"#ffffff\"\n                                                alpha: 0.7\n                                          color:\n                                            default:\n                                              hex: \"#ffffff\"\n                                              alpha: 1\n                                    unselected:\n                                      shapes:\n                                        - type: rectangle\n                                          aspect_ratio: 2.25\n                                          scale: .9\n                                          border:\n                                            radius: 4\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                hex: \"#ffffff\"\n                                                alpha: 0.7\n                                          color:\n                                            default:\n                                              hex: \"#000000\"\n                                              alpha: 0\n\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: pager_indicator_tapped\n                              value:\n                                equals: true\n                #\n                # Empty View\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Empty View\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 50%\n                          height: 75\n                        view:\n                          type: empty_view\n                          border:\n                            radius: 2\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                          event_handlers:\n                            - type: tap\n                              state_actions:\n                                - type: set\n                                  key: empty_tapped\n                                  value: true\n\n                      - size:\n                          width: auto\n                          height: auto\n                        margin:\n                          start: 8\n                        view:\n                          type: label\n                          text: <-- you tapped!\n                          text_appearance:\n                            color:\n                              default:\n                                hex: \"#000000\"\n                                alpha: 1\n                            font_size: 14\n                            alignment: start\n                          visibility:\n                            default: false\n                            invert_when_state_matches:\n                              key: empty_tapped\n                              value:\n                                equals: true\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/textInput",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 100%\n      height: 70%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\nview:\n  type: state_controller\n  view:\n    type: form_controller\n    identifier: a_form\n    submit: submit_event\n    view:\n      type: scroll_layout\n      direction: vertical\n      view:\n        type: container\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: auto\n            margin:\n              top: 32\n              bottom: 32\n              start: 16\n              end: 16\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n              \n                #\n                # Text Input type: text\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Text Input type text\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 200\n                          height: auto\n                        margin:\n                          start: 24\n                        view:\n                          type: text_input\n                          place_holder: Tap in here\n                          identifier: text_input_text\n                          border:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#cccccc\"\n                                alpha: 1\n                          text_appearance:\n                            alignment: start\n                            font_size: 15\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#325ca8\"\n                                alpha: 1\n                          input_type: text\n                          required: false\n                          \n                    \n                #\n                # Text Input type: email\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Text Input type email\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 200\n                          height: auto\n                        margin:\n                          start: 24\n                        view:\n                          type: text_input\n                          place_holder: Tap in here\n                          identifier: text_input_email\n                          border:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#cccccc\"\n                                alpha: 1\n                          text_appearance:\n                            alignment: start\n                            font_size: 20\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#a8323a\"\n                                alpha: 1\n                          input_type: email\n                          required: false\n                          \n                \n                #\n                # Text Input type: number\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Text Input type number\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 200\n                          height: auto\n                        margin:\n                          start: 24\n                        view:\n                          type: text_input\n                          place_holder: Tap in here\n                          identifier: text_input_number\n                          border:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#cccccc\"\n                                alpha: 1\n                          text_appearance:\n                            alignment: start\n                            font_size: 25\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#339c3f\"\n                                alpha: 1\n                            styles:\n                            - italic\n                            - underlined\n                          input_type: number\n                          required: false\n                          \n                    \n                #\n                # Text Input type: text_multiline\n                #\n\n                - size:\n                    width: 100%\n                    height: auto\n                  margin:\n                    top: 24\n                    bottom: 12\n                  view:\n                    type: label\n                    text: Text Input type text_multiline\n                    text_appearance:\n                      alignment: center\n                      font_size: 14\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    width: 100%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                      - size:\n                          width: 300\n                          height: auto\n                        margin:\n                          start: 24\n                        view:\n                          type: text_input\n                          place_holder: Tap in here\n                          identifier: text_input_multiline\n                          border:\n                            radius: 5\n                            stroke_width: 1\n                            stroke_color:\n                              default:\n                                type: hex\n                                hex: \"#cccccc\"\n                                alpha: 1\n                          text_appearance:\n                            alignment: start\n                            font_size: 30\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                            styles:\n                            - bold\n                            - italic\n                            - underlined\n                          input_type: text_multiline\n                          required: false\n                    \n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/toggle-branching-simple-quiz.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: '#000000'\n        alpha: 0.75\n    web: {}\nview:\n  type: pager_controller\n  identifier: 5ff76966-4e7e-4a5f-b3b4-3928cf7f4076\n  view:\n    type: linear_layout\n    direction: vertical\n    items:\n    - size:\n        width: 100%\n        height: 100%\n      view:\n        identifier: fa59a9dc-4f78-407e-bc89-4bb20c03c310\n        type: form_controller\n        validation_mode:\n          type: on_demand\n        form_enabled:\n        - form_submission\n        submit: submit_event\n        response_type: user_feedback\n        view:\n          type: container\n          background_color:\n            default:\n              hex: '#004bff'\n              alpha: 1\n          border:\n            radius: 20\n            stroke_width: 2\n            stroke_color:\n              default:\n                hex: '#FFFFFF'\n                alpha: 0.1\n          items:\n          - position:\n              horizontal: center\n              vertical: center\n            size:\n              width: 100%\n              height: 100%\n            view:\n              type: pager\n              disable_swipe: false\n              items:\n              - identifier: b5628f32-3da1-42ae-b76e-c20800922d47\n                type: pager_item\n                branching:\n                  next_page:\n                    selectors:\n                    - page_id: 24e09863-1dcf-40ea-8b81-b398d286d449\n                      when_state_matches:\n                        or:\n                        - scope:\n                          - $forms\n                          - current\n                          - data\n                          - children\n                          - edf9064c-54d5-4b29-9ac9-ad0997a62696\n                          value:\n                            equals: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7\n                          key: value\n                    - page_id: d495142a-38d3-482c-9f14-203501b0518a\n                      when_state_matches:\n                        and:\n                        - scope:\n                          - $forms\n                          - current\n                          - data\n                          - children\n                          - edf9064c-54d5-4b29-9ac9-ad0997a62696\n                          value:\n                            equals: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d\n                          key: value\n                view:\n                  type: container\n                  background_color:\n                    default:\n                      hex: '#004bff'\n                      alpha: 1\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 40\n                                    bottom: 8\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 0\n                                        bottom: 24\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: \"\\U0001F43E Favorite Animal \\U0001F43E\"\n                                        content_description: Favorite Animal\n                                        text_appearance:\n                                          font_size: 30\n                                          color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 1\n                                          alignment: center\n                                          styles:\n                                          - bold\n                                          font_families:\n                                          - sans-serif\n                                    - margin:\n                                        top: 0\n                                        bottom: 32\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: Please select your favorite animal\n                                        text_appearance:\n                                          alignment: center\n                                          font_size: 18\n                                          color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 0.9\n                                    - margin:\n                                        top: 0\n                                        bottom: 24\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: radio_input_controller\n                                        identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                        required: true\n                                        on_error:\n                                          state_actions:\n                                          - type: set\n                                            key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                            value: error\n                                        on_valid:\n                                          state_actions:\n                                          - type: set\n                                            key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                            value: valid\n                                        on_edit:\n                                          state_actions:\n                                          - type: set\n                                            key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                            value: editing\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: 4eb714c3-c19f-467b-8daa-0a27b5b4d9f7\n                                                  content_description: Cat\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: \"\\U0001F431 Cat\"\n                                                  content_description: Cat\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: 33c7f7cb-d703-4685-a0c8-0a6591cbb67d\n                                                  content_description: Dog\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: \"\\U0001F436 Dog\"\n                                                  content_description: Dog\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          randomize_children: false\n                                        attribute_name: {}\n                                - size:\n                                    width: 100%\n                                    height: 50\n                                  margin:\n                                    top: 24\n                                    bottom: 32\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: label_button\n                                    identifier: next_button_animal\n                                    background_color:\n                                      default:\n                                        hex: '#f1084f'\n                                        alpha: 1\n                                    border:\n                                      radius: 25\n                                      stroke_width: 0\n                                    button_click:\n                                    - pager_next\n                                    enabled:\n                                    - form_validation\n                                    label:\n                                      type: label\n                                      text: Next\n                                      text_appearance:\n                                        font_size: 18\n                                        alignment: center\n                                        styles:\n                                        - bold\n                                        color:\n                                          default:\n                                            hex: '#FFFFFF'\n                                            alpha: 1\n              - identifier: d495142a-38d3-482c-9f14-203501b0518a\n                type: pager_item\n                branching:\n                  next_page:\n                    selectors:\n                    - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                view:\n                  type: container\n                  background_color:\n                    default:\n                      hex: '#004bff'\n                      alpha: 1\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: 7beb5f48-a5fe-4c56-ae31-994263ffcdb2\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 40\n                                    bottom: 8\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 0\n                                        bottom: 24\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: \"\\U0001F436 Favorite Dog Collar \\U0001F436\"\n                                        content_description: Favorite Dog Collar Color\n                                        text_appearance:\n                                          font_size: 30\n                                          color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 1\n                                          alignment: center\n                                          styles:\n                                          - bold\n                                          font_families:\n                                          - sans-serif\n                                    - margin:\n                                        top: 0\n                                        bottom: 32\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: What color collar would you choose for\n                                          your dog?\n                                        text_appearance:\n                                          alignment: center\n                                          font_size: 18\n                                          color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 0.9\n                                    - margin:\n                                        top: 0\n                                        bottom: 24\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: radio_input_controller\n                                        identifier: 7beb5f48-a5fe-4c56-ae31-994263ffcdb2\n                                        required: false\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: d0596a26-f197-48d7-96b3-c61ace2fb16b\n                                                  content_description: Blue\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: \"\\U0001F535 Blue Collar\"\n                                                  content_description: Blue\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                              - size:\n                                                  width: 24\n                                                  height: 24\n                                                margin:\n                                                  end: 8\n                                                view:\n                                                  type: empty_view\n                                                  background_color:\n                                                    default:\n                                                      hex: '#001f9e'\n                                                      alpha: 1\n                                                  border:\n                                                    radius: 12\n                                                    stroke_width: 0\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: radio_input\n                                                  reporting_value: 55ffe2a4-d851-40a8-8046-28c8349e9ace\n                                                  content_description: Red\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 1\n                                                        - type: ellipse\n                                                          scale: 0.6\n                                                          aspect_ratio: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: ellipse\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 20\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: \"\\U0001F534 Red Collar\"\n                                                  content_description: Red\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                              - size:\n                                                  width: 24\n                                                  height: 24\n                                                margin:\n                                                  end: 8\n                                                view:\n                                                  type: empty_view\n                                                  background_color:\n                                                    default:\n                                                      hex: '#f1084f'\n                                                      alpha: 1\n                                                  border:\n                                                    radius: 12\n                                                    stroke_width: 0\n                                          randomize_children: false\n                                        attribute_name: {}\n                                    - size:\n                                        width: 100%\n                                        height: auto\n                                      margin:\n                                        top: 24\n                                        bottom: 32\n                                        start: 0\n                                        end: 0\n                                      view:\n                                        type: linear_layout\n                                        direction: horizontal\n                                        items:\n                                        - size:\n                                            width: 48%\n                                            height: 50\n                                          margin:\n                                            end: 8\n                                            start: 24\n                                          view:\n                                            type: label_button\n                                            identifier: prev_button_color\n                                            background_color:\n                                              default:\n                                                hex: '#001f9e'\n                                                alpha: 1\n                                            border:\n                                              radius: 25\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 0.3\n                                            button_click:\n                                            - pager_previous\n                                            label:\n                                              type: label\n                                              text: Previous\n                                              text_appearance:\n                                                font_size: 16\n                                                alignment: center\n                                                styles:\n                                                - bold\n                                                color:\n                                                  default:\n                                                    hex: '#FFFFFF'\n                                                    alpha: 1\n                                        - size:\n                                            width: 48%\n                                            height: 50\n                                          margin:\n                                            start: 8\n                                            end: 24\n                                          view:\n                                            type: label_button\n                                            identifier: next_button_color\n                                            background_color:\n                                              default:\n                                                hex: '#f1084f'\n                                                alpha: 1\n                                            border:\n                                              radius: 25\n                                              stroke_width: 0\n                                            button_click:\n                                            - pager_next\n                                            enabled:\n                                            - form_validation\n                                            label:\n                                              type: label\n                                              text: Next\n                                              text_appearance:\n                                                font_size: 16\n                                                alignment: center\n                                                styles:\n                                                - bold\n                                                color:\n                                                  default:\n                                                    hex: '#FFFFFF'\n                                                    alpha: 1\n                                            event_handlers:\n                                            - type: tap\n                                              state_actions:\n                                              - type: set\n                                                key: submitted\n                                                value: true\n              - identifier: 24e09863-1dcf-40ea-8b81-b398d286d449\n                type: pager_item\n                branching:\n                  next_page:\n                    selectors:\n                    - page_id: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                view:\n                  type: container\n                  background_color:\n                    default:\n                      hex: '#004bff'\n                      alpha: 1\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 40\n                                    bottom: 8\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - margin:\n                                        top: 0\n                                        bottom: 24\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: \"\\U0001F63A Favorite Cat Shows \\U0001F63A\"\n                                        content_description: What are your favorite\n                                          cat shows? (select at least 2)\n                                        text_appearance:\n                                          font_size: 30\n                                          color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 1\n                                          alignment: center\n                                          styles:\n                                          - bold\n                                          font_families:\n                                          - sans-serif\n                                    - margin:\n                                        top: 0\n                                        bottom: 32\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: label\n                                        text: Select your favorite cat TV shows (at\n                                          least 2)\n                                        text_appearance:\n                                          alignment: center\n                                          font_size: 18\n                                          color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 0.9\n                                    - margin:\n                                        top: 0\n                                        bottom: 8\n                                      size:\n                                        width: 100%\n                                        height: auto\n                                      view:\n                                        type: checkbox_controller\n                                        identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                        on_error:\n                                          state_actions:\n                                          - type: set\n                                            key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                            value: error\n                                        on_valid:\n                                          state_actions:\n                                          - type: set\n                                            key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                            value: valid\n                                        on_edit:\n                                          state_actions:\n                                          - type: set\n                                            key: 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                            value: editing\n                                        required: true\n                                        min_selection: 2\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: 646d668e-5d50-44cf-8ddf-7fe4e2a404ab\n                                                  content_description: My Cat From\n                                                    Hell\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#f1084f'\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: My Cat From Hell\n                                                  content_description: My Cat From\n                                                    Hell\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: 3a6f2143-968c-484e-ac2f-69efdbfaaabd\n                                                  content_description: Cat People\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#f1084f'\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Cat People\n                                                  content_description: Cat People\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: 86eaa518-2a24-4018-a303-4c9eb05ec2a3\n                                                  content_description: Cats 101\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#f1084f'\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Cats 101\n                                                  content_description: Cats 101\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: bbd19354-9728-4be6-baab-7d3779841eb2\n                                                  content_description: The Lion in\n                                                    Your Living Room\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#f1084f'\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: The Lion in Your Living Room\n                                                  content_description: The Lion in\n                                                    Your Living Room\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            margin:\n                                              top: 0\n                                              bottom: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items:\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: checkbox\n                                                  reporting_value: d27cc3e2-e1a9-4ed8-b013-a3b64017ee91\n                                                  content_description: Big Cat Diary\n                                                  style:\n                                                    type: checkbox\n                                                    bindings:\n                                                      selected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#f1084f'\n                                                              alpha: 1\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          scale: 0.8\n                                                          color:\n                                                            default:\n                                                              hex: '#FFFFFF'\n                                                              alpha: 1\n                                                      unselected:\n                                                        shapes:\n                                                        - type: rectangle\n                                                          scale: 1\n                                                          aspect_ratio: 1\n                                                          border:\n                                                            radius: 4\n                                                            stroke_width: 2\n                                                            stroke_color:\n                                                              default:\n                                                                hex: '#FFFFFF'\n                                                                alpha: 1\n                                                          color:\n                                                            default:\n                                                              hex: '#004bff'\n                                                              alpha: 0\n                                              - margin:\n                                                  end: 16\n                                                size:\n                                                  height: auto\n                                                  width: 100%\n                                                view:\n                                                  type: label\n                                                  text: Big Cat Diary\n                                                  content_description: Big Cat Diary\n                                                  text_appearance:\n                                                    font_size: 18\n                                                    color:\n                                                      default:\n                                                        hex: '#FFFFFF'\n                                                        alpha: 1\n                                                    alignment: start\n                                                    font_families:\n                                                    - sans-serif\n                                          randomize_children: true\n                                    - size:\n                                        width: 100%\n                                        height: auto\n                                      margin:\n                                        top: 24\n                                        bottom: 32\n                                        start: 0\n                                        end: 0\n                                      view:\n                                        type: linear_layout\n                                        direction: horizontal\n                                        items:\n                                        - size:\n                                            width: 48%\n                                            height: 50\n                                          margin:\n                                            end: 8\n                                            start: 24\n                                          view:\n                                            type: label_button\n                                            identifier: prev_button_shows\n                                            background_color:\n                                              default:\n                                                hex: '#001f9e'\n                                                alpha: 1\n                                            border:\n                                              radius: 25\n                                              stroke_width: 2\n                                              stroke_color:\n                                                default:\n                                                  hex: '#FFFFFF'\n                                                  alpha: 0.3\n                                            button_click:\n                                            - pager_previous\n                                            label:\n                                              type: label\n                                              text: Previous\n                                              text_appearance:\n                                                font_size: 16\n                                                alignment: center\n                                                styles:\n                                                - bold\n                                                color:\n                                                  default:\n                                                    hex: '#FFFFFF'\n                                                    alpha: 1\n                                        - size:\n                                            width: 48%\n                                            height: 50\n                                          margin:\n                                            start: 8\n                                            end: 24\n                                          view:\n                                            type: label_button\n                                            identifier: next_button_shows\n                                            background_color:\n                                              default:\n                                                hex: '#f1084f'\n                                                alpha: 1\n                                            border:\n                                              radius: 25\n                                              stroke_width: 0\n                                            button_click:\n                                            - pager_next\n                                            enabled:\n                                            - form_validation\n                                            label:\n                                              type: label\n                                              text: Next\n                                              text_appearance:\n                                                font_size: 16\n                                                alignment: center\n                                                styles:\n                                                - bold\n                                                color:\n                                                  default:\n                                                    hex: '#FFFFFF'\n                                                    alpha: 1\n              - identifier: 3cbc6d3e-94f2-4803-90ed-d8978b6a5573\n                type: pager_item\n                view:\n                  type: container\n                  background_color:\n                    default:\n                      hex: '#004bff'\n                      alpha: 1\n                  items:\n                  - size:\n                      width: 100%\n                      height: 100%\n                    position:\n                      horizontal: center\n                      vertical: center\n                    ignore_safe_area: false\n                    view:\n                      type: container\n                      items:\n                      - margin:\n                          bottom: 0\n                          top: 0\n                          end: 0\n                          start: 0\n                        position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                          - identifier: scroll_container\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: scroll_layout\n                              direction: vertical\n                              view:\n                                type: linear_layout\n                                direction: vertical\n                                items:\n                                - identifier: rating_title_label\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 40\n                                    bottom: 24\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: label\n                                    text: \"\\u2764\\uFE0F Your Information \\u2764\\uFE0F\"\n                                    content_description: Your Information\n                                    text_appearance:\n                                      font_size: 30\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      alignment: center\n                                      styles:\n                                      - bold\n                                      font_families:\n                                      - sans-serif\n                                - identifier: fe561774-93f5-4b09-bd0c-831b955042e6\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 16\n                                    bottom: 8\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: label\n                                    text: Email Address\n                                    text_appearance:\n                                      font_size: 18\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      alignment: start\n                                      styles:\n                                      - bold\n                                      font_families:\n                                      - sans-serif\n                                - identifier: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                  size:\n                                    width: 100%\n                                    height: 50\n                                  margin:\n                                    top: 8\n                                    bottom: 16\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: text_input\n                                    place_holder: email@email.com\n                                    identifier: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                    border:\n                                      radius: 8\n                                      stroke_width: 2\n                                      stroke_color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 0.5\n                                    text_appearance:\n                                      alignment: start\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                    input_type: email\n                                    required: true\n                                    view_overrides:\n                                      icon_end:\n                                      - when_state_matches:\n                                          scope:\n                                          - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                          value:\n                                            equals: error\n                                        value:\n                                          type: floating\n                                          icon:\n                                            type: icon\n                                            icon: exclamationmark_circle_fill\n                                            scale: 1\n                                            color:\n                                              default:\n                                                hex: '#f1084f'\n                                                alpha: 1\n                                      - when_state_matches:\n                                          scope:\n                                          - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                          value:\n                                            equals: valid\n                                        value:\n                                          type: floating\n                                          icon:\n                                            type: icon\n                                            icon: checkmark\n                                            scale: 1\n                                            color:\n                                              default:\n                                                hex: '#6ca15f'\n                                                alpha: 1\n                                      border:\n                                      - when_state_matches:\n                                          scope:\n                                          - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                          value:\n                                            equals: error\n                                        value:\n                                          radius: 8\n                                          stroke_width: 2\n                                          stroke_color:\n                                            default:\n                                              hex: '#f1084f'\n                                              alpha: 1\n                                      - when_state_matches:\n                                          scope:\n                                          - 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                          value:\n                                            equals: valid\n                                        value:\n                                          radius: 8\n                                          stroke_width: 2\n                                          stroke_color:\n                                            default:\n                                              hex: '#6ca15f'\n                                              alpha: 1\n                                    on_error:\n                                      state_actions:\n                                      - type: set\n                                        key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                        value: error\n                                    on_edit:\n                                      state_actions:\n                                      - type: set\n                                        key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                        value: editing\n                                    on_valid:\n                                      state_actions:\n                                      - type: set\n                                        key: 2cb5fa0f-1c25-4929-b951-66ce56910901\n                                        value: valid\n                                - identifier: 26b7ddd7-f995-4a27-8fcb-e1b86d00d210\n                                  size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 16\n                                    bottom: 8\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: label\n                                    text: Additional Email (Optional)\n                                    text_appearance:\n                                      font_size: 18\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      alignment: start\n                                      styles:\n                                      - bold\n                                      font_families:\n                                      - sans-serif\n                                - identifier: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5\n                                  size:\n                                    width: 100%\n                                    height: 50\n                                  margin:\n                                    top: 8\n                                    bottom: 24\n                                    start: 24\n                                    end: 24\n                                  view:\n                                    type: text_input\n                                    place_holder: Optional\n                                    identifier: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5\n                                    border:\n                                      radius: 8\n                                      stroke_width: 2\n                                      stroke_color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 0.5\n                                    text_appearance:\n                                      alignment: start\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                    input_type: text\n                                    required: false\n                                    view_overrides:\n                                      icon_end:\n                                      - when_state_matches:\n                                          scope:\n                                          - c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                          value:\n                                            equals: true\n                                        value:\n                                          type: floating\n                                          icon:\n                                            type: icon\n                                            icon: exclamationmark_circle_fill\n                                            scale: 1\n                                            color:\n                                              default:\n                                                hex: '#f1084f'\n                                                alpha: 1\n                                      border:\n                                      - when_state_matches:\n                                          scope:\n                                          - c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                          value:\n                                            equals: true\n                                        value:\n                                          radius: 8\n                                          stroke_width: 2\n                                          stroke_color:\n                                            default:\n                                              hex: '#f1084f'\n                                              alpha: 1\n                                    on_error:\n                                      state_actions:\n                                      - type: set\n                                        key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                        value: true\n                                    on_edit:\n                                      state_actions:\n                                      - type: set\n                                        key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                    on_valid:\n                                      state_actions:\n                                      - type: set\n                                        key: c69a7f9f-1c7c-4e87-bee1-0301d9b7b3c5_error\n                                - size:\n                                    width: 100%\n                                    height: auto\n                                  margin:\n                                    top: 24\n                                    bottom: 32\n                                    start: 0\n                                    end: 0\n                                  view:\n                                    type: linear_layout\n                                    direction: horizontal\n                                    items:\n                                    - size:\n                                        width: 48%\n                                        height: 50\n                                      margin:\n                                        end: 8\n                                        start: 24\n                                      view:\n                                        type: label_button\n                                        identifier: prev_button_email\n                                        background_color:\n                                          default:\n                                            hex: '#001f9e'\n                                            alpha: 1\n                                        border:\n                                          radius: 25\n                                          stroke_width: 2\n                                          stroke_color:\n                                            default:\n                                              hex: '#FFFFFF'\n                                              alpha: 0.3\n                                        button_click:\n                                        - pager_previous\n                                        label:\n                                          type: label\n                                          text: Previous\n                                          text_appearance:\n                                            font_size: 16\n                                            alignment: center\n                                            styles:\n                                            - bold\n                                            color:\n                                              default:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                    - size:\n                                        width: 48%\n                                        height: 50\n                                      margin:\n                                        start: 8\n                                        end: 24\n                                      view:\n                                        type: label_button\n                                        identifier: submit_feedback--Submit\n                                        background_color:\n                                          default:\n                                            hex: '#f1084f'\n                                            alpha: 1\n                                        border:\n                                          radius: 25\n                                          stroke_width: 0\n                                        button_click:\n                                        - form_submit\n                                        - dismiss\n                                        enabled:\n                                        - form_validation\n                                        label:\n                                          type: label\n                                          text: Submit\n                                          text_appearance:\n                                            font_size: 16\n                                            alignment: center\n                                            styles:\n                                            - bold\n                                            color:\n                                              default:\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                          view_overrides:\n                                            icon_start:\n                                            - value:\n                                                type: floating\n                                                space: 8\n                                                icon:\n                                                  type: icon\n                                                  icon: progress_spinner\n                                                  color:\n                                                    default:\n                                                      hex: '#FFFFFF'\n                                                      alpha: 1.0\n                                                  scale: 1\n                                              when_state_matches:\n                                                scope:\n                                                - $forms\n                                                - current\n                                                - status\n                                                - type\n                                                value:\n                                                  equals: validating\n                                            text:\n                                            - value: Processing ...\n                                              when_state_matches:\n                                                scope:\n                                                - $forms\n                                                - current\n                                                - status\n                                                - type\n                                                value:\n                                                  equals: validating\n                                        event_handlers:\n                                        - type: tap\n                                          state_actions:\n                                          - type: set\n                                            key: submitted\n                                            value: true\n          - position:\n              horizontal: end\n              vertical: top\n            margin:\n              top: 16\n              end: 16\n            size:\n              width: 32\n              height: 32\n            view:\n              type: image_button\n              identifier: dismiss_button\n              button_click:\n              - dismiss\n              image:\n                type: icon\n                icon: close\n                color:\n                  default:\n                    hex: '#FFFFFF'\n                    alpha: 0.8\n                scale: 0.7\n          - margin:\n              top: 4\n              bottom: 16\n              end: 0\n              start: 0\n            position:\n              horizontal: center\n              vertical: bottom\n            size:\n              height: 8\n              width: 100%\n            view:\n              type: pager_indicator\n              spacing: 8\n              bindings:\n                selected:\n                  shapes:\n                  - type: ellipse\n                    aspect_ratio: 1\n                    scale: 1\n                    color:\n                      default:\n                        hex: '#FFFFFF'\n                        alpha: 1\n                unselected:\n                  shapes:\n                  - type: ellipse\n                    aspect_ratio: 1\n                    scale: 1\n                    color:\n                      default:\n                        hex: '#FFFFFF'\n                        alpha: 0.3\n  branching:\n    pager_completions: []\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/toggle-layout-types.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: true\n    position:\n      horizontal: center\n      vertical: center\n    size:\n      width: 100%\n      height: 100%\n    background_color:\n      default:\n        type: hex\n        hex: \"#FFFFFF\"\n        alpha: 1\nview:\n  type: form_controller\n  identifier: toggle_layout_showcase\n  response_type: user_feedback\n  validation_mode:\n    type: on_demand\n  view:\n    type: container\n    items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: scroll_layout\n          direction: vertical\n          view:\n            type: linear_layout\n            direction: vertical\n            items:\n              # Header\n              - identifier: header\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 16\n                  bottom: 16\n                  start: 16\n                  end: 16\n                view:\n                  type: label\n                  text: Toggle Layout Test\n                  text_appearance:\n                    font_size: 24\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n                    styles:\n                      - bold\n                    font_families:\n                      - sans-serif\n              \n              # SECTION 1: Basic Toggle Layout (standalone)\n              - identifier: basic_toggle_section\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 24\n                  bottom: 8\n                  start: 16\n                  end: 16\n                view:\n                  type: label\n                  text: \"1. Basic Toggle Layout\"\n                  text_appearance:\n                    font_size: 20\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: start\n                    styles:\n                      - bold\n                    font_families:\n                      - sans-serif\n              \n              # Basic Toggle Example\n              - identifier: basic_toggle_item\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 8\n                  bottom: 24\n                  start: 16\n                  end: 16\n                view:\n                  type: basic_toggle_layout\n                  identifier: basic_toggle_1\n                  content_description: \"Basic toggle example\"\n                  on_toggle_on:\n                    state_actions:\n                      - type: set\n                        key: basic_toggle_1\n                        value: true\n                  on_toggle_off:\n                    state_actions:\n                      - type: set\n                        key: basic_toggle_1\n                        value: false\n                  view:\n                    type: container\n                    border:\n                      radius: 8\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          type: hex\n                          hex: \"#666666\"\n                          alpha: 1\n                    view_overrides:\n                      background_color:\n                        - when_state_matches:\n                            key: basic_toggle_1\n                            value:\n                              equals: true\n                          value:\n                            default:\n                              type: hex\n                              hex: \"#4285F4\"\n                              alpha: 0.3\n                    items:\n                      - size:\n                          width: 100%\n                          height: auto\n                        position:\n                          horizontal: center\n                          vertical: center\n                        margin:\n                          top: 12\n                          bottom: 12\n                          start: 16\n                          end: 16\n                        view:\n                          type: label\n                          text: \"Basic Toggle Layout\"\n                          text_appearance:\n                            font_size: 16\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                            alignment: center\n                            font_families:\n                              - sans-serif\n              \n              # SECTION 2: Radio Input Toggle Layout (within controller)\n              - identifier: radio_toggle_section\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 16\n                  bottom: 8\n                  start: 16\n                  end: 16\n                view:\n                  type: label\n                  text: \"2. Radio Input Toggle Layouts\"\n                  text_appearance:\n                    font_size: 20\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: start\n                    styles:\n                      - bold\n                    font_families:\n                      - sans-serif\n              \n              # Radio Controller\n              - identifier: radio_controller_item\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 8\n                  bottom: 24\n                  start: 16\n                  end: 16\n                view:\n                  type: radio_input_controller\n                  identifier: radio_controller_1\n                  required: true\n                  view:\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                      # Radio Option 1\n                      - identifier: radio_option_1\n                        size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 8\n                        view:\n                          type: radio_input_toggle_layout\n                          identifier: radio_toggle_1\n                          content_description: \"Radio Option One\"\n                          # Using \"reporting_value:\" to match Swift implementation\n                          reporting_value: \"option_one\"\n                          on_toggle_on:\n                            state_actions:\n                              - type: set\n                                key: selected_radio\n                                value: \"option_one\"\n                          on_toggle_off:\n                            state_actions: []\n                          view:\n                            type: container\n                            border:\n                              radius: 8\n                              stroke_width: 1\n                              stroke_color:\n                                default:\n                                  type: hex\n                                  hex: \"#666666\"\n                                  alpha: 1\n                            view_overrides:\n                              background_color:\n                                - when_state_matches:\n                                    key: selected_radio\n                                    value:\n                                      equals: \"option_one\"\n                                  value:\n                                    default:\n                                      type: hex\n                                      hex: \"#FFC107\"\n                                      alpha: 0.3\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                margin:\n                                  top: 12\n                                  bottom: 12\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: \"Radio Option One\"\n                                  text_appearance:\n                                    font_size: 16\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    alignment: center\n                                    font_families:\n                                      - sans-serif\n                      \n                      # Radio Option 2\n                      - identifier: radio_option_2\n                        size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 8\n                        view:\n                          type: radio_input_toggle_layout\n                          identifier: radio_toggle_2\n                          content_description: \"Radio Option Two\"\n                          # Using \"reporting_value:\" to match Swift implementation\n                          reporting_value: \"option_two\"\n                          on_toggle_on:\n                            state_actions:\n                              - type: set\n                                key: selected_radio\n                                value: \"option_two\"\n                          on_toggle_off:\n                            state_actions: []\n                          view:\n                            type: container\n                            border:\n                              radius: 8\n                              stroke_width: 1\n                              stroke_color:\n                                default:\n                                  type: hex\n                                  hex: \"#666666\"\n                                  alpha: 1\n                            view_overrides:\n                              background_color:\n                                - when_state_matches:\n                                    key: selected_radio\n                                    value:\n                                      equals: \"option_two\"\n                                  value:\n                                    default:\n                                      type: hex\n                                      hex: \"#FFC107\"\n                                      alpha: 0.3\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                margin:\n                                  top: 12\n                                  bottom: 12\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: \"Radio Option Two\"\n                                  text_appearance:\n                                    font_size: 16\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    alignment: center\n                                    font_families:\n                                      - sans-serif\n              \n              # SECTION 3: Checkbox Toggle Layout\n              - identifier: checkbox_toggle_section\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 16\n                  bottom: 8\n                  start: 16\n                  end: 16\n                view:\n                  type: label\n                  text: \"3. Checkbox Toggle Layouts\"\n                  text_appearance:\n                    font_size: 20\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: start\n                    styles:\n                      - bold\n                    font_families:\n                      - sans-serif\n              \n              # Checkbox Controller\n              - identifier: checkbox_controller_item\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 8\n                  bottom: 24\n                  start: 16\n                  end: 16\n                view:\n                  type: checkbox_controller\n                  identifier: checkbox_controller_1\n                  required: true\n                  view:\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                      # Checkbox Option 1\n                      - identifier: checkbox_option_1\n                        size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 8\n                        view:\n                          type: checkbox_toggle_layout\n                          identifier: checkbox_toggle_1\n                          content_description: \"Checkbox Option One\"\n                          # Using \"reporting_value:\" to match Swift implementation\n                          reporting_value: \"checkbox_one\"\n                          on_toggle_on:\n                            state_actions:\n                              - type: set\n                                key: checkbox_one\n                                value: true\n                          on_toggle_off:\n                            state_actions:\n                              - type: set\n                                key: checkbox_one\n                                value: false\n                          view:\n                            type: container\n                            border:\n                              radius: 8\n                              stroke_width: 1\n                              stroke_color:\n                                default:\n                                  type: hex\n                                  hex: \"#666666\"\n                                  alpha: 1\n                            view_overrides:\n                              background_color:\n                                - when_state_matches:\n                                    key: checkbox_one\n                                    value:\n                                      equals: true\n                                  value:\n                                    default:\n                                      type: hex\n                                      hex: \"#8BC34A\"\n                                      alpha: 0.3\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                margin:\n                                  top: 12\n                                  bottom: 12\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: \"Checkbox Option One\"\n                                  text_appearance:\n                                    font_size: 16\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    alignment: center\n                                    font_families:\n                                      - sans-serif\n                      \n                      # Checkbox Option 2\n                      - identifier: checkbox_option_2\n                        size:\n                          width: 100%\n                          height: auto\n                        margin:\n                          top: 8\n                          bottom: 8\n                        view:\n                          type: checkbox_toggle_layout\n                          identifier: checkbox_toggle_2\n                          content_description: \"Checkbox Option Two\"\n                          # Using \"reporting_value:\" to match Swift implementation\n                          reporting_value: \"checkbox_two\"\n                          on_toggle_on:\n                            state_actions:\n                              - type: set\n                                key: checkbox_two\n                                value: true\n                          on_toggle_off:\n                            state_actions:\n                              - type: set\n                                key: checkbox_two\n                                value: false\n                          view:\n                            type: container\n                            border:\n                              radius: 8\n                              stroke_width: 1\n                              stroke_color:\n                                default:\n                                  type: hex\n                                  hex: \"#666666\"\n                                  alpha: 1\n                            view_overrides:\n                              background_color:\n                                - when_state_matches:\n                                    key: checkbox_two\n                                    value:\n                                      equals: true\n                                  value:\n                                    default:\n                                      type: hex\n                                      hex: \"#8BC34A\"\n                                      alpha: 0.3\n                            items:\n                              - size:\n                                  width: 100%\n                                  height: auto\n                                position:\n                                  horizontal: center\n                                  vertical: center\n                                margin:\n                                  top: 12\n                                  bottom: 12\n                                  start: 16\n                                  end: 16\n                                view:\n                                  type: label\n                                  text: \"Checkbox Option Two\"\n                                  text_appearance:\n                                    font_size: 16\n                                    color:\n                                      default:\n                                        type: hex\n                                        hex: \"#000000\"\n                                        alpha: 1\n                                    alignment: center\n                                    font_families:\n                                      - sans-serif\n              \n              # Submit Button\n              - identifier: submit_button\n                size:\n                  width: 100%\n                  height: auto\n                margin:\n                  top: 24\n                  bottom: 32\n                  start: 16\n                  end: 16\n                view:\n                  type: label_button\n                  identifier: submit_button\n                  border:\n                    radius: 8\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#4285F4\"\n                      alpha: 1\n                  button_click:\n                    - form_submit\n                    - dismiss\n                  enabled:\n                    - form_validation\n                  label:\n                    type: label\n                    text: \"Submit\"\n                    content_description: \"Submit\"\n                    text_appearance:\n                      font_size: 16\n                      color:\n                        default:\n                          type: hex\n                          hex: \"#FFFFFF\"\n                          alpha: 1\n                      alignment: center\n                      styles:\n                        - bold\n                      font_families:\n                        - sans-serif\n              \n      # Close button\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 48\n          height: 48\n        view:\n          type: image_button\n          identifier: close_button\n          button_click:\n            - dismiss\n          image:\n            type: icon\n            icon: close\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            scale: 0.4\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/toggle-rating-numbers.yaml",
    "content": "version: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: '#ffffff'\n        alpha: 0.2\nview:\n  type: state_controller\n  background_color:\n    default:\n      type: hex\n      hex: '#FFFFFF'\n      alpha: 1\n  view:\n    type: pager_controller\n    identifier: 6b72219d-15ef-4bcf-832b-70850b419687\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          identifier: 4a560c36-28fa-452d-a2e0-f07a9e3d7521\n          type: form_controller\n          validation_mode:\n            type: on_demand\n          submit: submit_event\n          form_enabled:\n          - form_submission\n          view:\n            type: container\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                - identifier: d2432b09-48d6-40a9-8753-72e372319121\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: rating_title_label\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 48\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Rating\n                                      content_description: Rating Section Title\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: '#000000'\n                                            alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: rating_input_section\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 16\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: score_controller\n                                      identifier: score_radio_controller\n                                      required: true\n                                      view:\n                                        type: linear_layout\n                                        direction: horizontal\n                                        padding:\n                                          top: 8\n                                          bottom: 8\n                                        items:\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_1\n                                            content_description: Rating 1\n                                            reporting_value: 1\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 1\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 1\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '1'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 1\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_2\n                                            content_description: Rating 2\n                                            reporting_value: 2\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 2\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 2\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '2'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 2\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_3\n                                            content_description: Rating 3\n                                            reporting_value: 3\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 3\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 3\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '3'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 3\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_4\n                                            content_description: Rating 4\n                                            reporting_value: 4\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 4\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 4\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '4'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 4\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_5\n                                            content_description: Rating 5\n                                            reporting_value: 5\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 5\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 5\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '5'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 5\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_6\n                                            content_description: Rating 6\n                                            reporting_value: 6\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 6\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 6\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '6'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 6\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_7\n                                            content_description: Rating 7\n                                            reporting_value: 7\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 7\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 7\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '7'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 7\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_8\n                                            content_description: Rating 8\n                                            reporting_value: 8\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 8\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 8\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '8'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 8\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 4\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_9\n                                            content_description: Rating 9\n                                            reporting_value: 9\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 9\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 9\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '9'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 9\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_10\n                                            content_description: Rating 10\n                                            reporting_value: 10\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 10\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: container\n                                              border:\n                                                radius: 20\n                                                stroke_width: 1\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#CCCCCC'\n                                                    alpha: 1\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: '#DDDDDD'\n                                                  alpha: 1\n                                              view_overrides:\n                                                background_color:\n                                                - value:\n                                                    default:\n                                                      type: hex\n                                                      hex: '#FFA500'\n                                                      alpha: 1\n                                                  when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 10\n                                              items:\n                                              - position:\n                                                  horizontal: center\n                                                  vertical: center\n                                                size:\n                                                  width: 24\n                                                  height: 24\n                                                view:\n                                                  type: label\n                                                  text: '10'\n                                                  text_appearance:\n                                                    font_size: 14\n                                                    alignment: center\n                                                    styles:\n                                                    - bold\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#333333'\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    text_appearance:\n                                                    - value:\n                                                        font_size: 14\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: '#FFFFFF'\n                                                            alpha: 1\n                                                      when_state_matches:\n                                                        key: selected_score\n                                                        value:\n                                                          at_least: 10\n                                  - identifier: rating_display_label\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 16\n                                      bottom: 32\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: 'Your rating: Not selected'\n                                      view_overrides:\n                                        text:\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 1\n                                          value: 'Your rating: 1 - Poor'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 2\n                                          value: 'Your rating: 2 - Below Average'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 3\n                                          value: 'Your rating: 3 - Below Average'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 4\n                                          value: 'Your rating: 4 - Average'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 5\n                                          value: 'Your rating: 5 - Average'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 6\n                                          value: 'Your rating: 6 - Average'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 7\n                                          value: 'Your rating: 7 - Good'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 8\n                                          value: 'Your rating: 8 - Very Good'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 9\n                                          value: 'Your rating: 9 - Excellent'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 10\n                                          value: 'Your rating: 10 - Perfect'\n                                      text_appearance:\n                                        font_size: 18\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: '#000000'\n                                            alpha: 1\n                                        alignment: center\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: submit_button\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 32\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label_button\n                                      identifier: submit_button\n                                      border:\n                                        radius: 8\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: '#4285F4'\n                                          alpha: 1\n                                      button_click:\n                                      - form_submit\n                                      - dismiss\n                                      enabled:\n                                      - form_validation\n                                      label:\n                                        type: label\n                                        text: Submit Rating\n                                        content_description: Submit Rating\n                                        text_appearance:\n                                          font_size: 16\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: '#FFFFFF'\n                                              alpha: 1\n                                          alignment: center\n                                          styles:\n                                          - bold\n                                          font_families:\n                                          - sans-serif\n                        - position:\n                            horizontal: end\n                            vertical: top\n                          size:\n                            width: 48\n                            height: 48\n                          view:\n                            type: image_button\n                            identifier: dismiss_button\n                            button_click:\n                            - dismiss\n                            image:\n                              scale: 0.4\n                              type: icon\n                              icon: close\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#000000'\n                                  alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/toggle-rating-stars-small.yaml",
    "content": "version: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: top\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        type: hex\n        hex: '#ffffff'\n        alpha: 0.2\nview:\n  type: state_controller\n  background_color:\n    default:\n      type: hex\n      hex: '#FFFFFF'\n      alpha: 1\n  view:\n    type: pager_controller\n    identifier: rating-pager-controller\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          identifier: rating-form-controller\n          type: form_controller\n          validation_mode:\n            type: on_demand\n          submit: submit_event\n          form_enabled:\n          - form_submission\n          view:\n            type: container\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                - identifier: rating-page\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: rating_title_label\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 48\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Star Rating Example\n                                      content_description: Rating Section Title\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: '#000000'\n                                            alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: rating_description\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 16\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: 'Please rate your experience:'\n                                      labels:\n                                        type: \"labels\"\n                                        view_id: score_radio_controller\n                                        view_type: score_controller\n                                      text_appearance:\n                                        font_size: 16\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: '#000000'\n                                            alpha: 1\n                                        alignment: start\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: rating_input_section\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 16\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: score_controller\n                                      identifier: score_radio_controller\n                                      content_description: \"0 means a bad experience and 5 means a great experience\"\n                                      required: true\n                                      view:\n                                        type: linear_layout\n                                        direction: horizontal\n                                        main_axis_alignment: space_evenly\n                                        padding:\n                                          top: 8\n                                          bottom: 8\n                                        items:\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 2\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_1\n                                            content_description: Rating 1 star\n                                            reporting_value: '1'\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 1\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: star\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#808080'\n                                                    alpha: 0.5\n                                              view_overrides:\n                                                icon:\n                                                - when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 1\n                                                  value:\n                                                    icon: star_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#FFD700'\n                                                        alpha: 1.0\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 2\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_2\n                                            content_description: Rating 2 stars\n                                            reporting_value: '2'\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 2\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: star\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#808080'\n                                                    alpha: 0.5\n                                              view_overrides:\n                                                icon:\n                                                - when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 2\n                                                  value:\n                                                    icon: star_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#FFD700'\n                                                        alpha: 1.0\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 2\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_3\n                                            content_description: Rating 3 stars\n                                            reporting_value: '3'\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 3\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: star\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#808080'\n                                                    alpha: 0.5\n                                              view_overrides:\n                                                icon:\n                                                - when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 3\n                                                  value:\n                                                    icon: star_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#FFD700'\n                                                        alpha: 1.0\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          margin:\n                                            end: 2\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_4\n                                            content_description: Rating 4 stars\n                                            reporting_value: '4'\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 4\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: star\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#808080'\n                                                    alpha: 0.5\n                                              view_overrides:\n                                                icon:\n                                                - when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 4\n                                                  value:\n                                                    icon: star_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#FFD700'\n                                                        alpha: 1.0\n                                        - size:\n                                            width: auto\n                                            height: 40\n                                          view:\n                                            type: score_toggle_layout\n                                            identifier: score_toggle_5\n                                            content_description: Rating 5 stars\n                                            reporting_value: '5'\n                                            on_toggle_on:\n                                              state_actions:\n                                              - type: set\n                                                key: selected_score\n                                                value: 5\n                                            on_toggle_off:\n                                              state_actions: []\n                                            view:\n                                              type: icon_view\n                                              icon:\n                                                icon: star\n                                                scale: 1.0\n                                                color:\n                                                  default:\n                                                    type: hex\n                                                    hex: '#808080'\n                                                    alpha: 0.5\n                                              view_overrides:\n                                                icon:\n                                                - when_state_matches:\n                                                    key: selected_score\n                                                    value:\n                                                      at_least: 5\n                                                  value:\n                                                    icon: star_fill\n                                                    scale: 1.0\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: '#FFD700'\n                                                        alpha: 1.0\n                                  - identifier: rating_display_label\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 16\n                                      bottom: 32\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: 'Your rating: Not selected'\n                                      view_overrides:\n                                        text:\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 1\n                                          value: 'Your rating: 1 - Poor'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 2\n                                          value: 'Your rating: 2 - Fair'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 3\n                                          value: 'Your rating: 3 - Good'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 4\n                                          value: 'Your rating: 4 - Very Good'\n                                        - when_state_matches:\n                                            key: selected_score\n                                            value:\n                                              equals: 5\n                                          value: 'Your rating: 5 - Excellent'\n                                      text_appearance:\n                                        font_size: 18\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: '#000000'\n                                            alpha: 1\n                                        alignment: center\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: submit_button\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 32\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label_button\n                                      identifier: submit_button\n                                      border:\n                                        radius: 8\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: '#4285F4'\n                                          alpha: 1\n                                      button_click:\n                                      - form_submit\n                                      - dismiss\n                                      enabled:\n                                      - form_validation\n                                      label:\n                                        type: label\n                                        text: Submit Rating\n                                        content_description: Submit Rating\n                                        text_appearance:\n                                          font_size: 16\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: '#FFFFFF'\n                                              alpha: 1\n                                          alignment: center\n                                          styles:\n                                          - bold\n                                          font_families:\n                                          - sans-serif\n                        - position:\n                            horizontal: end\n                            vertical: top\n                          size:\n                            width: 48\n                            height: 48\n                          view:\n                            type: image_button\n                            identifier: dismiss_button\n                            button_click:\n                            - dismiss\n                            image:\n                              scale: 0.4\n                              type: icon\n                              icon: close\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#000000'\n                                  alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/toggleLayout.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  placement_selectors: []\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: top\n    shade_color:\n      default:\n        type: hex\n        hex: \"#ffffff\"\n        alpha: 0.2\nview:\n  type: state_controller\n  background_color:\n    default:\n      type: hex\n      hex: \"#FFFFFF\"\n      alpha: 1\n  view:\n    type: pager_controller\n    identifier: 6b72219d-15ef-4bcf-832b-70850b419687\n    view:\n      type: linear_layout\n      direction: vertical\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          identifier: 4a560c36-28fa-452d-a2e0-f07a9e3d7521\n          type: form_controller\n          validation_mode:\n            type: on_demand\n          submit: submit_event\n          form_enabled:\n          - form_submission\n          view:\n            type: container\n            items:\n            - position:\n                horizontal: center\n                vertical: center\n              size:\n                width: 100%\n                height: 100%\n              view:\n                type: pager\n                disable_swipe: true\n                items:\n                - identifier: d2432b09-48d6-40a9-8753-72e372319121\n                  type: pager_item\n                  view:\n                    type: container\n                    items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      position:\n                        horizontal: center\n                        vertical: center\n                      ignore_safe_area: false\n                      view:\n                        type: container\n                        items:\n                        - margin:\n                            bottom: 0\n                            top: 0\n                            end: 0\n                            start: 0\n                          position:\n                            horizontal: center\n                            vertical: center\n                          size:\n                            width: 100%\n                            height: 100%\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - identifier: scroll_container\n                              size:\n                                width: 100%\n                                height: 100%\n                              view:\n                                type: scroll_layout\n                                direction: vertical\n                                view:\n                                  type: linear_layout\n                                  direction: vertical\n                                  items:\n                                  - identifier: 8ce7c2d5-23f6-4eda-b840-c76ba483fb68\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 48\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Single Choice\n                                      content_description: Single Choice\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: edf9064c-54d5-4b29-9ac9-0000000\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: \"Favorite Animal\"\n                                          content_description: \"Favorite Animal\"\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: radio_input_controller\n                                          identifier: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                          required: true\n                                          on_error:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"error\"\n                                          on_valid:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"valid\"\n                                          on_edit:\n                                            state_actions:\n                                              - type: set\n                                                key: edf9064c-54d5-4b29-9ac9-ad0997a62696\n                                                value: \"editing\"\n                                          view:\n                                            type: linear_layout\n                                            direction: horizontal\n                                            items:\n                                            - margin:\n                                                end: 8\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: radio_input_toggle_layout\n                                                identifier: cat_toggle_id\n                                                content_description: Cat\n                                                reporting_value: cat_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: animal\n                                                    value: cat\n                                                on_toggle_off:\n                                                  state_actions: []\n                                                view:\n                                                  type: container\n                                                  border:\n                                                    radius: 15\n                                                    stroke_width: 1\n                                                    stroke_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#231ebd\"\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    background_color:\n                                                      - value:\n                                                          default:\n                                                            hex: \"#231ebd\"\n                                                            alpha: .6\n                                                        when_state_matches:\n                                                          key: animal\n                                                          value:\n                                                            equals: cat\n                                                  items:\n                                                    - size:\n                                                        width: 100%\n                                                        height: 100%\n                                                      margin:\n                                                        top: 8\n                                                        bottom: 8\n                                                        start: 8\n                                                        end: 8\n                                                      position:\n                                                        horizontal: center\n                                                        vertical: center\n                                                      view:\n                                                        type: label\n                                                        text: Cat\n                                                        content_description: Cat\n                                                        text_appearance:\n                                                          font_size: 16\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          alignment: center\n                                                          styles: []\n                                                          font_families:\n                                                            - sans-serif\n                                            - margin:\n                                                end: 8\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: radio_input_toggle_layout\n                                                identifier: dog_toggle_id\n                                                content_description: Dog\n                                                reporting_value: dog_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: animal\n                                                    value: dog\n                                                on_toggle_off:\n                                                  state_actions: []\n                                                view:\n                                                  type: container\n                                                  border:\n                                                    radius: 15\n                                                    stroke_width: 1\n                                                    stroke_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#231ebd\"\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    background_color:\n                                                      - value:\n                                                          default:\n                                                            hex: \"#231ebd\"\n                                                            alpha: .6\n                                                        when_state_matches:\n                                                          key: animal\n                                                          value:\n                                                            equals: dog\n                                                  items:\n                                                    - size:\n                                                        width: 100%\n                                                        height: 100%\n                                                      margin:\n                                                        top: 8\n                                                        bottom: 8\n                                                        start: 8\n                                                        end: 8\n                                                      position:\n                                                        horizontal: center\n                                                        vertical: center\n                                                      view:\n                                                        type: label\n                                                        text: Dog\n                                                        content_description: Dog\n                                                        text_appearance:\n                                                          font_size: 16\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          alignment: center\n                                                          styles: []\n                                                          font_families:\n                                                            - sans-serif\n                                            - margin:\n                                                end: 8\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: radio_input_toggle_layout\n                                                identifier: dragon_toggle_id\n                                                content_description: Dragon\n                                                reporting_value: dragon_toggle\n                                                view_overrides:\n                                                  background_color:\n                                                    - value:\n                                                        default:\n                                                          hex: \"#ffbbbb\"\n                                                          alpha: 1\n                                                      when_state_matches:\n                                                          key: animal\n                                                          value:\n                                                            equals: Dragon\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: animal\n                                                    value: dragon\n                                                on_toggle_off:\n                                                  state_actions: []\n                                                view:\n                                                  type: container\n                                                  border:\n                                                    radius: 15\n                                                    stroke_width: 1\n                                                    stroke_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#231ebd\"\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    background_color:\n                                                      - value:\n                                                          default:\n                                                            hex: \"#231ebd\"\n                                                            alpha: .6\n                                                        when_state_matches:\n                                                          key: animal\n                                                          value:\n                                                            equals: dragon\n                                                  items:\n                                                    - size:\n                                                        width: 100%\n                                                        height: 100%\n                                                      margin:\n                                                        top: 8\n                                                        bottom: 8\n                                                        start: 8\n                                                        end: 8\n                                                      position:\n                                                        horizontal: center\n                                                        vertical: center\n                                                      view:\n                                                        type: label\n                                                        text: Dragon\n                                                        content_description: Dragon\n                                                        text_appearance:\n                                                          font_size: 16\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          alignment: center\n                                                          styles: []\n                                                          font_families:\n                                                            - sans-serif\n                                  - identifier: 7beb5f48-a5fe-4c56-ae31-000000\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: Favorite Color\n                                          content_description: Favorite Color\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: radio_input_controller\n                                          identifier: 7beb5f48-a5fe-4c56-ae31-994263ffcdb2\n                                          required: false\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - margin:\n                                                end: 8\n                                                start: 8\n                                                top: 8\n                                                bottom: 8\n                                              size:\n                                                width: 100%\n                                                height: 100%\n                                              view:\n                                                type: radio_input_toggle_layout\n                                                identifier: blue_toggle_id\n                                                content_description: Blue\n                                                reporting_value: blue_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: color\n                                                    value: blue\n                                                on_toggle_off:\n                                                  state_actions: []\n                                                view:\n                                                  type: container\n                                                  border:\n                                                    radius: 8\n                                                    stroke_width: 4\n                                                    stroke_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#231ebd\"\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    background_color:\n                                                      - value:\n                                                          default:\n                                                            hex: \"#231ebd\"\n                                                            alpha: .6\n                                                        when_state_matches:\n                                                          key: color\n                                                          value:\n                                                            equals: blue\n                                                  items:\n                                                    - size:\n                                                        width: 100%\n                                                        height: 100%\n                                                      margin:\n                                                        top: 8\n                                                        bottom: 8\n                                                        start: 8\n                                                        end: 8\n                                                      position:\n                                                        horizontal: center\n                                                        vertical: center\n                                                      view:\n                                                        type: label\n                                                        text: Blue\n                                                        view_overrides:\n                                                            icon_start:\n                                                                - value:\n                                                                    space: 8\n                                                                    type: floating\n                                                                    icon:\n                                                                      type: icon\n                                                                      icon: checkmark\n                                                                      color:\n                                                                          default:\n                                                                              hex: \"#000000\"\n                                                                              alpha: 1.0\n                                                                      scale: 1\n                                                                  when_state_matches:\n                                                                    key: color\n                                                                    value:\n                                                                      equals: blue\n                                                        content_description: Blue\n                                                        text_appearance:\n                                                          font_size: 16\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          alignment: center\n                                                          styles: []\n                                                          font_families:\n                                                            - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                end: 8\n                                                start: 8\n                                                bottom: 8\n                                              view:\n                                                type: radio_input_toggle_layout\n                                                identifier: red_toggle_id\n                                                content_description: Red\n                                                reporting_value: red_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: color\n                                                    value: red\n                                                on_toggle_off:\n                                                  state_actions: []\n                                                view:\n                                                  type: container\n                                                  border:\n                                                    radius: 8\n                                                    stroke_width: 4\n                                                    stroke_color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#bd1e1e\"\n                                                        alpha: 1\n                                                  view_overrides:\n                                                    background_color:\n                                                      - value:\n                                                          default:\n                                                            hex: \"#bd1e1e\"\n                                                            alpha: .6\n                                                        when_state_matches:\n                                                          key: color\n                                                          value:\n                                                            equals: red\n                                                  items:\n                                                    - size:\n                                                        width: 100%\n                                                        height: 100%\n                                                      margin:\n                                                        top: 8\n                                                        bottom: 8\n                                                      position:\n                                                        horizontal: center\n                                                        vertical: center\n                                                      view:\n                                                        type: label\n                                                        text: Red\n                                                        view_overrides:\n                                                            icon_start:\n                                                                - value:\n                                                                    space: 8\n                                                                    type: floating\n                                                                    icon:\n                                                                      type: icon\n                                                                      icon: checkmark\n                                                                      color:\n                                                                          default:\n                                                                              hex: \"#000000\"\n                                                                              alpha: 1.0\n                                                                      scale: 1\n                                                                  when_state_matches:\n                                                                    key: color\n                                                                    value:\n                                                                      equals: red\n                                                        content_description: Red\n                                                        text_appearance:\n                                                          font_size: 16\n                                                          color:\n                                                            default:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          alignment: center\n                                                          styles: []\n                                                          font_families:\n                                                            - sans-serif\n                                  - identifier: 413f50b9-0b78-4a42-9ca7-cf64e7125253\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Multi Choice\n                                      content_description: Multi Choice\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: 4e3f3d9e-a4a2-4a01-a7e4-000000000\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: \"What are your favorite Guy Fieri shows? (select at least 2)\"\n                                          content_description: \"What are your favorite Guy Fieri shows? (select at least 2)\"\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: checkbox_controller\n                                          identifier: 4e3f3d9e-a4a2-4a01-a7e4-76654455\n                                          required: true\n                                          view:\n                                            type: linear_layout\n                                            direction: vertical\n                                            items:\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              margin:\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                type: checkbox_toggle_layout\n                                                identifier: grocery_games_toggle_id\n                                                content_description: Guy's\n                                                  Grocery Games\n                                                reporting_value: grocery_games_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                    - type: set\n                                                      key: grocery\n                                                      value: true\n                                                on_toggle_off:\n                                                  state_actions:\n                                                    - type: set\n                                                      key: grocery\n                                                      value: false\n                                                view:\n                                                  type: label\n                                                  text: Guy's Grocery Games\n                                                  content_description: Guy's\n                                                    Grocery Games\n                                                  view_overrides:\n                                                    icon_start:\n                                                    - when_state_matches:\n                                                        key: grocery\n                                                        value:\n                                                          equals: true\n                                                      value:\n                                                        space: 16\n                                                        type: floating\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          color:\n                                                              default:\n                                                                  hex: \"#0bb31b\"\n                                                                  alpha: 1.0\n                                                          scale: .8\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              margin:\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                type: checkbox_toggle_layout\n                                                identifier: big_bite_toggle_id\n                                                content_description: Guy's\n                                                  Big Bite\n                                                reporting_value: big_bite_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: guy\n                                                    value: true\n                                                on_toggle_off:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: guy\n                                                    value: false\n                                                view:\n                                                  type: label\n                                                  text: Guy's Big Bite\n                                                  content_description: Guy's\n                                                    Big Bite\n                                                  view_overrides:\n                                                    icon_start:\n                                                    - when_state_matches:\n                                                        key: guy\n                                                        value:\n                                                          equals: true\n                                                      value:\n                                                        space: 16\n                                                        type: floating\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          color:\n                                                              default:\n                                                                  hex: \"#0bb31b\"\n                                                                  alpha: 1.0\n                                                          scale: .8\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: auto\n                                              margin:\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                type: checkbox_toggle_layout\n                                                identifier: family_road_trip_toggle_id\n                                                content_description: Guy's\n                                                  Family Road Trip\n                                                reporting_value: family_road_trip_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: family_road_trip\n                                                    value: true\n                                                on_toggle_off:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: family_road_trip\n                                                    value: false\n                                                view:\n                                                  type: label\n                                                  text: Guy's Family Road\n                                                    Trip\n                                                  content_description: Guy's\n                                                    Family Road Trip\n                                                  view_overrides:\n                                                    icon_start:\n                                                    - when_state_matches:\n                                                        key: family_road_trip\n                                                        value:\n                                                          equals: true\n                                                      value:\n                                                        space: 16\n                                                        type: floating\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          color:\n                                                              default:\n                                                                  hex: \"#0bb31b\"\n                                                                  alpha: 1.0\n                                                          scale: .8\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                            - size:\n                                                width: 100%\n                                                height: 100%\n                                              margin:\n                                                top: 8\n                                                bottom: 8\n                                              view:\n                                                type: checkbox_toggle_layout\n                                                identifier: minute_to_win_it_toggle_id\n                                                content_description: Minute\n                                                  to Win It\n                                                reporting_value: minute_to_win_it_toggle\n                                                on_toggle_on:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: minute_to_win_it\n                                                    value: true\n                                                on_toggle_off:\n                                                  state_actions:\n                                                  - type: set\n                                                    key: minute_to_win_it\n                                                    value: false\n                                                view:\n                                                  type: label\n                                                  text: Minute to Win It\n                                                  content_description: Minute\n                                                    to Win It\n                                                  view_overrides:\n                                                    icon_start:\n                                                    - when_state_matches:\n                                                        key: minute_to_win_it\n                                                        value:\n                                                          equals: true\n                                                      value:\n                                                        space: 16\n                                                        type: floating\n                                                        icon:\n                                                          type: icon\n                                                          icon: checkmark\n                                                          color:\n                                                              default:\n                                                                  hex: \"#0bb31b\"\n                                                                  alpha: 1.0\n                                                          scale: .8\n                                                  text_appearance:\n                                                    font_size: 16\n                                                    color:\n                                                      default:\n                                                        type: hex\n                                                        hex: \"#000000\"\n                                                        alpha: 1\n                                                    alignment: start\n                                                    styles: []\n                                                    font_families:\n                                                    - sans-serif\n                                  - identifier: 413f50b9-0b78-4a42-9ca7-cf64e7125253\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: label\n                                      text: Standalone\n                                      content_description: Standalone\n                                      text_appearance:\n                                        font_size: 20\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        alignment: start\n                                        styles:\n                                        - bold\n                                        font_families:\n                                        - sans-serif\n                                  - identifier: 4e3f3d9e-a4a2-4a01-a7e4-09925d5bb30F\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    view:\n                                      type: linear_layout\n                                      direction: vertical\n                                      items:\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: label\n                                          text: \"What are your hobbies?\"\n                                          content_description: \"What are your hobbies?\"\n                                          icon_start:\n                                            space: 8\n                                            type: floating\n                                            icon:\n                                              type: icon\n                                              icon: asterisk\n                                              color:\n                                                  default:\n                                                      hex: \"#000000\"\n                                                      alpha: 1.0\n                                              scale: .75\n                                          view_overrides:\n                                            icon_start:\n                                            - when_state_matches:\n                                                scope:\n                                                - 4e3f3d9e-a4a2-4a01-a7e4-09925d5aa30f\n                                                value:\n                                                  equals: \"error\"\n                                              value:\n                                                space: 8\n                                                type: floating\n                                                icon:\n                                                  type: icon\n                                                  icon: asterisk_circle_fill\n                                                  color:\n                                                      default:\n                                                          hex: \"#ff0000\"\n                                                          alpha: 1.0\n                                                  scale: .75\n                                          text_appearance:\n                                            font_size: 20\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                            alignment: start\n                                            styles: []\n                                            font_families:\n                                            - sans-serif\n                                      - margin:\n                                          top: 0\n                                          bottom: 8\n                                        size:\n                                          width: 100%\n                                          height: auto\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - margin:\n                                              end: 8\n                                              bottom: 8\n                                              top: 8\n                                              start: 8\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                            view:\n                                              type: basic_toggle_layout\n                                              identifier: reading_toggle_id\n                                              content_description: Reading\n                                              reporting_value: reading_toggle\n                                              on_toggle_on:\n                                                state_actions:\n                                                - type: set\n                                                  key: reading\n                                                  value: true\n                                              on_toggle_off:\n                                                state_actions:\n                                                - type: set\n                                                  key: reading\n                                                  value: false\n                                              view:\n                                                type: container\n                                                border:\n                                                  radius: 2\n                                                  stroke_width: 4\n                                                  stroke_color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#bd1e90\"\n                                                      alpha: 1\n                                                view_overrides:\n                                                  background_color:\n                                                    - value:\n                                                        default:\n                                                          hex: \"#bd1e90\"\n                                                          alpha: .6\n                                                      when_state_matches:\n                                                        key: reading\n                                                        value:\n                                                          equals: true\n                                                items:\n                                                  - size:\n                                                      width: 100%\n                                                      height: 100%\n                                                    margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                      start: 8\n                                                      end: 8\n                                                    position:\n                                                      horizontal: center\n                                                      vertical: center\n                                                    view:\n                                                      type: label\n                                                      text: Reading\n                                                      content_description: Reading\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: auto\n                                            margin:\n                                              end: 8\n                                              bottom: 8\n                                              top: 8\n                                              start: 8\n                                            view:\n                                              type: basic_toggle_layout\n                                              identifier: travelling_toggle_id\n                                              content_description: Travelling\n                                              reporting_value: travelling_toggle\n                                              on_toggle_on:\n                                                state_actions:\n                                                - type: set\n                                                  key: travelling\n                                                  value: true\n                                              on_toggle_off:\n                                                state_actions:\n                                                - type: set\n                                                  key: travelling\n                                                  value: false\n                                              view:\n                                                type: container\n                                                border:\n                                                  radius: 2\n                                                  stroke_width: 4\n                                                  stroke_color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#bd1e90\"\n                                                      alpha: 1\n                                                view_overrides:\n                                                  background_color:\n                                                    - value:\n                                                        default:\n                                                          hex: \"#bd1e90\"\n                                                          alpha: .6\n                                                      when_state_matches:\n                                                        key: travelling\n                                                        value:\n                                                          equals: true\n                                                items:\n                                                  - size:\n                                                      width: 100%\n                                                      height: 100%\n                                                    margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                      start: 8\n                                                      end: 8\n                                                    position:\n                                                      horizontal: center\n                                                      vertical: center\n                                                    view:\n                                                      type: label\n                                                      text: Travelling\n                                                      content_description: Travelling\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: auto\n                                            margin:\n                                              end: 8\n                                              bottom: 8\n                                              top: 8\n                                              start: 8\n                                            view:\n                                              type: basic_toggle_layout\n                                              identifier: music_toggle_id\n                                              content_description: Music\n                                              reporting_value: music_toggle\n                                              on_toggle_on:\n                                                state_actions:\n                                                - type: set\n                                                  key: music\n                                                  value: true\n                                              on_toggle_off:\n                                                state_actions:\n                                                - type: set\n                                                  key: music\n                                                  value: false\n                                              view:\n                                                type: container\n                                                border:\n                                                  radius: 2\n                                                  stroke_width: 4\n                                                  stroke_color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#bd1e90\"\n                                                      alpha: 1\n                                                view_overrides:\n                                                  background_color:\n                                                    - value:\n                                                        default:\n                                                          hex: \"#bd1e90\"\n                                                          alpha: .6\n                                                      when_state_matches:\n                                                        key: music\n                                                        value:\n                                                          equals: true\n                                                items:\n                                                  - size:\n                                                      width: 100%\n                                                      height: 100%\n                                                    margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                      start: 8\n                                                      end: 8\n                                                    position:\n                                                      horizontal: center\n                                                      vertical: center\n                                                    view:\n                                                      type: label\n                                                      text: Music\n                                                      content_description: Music\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: auto\n                                            margin:\n                                              end: 8\n                                              bottom: 8\n                                              top: 8\n                                              start: 8\n                                            view:\n                                              type: basic_toggle_layout\n                                              identifier: news_toggle_id\n                                              content_description: News\n                                              reporting_value: news_toggle\n                                              on_toggle_on:\n                                                state_actions:\n                                                - type: set\n                                                  key: news\n                                                  value: true\n                                              on_toggle_off:\n                                                state_actions:\n                                                - type: set\n                                                  key: news\n                                                  value: false\n                                              view:\n                                                type: container\n                                                border:\n                                                  radius: 2\n                                                  stroke_width: 4\n                                                  stroke_color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#bd1e90\"\n                                                      alpha: 1\n                                                view_overrides:\n                                                  background_color:\n                                                    - value:\n                                                        default:\n                                                          hex: \"#bd1e90\"\n                                                          alpha: .6\n                                                      when_state_matches:\n                                                        key: news\n                                                        value:\n                                                          equals: true\n                                                items:\n                                                  - size:\n                                                      width: 100%\n                                                      height: 100%\n                                                    margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                      start: 8\n                                                      end: 8\n                                                    position:\n                                                      horizontal: center\n                                                      vertical: center\n                                                    view:\n                                                      type: label\n                                                      text: News\n                                                      content_description: News\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                          - size:\n                                              width: 100%\n                                              height: auto\n                                            margin:\n                                              end: 8\n                                              bottom: 8\n                                              top: 8\n                                              start: 8\n                                            view:\n                                              type: basic_toggle_layout\n                                              identifier: sport_toggle_id\n                                              content_description: Sport\n                                              reporting_value: sport_toggle\n                                              on_toggle_on:\n                                                state_actions:\n                                                - type: set\n                                                  key: sport\n                                                  value: true\n                                              on_toggle_off:\n                                                state_actions:\n                                                - type: set\n                                                  key: sport\n                                                  value: false\n                                              view:\n                                                type: container\n                                                border:\n                                                  radius: 2\n                                                  stroke_width: 4\n                                                  stroke_color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#bd1e90\"\n                                                      alpha: 1\n                                                view_overrides:\n                                                  background_color:\n                                                    - value:\n                                                        default:\n                                                          hex: \"#bd1e90\"\n                                                          alpha: .6\n                                                      when_state_matches:\n                                                        key: sport\n                                                        value:\n                                                          equals: true\n                                                items:\n                                                  - size:\n                                                      width: 100%\n                                                      height: 100%\n                                                    margin:\n                                                      top: 8\n                                                      bottom: 8\n                                                      start: 8\n                                                      end: 8\n                                                    position:\n                                                      horizontal: center\n                                                      vertical: center\n                                                    view:\n                                                      type: label\n                                                      text: Sport\n                                                      content_description: Sport\n                                                      text_appearance:\n                                                        font_size: 16\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#000000\"\n                                                            alpha: 1\n                                                        alignment: center\n                                                        styles: []\n                                                        font_families:\n                                                          - sans-serif\n                                  - identifier: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                    margin:\n                                      top: 8\n                                      bottom: 8\n                                      start: 16\n                                      end: 16\n                                    size:\n                                      width: 100%\n                                      height: auto\n                                    view:\n                                      type: label_button\n                                      identifier: submit_feedback--Submit\n                                      reporting_metadata:\n                                        trigger_link_id: f1047936-d73a-403f-9ac8-68bf6bacb802\n                                      label:\n                                        view_overrides:\n                                            icon_start:\n                                              - value:\n                                                  type: \"floating\"\n                                                  space: 8\n                                                  icon:\n                                                    type: icon\n                                                    icon: progress_spinner\n                                                    color:\n                                                        default:\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1.0\n                                                    scale: 1\n                                                when_state_matches:\n                                                  scope:\n                                                      - $forms\n                                                      - current\n                                                      - status\n                                                      - type\n                                                  value:\n                                                    equals: \"validating\"\n                                            text:\n                                              - value: \"Processing ...\"\n                                                when_state_matches:\n                                                  scope:\n                                                      - $forms\n                                                      - current\n                                                      - status\n                                                      - type\n                                                  value:\n                                                    equals: \"validating\"\n                                        type: label\n                                        text: Submit\n                                        content_description: Submit\n                                        text_appearance:\n                                          font_size: 16\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                            selectors:\n                                            - platform: ios\n                                              dark_mode: false\n                                              color:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          alignment: center\n                                          styles: []\n                                          font_families:\n                                          - sans-serif\n                                      actions: {}\n                                      enabled:\n                                      - form_validation\n                                      button_click:\n                                      - form_submit\n                                      - dismiss\n                                      background_color:\n                                        default:\n                                          type: hex\n                                          hex: \"#63AFF1\"\n                                          alpha: 1\n                                      border:\n                                        radius: 0\n                                        stroke_width: 16\n                                        stroke_color:\n                                          default:\n                                            type: hex\n                                            hex: \"#63AFF1\"\n                                            alpha: 1\n                                      event_handlers:\n                                      - type: tap\n                                        state_actions:\n                                        - type: set\n                                          key: submitted\n                                          value: true\n                                  \n            - position:\n                horizontal: end\n                vertical: top\n              size:\n                width: 48\n                height: 48\n              view:\n                type: image_button\n                image:\n                  scale: 0.4\n                  type: icon\n                  icon: close\n                  color:\n                    default:\n                      type: hex\n                      hex: \"#000000\"\n                      alpha: 1\n                identifier: dismiss_button\n                button_click:\n                - dismiss\n\n\n\n\n\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/tour-example.yml",
    "content": "---\npresentation:\n  default_placement:\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      max_height: 100%\n      max_width: 100%\n      min_height: 100%\n      min_width: 100%\n      width: 100%\n  disable_back_button: false\n  dismiss_on_touch_outside: false\n  type: modal\nversion: 1\nview:\n  identifier: bafbfd98-3bd1-479a-a667-e3de75a018bb\n  type: pager_controller\n  view:\n    items:\n      - ignore_safe_area: false\n        position:\n          horizontal: center\n          vertical: center\n        size:\n          height: 100%\n          width: 100%\n        view:\n          disable_swipe: false\n          items:\n            - identifier: 8f219146-8624-49b4-9de9-a05dd939e7d9\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            top: 25\n                          size:\n                            height: auto\n                            width: 100%\n                          view:\n                            media_fit: center_crop\n                            media_type: image\n                            type: media\n                            url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                        - margin:\n                            bottom: 0\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 80\n                            width: 90%\n                          view:\n                            text: Welcome to the app\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#020202\"\n                                  type: hex\n                              font_families:\n                                - sans-serif\n                              font_size: 24\n                              styles:\n                                - bold\n                            type: label\n                        - margin:\n                            bottom: 10\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 100%\n                            width: 90%\n                          view:\n                            direction: vertical\n                            type: scroll_layout\n                            view:\n                              text: This is the app\n                              text_appearance:\n                                alignment: center\n                                color:\n                                  default:\n                                    alpha: 1\n                                    hex: \"#222222\"\n                                    type: hex\n                                font_families:\n                                  - sans-serif\n                                font_size: 18\n                                styles: []\n                              type: label\n                        - size:\n                            height: 0\n                            width: 0\n                          view:\n                            text: ''\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#333333\"\n                                  type: hex\n                              font_families:\n                                - serif\n                              font_size: 14\n                              styles:\n                                - underlined\n                            type: label\n                        - margin:\n                            bottom: 0\n                            top: 10\n                          size:\n                            height: 100\n                            width: 100%\n                          view:\n                            direction: horizontal\n                            items:\n                              - margin:\n                                  bottom: 30\n                                  end: 20\n                                  start: 20\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 85%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#222222\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click:\n                                    - pager_next\n                                  enabled:\n                                    - pager_next\n                                  identifier: a9b96212-98ab-4695-ad90-1e37cbacc3e3\n                                  label:\n                                    text: Coool\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#FFFFFF\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 24\n                                    type: label\n                                  type: label_button\n                            type: linear_layout\n                      type: linear_layout\n                type: container\n            - identifier: b7a62f09-218b-4fc5-8672-fedd0eb68223\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            top: 25\n                          size:\n                            height: auto\n                            width: 100%\n                          view:\n                            media_fit: center_crop\n                            media_type: image\n                            type: media\n                            url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                        - margin:\n                            bottom: 0\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 80\n                            width: 90%\n                          view:\n                            text: 'Highlight of feature #1'\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#020202\"\n                                  type: hex\n                              font_families:\n                                - sans-serif\n                              font_size: 24\n                              styles:\n                                - bold\n                            type: label\n                        - margin:\n                            bottom: 10\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 100%\n                            width: 90%\n                          view:\n                            direction: vertical\n                            type: scroll_layout\n                            view:\n                              text: 'Description about feature #1'\n                              text_appearance:\n                                alignment: center\n                                color:\n                                  default:\n                                    alpha: 1\n                                    hex: \"#222222\"\n                                    type: hex\n                                font_families:\n                                  - sans-serif\n                                font_size: 18\n                                styles: []\n                              type: label\n                        - size:\n                            height: 0\n                            width: 0\n                          view:\n                            text: ''\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#333333\"\n                                  type: hex\n                              font_families:\n                                - serif\n                              font_size: 14\n                              styles:\n                                - underlined\n                            type: label\n                      type: linear_layout\n                type: container\n            - identifier: '0235975e-a30c-4e0b-91ef-284ace180968'\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            top: 25\n                          size:\n                            height: auto\n                            width: 100%\n                          view:\n                            media_fit: center_crop\n                            media_type: image\n                            type: media\n                            url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                        - margin:\n                            bottom: 0\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 80\n                            width: 90%\n                          view:\n                            text: 'Highlight of feature #2'\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#020202\"\n                                  type: hex\n                              font_families:\n                                - sans-serif\n                              font_size: 24\n                              styles:\n                                - bold\n                            type: label\n                        - margin:\n                            bottom: 10\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 100%\n                            width: 90%\n                          view:\n                            direction: vertical\n                            type: scroll_layout\n                            view:\n                              text: 'Description about feature #2'\n                              text_appearance:\n                                alignment: center\n                                color:\n                                  default:\n                                    alpha: 1\n                                    hex: \"#222222\"\n                                    type: hex\n                                font_families:\n                                  - sans-serif\n                                font_size: 18\n                                styles: []\n                              type: label\n                        - size:\n                            height: 0\n                            width: 0\n                          view:\n                            text: ''\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#333333\"\n                                  type: hex\n                              font_families:\n                                - serif\n                              font_size: 14\n                              styles:\n                                - underlined\n                            type: label\n                      type: linear_layout\n                type: container\n            - identifier: d780b32a-434a-4f77-87e7-15b46af39373\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            bottom: 0\n                            end: 20\n                            start: 20\n                            top: 0\n                          size:\n                            height: 100%\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - margin:\n                                  bottom: 0\n                                  end: 0\n                                  start: 0\n                                  top: 25\n                                size:\n                                  height: 80\n                                  width: 90%\n                                view:\n                                  text: Do you mind if we stay in touch?\n                                  text_appearance:\n                                    alignment: center\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#020202\"\n                                        type: hex\n                                    font_families:\n                                      - sans-serif\n                                    font_size: 24\n                                    styles:\n                                      - bold\n                                  type: label\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 100%\n                                  width: 90%\n                                view:\n                                  text: 'Enable push notifications so you can:'\n                                  text_appearance:\n                                    alignment: start\n                                    color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#222222\"\n                                        type: hex\n                                    font_families:\n                                      - sans-serif\n                                    font_size: 18\n                                    styles: []\n                                  type: label\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: horizontal\n                                  items:\n                                    - margin:\n                                        bottom: 0\n                                        end: 20\n                                        start: 20\n                                        top: 0\n                                      size:\n                                        height: 60\n                                        width: 60\n                                      view:\n                                        media_fit: center_inside\n                                        media_type: image\n                                        type: media\n                                        url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                    - margin:\n                                        bottom: 0\n                                        end: 0\n                                        start: 0\n                                        top: 0\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        text: 'Value proposition #1'\n                                        text_appearance:\n                                          alignment: start\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#222222\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 18\n                                          styles: []\n                                        type: label\n                                  type: linear_layout\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: horizontal\n                                  items:\n                                    - margin:\n                                        bottom: 0\n                                        end: 20\n                                        start: 20\n                                        top: 0\n                                      size:\n                                        height: 60\n                                        width: 60\n                                      view:\n                                        media_fit: center_inside\n                                        media_type: image\n                                        type: media\n                                        url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                    - margin:\n                                        bottom: 0\n                                        end: 0\n                                        start: 0\n                                        top: 0\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        text: 'Value proposition #2'\n                                        text_appearance:\n                                          alignment: start\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#222222\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 18\n                                          styles: []\n                                        type: label\n                                  type: linear_layout\n                              - margin:\n                                  bottom: 10\n                                  end: 0\n                                  start: 0\n                                  top: 10\n                                size:\n                                  height: 100%\n                                  width: 100%\n                                view:\n                                  direction: horizontal\n                                  items:\n                                    - margin:\n                                        bottom: 0\n                                        end: 20\n                                        start: 20\n                                        top: 0\n                                      size:\n                                        height: 60\n                                        width: 60\n                                      view:\n                                        media_fit: center_inside\n                                        media_type: image\n                                        type: media\n                                        url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                                    - margin:\n                                        bottom: 0\n                                        end: 0\n                                        start: 0\n                                        top: 0\n                                      size:\n                                        height: 100%\n                                        width: 100%\n                                      view:\n                                        text: 'Value proposition #3'\n                                        text_appearance:\n                                          alignment: start\n                                          color:\n                                            default:\n                                              alpha: 1\n                                              hex: \"#222222\"\n                                              type: hex\n                                          font_families:\n                                            - sans-serif\n                                          font_size: 18\n                                          styles: []\n                                        type: label\n                                  type: linear_layout\n                            type: linear_layout\n                        - margin:\n                            bottom: 0\n                            top: 10\n                          size:\n                            height: 100\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - margin:\n                                  bottom: 4\n                                  end: 2\n                                  start: 2\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 85%\n                                view:\n                                  actions:\n                                    enable_feature: user_notifications\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#e2e2e2\"\n                                      type: hex\n                                  border:\n                                    radius: 3\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#e2e2e2\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click: []\n                                  enabled: []\n                                  identifier: 0b0c6e37-dfaf-4021-b345-79fe46d48865\n                                  label:\n                                    text: Yes please\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#666666\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 18\n                                    type: label\n                                  type: label_button\n                              - margin:\n                                  bottom: 30\n                                  end: 2\n                                  start: 2\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 85%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#ffffff\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#ffffff\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click: []\n                                  enabled: []\n                                  identifier: ea7010f3-a471-4335-86a9-a61a7066de12\n                                  label:\n                                    text: Maybe later\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#666666\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 18\n                                    type: label\n                                  type: label_button\n                            type: linear_layout\n                      type: linear_layout\n                type: container\n            - identifier: 5a5b6768-e9f2-4453-aa78-5893b75a9948\n              type: pager_item\n              view:\n                background_color:\n                  default:\n                    alpha: 1\n                    hex: \"#FFFFFF\"\n                    type: hex\n                items:\n                  - margin:\n                      bottom: 28\n                      end: 0\n                      start: 0\n                      top: 0\n                    position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      direction: vertical\n                      items:\n                        - margin:\n                            bottom: 0\n                            end: 0\n                            start: 0\n                            top: 25\n                          size:\n                            height: 80\n                            width: 90%\n                          view:\n                            text: Thank you\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#020202\"\n                                  type: hex\n                              font_families:\n                                - sans-serif\n                              font_size: 24\n                              styles:\n                                - bold\n                            type: label\n                        - margin:\n                            top: 10\n                          size:\n                            height: auto\n                            width: 100%\n                          view:\n                            media_fit: center_crop\n                            media_type: image\n                            type: media\n                            url: https://unroll-images-production.s3.amazonaws.com/projects/2487/1565801064410-banner-500x500-no-words.jpg\n                        - margin:\n                            bottom: 10\n                            end: 0\n                            start: 0\n                            top: 10\n                          size:\n                            height: 100%\n                            width: 90%\n                          view:\n                            direction: vertical\n                            type: scroll_layout\n                            view:\n                              text: \"Weâ\\x80\\x99re so glad youâ\\x80\\x99re here. Customize\n                        your experience by setting your notification preferences today!\"\n                              text_appearance:\n                                alignment: center\n                                color:\n                                  default:\n                                    alpha: 1\n                                    hex: \"#222222\"\n                                    type: hex\n                                font_families:\n                                  - sans-serif\n                                font_size: 18\n                                styles: []\n                              type: label\n                        - size:\n                            height: 0\n                            width: 0\n                          view:\n                            text: ''\n                            text_appearance:\n                              alignment: center\n                              color:\n                                default:\n                                  alpha: 1\n                                  hex: \"#333333\"\n                                  type: hex\n                              font_families:\n                                - serif\n                              font_size: 14\n                              styles:\n                                - underlined\n                            type: label\n                        - margin:\n                            bottom: 0\n                            top: 10\n                          size:\n                            height: 100\n                            width: 100%\n                          view:\n                            direction: vertical\n                            items:\n                              - margin:\n                                  bottom: 4\n                                  end: 2\n                                  start: 2\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 85%\n                                view:\n                                  actions:\n                                    deep_link_action: ''\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#e2e2e2\"\n                                      type: hex\n                                  border:\n                                    radius: 4\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#e2e2e2\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click: []\n                                  enabled: []\n                                  identifier: 6205af07-1c6b-4668-9f2e-01418006d09a\n                                  label:\n                                    text: Set my preferences\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#666666\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 18\n                                    type: label\n                                  type: label_button\n                              - margin:\n                                  bottom: 30\n                                  end: 2\n                                  start: 2\n                                  top: 4\n                                size:\n                                  height: 40\n                                  width: 85%\n                                view:\n                                  actions: {}\n                                  background_color:\n                                    default:\n                                      alpha: 1\n                                      hex: \"#ffffff\"\n                                      type: hex\n                                  border:\n                                    radius: 0\n                                    stroke_color:\n                                      default:\n                                        alpha: 1\n                                        hex: \"#ffffff\"\n                                        type: hex\n                                    stroke_width: 1\n                                  button_click: []\n                                  enabled: []\n                                  identifier: c19e0cba-bb93-4fd5-97cf-85800bfdaf05\n                                  label:\n                                    text: Maybe later\n                                    text_appearance:\n                                      alignment: center\n                                      color:\n                                        default:\n                                          alpha: 1\n                                          hex: \"#666666\"\n                                          type: hex\n                                      font_families:\n                                        - sans-serif\n                                      font_size: 18\n                                    type: label\n                                  type: label_button\n                            type: linear_layout\n                      type: linear_layout\n                type: container\n          type: pager\n      - margin:\n          bottom: 0\n          end: 10\n          start: 0\n          top: 10\n        position:\n          horizontal: end\n          vertical: top\n        size:\n          height: 48\n          width: 48\n        view:\n          button_click:\n            - dismiss\n          identifier: dismiss_button\n          image:\n            color:\n              default:\n                alpha: 1\n                hex: \"#000000\"\n                type: hex\n            icon: close\n            scale: 0.4\n            type: icon\n          type: image_button\n      - margin:\n          bottom: 4\n          end: 0\n          start: 0\n          top: 0\n        position:\n          horizontal: center\n          vertical: bottom\n        size:\n          height: 20\n          width: 100%\n        view:\n          bindings:\n            selected:\n              shapes:\n                - aspect_ratio: 1\n                  color:\n                    default:\n                      alpha: 1\n                      hex: \"#AAAAAA\"\n                      type: hex\n                  scale: 1\n                  type: ellipse\n            unselected:\n              shapes:\n                - aspect_ratio: 1\n                  color:\n                    default:\n                      alpha: 1\n                      hex: \"#CCCCCC\"\n                      type: hex\n                  scale: 1\n                  type: ellipse\n          spacing: 4\n          type: pager_indicator\n    type: container"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/tour-no-safe-areas.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  placement_selectors:\n    - window_size: small\n      placement:\n        size:\n          width: 100%\n          height: 100%\n        position:\n          horizontal: center\n          vertical: center\n        device:\n          lock_orientation: portrait\n        shade_color:\n          default:\n            hex: \"#FF0000\"\n            alpha: 0.6\n    - window_size: medium\n      placement:\n        size:\n          width: 80%\n          height: 450\n        position:\n          horizontal: center\n          vertical: center\n        device:\n          lock_orientation: portrait\n        shade_color:\n          default:\n            hex: \"#00FF00\"\n            alpha: 0.6\n    - window_size: large\n      placement:\n        size:\n          width: 60%\n          height: 450\n        position:\n          horizontal: center\n          vertical: center\n        device:\n          lock_orientation: portrait\n        shade_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 0.6\n  default_placement:\n    ignore_safe_area: false\n    device:\n      lock_orientation: portrait\n    size:\n      width: 100%\n      height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: f3500f42-5926-49a9-be43-ac09991a8432\n  view:\n    identifier: 38b583bf-f6f5-446d-a118-ebbece4e747c\n    nps_identifier: 562bb2a0-991e-449c-a750-a4045d590a48\n    type: nps_form_controller\n    submit: submit_event\n    response_type: nps\n    view:\n      type: container\n      background_color:\n        default:\n          type: hex\n          hex: \"#FFFFFF\"\n          alpha: 1\n      items:\n        - ignore_safe_area: false\n          position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          view:\n            type: pager\n            disable_swipe: true\n            items:\n              - identifier: 6f2151e2-a685-473c-a0d9-1a12d49eb891\n                type: pager_item\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                    - size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: scroll_layout\n                        direction: vertical\n                        view: # Scroll content\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                            - margin:\n                                top: 72\n                                bottom: 32\n                                start: 16\n                                end: 16\n                              size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: label\n                                text: How likely is it that you would recommend Airship to a friend or colleague?\n                                text_appearance:\n                                  font_size: 16\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#000000\"\n                                      alpha: 1\n                                  alignment: center\n                                  font_families:\n                                    - sans-serif\n                            - size: # \"Not Likely\", \"Very Likely\"\n                                width: 100%\n                                height: auto\n                              margin:\n                                bottom: 8\n                                start: 24\n                                end: 24\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                  - size:\n                                      width: 50%\n                                      height: auto\n                                    view:\n                                      type: label\n                                      text: Not Likely\n                                      text_appearance:\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        alignment: start\n                                        font_families:\n                                          - sans-serif\n                                  - size:\n                                      width: 50%\n                                      height: auto\n                                    view:\n                                      type: label\n                                      text: Very Likely\n                                      text_appearance:\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                        alignment: end\n                                        font_families:\n                                          - sans-serif\n                            - size:\n                                height: auto\n                                width: 100%\n                              margin:\n                                top: 8\n                                start: 16\n                                end: 16\n                                bottom: 16\n                              view:\n                                type: score\n                                style:\n                                  type: number_range\n                                  start: 0\n                                  end: 10\n                                  spacing: 2\n                                  bindings:\n                                    selected:\n                                      shapes:\n                                        - type: rectangle\n                                          scale: 1\n                                          border:\n                                            radius: 2\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#DDDDDD\"\n                                              alpha: 1\n                                      text_appearance:\n                                        alignment: center\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    unselected:\n                                      shapes:\n                                        - type: rectangle\n                                          scale: 1\n                                          border:\n                                            radius: 2\n                                            stroke_width: 1\n                                            stroke_color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                          color:\n                                            default:\n                                              type: hex\n                                              hex: \"#FFFFFF\"\n                                              alpha: 1\n                                      text_appearance:\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                identifier: 562bb2a0-991e-449c-a750-a4045d590a48\n                                required: true\n                            - margin: # \"What is the primary reason...\", text input\n                                bottom: 16\n                                start: 16\n                                end: 16\n                              size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: label\n                                text: What is the primary reason for your score?\n                                text_appearance:\n                                  font_size: 16\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#000000\"\n                                      alpha: 1\n                                  alignment: center\n                                  font_families:\n                                    - sans-serif\n                            - size:\n                                width: 100%\n                                height: 72\n                              margin:\n                                start: 16\n                                end: 16\n                              view:\n                                background_color:\n                                  default:\n                                    type: hex\n                                    hex: \"#ffffff\"\n                                    alpha: 1\n                                border:\n                                  radius: 2\n                                  stroke_width: 1\n                                  stroke_color:\n                                    default:\n                                      type: hex\n                                      hex: \"#63656b\"\n                                      alpha: 1\n                                type: text_input\n                                text_appearance:\n                                  alignment: start\n                                  font_size: 14\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#000000\"\n                                      alpha: 1\n                                identifier: 81727468-1ebe-48cd-a538-562f98ab9739\n                                input_type: text_multiline\n                                required: false\n                            - size:\n                                width: 100%\n                                height: 200\n                              margin:\n                                top: 16\n                                bottom: 16\n                                start: 16\n                                end: 16\n                              view:\n                                type: empty_view\n                                background_color:\n                                  default:\n                                    hex: \"#ff00ff\"\n                                    alpha: 1\n\n                    - size: # Linear layout for button\n                        height: auto\n                        width: 100%\n                      view:\n                        type: linear_layout\n                        direction: horizontal\n                        background_color:\n                          default:\n                            hex: \"#FFFFFF\"\n                            alpha: 1\n                        items:\n                          - margin:\n                              top: 16\n                              bottom: 16\n                              start: 16\n                              end: 16\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              type: label_button\n                              identifier: submit_feedback--Submit\n                              label:\n                                type: label\n                                text: Submit\n                                text_appearance:\n                                  font_size: 16\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#FFFFFF\"\n                                      alpha: 1\n                                  alignment: center\n                                  font_families:\n                                    - SF Pro\n                                    - sans-serif\n                              enabled:\n                                - form_validation\n                              button_click:\n                                - form_submit\n                                - dismiss\n                              background_color:\n                                default:\n                                  type: hex\n                                  hex: \"#123456\"\n                                  alpha: 1\n                              border:\n                                radius: 0\n                                stroke_width: 1\n                                stroke_color:\n                                  default:\n                                    type: hex\n                                    hex: \"#123456\"\n                                    alpha: 1\n\n        - position:\n            horizontal: center\n            vertical: top\n          size:\n            width: 100%\n            height: 48\n          view:\n            type: container\n            background_color:\n              default:\n                type: hex\n                hex: \"#FFFFFF\"\n                alpha: 1\n            items:\n              - position: # X button\n                  horizontal: end\n                  vertical: top\n                size:\n                  width: 48\n                  height: 48\n                view:\n                  type: image_button\n                  image:\n                    scale: 0.4\n                    type: icon\n                    icon: close\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                  identifier: dismiss_button\n                  button_click:\n                    - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/tour-safe-areas.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    device:\n      lock_orientation: portrait\n    size:\n      max_width: 100%\n      max_height: 100%\n      width: 100%\n      min_width: 100%\n      height: 100%\n      min_height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: f3500f42-5926-49a9-be43-ac09991a8432\n  view:\n    identifier: e42d88f2-c83d-437b-823b-85266ca31d34\n    nps_identifier: ea8eed71-7bc8-4e62-874e-7054425c5863\n    type: nps_form_controller\n    submit: submit_event\n    response_type: nps\n    view:\n      type: container\n      items:\n      - ignore_safe_area: false\n        position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: pager\n          disable_swipe: true\n          items:\n          - identifier: 6f2151e2-a685-473c-a0d9-1a12d49eb891\n            type: pager_item\n            view:\n              type: scroll_layout\n              direction: vertical\n              view:\n                type: linear_layout\n                direction: vertical\n                items:\n                - margin:\n                    top: 45\n                    bottom: 10\n                    start: 0\n                    end: 0\n                  size:\n                    width: 92%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                    - size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - margin:\n                            top: 20\n                            bottom: 4\n                            start: 0\n                            end: 0\n                          size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: label\n                            text: How likely is it that you would recommend [your\n                              company, product, etc.] to a friend or colleague?\n                            text_appearance:\n                              font_size: 20\n                              color:\n                                default:\n                                  type: hex\n                                  hex: \"#111111\"\n                                  alpha: 1\n                              alignment: center\n                              styles:\n                              - underlined\n                              - bold\n                              - italic\n                              font_families:\n                              - sans-serif\n                    - size:\n                        width: 100%\n                        height: auto\n                      margin:\n                        top: 0\n                        bottom: 0\n                        start: 10\n                        end: 10\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            bottom: 8\n                            top: 10\n                          view:\n                            type: linear_layout\n                            direction: horizontal\n                            items:\n                            - size:\n                                width: 50%\n                                height: auto\n                              view:\n                                type: label\n                                text: Not Likely\n                                text_appearance:\n                                  font_size: 34\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#111111\"\n                                      alpha: 1\n                                  alignment: start\n                                  styles:\n                                  - underlined\n                                  - bold\n                                  - italic\n                                  font_families:\n                                  - sans-serif\n                            - size:\n                                width: 50%\n                                height: auto\n                              view:\n                                type: label\n                                text: Very Likely\n                                text_appearance:\n                                  font_size: 34\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#111111\"\n                                      alpha: 1\n                                  alignment: end\n                                  styles:\n                                  - underlined\n                                  - bold\n                                  - italic\n                                  font_families:\n                                  - sans-serif\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: linear_layout\n                            direction: horizontal\n                            items:\n                            - size:\n                                height: 40\n                                width: 100%\n                              margin:\n                                top: 0\n                                bottom: 0\n                                start: 2\n                                end: 2\n                              view:\n                                type: score\n                                style:\n                                  type: number_range\n                                  start: 0\n                                  end: 10\n                                  spacing: 2\n                                  bindings:\n                                    selected:\n                                      shapes:\n                                      - type: rectangle\n                                        scale: 1\n                                        border:\n                                          radius: 2\n                                          stroke_width: 1\n                                          stroke_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#DDDDDD\"\n                                            alpha: 1\n                                      text_appearance:\n                                        alignment: center\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    unselected:\n                                      shapes:\n                                      - type: rectangle\n                                        scale: 1\n                                        border:\n                                          radius: 2\n                                          stroke_width: 1\n                                          stroke_color:\n                                            default:\n                                              type: hex\n                                              hex: \"#000000\"\n                                              alpha: 1\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#FFFFFF\"\n                                            alpha: 1\n                                      text_appearance:\n                                        font_size: 12\n                                        color:\n                                          default:\n                                            type: hex\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                identifier: ea8eed71-7bc8-4e62-874e-7054425c5863\n                                required: true\n                - margin:\n                    top: 10\n                    bottom: 10\n                    start: 0\n                    end: 0\n                  size:\n                    width: 92%\n                    height: auto\n                  view:\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                    - margin:\n                        top: 0\n                        bottom: 10\n                        end: 0\n                        start: 0\n                      size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: label\n                        text: What is the primary reason for your score?\n                        text_appearance:\n                          font_size: 20\n                          color:\n                            default:\n                              type: hex\n                              hex: \"#111111\"\n                              alpha: 1\n                          alignment: center\n                          styles:\n                          - underlined\n                          - bold\n                          - italic\n                          font_families:\n                          - sans-serif\n                    - size:\n                        width: 100%\n                        height: 70\n                      view:\n                        background_color:\n                          default:\n                            type: hex\n                            hex: \"#eae9e9\"\n                            alpha: 1\n                        border:\n                          radius: 2\n                          stroke_width: 1\n                          stroke_color:\n                            default:\n                              type: hex\n                              hex: \"#63656b\"\n                              alpha: 1\n                        type: text_input\n                        text_appearance:\n                          alignment: start\n                          font_size: 14\n                          color:\n                            default:\n                              type: hex\n                              hex: \"#000000\"\n                              alpha: 1\n                        identifier: 4611e2b3-33de-4fef-908e-74a4a9ed6ee8\n                        input_type: text\n                        required: false\n                - margin:\n                    top: 0\n                    start: 0\n                    end: 0\n                    bottom: 0\n                  size:\n                    height: 100%\n                    width: 100%\n                  view:\n                    type: empty_view\n                - margin:\n                    top: 10\n                    bottom: 0\n                  size:\n                    height: auto\n                    width: 92%\n                  view:\n                    type: linear_layout\n                    direction: horizontal\n                    items:\n                    - margin:\n                        top: 4\n                        bottom: 20\n                        start: 0\n                        end: 0\n                      size:\n                        width: 100%\n                        height: 40\n                      view:\n                        type: label_button\n                        identifier: submit_feedback--fSubmit\n                        label:\n                          type: label\n                          text: fSubmit\n                          text_appearance:\n                            font_size: 14\n                            color:\n                              default:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                            alignment: center\n                            styles: []\n                            font_families:\n                            - sans-serif\n                        actions: {}\n                        enabled:\n                        - form_validation\n                        button_click:\n                        - form_submit\n                        - dismiss\n                        background_color:\n                          default:\n                            type: hex\n                            hex: \"#222222\"\n                            alpha: 1\n                        border:\n                          radius: 0\n                          stroke_width: 1\n                          stroke_color:\n                            default:\n                              type: hex\n                              hex: \"#222222\"\n                              alpha: 1\n              background_color:\n                default:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 48\n          height: 48\n        view:\n          type: image_button\n          image:\n            scale: 0.4\n            type: icon\n            icon: close\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n          identifier: dismiss_button\n          button_click:\n          - dismiss"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/unsafe_areas.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.5 # no shade\n    ignore_safe_area: true\nview:\n  # Top-level container (yellow bkg, ignores safe area)\n  type: container\n  background_color:\n    default:\n      hex: \"#FFFF00\"\n      alpha: 1\n  items:\n  # TOP|END linear_layout (ignores safe area)\n  - position:\n      horizontal: end\n      vertical: top\n    size:\n      height: 50%\n      width: 50%\n    ignore_safe_area: true\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#FFFFFF\"\n          alpha: 1\n      border:\n        stroke_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 1\n        stroke_width: 2\n      items:\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Linear Layout\n          background_color:\n            default:\n              hex: \"#FF7586\"\n              alpha: 1\n          text_appearance:\n            font_size: 12\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"ignore_safe_area: true\"\n          background_color:\n            default:\n              hex: \"#7385FF\"\n              alpha: 1\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n  # TOP|START linear_layout (respects safe area)\n  - position:\n      horizontal: start\n      vertical: top\n    size:\n      height: 50%\n      width: 50%\n    ignore_safe_area: true # originally false\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#FFFFFF\"\n          alpha: 1\n      border:\n        stroke_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 1\n        stroke_width: 2\n      items:\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Linear Layout\n          background_color:\n            default:\n              hex: \"#FF7586\"\n              alpha: 1\n          text_appearance:\n            font_size: 12\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"ignore_safe_area: true\" # originally false\n          background_color:\n            default:\n              hex: \"#7385FF\"\n              alpha: 1\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n  # BOTTOM|END linear_layout (ignores safe area, 4 nested children)\n  - position:\n      horizontal: end\n      vertical: bottom\n    size:\n      height: 50%\n      width: 50%\n    ignore_safe_area: true\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#FFFFFF\"\n          alpha: 1\n      border:\n        stroke_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 1\n        stroke_width: 2\n      items:\n      - size:\n          width: 100%\n          height: 100%\n        view:\n          type: container\n          background_color:\n            default:\n              hex: \"#FFFF00\"\n              alpha: 1\n          items:\n          - position:\n              horizontal: end\n              vertical: top\n            size:\n              height: 50%\n              width: 50%\n            ignore_safe_area: true\n            view:\n              type: linear_layout\n              direction: vertical\n              background_color:\n                default:\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              border:\n                stroke_color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: 1\n                stroke_width: 2\n              items:\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: Linear Layout\n                  background_color:\n                    default:\n                      hex: \"#FF7586\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 12\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: \"ignore_safe_area: true\"\n                  background_color:\n                    default:\n                      hex: \"#7385FF\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n\n          - position:\n              horizontal: start\n              vertical: top\n            size:\n              height: 50%\n              width: 50%\n            ignore_safe_area: false\n            view:\n              type: linear_layout\n              direction: vertical\n              background_color:\n                default:\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              border:\n                stroke_color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: 1\n                stroke_width: 2\n              items:\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: Linear Layout\n                  background_color:\n                    default:\n                      hex: \"#FF7586\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 12\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: \"ignore_safe_area: false\"\n                  background_color:\n                    default:\n                      hex: \"#7385FF\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n          - position:\n              horizontal: end\n              vertical: bottom\n            size:\n              height: 50%\n              width: 50%\n            ignore_safe_area: true\n            view:\n              type: linear_layout\n              direction: vertical\n              background_color:\n                default:\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              border:\n                stroke_color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: 1\n                stroke_width: 2\n              items:\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: Linear Layout\n                  background_color:\n                    default:\n                      hex: \"#FF7586\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 12\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: \"ignore_safe_area: true\"\n                  background_color:\n                    default:\n                      hex: \"#7385FF\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n          - position:\n              horizontal: start\n              vertical: bottom\n            size:\n              height: 50%\n              width: 50%\n            ignore_safe_area: false\n            view:\n              type: linear_layout\n              direction: vertical\n              background_color:\n                default:\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n              border:\n                stroke_color:\n                  default:\n                    hex: \"#0000FF\"\n                    alpha: 1\n                stroke_width: 2\n              items:\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: Linear Layout\n                  background_color:\n                    default:\n                      hex: \"#FF7586\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 12\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n              - size:\n                  width: 100%\n                  height: auto\n                view:\n                  type: label\n                  text: \"ignore_safe_area: false\"\n                  background_color:\n                    default:\n                      hex: \"#7385FF\"\n                      alpha: 1\n                  text_appearance:\n                    font_size: 10\n                    color:\n                      default:\n                        type: hex\n                        hex: \"#000000\"\n                        alpha: 1\n                    alignment: center\n\n  - position:\n      horizontal: start\n      vertical: bottom\n    size:\n      height: 50%\n      width: 50%\n    ignore_safe_area: false\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#FFFFFF\"\n          alpha: 1\n      border:\n        stroke_color:\n          default:\n            hex: \"#0000FF\"\n            alpha: 1\n        stroke_width: 2\n      items:\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: Linear Layout\n          background_color:\n            default:\n              hex: \"#FF7586\"\n              alpha: 1\n          text_appearance:\n            font_size: 12\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center\n      - size:\n          width: 100%\n          height: auto\n        view:\n          type: label\n          text: \"ignore_safe_area: false\"\n          background_color:\n            default:\n              hex: \"#7385FF\"\n              alpha: 1\n          text_appearance:\n            font_size: 10\n            color:\n              default:\n                type: hex\n                hex: \"#000000\"\n                alpha: 1\n            alignment: center"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video-controls.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.2\n  dismiss_on_touch_outside: false\nview:\n  type: video_controller\n  identifier: vc-parent\n  mute_group:\n    identifier: shared-mute\n  view:\n    type: pager_controller\n    identifier: pager-controller\n    view:\n      type: container\n      background_color:\n        default:\n          type: hex\n          hex: '#000000'\n          alpha: 1\n      items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          ignore_safe_area: true\n          view:\n            type: pager\n            gestures:\n              - type: swipe\n                identifier: swipe-up-id\n                direction: up\n                behavior:\n                  behaviors:\n                    - dismiss\n            items:\n              # PAGE 1\n              - identifier: page-1\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-1\n                  mute_group:\n                    identifier: shared-mute\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-1\n                          media_type: video\n                          media_fit: center_crop\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: false\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                            - size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: label\n                                text: \"Page 1 — Big Buck Bunny\"\n                                text_appearance:\n                                  font_size: 24\n                                  alignment: center\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n                                  styles:\n                                    - bold\n                            - size:\n                                width: 100%\n                                height: auto\n                              margin:\n                                top: 6\n                              view:\n                                type: label\n                                text: \"Muting here mutes all pages (shared mute group).\"\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: '#FFFFFF'\n                                      alpha: 0.85\n                      - position:\n                          horizontal: center\n                          vertical: bottom\n                        margin:\n                          bottom: 40\n                        size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                            - size:\n                                width: 56\n                                height: 56\n                              margin:\n                                end: 16\n                              view:\n                                type: stack_image_button\n                                identifier: play_pause_btn_1\n                                button_click:\n                                  - video_toggle_play\n                                localized_content_description:\n                                  refs:\n                                    - ua_play\n                                  fallback: Play\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: play\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  localized_content_description:\n                                    - value:\n                                        refs:\n                                          - ua_pause\n                                        fallback: Pause\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: pause\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                            - size:\n                                width: 56\n                                height: 56\n                              view:\n                                type: stack_image_button\n                                identifier: mute_btn_1\n                                button_click:\n                                  - video_toggle_mute\n                                localized_content_description:\n                                  refs:\n                                    - ua_mute\n                                  fallback: Mute\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: unmute\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  localized_content_description:\n                                    - value:\n                                        refs:\n                                          - ua_unmute\n                                        fallback: Unmute\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: mute\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n\n              # PAGE 2\n              - identifier: page-2\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-2\n                  mute_group:\n                    identifier: shared-mute\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-2\n                          media_type: video\n                          media_fit: center_crop\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: false\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                            - size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: label\n                                text: \"Page 2 — Elephants Dream\"\n                                text_appearance:\n                                  font_size: 24\n                                  alignment: center\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n                                  styles:\n                                    - bold\n                            - size:\n                                width: 100%\n                                height: auto\n                              margin:\n                                top: 6\n                              view:\n                                type: label\n                                text: \"Muting here mutes all pages (shared mute group).\"\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: '#FFFFFF'\n                                      alpha: 0.85\n                      - position:\n                          horizontal: center\n                          vertical: bottom\n                        margin:\n                          bottom: 40\n                        size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                            - size:\n                                width: 56\n                                height: 56\n                              margin:\n                                end: 16\n                              view:\n                                type: stack_image_button\n                                identifier: play_pause_btn_2\n                                button_click:\n                                  - video_toggle_play\n                                localized_content_description:\n                                  refs:\n                                    - ua_play\n                                  fallback: Play\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: play\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  localized_content_description:\n                                    - value:\n                                        refs:\n                                          - ua_pause\n                                        fallback: Pause\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: pause\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                            - size:\n                                width: 56\n                                height: 56\n                              view:\n                                type: stack_image_button\n                                identifier: mute_btn_2\n                                button_click:\n                                  - video_toggle_mute\n                                localized_content_description:\n                                  refs:\n                                    - ua_mute\n                                  fallback: Mute\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: unmute\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  localized_content_description:\n                                    - value:\n                                        refs:\n                                          - ua_unmute\n                                        fallback: Unmute\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: mute\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n\n              # PAGE 3\n              - identifier: page-3\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-3\n                  mute_group:\n                    identifier: shared-mute\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-3\n                          media_type: video\n                          media_fit: center_crop\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: false\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: vertical\n                          items:\n                            - size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: label\n                                text: \"Page 3 — For Bigger Blazes\"\n                                text_appearance:\n                                  font_size: 24\n                                  alignment: center\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: '#FFFFFF'\n                                      alpha: 1\n                                  styles:\n                                    - bold\n                            - size:\n                                width: 100%\n                                height: auto\n                              margin:\n                                top: 6\n                              view:\n                                type: label\n                                text: \"Muting here mutes all pages (shared mute group).\"\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: '#FFFFFF'\n                                      alpha: 0.85\n                      - position:\n                          horizontal: center\n                          vertical: bottom\n                        margin:\n                          bottom: 40\n                        size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                            - size:\n                                width: 56\n                                height: 56\n                              margin:\n                                end: 16\n                              view:\n                                type: stack_image_button\n                                identifier: play_pause_btn_3\n                                button_click:\n                                  - video_toggle_play\n                                localized_content_description:\n                                  refs:\n                                    - ua_play\n                                  fallback: Play\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: play\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  localized_content_description:\n                                    - value:\n                                        refs:\n                                          - ua_pause\n                                        fallback: Pause\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: pause\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                            - size:\n                                width: 56\n                                height: 56\n                              view:\n                                type: stack_image_button\n                                identifier: mute_btn_3\n                                button_click:\n                                  - video_toggle_mute\n                                localized_content_description:\n                                  refs:\n                                    - ua_mute\n                                  fallback: Mute\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: unmute\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  localized_content_description:\n                                    - value:\n                                        refs:\n                                          - ua_unmute\n                                        fallback: Unmute\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: mute\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n\n        # Dismiss button (overlay on top of pager)\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            identifier: dismiss_button\n            button_click:\n              - dismiss\n            localized_content_description:\n              refs:\n                - ua_dismiss\n              fallback: Dismiss\n            image:\n              type: icon\n              icon: close\n              scale: 0.4\n              color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 1\n\n        # Pager indicator\n        - position:\n            horizontal: center\n            vertical: bottom\n          margin:\n            bottom: 16\n          size:\n            width: auto\n            height: 8\n          view:\n            type: pager_indicator\n            spacing: 8\n            bindings:\n              selected:\n                shapes:\n                  - type: ellipse\n                    scale: 1\n                    color:\n                      default:\n                        type: hex\n                        hex: '#FFFFFF'\n                        alpha: 1\n              unselected:\n                shapes:\n                  - type: ellipse\n                    scale: 1\n                    color:\n                      default:\n                        type: hex\n                        hex: '#FFFFFF'\n                        alpha: 0.4\ndisplay_type: layout\nname: Video Controls\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video-qa.yaml",
    "content": "---\npresentation:\n  android:\n    disable_back_button: false\n  default_placement:\n    device:\n      lock_orientation: portrait\n    ignore_safe_area: false\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.2\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n    web:\n      ignore_shade: true\n  dismiss_on_touch_outside: false\n  placement_selectors:\n    - orientation: landscape\n      placement:\n        border:\n          radius: 10\n          stroke_color:\n            default:\n              alpha: 1\n              hex: \"#000000\"\n              type: hex\n        device:\n          lock_orientation: portrait\n        ignore_safe_area: false\n        position:\n          horizontal: center\n          vertical: center\n        shade_color:\n          default:\n            alpha: 0.2\n            hex: \"#000000\"\n            type: hex\n          selectors:\n            - color:\n                alpha: 0.2\n                hex: \"#FFFFFF\"\n                type: hex\n              dark_mode: true\n        size:\n          height: 100%\n          width: 100%\n        web:\n          ignore_shade: false\n      window_size: large\n  type: modal\nversion: 1\nview:\n  type: state_controller\n  view:\n    identifier: 56c21953-ce73-4d79-ba9e-d590bb465b50\n    type: pager_controller\n    view:\n      direction: vertical\n      items:\n        - size:\n            height: 100%\n            width: 100%\n          view:\n            items:\n              - identifier: 3b61f71f-328f-48dc-a8b3-c8e1bc1a206f_pager_container_item\n                ignore_safe_area: false\n                position:\n                  horizontal: center\n                  vertical: center\n                size:\n                  height: 100%\n                  width: 100%\n                view:\n                  disable_swipe: true\n                  gestures:\n                    - behavior:\n                        behaviors:\n                          - pager_previous\n                      identifier: 56c21953-ce73-4d79-ba9e-d590bb465b50_tap_start\n                      location: start\n                      type: tap\n                    - behavior:\n                        behaviors:\n                          - pager_next_or_dismiss\n                      identifier: 56c21953-ce73-4d79-ba9e-d590bb465b50_tap_end\n                      location: end\n                      type: tap\n                    - behavior:\n                        behaviors:\n                          - dismiss\n                      direction: up\n                      identifier: 56c21953-ce73-4d79-ba9e-d590bb465b50_swipe_up\n                      type: swipe\n                    - behavior:\n                        behaviors:\n                          - dismiss\n                      direction: down\n                      identifier: 56c21953-ce73-4d79-ba9e-d590bb465b50_swipe_down\n                      type: swipe\n                    - identifier: 56c21953-ce73-4d79-ba9e-d590bb465b50_hold\n                      press_behavior:\n                        behaviors:\n                          - pager_pause\n                      release_behavior:\n                        behaviors:\n                          - pager_resume\n                      type: hold\n                  items:\n                    - automated_actions:\n                        - behaviors:\n                            - pager_next\n                          delay: 5\n                          identifier: \"[pager_next]_3867ec4b-dea8-4682-98f6-697a14c9fcc8\"\n                      identifier: 3867ec4b-dea8-4682-98f6-697a14c9fcc8\n                      state_actions:\n                        - key: 3867ec4b-dea8-4682-98f6-697a14c9fcc8_next\n                          type: set\n                      type: pager_item\n                      view:\n                        background_color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFA\"\n                            type: hex\n                          selectors:\n                            - color:\n                                alpha: 1\n                                hex: \"#111111\"\n                                type: hex\n                              dark_mode: true\n                        items:\n                          - identifier: cc467337-1b3b-44f2-94fd-8abf6ce94b24_background_image_container\n                            ignore_safe_area: false\n                            margin:\n                              end: 0\n                              start: 0\n                            position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              height: 100%\n                              width: 100%\n                            view:\n                              media_fit: center_crop\n                              media_type: youtube\n                              type: media\n                              url: https://www.youtube.com/embed/7sxVHYZ_PnA/?autoplay=1&controls=0&loop=1&mute=1\n                              video:\n                                aspect_ratio: 0.5625\n                                autoplay: true\n                                loop: true\n                                muted: true\n                                show_controls: false\n                          - identifier: 4c7af3ba-2396-459e-87c7-58abeb565fdf_main_view_container_item\n                            ignore_safe_area: false\n                            position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              height: 100%\n                              width: 100%\n                            view:\n                              items:\n                                - identifier: 0f664c4a-8969-4a62-bb07-d162c069e169_container_item\n                                  margin:\n                                    bottom: 0\n                                    end: 0\n                                    start: 0\n                                    top: 0\n                                  position:\n                                    horizontal: center\n                                    vertical: center\n                                  size:\n                                    height: 100%\n                                    width: 100%\n                                  view:\n                                    direction: vertical\n                                    items:\n                                      - identifier: layout_container\n                                        size:\n                                          height: 100%\n                                          width: 100%\n                                        view:\n                                          direction: vertical\n                                          items:\n                                            - identifier: 0e88653c-4ee2-4f15-94a5-e71e3256e043\n                                              margin:\n                                                bottom: 8\n                                                end: 16\n                                                start: 16\n                                                top: 48\n                                              size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                accessibility_hidden: false\n                                                accessibility_role:\n                                                  level: 1\n                                                  type: heading\n                                                content_description: QA VIDEO\n                                                text: QA VIDEO\n                                                text_appearance:\n                                                  alignment: start\n                                                  color:\n                                                    default:\n                                                      alpha: 1\n                                                      hex: \"#000000\"\n                                                      type: hex\n                                                    selectors:\n                                                      - color:\n                                                          alpha: 1\n                                                          hex: \"#FFFFFF\"\n                                                          type: hex\n                                                        dark_mode: true\n                                                  font_families:\n                                                    - sans-serif\n                                                  font_size: 29\n                                                  styles:\n                                                    - bold\n                                                type: label\n                                            - identifier: 4b00b79c-d7f1-497b-b608-81a0b407588e\n                                              margin:\n                                                bottom: 0\n                                                end: 0\n                                                start: 0\n                                                top: 0\n                                              size:\n                                                height: auto\n                                                width: 50%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: youtube\n                                                type: media\n                                                url: https://www.youtube.com/embed/pJtu4nBhkHo/?autoplay=0&controls=1&loop=0&mute=0\n                                                video:\n                                                  aspect_ratio: 1.3333333333333333\n                                                  autoplay: false\n                                                  loop: false\n                                                  muted: false\n                                                  show_controls: true\n                                          type: linear_layout\n                                    type: linear_layout\n                              type: container\n                        type: container\n                    - automated_actions:\n                        - behaviors:\n                            - pager_next\n                          delay: 5\n                          identifier: \"[pager_next]_08b41094-e0e4-4c8f-9827-883bba4b28e6\"\n                      identifier: '08b41094-e0e4-4c8f-9827-883bba4b28e6'\n                      state_actions:\n                        - key: '08b41094-e0e4-4c8f-9827-883bba4b28e6_next'\n                          type: set\n                      type: pager_item\n                      view:\n                        background_color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFA\"\n                            type: hex\n                          selectors:\n                            - color:\n                                alpha: 1\n                                hex: \"#111111\"\n                                type: hex\n                              dark_mode: true\n                        items:\n                          - identifier: cd028436-5d8c-4b8e-98f9-74ad9fe81e11_main_view_container_item\n                            ignore_safe_area: false\n                            position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              height: 100%\n                              width: 100%\n                            view:\n                              items:\n                                - identifier: 8e703435-11e0-4420-b0dc-6b428b500ad3_container_item\n                                  margin:\n                                    bottom: 0\n                                    end: 0\n                                    start: 0\n                                    top: 0\n                                  position:\n                                    horizontal: center\n                                    vertical: center\n                                  size:\n                                    height: 100%\n                                    width: 100%\n                                  view:\n                                    direction: vertical\n                                    items:\n                                      - identifier: layout_container\n                                        size:\n                                          height: 100%\n                                          width: 100%\n                                        view:\n                                          direction: vertical\n                                          items:\n                                            - identifier: 03221167-63d1-4fa2-bd5d-fdc0d6ed425b\n                                              margin:\n                                                bottom: 0\n                                                end: 0\n                                                start: 0\n                                                top: 0\n                                              size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: video\n                                                type: media\n                                                url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/b63811eb-17dc-4ba3-a4f0-092c073d7081\n                                                video:\n                                                  aspect_ratio: 1.3333333333333333\n                                                  autoplay: false\n                                                  loop: false\n                                                  muted: true\n                                                  show_controls: true\n                                          type: linear_layout\n                                    type: linear_layout\n                              type: container\n                        type: container\n                    - automated_actions:\n                        - behaviors:\n                            - pager_next_or_dismiss\n                          delay: 5\n                          identifier: pager_next_or_dismiss_aa8dd148-663d-455c-8df4-4e0e1f50d676\n                      identifier: aa8dd148-663d-455c-8df4-4e0e1f50d676\n                      state_actions:\n                        - key: aa8dd148-663d-455c-8df4-4e0e1f50d676_next\n                          type: set\n                      type: pager_item\n                      view:\n                        background_color:\n                          default:\n                            alpha: 1\n                            hex: \"#FFFFFA\"\n                            type: hex\n                          selectors:\n                            - color:\n                                alpha: 1\n                                hex: \"#111111\"\n                                type: hex\n                              dark_mode: true\n                        items:\n                          - identifier: 26436888-0192-4e3e-9c20-91d85c067ec5_main_view_container_item\n                            ignore_safe_area: false\n                            position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              height: 100%\n                              width: 100%\n                            view:\n                              items:\n                                - identifier: aaf45ec1-c0de-45a3-b84f-1681f759d980_container_item\n                                  margin:\n                                    bottom: 0\n                                    end: 0\n                                    start: 0\n                                    top: 0\n                                  position:\n                                    horizontal: center\n                                    vertical: center\n                                  size:\n                                    height: 100%\n                                    width: 100%\n                                  view:\n                                    direction: vertical\n                                    items:\n                                      - identifier: layout_container\n                                        size:\n                                          height: 100%\n                                          width: 100%\n                                        view:\n                                          direction: vertical\n                                          items:\n                                            - identifier: 06c3d487-8f24-44b0-a500-17b23ce73c83\n                                              margin:\n                                                bottom: 0\n                                                end: 0\n                                                start: 0\n                                                top: 0\n                                              size:\n                                                height: auto\n                                                width: 100%\n                                              view:\n                                                media_fit: center_inside\n                                                media_type: youtube\n                                                type: media\n                                                url: https://www.youtube.com/embed/pJtu4nBhkHo/?autoplay=1&controls=1&loop=0&mute=0\n                                                video:\n                                                  aspect_ratio: 1.7777777777777777\n                                                  autoplay: true\n                                                  loop: false\n                                                  muted: false\n                                                  show_controls: true\n                                          type: linear_layout\n                                    type: linear_layout\n                              type: container\n                        type: container\n                  type: pager\n              - margin:\n                  top: 8\n                position:\n                  horizontal: end\n                  vertical: top\n                size:\n                  height: 48\n                  width: 48\n                view:\n                  button_click:\n                    - dismiss\n                  identifier: dismiss_button\n                  image:\n                    color:\n                      default:\n                        alpha: 1\n                        hex: \"#000000\"\n                        type: hex\n                      selectors:\n                        - color:\n                            alpha: 1\n                            hex: \"#FFFFFF\"\n                            type: hex\n                          dark_mode: true\n                    icon: close\n                    scale: 0.4\n                    type: icon\n                  localized_content_description:\n                    fallback: Dismiss\n                    ref: ua_dismiss\n                    refs:\n                      - ua_close\n                      - ua_dismiss\n                  reporting_metadata:\n                    button_action: dismiss\n                    button_id: dismiss_button\n                  type: image_button\n              - margin:\n                  bottom: 0\n                  end: 16\n                  start: 16\n                  top: 8\n                position:\n                  horizontal: center\n                  vertical: top\n                size:\n                  height: 4\n                  width: 100%\n                view:\n                  automated_accessibility_actions:\n                    - type: announce\n                  source:\n                    type: pager\n                  style:\n                    direction: horizontal\n                    progress_color:\n                      default:\n                        alpha: 1\n                        hex: \"#000000\"\n                        type: hex\n                      selectors:\n                        - color:\n                            alpha: 1\n                            hex: \"#FFFFFF\"\n                            type: hex\n                          dark_mode: true\n                    sizing: equal\n                    spacing: 4\n                    track_color:\n                      default:\n                        alpha: 1\n                        hex: \"#BFBFB0\"\n                        type: hex\n                    type: linear_progress\n                  type: story_indicator\n            type: container\n      type: linear_layout\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video-story-multipage.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\n  dismiss_on_touch_outside: false\nview:\n  type: video_controller\n  identifier: vc-story-multi\n  view:\n    type: pager_controller\n    identifier: pager-controller-multi\n    view:\n      type: container\n      background_color:\n        default:\n          type: hex\n          hex: '#000000'\n          alpha: 1\n      items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          ignore_safe_area: true\n          view:\n            type: pager\n            gestures:\n              - type: hold\n                identifier: hold-id\n                press_behavior:\n                  behaviors:\n                    - pager_pause\n                release_behavior:\n                  behaviors:\n                    - pager_resume\n              - type: swipe\n                identifier: swipe-up-id\n                direction: up\n                behavior:\n                  behaviors:\n                    - dismiss\n            items:\n              # PAGE 1 — tests: first-load autoplay, navigate-away and return (should restart)\n              - identifier: story-page-1\n                type: pager_item\n                automated_actions:\n                  - delay: 15\n                    identifier: auto-next-1\n                    behaviors:\n                      - pager_next_or_dismiss\n                view:\n                  type: container\n                  items:\n                    - position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      ignore_safe_area: true\n                      view:\n                        type: media\n                        identifier: story-video-1\n                        media_type: video\n                        media_fit: center_crop\n                        url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                        video:\n                          aspect_ratio: 1.77777777777778\n                          autoplay: true\n                          muted: false\n                          loop: true\n                          show_controls: false\n                    - position:\n                        horizontal: center\n                        vertical: top\n                      margin:\n                        top: 28\n                        start: 16\n                        end: 16\n                      size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 100%\n                              height: auto\n                            view:\n                              type: label\n                              text: \"1 / 3\"\n                              text_appearance:\n                                font_size: 22\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 1\n                                styles:\n                                  - bold\n                          - size:\n                              width: 100%\n                              height: auto\n                            margin:\n                              top: 6\n                            view:\n                              type: label\n                              text: \"Swipe → next. Come back — video should restart.\"\n                              text_appearance:\n                                font_size: 14\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 0.85\n\n              # PAGE 2 — tests: autoplay on new page, navigate back restarts page 1\n              - identifier: story-page-2\n                type: pager_item\n                automated_actions:\n                  - delay: 15\n                    identifier: auto-next-2\n                    behaviors:\n                      - pager_next_or_dismiss\n                view:\n                  type: container\n                  items:\n                    - position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      ignore_safe_area: true\n                      view:\n                        type: media\n                        identifier: story-video-2\n                        media_type: video\n                        media_fit: center_crop\n                        url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                        video:\n                          aspect_ratio: 1.77777777777778\n                          autoplay: true\n                          muted: false\n                          loop: true\n                          show_controls: false\n                    - position:\n                        horizontal: center\n                        vertical: top\n                      margin:\n                        top: 28\n                        start: 16\n                        end: 16\n                      size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 100%\n                              height: auto\n                            view:\n                              type: label\n                              text: \"2 / 3\"\n                              text_appearance:\n                                font_size: 22\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 1\n                                styles:\n                                  - bold\n                          - size:\n                              width: 100%\n                              height: auto\n                            margin:\n                              top: 6\n                            view:\n                              type: label\n                              text: \"Swipe ← back — page 1 should autoplay from start.\"\n                              text_appearance:\n                                font_size: 14\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 0.85\n\n              # PAGE 3 — tests: user pause intent preserved across navigation\n              - identifier: story-page-3\n                type: pager_item\n                automated_actions:\n                  - delay: 15\n                    identifier: auto-next-3\n                    behaviors:\n                      - pager_next_or_dismiss\n                view:\n                  type: container\n                  items:\n                    - position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      ignore_safe_area: true\n                      view:\n                        type: media\n                        identifier: story-video-3\n                        media_type: video\n                        media_fit: center_crop\n                        url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                        video:\n                          aspect_ratio: 1.77777777777778\n                          autoplay: true\n                          muted: false\n                          loop: true\n                          show_controls: false\n                    - position:\n                        horizontal: center\n                        vertical: top\n                      margin:\n                        top: 28\n                        start: 16\n                        end: 16\n                      size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 100%\n                              height: auto\n                            view:\n                              type: label\n                              text: \"3 / 3\"\n                              text_appearance:\n                                font_size: 22\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 1\n                                styles:\n                                  - bold\n                          - size:\n                              width: 100%\n                              height: auto\n                            margin:\n                              top: 6\n                            view:\n                              type: label\n                              text: \"Pause ↓ then swipe ← back — page 1 should stay paused.\"\n                              text_appearance:\n                                font_size: 14\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 0.85\n\n        - size:\n            height: 5\n            width: 80%\n          position:\n            vertical: top\n            horizontal: center\n          margin:\n            top: 8\n          view:\n            type: story_indicator\n            source:\n              type: pager\n            style:\n              type: linear_progress\n              direction: horizontal\n              sizing: equal\n              spacing: 4\n              progress_color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 1\n              track_color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 0.4\n\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            identifier: dismiss_button\n            button_click:\n              - dismiss\n            localized_content_description:\n              refs:\n                - ua_dismiss\n              fallback: Dismiss\n            image:\n              type: icon\n              icon: close\n              scale: 0.4\n              color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 1\n\n        - position:\n            horizontal: center\n            vertical: bottom\n          margin:\n            bottom: 40\n          size:\n            width: auto\n            height: auto\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n              - size:\n                  width: 64\n                  height: 64\n                margin:\n                  end: 16\n                view:\n                  type: stack_image_button\n                  identifier: play_pause_btn\n                  button_click:\n                    - pager_toggle_pause\n                  localized_content_description:\n                    refs:\n                      - ua_play\n                    fallback: Play\n                  items:\n                    - type: icon\n                      icon:\n                        type: icon\n                        icon: play\n                        color:\n                          default:\n                            type: hex\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        scale: 0.5\n                  view_overrides:\n                    localized_content_description:\n                      - value:\n                          refs:\n                            - ua_pause\n                          fallback: Pause\n                        when_state_matches:\n                          scope:\n                            - $pagers\n                            - current\n                            - paused\n                          value:\n                            equals: false\n                    items:\n                      - value:\n                          - type: icon\n                            icon:\n                              type: icon\n                              icon: pause\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              scale: 0.5\n                        when_state_matches:\n                          scope:\n                            - $pagers\n                            - current\n                            - paused\n                          value:\n                            equals: false\n              - size:\n                  width: 64\n                  height: 64\n                view:\n                  type: stack_image_button\n                  identifier: mute_btn\n                  button_click:\n                    - video_toggle_mute\n                  localized_content_description:\n                    refs:\n                      - ua_mute\n                    fallback: Mute\n                  items:\n                    - type: icon\n                      icon:\n                        type: icon\n                        icon: unmute\n                        color:\n                          default:\n                            type: hex\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        scale: 0.5\n                  view_overrides:\n                    localized_content_description:\n                      - value:\n                          refs:\n                            - ua_unmute\n                          fallback: Unmute\n                        when_state_matches:\n                          scope:\n                            - $video\n                            - current\n                            - muted\n                          value:\n                            equals: true\n                    items:\n                      - value:\n                          - type: icon\n                            icon:\n                              type: icon\n                              icon: mute\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              scale: 0.5\n                        when_state_matches:\n                          scope:\n                            - $video\n                            - current\n                            - muted\n                          value:\n                            equals: true\ndisplay_type: layout\nname: Video Story (Multi-Page)\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video-story.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\n  dismiss_on_touch_outside: false\nview:\n  type: video_controller\n  identifier: vc-story\n  view:\n    type: pager_controller\n    identifier: pager-controller-story\n    view:\n      type: container\n      background_color:\n        default:\n          type: hex\n          hex: '#000000'\n          alpha: 1\n      items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          ignore_safe_area: true\n          view:\n            type: pager\n            disable_swipe: true\n            gestures:\n              - type: tap\n                identifier: tap-start-id\n                location: start\n                behavior:\n                  behaviors:\n                    - pager_previous\n              - type: tap\n                identifier: tap-end-id\n                location: end\n                behavior:\n                  behaviors:\n                    - pager_next\n              - type: hold\n                identifier: hold-id\n                press_behavior:\n                  behaviors:\n                    - pager_pause\n                release_behavior:\n                  behaviors:\n                    - pager_resume\n              - type: swipe\n                identifier: swipe-up-id\n                direction: up\n                behavior:\n                  behaviors:\n                    - dismiss\n            items:\n              - identifier: story-page-1\n                type: pager_item\n                automated_actions:\n                  - delay: 7\n                    identifier: auto-next-id\n                    behaviors:\n                      - pager_next_or_dismiss\n                view:\n                  type: container\n                  items:\n                    - position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      ignore_safe_area: true\n                      view:\n                        type: media\n                        identifier: story-video\n                        media_type: video\n                        media_fit: center_crop\n                        url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                        video:\n                          aspect_ratio: 1.77777777777778\n                          autoplay: true\n                          muted: false\n                          loop: true\n                          show_controls: false\n                    - position:\n                        horizontal: center\n                        vertical: top\n                      margin:\n                        top: 28\n                        start: 16\n                        end: 16\n                      size:\n                        width: 100%\n                        height: auto\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 100%\n                              height: auto\n                            view:\n                              type: label\n                              text: Big Buck Bunny\n                              text_appearance:\n                                font_size: 28\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 1\n                                styles:\n                                  - bold\n                          - size:\n                              width: 100%\n                              height: auto\n                            margin:\n                              top: 8\n                            view:\n                              type: label\n                              text: Hold to pause. Tap the button to toggle play and audio.\n                              text_appearance:\n                                font_size: 16\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 0.85\n        - size:\n            height: 5\n            width: 80%\n          position:\n            vertical: top\n            horizontal: center\n          margin:\n            top: 8\n          view:\n            type: story_indicator\n            source:\n              type: pager\n            style:\n              type: linear_progress\n              direction: horizontal\n              sizing: equal\n              spacing: 4\n              progress_color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 1\n              track_color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 0.4\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            identifier: dismiss_button\n            button_click:\n              - dismiss\n            localized_content_description:\n              refs:\n                - ua_dismiss\n              fallback: Dismiss\n            image:\n              type: icon\n              icon: close\n              scale: 0.4\n              color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 1\n        - position:\n            horizontal: center\n            vertical: bottom\n          margin:\n            bottom: 40\n          size:\n            width: auto\n            height: auto\n          view:\n            type: linear_layout\n            direction: horizontal\n            items:\n              - size:\n                  width: 64\n                  height: 64\n                margin:\n                  end: 16\n                view:\n                  type: stack_image_button\n                  identifier: play_pause_btn\n                  button_click:\n                    - pager_toggle_pause\n                    - video_toggle_play\n                  localized_content_description:\n                    refs:\n                      - ua_play\n                    fallback: Play\n                  items:\n                    - type: icon\n                      icon:\n                        type: icon\n                        icon: play\n                        color:\n                          default:\n                            type: hex\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        scale: 0.5\n                  view_overrides:\n                    localized_content_description:\n                      - value:\n                          refs:\n                            - ua_pause\n                          fallback: Pause\n                        when_state_matches:\n                          scope:\n                            - $video\n                            - current\n                            - playing\n                          value:\n                            equals: true\n                    items:\n                      - value:\n                          - type: icon\n                            icon:\n                              type: icon\n                              icon: pause\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              scale: 0.5\n                        when_state_matches:\n                          scope:\n                            - $video\n                            - current\n                            - playing\n                          value:\n                            equals: true\n              - size:\n                  width: 64\n                  height: 64\n                view:\n                  type: stack_image_button\n                  identifier: mute_btn\n                  button_click:\n                    - video_toggle_mute\n                  localized_content_description:\n                    refs:\n                      - ua_mute\n                    fallback: Mute\n                  items:\n                    - type: icon\n                      icon:\n                        type: icon\n                        icon: unmute\n                        color:\n                          default:\n                            type: hex\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        scale: 0.5\n                  view_overrides:\n                    localized_content_description:\n                      - value:\n                          refs:\n                            - ua_unmute\n                          fallback: Unmute\n                        when_state_matches:\n                          scope:\n                            - $video\n                            - current\n                            - muted\n                          value:\n                            equals: true\n                    items:\n                      - value:\n                          - type: icon\n                            icon:\n                              type: icon\n                              icon: mute\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              scale: 0.5\n                        when_state_matches:\n                          scope:\n                            - $video\n                            - current\n                            - muted\n                          value:\n                            equals: true\ndisplay_type: layout\nname: Video Story\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video-test.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.2\n  dismiss_on_touch_outside: false\nview:\n  type: video_controller\n  identifier: vc-parent\n  mute_group:\n    identifier: shared-mute\n  view:\n    type: pager_controller\n    identifier: pager-controller\n    view:\n      type: container\n      background_color:\n        default:\n          type: hex\n          hex: '#000000'\n          alpha: 1\n      items:\n        - position:\n            horizontal: center\n            vertical: center\n          size:\n            width: 100%\n            height: 100%\n          ignore_safe_area: true\n          view:\n            type: pager\n            items:\n              # PAGE 1: YouTube - Autoplay + No Controls (auto_reset_position defaults true)\n              - identifier: page-1\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-1\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-1\n                          media_fit: center_crop\n                          url: \"https://www.youtube.com/embed/7sxVHYZ_PnA/?autoplay=1&controls=0&loop=1&mute=1\"\n                          media_type: youtube\n                          video:\n                            aspect_ratio: 0.5625\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: false\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"1. YouTube — Autoplay + No Controls\\nauto_reset_position: true (default)\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 2: YouTube - Autoplay + Controls + auto_reset_position: true (override)\n              - identifier: page-2\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-2\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-2\n                          media_fit: center_inside\n                          url: \"https://www.youtube.com/embed/M7lc1UVf-VE/?autoplay=1&controls=1&loop=1&mute=1\"\n                          media_type: youtube\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: true\n                            auto_reset_position: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"2. YouTube — Autoplay + Controls\\nauto_reset_position: true (override)\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 3: Native - Autoplay + Controls (auto_reset_position defaults false)\n              - identifier: page-3\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-3\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-3\n                          media_type: video\n                          media_fit: center_crop\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"3. Native — Autoplay + Controls\\nauto_reset_position: false (default)\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 4: Native - Non-autoplay + Controls\n              - identifier: page-4\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-4\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-4\n                          media_type: video\n                          media_fit: center_inside\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ForBiggerBlazes.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: false\n                            muted: false\n                            loop: false\n                            show_controls: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"4. Native — Non-autoplay + Controls\\nShould not play until user taps play\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 5: Vimeo - Autoplay + Controls\n              - identifier: page-5\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-5\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-5\n                          media_fit: center_inside\n                          url: \"https://player.vimeo.com/video/76979871?background=1\"\n                          media_type: vimeo\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"5. Vimeo — Autoplay + Controls\\nPause should survive swipe\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 6: Vimeo - Non-autoplay + Controls\n              - identifier: page-6\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-6\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-6\n                          media_fit: center_inside\n                          url: \"https://player.vimeo.com/video/76979871?background=0\"\n                          media_type: vimeo\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: false\n                            muted: false\n                            loop: false\n                            show_controls: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"6. Vimeo — Non-autoplay + Controls\\nShould not play until user taps play\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 7: Native - Autoplay + No Controls + auto_reset_position\n              - identifier: page-7\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-7\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-7\n                          media_type: video\n                          media_fit: center_crop\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: false\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"7. Native — Autoplay + No Controls\\nauto_reset_position: true (default)\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 8: YouTube - Non-autoplay + Controls\n              - identifier: page-8\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-8\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-8\n                          media_fit: center_inside\n                          url: \"https://www.youtube.com/embed/jNQXAC9IVRw/?autoplay=0&controls=1&loop=0&mute=0\"\n                          media_type: youtube\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: false\n                            muted: false\n                            loop: false\n                            show_controls: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"8. YouTube — Non-autoplay + Controls\\nShould not play until user taps play\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n\n              # PAGE 9: Native + Controller (shared mute with page 10)\n              - identifier: page-9\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-9\n                  mute_group:\n                    identifier: shared-mute\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-9\n                          media_type: video\n                          media_fit: center_crop\n                          url: \"https://commondatastorage.googleapis.com/gtv-videos-bucket/sample/BigBuckBunny.mp4\"\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: false\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"9. Native — Controller\\nShared mute with page 10\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n                      - position:\n                          horizontal: center\n                          vertical: bottom\n                        margin:\n                          bottom: 40\n                        size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                            - size:\n                                width: 56\n                                height: 56\n                              margin:\n                                end: 16\n                              view:\n                                type: stack_image_button\n                                identifier: play_pause_btn_9\n                                button_click:\n                                  - video_toggle_play\n                                localized_content_description:\n                                  refs:\n                                    - ua_play\n                                  fallback: Play\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: play\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: pause\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                            - size:\n                                width: 56\n                                height: 56\n                              view:\n                                type: stack_image_button\n                                identifier: mute_btn_9\n                                button_click:\n                                  - video_toggle_mute\n                                localized_content_description:\n                                  refs:\n                                    - ua_mute\n                                  fallback: Mute\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: unmute\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: mute\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n\n              # PAGE 10: YouTube + Controller (shared mute with page 9)\n              - identifier: page-10\n                type: pager_item\n                view:\n                  type: video_controller\n                  identifier: vc-page-10\n                  mute_group:\n                    identifier: shared-mute\n                  view:\n                    type: container\n                    items:\n                      - position:\n                          horizontal: center\n                          vertical: center\n                        size:\n                          width: 100%\n                          height: 100%\n                        ignore_safe_area: true\n                        view:\n                          type: media\n                          identifier: video-10\n                          media_fit: center_inside\n                          url: \"https://www.youtube.com/embed/dQw4w9WgXcQ/?autoplay=1&controls=1&loop=1&mute=1\"\n                          media_type: youtube\n                          video:\n                            aspect_ratio: 1.77777777777778\n                            autoplay: true\n                            muted: true\n                            loop: true\n                            show_controls: true\n                      - position:\n                          horizontal: center\n                          vertical: top\n                        margin:\n                          top: 64\n                          start: 16\n                          end: 16\n                        size:\n                          width: 100%\n                          height: auto\n                        view:\n                          type: label\n                          text: \"10. YouTube — Controller\\nShared mute with page 9\"\n                          text_appearance:\n                            font_size: 20\n                            alignment: center\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            styles:\n                              - bold\n                      - position:\n                          horizontal: center\n                          vertical: bottom\n                        margin:\n                          bottom: 40\n                        size:\n                          width: auto\n                          height: auto\n                        view:\n                          type: linear_layout\n                          direction: horizontal\n                          items:\n                            - size:\n                                width: 56\n                                height: 56\n                              margin:\n                                end: 16\n                              view:\n                                type: stack_image_button\n                                identifier: play_pause_btn_10\n                                button_click:\n                                  - video_toggle_play\n                                localized_content_description:\n                                  refs:\n                                    - ua_play\n                                  fallback: Play\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: play\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: pause\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - playing\n                                        value:\n                                          equals: true\n                            - size:\n                                width: 56\n                                height: 56\n                              view:\n                                type: stack_image_button\n                                identifier: mute_btn_10\n                                button_click:\n                                  - video_toggle_mute\n                                localized_content_description:\n                                  refs:\n                                    - ua_mute\n                                  fallback: Mute\n                                items:\n                                  - type: icon\n                                    icon:\n                                      type: icon\n                                      icon: unmute\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: '#FFFFFF'\n                                          alpha: 1\n                                      scale: 0.5\n                                view_overrides:\n                                  items:\n                                    - value:\n                                        - type: icon\n                                          icon:\n                                            type: icon\n                                            icon: mute\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: '#FFFFFF'\n                                                alpha: 1\n                                            scale: 0.5\n                                      when_state_matches:\n                                        scope:\n                                          - $video\n                                          - current\n                                          - muted\n                                        value:\n                                          equals: true\n\n        # Dismiss button\n        - position:\n            horizontal: end\n            vertical: top\n          size:\n            width: 48\n            height: 48\n          view:\n            type: image_button\n            identifier: dismiss_button\n            button_click:\n              - dismiss\n            localized_content_description:\n              refs:\n                - ua_dismiss\n              fallback: Dismiss\n            image:\n              type: icon\n              icon: close\n              scale: 0.4\n              color:\n                default:\n                  type: hex\n                  hex: '#FFFFFF'\n                  alpha: 1\n\n        # Pager indicator\n        - position:\n            horizontal: center\n            vertical: bottom\n          margin:\n            bottom: 16\n          size:\n            width: auto\n            height: 8\n          view:\n            type: pager_indicator\n            spacing: 8\n            bindings:\n              selected:\n                shapes:\n                  - type: ellipse\n                    scale: 1\n                    color:\n                      default:\n                        type: hex\n                        hex: '#FFFFFF'\n                        alpha: 1\n              unselected:\n                shapes:\n                  - type: ellipse\n                    scale: 1\n                    color:\n                      default:\n                        type: hex\n                        hex: '#FFFFFF'\n                        alpha: 0.4\ndisplay_type: layout\nname: Video Test Matrix\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video-youtube.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.2\n  dismiss_on_touch_outside: false\nview:\n  type: video_controller\n  identifier: vc-yt\n  view:\n    type: container\n    background_color:\n      default:\n        type: hex\n        hex: '#000000'\n        alpha: 1\n    items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        ignore_safe_area: true\n        view:\n          type: media\n          identifier: yt-video\n          media_type: youtube\n          media_fit: center_crop\n          url: \"https://www.youtube.com/embed/YE7VzlLtp-4\"\n          video:\n            aspect_ratio: 1.77777777777778\n            autoplay: true\n            muted: true\n            loop: false\n            show_controls: false\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 48\n          height: 48\n        view:\n          type: image_button\n          identifier: dismiss_button\n          button_click:\n            - dismiss\n          localized_content_description:\n            refs:\n              - ua_dismiss\n            fallback: Dismiss\n          image:\n            type: icon\n            icon: close\n            scale: 0.4\n            color:\n              default:\n                type: hex\n                hex: '#FFFFFF'\n                alpha: 1\n      - position:\n          horizontal: center\n          vertical: top\n        margin:\n          top: 64\n          start: 16\n          end: 16\n        size:\n          width: 100%\n          height: auto\n        view:\n          type: linear_layout\n          direction: vertical\n          items:\n            - size:\n                width: 100%\n                height: auto\n              view:\n                type: label\n                text: Big Buck Bunny\n                text_appearance:\n                  font_size: 28\n                  alignment: center\n                  color:\n                    default:\n                      type: hex\n                      hex: '#FFFFFF'\n                      alpha: 1\n                  styles:\n                    - bold\n            - size:\n                width: 100%\n                height: auto\n              margin:\n                top: 8\n              view:\n                type: label\n                text: YouTube background video — tests play/pause and mute state sync via onStateChange.\n                text_appearance:\n                  font_size: 16\n                  alignment: center\n                  color:\n                    default:\n                      type: hex\n                      hex: '#FFFFFF'\n                      alpha: 0.85\n      - position:\n          horizontal: center\n          vertical: bottom\n        margin:\n          bottom: 40\n        size:\n          width: auto\n          height: auto\n        view:\n          type: linear_layout\n          direction: horizontal\n          items:\n            - size:\n                width: 56\n                height: 56\n              margin:\n                end: 16\n              view:\n                type: stack_image_button\n                identifier: play_pause_btn\n                button_click:\n                  - video_toggle_play\n                localized_content_description:\n                  refs:\n                    - ua_play\n                  fallback: Play\n                items:\n                  - type: icon\n                    icon:\n                      type: icon\n                      icon: play\n                      color:\n                        default:\n                          type: hex\n                          hex: '#FFFFFF'\n                          alpha: 1\n                      scale: 0.5\n                view_overrides:\n                  localized_content_description:\n                    - value:\n                        refs:\n                          - ua_pause\n                        fallback: Pause\n                      when_state_matches:\n                        scope:\n                          - $video\n                          - current\n                          - playing\n                        value:\n                          equals: true\n                  items:\n                    - value:\n                        - type: icon\n                          icon:\n                            type: icon\n                            icon: pause\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            scale: 0.5\n                      when_state_matches:\n                        scope:\n                          - $video\n                          - current\n                          - playing\n                        value:\n                          equals: true\n            - size:\n                width: 56\n                height: 56\n              view:\n                type: stack_image_button\n                identifier: mute_btn\n                button_click:\n                  - video_toggle_mute\n                localized_content_description:\n                  refs:\n                    - ua_mute\n                  fallback: Mute\n                items:\n                  - type: icon\n                    icon:\n                      type: icon\n                      icon: unmute\n                      color:\n                        default:\n                          type: hex\n                          hex: '#FFFFFF'\n                          alpha: 1\n                      scale: 0.5\n                view_overrides:\n                  localized_content_description:\n                    - value:\n                        refs:\n                          - ua_unmute\n                        fallback: Unmute\n                      when_state_matches:\n                        scope:\n                          - $video\n                          - current\n                          - muted\n                        value:\n                          equals: true\n                  items:\n                    - value:\n                        - type: icon\n                          icon:\n                            type: icon\n                            icon: mute\n                            color:\n                              default:\n                                type: hex\n                                hex: '#FFFFFF'\n                                alpha: 1\n                            scale: 0.5\n                      when_state_matches:\n                        scope:\n                          - $video\n                          - current\n                          - muted\n                        value:\n                          equals: true\ndisplay_type: layout\nname: Video YouTube\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/video_cropping.yml",
    "content": "---\npresentation:\n  dismiss_on_touch_outside: true\n  default_placement:\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        alpha: 0.5\n        hex: \"#000000\"\n        type: hex\n    size:\n      height: 100%\n      width: 100%\n  type: modal\nversion: 1\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n      - position:\n          vertical: center\n          horizontal: center\n        size:\n          height: 100%\n          width: 100%\n        border:\n          radius: 25\n        margin:\n          top: 36\n        view:\n          type: pager\n          items:\n            - identifier: \"page-1\"\n              view:\n                type: container\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: scroll_layout\n                      direction: vertical\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Wide Image (100% x auto)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 24\n                              end: 24\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              media_fit: center_inside\n                              media_type: video\n                              type: media\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                  aspect_ratio: 0.5625\n                                  show_controls: true\n                                  autoplay: true\n                                  muted: true\n                                  loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Center (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              media_type: video\n                              video:\n                                  aspect_ratio: 0.5625\n                                  show_controls: true\n                                  autoplay: true\n                                  muted: true\n                                  loop: true\n                              type: media\n                              position:\n                                horizontal: center\n                                vertical: center\n                              video:\n                                  aspect_ratio: 0.5625\n                                  show_controls: true\n                                  autoplay: true\n                                  muted: true\n                                  loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Top Start (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: start\n                                vertical: top\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                  aspect_ratio: 0.5625\n                                  show_controls: true\n                                  autoplay: true\n                                  muted: true\n                                  loop: true\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Bottom End (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: end\n                                vertical: bottom\n                              url: https://storage.googleapis.com/airship-media-url/ProductTeam/Maxime/PaddleInMP4.mp4\n                              video:\n                                  aspect_ratio: 0.5625\n                                  show_controls: true\n                                  autoplay: true\n                                  muted: true\n                                  loop: true\n            - identifier: \"page-2\"\n              view:\n                type: container\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: 100%\n                      width: 100%\n                    view:\n                      type: scroll_layout\n                      direction: vertical\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Tall Image (100% x auto)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 48\n                              end: 48\n                            size:\n                              width: 100%\n                              height: auto\n                            view:\n                              media_fit: center_inside\n                              media_type: video\n                              type: media\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Center (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: center\n                                vertical: center\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Top Start (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: start\n                                vertical: top\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n\n                          - margin:\n                              top: 8\n                            size:\n                              width: auto\n                              height: auto\n                            view:\n                              type: label\n                              text: \"Crop Bottom End (150 x 150)\"\n                              text_appearance:\n                                font_size: 14\n                                color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                          - margin:\n                              top: 8\n                              bottom: 8\n                              start: 8\n                              end: 8\n                            size:\n                              width: 150\n                              height: 150\n                            view:\n                              media_fit: fit_crop\n                              border:\n                                stroke_color:\n                                  default:\n                                    hex: \"#000000\"\n                                    alpha: 1\n                                stroke_width: 1\n                              media_type: video\n                              type: media\n                              position:\n                                horizontal: end\n                                vertical: bottom\n                              url: https://upload.wikimedia.org/wikipedia/commons/thumb/6/61/Multnomah_Falls_and_Bridge.jpg/768px-Multnomah_Falls_and_Bridge.jpg\n      - size:\n          height: 16\n          width: auto\n        position:\n          vertical: top\n          horizontal: center\n        margin:\n          top: 12\n        view:\n          type: pager_indicator\n          carousel_identifier: CAROUSEL_ID\n          border:\n            radius: 8\n          spacing: 4\n          bindings:\n            selected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  scale: 0.75\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: ellipse\n                  aspect_ratio: 1\n                  scale: 0.75\n                  border:\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#333333\"\n                        alpha: 1\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n      - position:\n          vertical: top\n          horizontal: end\n        size:\n          width: 36\n          height: 36\n        margin:\n          top: 0\n          end: 0\n        view:\n          type: image_button\n          identifier: x_button\n          button_click: [ dismiss ]\n          image:\n            type: icon\n            icon: close\n            scale: 0.5\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/view_overrides.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    margin:\n      start: 16\n      end: 16\n    size:\n      width: 100%\n      height: auto\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.75\n    border:\n      stroke_color:\n        default:\n          hex: \"#000000\"\n          alpha: 1\n      stroke_width: 3\n      radius: 10\n    background_color:\n      default:\n        hex: '#ffffff'\n        alpha: 1\nview:\n  type: container\n  items:\n    - size:\n        height: auto\n        width: 100%\n      position:\n        vertical: center\n        horizontal: center\n      margin:\n        start: 32\n        end: 32\n        top: 32\n        bottom: 32\n      view:\n        type: linear_layout\n        direction: vertical\n        background_color:\n          default:\n            hex: \"#ffffff\"\n            alpha: 1\n        border:\n            stroke_color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n            stroke_width: 3\n        view_overrides:\n          background_color:\n            - value:\n                default:\n                  hex: \"#ffbbbb\"\n                  alpha: 1\n              when_state_matches:\n                  key: favorite_color\n                  value:\n                    equals: red\n            - value:\n                default:\n                  hex: \"#bbffbb\"\n                  alpha: 1\n              when_state_matches:\n                  key: favorite_color\n                  value:\n                    equals: green\n            - value:\n                default:\n                  hex: \"#bbbbff\"\n                  alpha: 1\n              when_state_matches:\n                  key: favorite_color\n                  value:\n                    equals: blue\n          border:\n            - value:\n                stroke_color:\n                  default:\n                    hex: \"#ff0000\"\n                    alpha: 1\n                stroke_width: 3\n              when_state_matches:\n                key: favorite_color\n                value:\n                  equals: red\n            - value:\n                stroke_color:\n                  default:\n                    hex: \"#00ff00\"\n                    alpha: 1\n                stroke_width: 3\n              when_state_matches:\n                  key: favorite_color\n                  value:\n                    equals: green\n            - value:\n                stroke_color:\n                  default:\n                    hex: \"#0000ff\"\n                    alpha: 1\n                stroke_width: 3\n              when_state_matches:\n                  key: favorite_color\n                  value:\n                    equals: blue\n        items:\n          - size:\n              height: auto\n              width: 100%\n            view:\n              type: linear_layout\n              direction: horizontal\n              items:\n                - size:\n                    height: auto\n                    width: auto\n                  margin:\n                    start: 16\n                  view:\n                    type: label\n                    text: 'Favorite Color:'\n                    text_appearance:\n                      alignment: start\n                      styles:\n                      - bold\n                      - underlined\n                      font_size: 16\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                - size:\n                    height: auto\n                    width: 100%\n                  margin:\n                    start: 16\n                  view:\n                    type: label\n                    text: \"\"\n                    text_appearance:\n                      alignment: start\n                      styles:\n                      - bold\n                      font_size: 16\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                    view_overrides:\n                      text:\n                        - value: Red\n                          when_state_matches:\n                              key: favorite_color\n                              value:\n                                equals: red\n                        - value: Green\n                          when_state_matches:\n                              key: favorite_color\n                              value:\n                                equals: green\n                        - value: Blue\n                          when_state_matches:\n                              key: favorite_color\n                              value:\n                                equals: blue\n          - size:\n              height: auto\n              width: 300\n            margin:\n              start: 16\n              end: 16\n              top: 16\n              bottom: 8\n            view:\n              type: label_button\n              identifier: red\n              background_color:\n                default:\n                  hex: \"#FF3333\"\n                  alpha: 1\n              label:\n                type: label\n                text: Red\n                text_appearance:\n                  font_size: 10\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              event_handlers:\n                - type: tap\n                  state_actions:\n                    - type: set\n                      key: favorite_color\n                      value: red\n          - size:\n              height: auto\n              width: 300\n            margin:\n              start: 16\n              end: 16\n              bottom: 8\n              top: 8\n            view:\n              type: label_button\n              identifier: green\n              background_color:\n                default:\n                  hex: \"#33FF33\"\n                  alpha: 1\n              label:\n                type: label\n                text: Green\n                text_appearance:\n                  font_size: 10\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              event_handlers:\n                - type: tap\n                  state_actions:\n                    - type: set\n                      key: favorite_color\n                      value: green\n          - size:\n              height: auto\n              width: 300\n            margin:\n              start: 16\n              end: 16\n              top: 8\n              bottom: 16\n            view:\n              type: label_button\n              identifier: blue\n              background_color:\n                default:\n                  hex: \"#3333FF\"\n                  alpha: 1\n              label:\n                type: label\n                text: Blue\n                text_appearance:\n                  font_size: 10\n                  alignment: center\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              event_handlers:\n                - type: tap\n                  state_actions:\n                    - type: set\n                      key: favorite_color\n                      value: blue"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wide-image-pager-test.yml",
    "content": "version: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: 100%\n    ignore_safe_area: true\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.4\n  dismiss_on_touch_outside: false\nview:\n  type: pager_controller\n  identifier: wide-image-pager-controller\n  view:\n    type: container\n    background_color:\n      default:\n        type: hex\n        hex: '#1A1A2E'\n        alpha: 1\n    items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: pager\n          items:\n            # PAGE 1 - Super wide aspect ratio image, center_crop overflows\n            - identifier: page-wide-1\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 100%\n                    view:\n                      type: media\n                      identifier: wide-image-1\n                      media_type: image\n                      media_fit: fit_crop\n                      position:\n                        horizontal: center\n                        vertical: center\n                      url: \"https://picsum.photos/id/10/5000/400\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 100\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: label\n                      text: \"Page 1 — Wide Aspect Ratio (15:1) center_crop\"\n                      text_appearance:\n                        font_size: 20\n                        alignment: center\n                        color:\n                          default:\n                            type: hex\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        styles:\n                          - bold\n\n            # PAGE 2 - Button page (test if touches work)\n            - identifier: page-button\n              type: pager_item\n              view:\n                type: container\n                background_color:\n                  default:\n                    type: hex\n                    hex: '#2D2D44'\n                    alpha: 1\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 80%\n                      height: auto\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            bottom: 16\n                          view:\n                            type: label\n                            text: \"Page 2 — Button Test\"\n                            text_appearance:\n                              font_size: 24\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 1\n                              styles:\n                                - bold\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            bottom: 24\n                          view:\n                            type: label\n                            text: \"If the wide images from pages 1 & 3 block touches, this button won't work.\"\n                            text_appearance:\n                              font_size: 14\n                              alignment: center\n                              color:\n                                default:\n                                  type: hex\n                                  hex: '#FFFFFF'\n                                  alpha: 0.7\n                        - size:\n                            width: 100%\n                            height: 50\n                          view:\n                            type: label_button\n                            identifier: test-button\n                            label:\n                              type: label\n                              text: \"TAP ME — Dismiss\"\n                              text_appearance:\n                                font_size: 18\n                                alignment: center\n                                color:\n                                  default:\n                                    type: hex\n                                    hex: '#FFFFFF'\n                                    alpha: 1\n                                styles:\n                                  - bold\n                            background_color:\n                              default:\n                                type: hex\n                                hex: '#4CAF50'\n                                alpha: 1\n                            border:\n                              radius: 12\n                            button_click:\n                              - dismiss\n\n            # PAGE 3 - Another super wide aspect ratio image, center_crop overflows\n            - identifier: page-wide-3\n              type: pager_item\n              view:\n                type: container\n                items:\n                  - position:\n                      horizontal: center\n                      vertical: center\n                    size:\n                      width: 100%\n                      height: 300\n                    view:\n                      type: media\n                      identifier: wide-image-3\n                      media_type: image\n                      media_fit: fit_crop\n                      position:\n                        horizontal: center\n                        vertical: center\n                      url: \"https://picsum.photos/id/47/5000/400\"\n                  - position:\n                      horizontal: center\n                      vertical: bottom\n                    margin:\n                      bottom: 100\n                    size:\n                      width: 100%\n                      height: auto\n                    view:\n                      type: label\n                      text: \"Page 3 — Wide Aspect Ratio (15:1) center_crop\"\n                      text_appearance:\n                        font_size: 20\n                        alignment: center\n                        color:\n                          default:\n                            type: hex\n                            hex: '#FFFFFF'\n                            alpha: 1\n                        styles:\n                          - bold\n\n      # Dismiss button\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 48\n          height: 48\n        margin:\n          top: 8\n          end: 8\n        view:\n          type: image_button\n          identifier: dismiss_button\n          button_click:\n            - dismiss\n          localized_content_description:\n            refs:\n              - ua_dismiss\n            fallback: Dismiss\n          image:\n            type: icon\n            icon: close\n            scale: 0.4\n            color:\n              default:\n                type: hex\n                hex: '#FFFFFF'\n                alpha: 1\n\n      # Pager indicator\n      - position:\n          horizontal: center\n          vertical: bottom\n        margin:\n          bottom: 24\n        size:\n          width: auto\n          height: 8\n        view:\n          type: pager_indicator\n          spacing: 8\n          bindings:\n            selected:\n              shapes:\n                - type: ellipse\n                  scale: 1\n                  color:\n                    default:\n                      type: hex\n                      hex: '#FFFFFF'\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: ellipse\n                  scale: 1\n                  color:\n                    default:\n                      type: hex\n                      hex: '#FFFFFF'\n                      alpha: 0.4\ndisplay_type: layout\nname: Wide Image Pager Test\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-modal-score.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.75\nview:\n  type: form_controller\n  identifier: parent_form\n  submit: submit_event\n  view:\n    type: scroll_layout\n    direction: vertical\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#ffffff\"\n          alpha: 1\n      items:\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 16\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: 100% x auto\\nmaxItemsPerLine: 11\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 1 (0 - 10)\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 24\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 11\n                line_spacing: 24\n              bindings:\n                selected:\n                  shapes:\n                  - type: ellipse\n                    color:\n                      default:\n                        hex: \"#000000\"\n                        alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                    - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                  - type: ellipse\n                    border:\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          hex: \"#999999\"\n                          alpha: 1\n                    color:\n                      default:\n                        hex: \"#dedede\"\n                        alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: auto x auto\\nmaxItemsPerLine: 6\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 2 (0 - 10)\n      - size:\n          width: auto\n          height: auto\n        margin:\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 0\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 6\n                line_spacing: 0\n              bindings:\n                selected:\n                  shapes:\n                    - type: ellipse\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                      - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                    - type: ellipse\n                      border:\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            hex: \"#999999\"\n                            alpha: 1\n                      color:\n                        default:\n                          hex: \"#dedede\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: 100% x auto\\nmaxItemsPerLine: 11\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 3 (0 - 10)\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 0\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 11\n                line_spacing: 0\n              bindings:\n                selected:\n                  shapes:\n                    - type: ellipse\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                      - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                    - type: rectangle\n                      border:\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            hex: \"#999999\"\n                            alpha: 1\n                      color:\n                        default:\n                          hex: \"#dedede\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: 100% x auto\\nmaxItemsPerLine: 6\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 4 (0 - 10)\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 0\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 6\n                line_spacing: 0\n              bindings:\n                selected:\n                  shapes:\n                    - type: rectangle\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                      - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                    - type: rectangle\n                      border:\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            hex: \"#999999\"\n                            alpha: 1\n                      color:\n                        default:\n                          hex: \"#dedede\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      # BOTTOM-PINNED BUTTON\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 16\n          bottom: 16\n          start: 16\n          end: 16\n        view:\n          type: label_button\n          identifier: SUBMIT_BUTTON\n          background_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n          button_click: [\"form_submit\", \"cancel\"]\n          enabled: [\"form_validation\"]\n          label:\n            type: label\n            text: 'SEND IT!'\n            text_appearance:\n              font_size: 14\n              alignment: center\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-multi-page.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: 80%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    border:\n      radius: 30\n      stroke_width: 2\n      stroke_color:\n        default:\n          hex: \"#333333\"\n          alpha: 0.8\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n      - position:\n          vertical: center\n          horizontal: center\n        size:\n          height: 100%\n          width: 100%\n        border:\n          radius: 25\n        view:\n          type: pager\n          items:\n            - identifier: \"pager-page-1-id\"\n              accessibility_actions:\n                - type: default\n                  reporting_metadata:\n                    key: \"page_1_next\"\n                  localized_content_description:\n                    fallback: \"Next Page\"\n                    ref: \"ua_next\"\n                  actions:\n                    - add_tags_action: \"page_1_next_action\"\n                  behaviors:\n                    - pager_next\n                - type: escape\n                  reporting_metadata:\n                    key: \"page_1_escape\"\n                  localized_content_description:\n                    fallback: \"Dismiss\"\n                    ref: \"ua_escape\"\n                  actions:\n                    - add_tags_action: \"page_1_dismiss_action\"\n                  behaviors:\n                    - dismiss\n              automated_actions:\n                - identifier: \"auto_announce_page_1\"\n                  delay: 0.5\n                  behaviors: [\"pager_pause\"]\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#FF0000\"\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_1_nps_form\n                      nps_identifier: score_identifier_1\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier_1\n                              required: true\n                              style:\n                                type: number_range\n                                start: 989\n                                end: 1000\n                                spacing: 42\n                                wrapping:\n                                  line_spacing: 10\n                                  max_items_per_line: 11\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#FFFFFF\"\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 42\n                                      color:\n                                        default:\n                                          hex: \"#0F0FF0\"\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 42\n                                      color:\n                                        default:\n                                          hex: \"#ffffff\"\n                                          alpha: 1\n                              content_description: \"Rate your experience from 0 to 10\"\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              button_click: [\"form_submit\", \"cancel\"]\n                              enabled: [\"form_validation\"]\n                              display_actions:\n                                add_tags_action: 'pager-page-1-form-submit'\n                              label:\n                                type: label\n                                text: width:auto height:auto\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: \"#000000\"\n                                      alpha: 1\n                              content_description: \"Submit button\"\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_1\n                      button_click: [ dismiss ]\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: \"#000000\"\n                            alpha: 1\n                      content_description: \"Close button\"\n            - identifier: \"pager-page-2-id\"\n              display_actions:\n                add_tags_action: 'pager-page-2x'\n              accessibility_actions:\n                - type: default\n                  reporting_metadata:\n                    key: \"page_2_previous\"\n                  localized_content_description:\n                    fallback: \"Previous Page\"\n                    ref: \"ua_previous\"\n                  actions:\n                    - add_tags_action: \"page_2_previous_action\"\n                  behaviors:\n                    - pager_previous\n                - type: escape\n                  reporting_metadata:\n                    key: \"page_2_escape\"\n                  localized_content_description:\n                    fallback: \"Dismiss\"\n                    ref: \"ua_escape\"\n                  actions:\n                    - add_tags_action: \"page_2_dismiss_action\"\n                  behaviors:\n                    - dismiss\n              automated_actions:\n                - identifier: \"auto_announce_page_2\"\n                  delay: 0.5\n                  behaviors: [\"pager_pause\"]\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_2_nps_form\n                      nps_identifier: score_identifier_2\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 200\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier_2\n                              required: true\n                              style:\n                                type: number_range\n                                start: 0\n                                end: 10\n                                spacing: 0\n                                wrapping:\n                                  line_spacing: 0\n                                  max_items_per_line: 11\n                                spacing: 8\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#FFFFFF\"\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: \"#ffffff\"\n                                          alpha: 1\n                              content_description: \"Rate your experience from 0 to 10\"\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              button_click: [\"form_submit\", \"cancel\"]\n                              enabled: [\"form_validation\"]\n                              display_actions:\n                                add_tags_action: 'pager-page-2-form-submit'\n                              label:\n                                type: label\n                                text: width:200 height:auto\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: \"#000000\"\n                                      alpha: 1\n                              content_description: \"Submit button\"\n            - identifier: \"pager-page-3-id\"\n              display_actions:\n                add_tags_action: 'pager-page-3x'\n              accessibility_actions:\n                - type: default\n                  reporting_metadata:\n                    key: \"page_3_previous\"\n                  localized_content_description:\n                    fallback: \"Previous Page\"\n                    ref: \"ua_previous\"\n                  actions:\n                    - add_tags_action: \"page_3_previous_action\"\n                  behaviors:\n                    - pager_previous\n                - type: escape\n                  reporting_metadata:\n                    key: \"page_3_escape\"\n                  localized_content_description:\n                    fallback: \"Dismiss\"\n                    ref: \"ua_escape\"\n                  actions:\n                    - add_tags_action: \"page_3_dismiss_action\"\n                  behaviors:\n                    - dismiss\n              automated_actions:\n                - identifier: \"auto_announce_page_3\"\n                  delay: 0.5\n                  behaviors: [\"pager_pause\"]\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FFF0\"\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_2_nps_form\n                      nps_identifier: score_identifier_2\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 50\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier_2\n                              required: true\n                              style:\n                                type: number_range\n                                start: 0\n                                end: 10\n                                spacing: 0\n                                wrapping:\n                                  line_spacing: 0\n                                  max_items_per_line: 11\n                                spacing: 2\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#FFFFFF\"\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 14\n                                      color:\n                                        default:\n                                          hex: \"#ffffff\"\n                                          alpha: 1\n                              content_description: \"Rate your experience from 0 to 10\"\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              button_click: [\"form_submit\", \"cancel\"]\n                              enabled: [\"form_validation\"]\n                              display_actions:\n                                add_tags_action: 'pager-page-2-form-submit'\n                              label:\n                                type: label\n                                text: width:50 height:auto\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: \"#000000\"\n                                      alpha: 1\n                              content_description: \"Submit button\"\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_2\n                      button_click: [ dismiss ]\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: \"#000000\"\n                            alpha: 1\n                      content_description: \"Close button\"\n          gestures:\n            - type: swipe\n              identifier: \"swipe_next\"\n              direction: up\n              behavior:\n                behaviors: [\"pager_next\"]\n            - type: tap\n              identifier: \"tap_next\"\n              location: end\n              behavior:\n                behaviors: [\"pager_next\"]\n      - size:\n          height: 16\n          width: auto\n        position:\n          vertical: bottom\n          horizontal: center\n        margin:\n          bottom: 8\n        view:\n          type: pager_indicator\n          spacing: 4\n          bindings:\n            selected:\n              shapes:\n                - type: rectangle\n                  aspect_ratio: 2.25\n                  scale: 0.9\n                  border:\n                    radius: 3\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 0.7\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: rectangle\n                  aspect_ratio: 2.25\n                  scale: .9\n                  border:\n                    radius: 3\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 0.7\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0\n          automated_accessibility_actions:\n            - type: announce\n          background_color:\n            default:\n              hex: \"#333333\"\n              alpha: 0.7\n          border:\n            radius: 8\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-nps-test-2.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  dismiss_on_touch_outside: true\n  default_placement:\n    size:\n      width: 90%\n      height: auto\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.75\nview:\n  type: form_controller\n  identifier: parent_form\n  submit: submit_event\n  view:\n    type: linear_layout\n    direction: vertical\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n    # Score 1 (0 - 10)\n    - size:\n        width: auto\n        height: 40\n      margin:\n        top: 8\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: nps_form_controller\n        identifier: nps_zero_to_ten_form\n        nps_identifier: nps_zero_to_ten\n        view:\n          type: score\n          identifier: \"nps_zero_to_ten\"\n          required: true\n          style:\n            type: number_range\n            spacing: 2\n            start: 0\n            end: 10\n            bindings:\n              selected:\n                shapes:\n                - type: rectangle\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 24\n                  styles:\n                  - bold\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n              unselected:\n                shapes:\n                - type: rectangle\n                  border:\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#999999\"\n                        alpha: 1\n                  color:\n                    default:\n                      hex: \"#dedede\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 24\n                  color:\n                    default:\n                      hex: \"#666666\"\n                      alpha: 1\n    # Score 2 (1 - 5)\n    - size:\n        width: auto\n        height: 24\n      margin:\n        top: 8\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: nps_form_controller\n        identifier: nps_zero_to_ten_form\n        nps_identifier: nps_zero_to_ten\n        view:\n          type: score\n          identifier: \"nps_zero_to_ten\"\n          required: true\n          style:\n            type: number_range\n            spacing: 8\n            start: 1\n            end: 5\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#FFDD33\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 24\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              unselected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#3333ff\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 24\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n\n    # Score 3 (97 - 105)\n    - size:\n        width: 90%\n        height: auto\n      margin:\n        top: 8\n        bottom: 8\n        start: 16\n        end: 16\n      view:\n        type: nps_form_controller\n        identifier: nps_zero_to_ten_form\n        nps_identifier: nps_zero_to_ten\n        view:\n          type: score\n          identifier: \"nps_zero_to_ten\"\n          required: true\n          style:\n            type: number_range\n            spacing: 8\n            wrapping:\n              line_spacing: 10\n              max_items_per_line: 11\n            start: 994\n            end: 1000\n            bindings:\n              selected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#FF0000\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 24\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 1\n              unselected:\n                shapes:\n                - type: ellipse\n                  color:\n                    default:\n                      hex: \"#0000FF\"\n                      alpha: 1\n                text_appearance:\n                  font_size: 24\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n\n    # BOTTOM-PINNED BUTTON\n    - size:\n        width: 100%\n        height: auto\n      margin:\n        top: 16\n        bottom: 16\n        start: 16\n        end: 16\n      view:\n        type: label_button\n        identifier: SUBMIT_BUTTON\n        background_color:\n          default:\n            hex: \"#000000\"\n            alpha: 1\n        button_click: [\"form_submit\", \"cancel\"]\n        enabled: [\"form_validation\"]\n        label:\n          type: label\n          text: 'SEND IT!'\n          text_appearance:\n            font_size: 14\n            alignment: center\n            color:\n              default:\n                hex: \"#ffffff\"\n                alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-nps-test-3.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 100%\n      height: auto\n    shade_color:\n      default:\n        hex: \"#000000\"\n        alpha: 0.75\nview:\n  type: form_controller\n  identifier: parent_form\n  submit: submit_event\n  view:\n    type: scroll_layout\n    direction: vertical\n    view:\n      type: linear_layout\n      direction: vertical\n      background_color:\n        default:\n          hex: \"#ffffff\"\n          alpha: 1\n      items:\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 16\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: 100% x auto\\nmaxItemsPerLine: 11\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 1 (0 - 10)\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 24\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 11\n                item_padding: 24\n              bindings:\n                selected:\n                  shapes:\n                  - type: ellipse\n                    color:\n                      default:\n                        hex: \"#000000\"\n                        alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                    - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                  - type: ellipse\n                    border:\n                      stroke_width: 1\n                      stroke_color:\n                        default:\n                          hex: \"#999999\"\n                          alpha: 1\n                    color:\n                      default:\n                        hex: \"#dedede\"\n                        alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: auto x auto\\nmaxItemsPerLine: 6\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 2 (0 - 10)\n      - size:\n          width: auto\n          height: auto\n        margin:\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 0\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 6\n                item_padding: 0\n              bindings:\n                selected:\n                  shapes:\n                    - type: ellipse\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                      - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                    - type: ellipse\n                      border:\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            hex: \"#999999\"\n                            alpha: 1\n                      color:\n                        default:\n                          hex: \"#dedede\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: 100% x auto\\nmaxItemsPerLine: 11\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 3 (0 - 10)\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 0\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 11\n                item_padding: 0\n              bindings:\n                selected:\n                  shapes:\n                    - type: ellipse\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                      - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                    - type: rectangle\n                      border:\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            hex: \"#999999\"\n                            alpha: 1\n                      color:\n                        default:\n                          hex: \"#dedede\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: label\n          text: \"size: 100% x auto\\nmaxItemsPerLine: 6\"\n          text_appearance:\n            font_size: 12\n            alignment: start\n            color:\n              default:\n                hex: \"#000000\"\n                alpha: 1\n\n      # Score 4 (0 - 10)\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 8\n          bottom: 8\n          start: 16\n          end: 16\n        view:\n          type: nps_form_controller\n          identifier: nps_zero_to_ten_form\n          nps_identifier: nps_zero_to_ten\n          view:\n            type: score\n            identifier: \"nps_zero_to_ten\"\n            required: true\n            style:\n              type: number_range\n              spacing: 0\n              start: 0\n              end: 10\n              wrapping:\n                max_items_per_line: 6\n                item_padding: 0\n              bindings:\n                selected:\n                  shapes:\n                    - type: rectangle\n                      color:\n                        default:\n                          hex: \"#000000\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    styles:\n                      - bold\n                    color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 1\n                unselected:\n                  shapes:\n                    - type: rectangle\n                      border:\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            hex: \"#999999\"\n                            alpha: 1\n                      color:\n                        default:\n                          hex: \"#dedede\"\n                          alpha: 1\n                  text_appearance:\n                    font_size: 14\n                    color:\n                      default:\n                        hex: \"#666666\"\n                        alpha: 1\n\n      # BOTTOM-PINNED BUTTON\n      - size:\n          width: 100%\n          height: auto\n        margin:\n          top: 16\n          bottom: 16\n          start: 16\n          end: 16\n        view:\n          type: label_button\n          identifier: SUBMIT_BUTTON\n          background_color:\n            default:\n              hex: \"#000000\"\n              alpha: 1\n          button_click: [\"form_submit\", \"cancel\"]\n          enabled: [\"form_validation\"]\n          label:\n            type: label\n            text: 'SEND IT!'\n            text_appearance:\n              font_size: 14\n              alignment: center\n              color:\n                default:\n                  hex: \"#ffffff\"\n                  alpha: 1\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-nps-test-4.yml",
    "content": "# MOBILE-3069\n---\nversion: 1\npresentation:\n  type: modal\n  android:\n    disable_back_button: false\n  dismiss_on_touch_outside: false\n  default_placement:\n    ignore_safe_area: false\n    device:\n      lock_orientation: portrait\n    size:\n      max_width: 100%\n      max_height: 100%\n      width: 100%\n      min_width: 100%\n      height: 100%\n      min_height: 100%\n    position:\n      horizontal: center\n      vertical: center\n    shade_color:\n      default:\n        type: hex\n        hex: \"#000000\"\n        alpha: 0.2\nview:\n  type: pager_controller\n  identifier: ea47eaac-4865-428a-bdfc-b512b899cc51\n  view:\n    identifier: 6d4062aa-16ff-4234-aa70-014dc0e1de88\n    nps_identifier: 58c4f1c2-99f5-4a9a-998c-e5561abf056a\n    type: nps_form_controller\n    submit: submit_event\n    response_type: nps\n    view:\n      type: container\n      items:\n      - position:\n          horizontal: center\n          vertical: center\n        size:\n          width: 100%\n          height: 100%\n        view:\n          type: pager\n          disable_swipe: true\n          items:\n          - identifier: '068715c7-6b86-4530-a930-8546607e3776'\n            type: pager_item\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n              - identifier: scroll_container\n                size:\n                  width: 100%\n                  height: 100%\n                view:\n                  type: scroll_layout\n                  direction: vertical\n                  view:\n                    type: linear_layout\n                    direction: vertical\n                    items:\n                    - size:\n                        width: 100%\n                        height: auto\n                      margin:\n                        top: 48\n                        bottom: 8\n                        start: 16\n                        end: 16\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                        - size:\n                            width: 100%\n                            height: auto\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - margin:\n                                top: 20\n                                bottom: 8\n                              size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: label\n                                text: How likely is it that you would recommend [your\n                                  company, product, etc.] to a friend or colleague?\n                                text_appearance:\n                                  font_size: 20\n                                  color:\n                                    default:\n                                      type: hex\n                                      hex: \"#111111\"\n                                      alpha: 1\n                                  alignment: center\n                                  styles:\n                                  - underlined\n                                  - bold\n                                  - italic\n                                  font_families:\n                                  - sans-serif\n                        - size:\n                            width: 100%\n                            height: auto\n                          margin:\n                            top: 0\n                            bottom: 0\n                          view:\n                            type: linear_layout\n                            direction: vertical\n                            items:\n                            - size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                - size:\n                                    width: 50%\n                                    height: auto\n                                  margin:\n                                    bottom: 4\n                                  view:\n                                    type: label\n                                    text: Not Likely\n                                    text_appearance:\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: \"#111111\"\n                                          alpha: 1\n                                      alignment: start\n                                      styles:\n                                      - underlined\n                                      - bold\n                                      - italic\n                                      font_families:\n                                      - sans-serif\n                                - size:\n                                    width: 50%\n                                    height: auto\n                                  margin:\n                                    bottom: 4\n                                  view:\n                                    type: label\n                                    text: Very Likely\n                                    text_appearance:\n                                      font_size: 16\n                                      color:\n                                        default:\n                                          type: hex\n                                          hex: \"#111111\"\n                                          alpha: 1\n                                      alignment: end\n                                      styles:\n                                      - underlined\n                                      - bold\n                                      - italic\n                                      font_families:\n                                      - sans-serif\n                            - size:\n                                width: 100%\n                                height: auto\n                              view:\n                                type: linear_layout\n                                direction: horizontal\n                                items:\n                                - size:\n                                    height: 50\n                                    width: 100%\n                                  view:\n                                    type: score\n                                    style:\n                                      type: number_range\n                                      start: 0\n                                      end: 10\n                                      spacing: 2\n                                      bindings:\n                                        selected:\n                                          shapes:\n                                          - type: rectangle\n                                            scale: 1\n                                            border:\n                                              radius: 2\n                                              stroke_width: 1\n                                              stroke_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#DDDDDD\"\n                                                alpha: 1\n                                          text_appearance:\n                                            alignment: center\n                                            font_size: 12\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                        unselected:\n                                          shapes:\n                                          - type: rectangle\n                                            scale: 1\n                                            border:\n                                              radius: 2\n                                              stroke_width: 1\n                                              stroke_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#000000\"\n                                                  alpha: 1\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#FFFFFF\"\n                                                alpha: 1\n                                          text_appearance:\n                                            font_size: 12\n                                            color:\n                                              default:\n                                                type: hex\n                                                hex: \"#000000\"\n                                                alpha: 1\n                                    identifier: 58c4f1c2-99f5-4a9a-998c-e5561abf056a\n                                    required: true\n                    - size:\n                        height: 100%\n                        width: 100%\n                      view:\n                        type: media\n                        media_fit: center_inside\n                        url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/9e13236f-de64-49ef-b77b-3140fa7a1f03\n                        media_type: image\n                      margin:\n                        top: 0\n                        bottom: 8\n                        start: 0\n                        end: 0\n              - margin:\n                  top: 8\n                  bottom: 0\n                  start: 16\n                  end: 16\n                size:\n                  height: auto\n                  width: 100%\n                view:\n                  type: linear_layout\n                  direction: horizontal\n                  items:\n                  - margin:\n                      top: 4\n                      bottom: 16\n                      start: 0\n                      end: 0\n                    size:\n                      width: 100%\n                      height: 48\n                    view:\n                      type: label_button\n                      identifier: submit_feedback--Submit\n                      label:\n                        type: label\n                        text: Submit\n                        text_appearance:\n                          font_size: 14\n                          color:\n                            default:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                          alignment: center\n                          styles: [ ]\n                          font_families:\n                          - sans-serif\n                      actions: { }\n                      enabled:\n                      - form_validation\n                      button_click:\n                      - form_submit\n                      - pager_next\n                      background_color:\n                        default:\n                          type: hex\n                          hex: \"#222222\"\n                          alpha: 1\n                      border:\n                        radius: 0\n                        stroke_width: 1\n                        stroke_color:\n                          default:\n                            type: hex\n                            hex: \"#222222\"\n                            alpha: 1\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n              background_color:\n                default:\n                  type: hex\n                  hex: \"#FFFFFF\"\n                  alpha: 1\n          - identifier: a232866d-dcc1-45f9-83e1-a6b5df90f40a\n            type: pager_item\n            view:\n              type: container\n              items:\n              - margin:\n                  start: 0\n                  end: 0\n                position:\n                  horizontal: center\n                  vertical: center\n                size:\n                  width: 100%\n                  height: 100%\n                view:\n                  type: linear_layout\n                  direction: horizontal\n                  items: [ ]\n                  background_color:\n                    default:\n                      type: hex\n                      hex: \"#FFFFFF\"\n                      alpha: 1\n              - margin:\n                  start: 0\n                  end: 0\n                position:\n                  horizontal: center\n                  vertical: center\n                size:\n                  width: 100%\n                  height: 100%\n                view:\n                  type: media\n                  media_fit: center_crop\n                  url: https://dl-staging.urbanairship.com/binary/public/Vl0wyG8kSyCyOUW98Wj4xg/84bdd2d5-f345-431a-aacc-dc246072ddb5\n                  media_type: image\n              - margin:\n                  top: 0\n                  bottom: 0\n                  start: 16\n                  end: 16\n                position:\n                  horizontal: center\n                  vertical: center\n                size:\n                  width: 100%\n                  height: 100%\n                view:\n                  type: linear_layout\n                  direction: vertical\n                  items:\n                  - identifier: scroll_container\n                    size:\n                      width: 100%\n                      height: 100%\n                    view:\n                      type: linear_layout\n                      direction: vertical\n                      items:\n                      - size:\n                          height: 100%\n                          width: 100%\n                        view:\n                          type: container\n                          items:\n                          - margin:\n                              bottom: 16\n                            position:\n                              horizontal: center\n                              vertical: center\n                            size:\n                              width: 100%\n                              height: 100%\n                            view:\n                              type: linear_layout\n                              direction: vertical\n                              items:\n                              - identifier: scroll_container\n                                size:\n                                  width: 100%\n                                  height: 100%\n                                view:\n                                  type: scroll_layout\n                                  direction: vertical\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items: [ ]\n      - position:\n          horizontal: end\n          vertical: top\n        size:\n          width: 48\n          height: 48\n        view:\n          type: image_button\n          image:\n            scale: 0.4\n            type: icon\n            icon: close\n            color:\n              default:\n                type: hex\n                hex: \"#F07ADC\"\n                alpha: 1\n          identifier: dismiss_button\n          button_click:\n          - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-nps-test-5.yml",
    "content": "\n        version: 1\n        presentation:\n          type: modal\n          placement_selectors:\n          - placement:\n              ignore_safe_area: true\n              size:\n                width: 50%\n                height: 60%\n              position:\n                horizontal: center\n                vertical: center\n              shade_color:\n                default:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n                selectors:\n                - platform: ios\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#868686\"\n                    alpha: 0.2\n                - platform: ios\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#868686\"\n                    alpha: 0.2\n                - platform: android\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#868686\"\n                    alpha: 0.2\n                - platform: android\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#868686\"\n                    alpha: 0.2\n                - platform: web\n                  dark_mode: false\n                  color:\n                    type: hex\n                    hex: \"#868686\"\n                    alpha: 0.2\n                - platform: web\n                  dark_mode: true\n                  color:\n                    type: hex\n                    hex: \"#868686\"\n                    alpha: 0.2\n              web:\n                ignore_shade: false\n              border:\n                radius: 7\n            window_size: large\n            orientation: landscape\n          android:\n            disable_back_button: false\n          dismiss_on_touch_outside: false\n          default_placement:\n            ignore_safe_area: false\n            size:\n              width: 90%\n              height: 55%\n            position:\n              horizontal: center\n              vertical: center\n            shade_color:\n              default:\n                type: hex\n                hex: \"#868686\"\n                alpha: 0.2\n              selectors:\n              - platform: ios\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n              - platform: ios\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n              - platform: android\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n              - platform: android\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n              - platform: web\n                dark_mode: false\n                color:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n              - platform: web\n                dark_mode: true\n                color:\n                  type: hex\n                  hex: \"#868686\"\n                  alpha: 0.2\n            web:\n              ignore_shade: true\n            border:\n              radius: 0\n        view:\n          type: state_controller\n          view:\n            type: pager_controller\n            identifier: acfa4641-a21b-4887-8ec9-ec3e23c73470\n            view:\n              type: linear_layout\n              direction: vertical\n              items:\n              - size:\n                  width: 100%\n                  height: 100%\n                view:\n                  identifier: 6f9f0cd7-5f05-4fbd-b8b2-0947f36a00ea\n                  nps_identifier: df83673d-a0a0-4b8c-b307-6d5c676e020e\n                  type: nps_form_controller\n                  content_description: \"How likely?\"\n                  submit: submit_event\n                  form_enabled:\n                  - form_submission\n                  response_type: nps\n                  view:\n                    type: container\n                    items:\n                    - position:\n                        horizontal: center\n                        vertical: center\n                      size:\n                        width: 100%\n                        height: 100%\n                      view:\n                        type: pager\n                        disable_swipe: true\n                        items:\n                        - identifier: 52bbe423-f050-46f8-8d34-aa9d15fd07a3\n                          type: pager_item\n                          view:\n                            type: container\n                            items:\n                            - size:\n                                width: 100%\n                                height: 100%\n                              position:\n                                horizontal: center\n                                vertical: center\n                              ignore_safe_area: false\n                              view:\n                                type: container\n                                items:\n                                - margin:\n                                    bottom: 0\n                                    top: 0\n                                    end: 0\n                                    start: 0\n                                  position:\n                                    horizontal: center\n                                    vertical: center\n                                  size:\n                                    width: 100%\n                                    height: 100%\n                                  view:\n                                    type: linear_layout\n                                    direction: vertical\n                                    items:\n                                    - identifier: scroll_container\n                                      size:\n                                        width: 100%\n                                        height: 100%\n                                      view:\n                                        type: scroll_layout\n                                        direction: vertical\n                                        view:\n                                          type: linear_layout\n                                          direction: vertical\n                                          items:\n                                          - identifier: df83673d-a0a0-4b8c-b307-6d5c676e020e\n                                            size:\n                                              width: 100%\n                                              height: auto\n                                            margin:\n                                              top: 48\n                                              bottom: 8\n                                              start: 16\n                                              end: 16\n                                            view:\n                                              type: linear_layout\n                                              direction: vertical\n                                              items:\n                                              - size:\n                                                  width: 100%\n                                                  height: auto\n                                                view:\n                                                  type: linear_layout\n                                                  direction: vertical\n                                                  items:\n                                                  - margin:\n                                                      top: 4\n                                                      bottom: 8\n                                                    size:\n                                                      width: 100%\n                                                      height: auto\n                                                    view:\n                                                      type: label\n                                                      text: How likely are you to\n                                                        recommend Airship to a friend\n                                                        or colleague?\n                                                      content_description: How likely\n                                                        are you to recommend Airship\n                                                        to a friend or colleague?\n                                                      labels:\n                                                        type: labels\n                                                        view_id: 2d6a001d-254d-4211-8dd3-074a2d43c7e5\n                                                        view_type: score\n                                                      text_appearance:\n                                                        font_size: 20\n                                                        color:\n                                                          default:\n                                                            type: hex\n                                                            hex: \"#FFFFFF\"\n                                                            alpha: 1\n                                                          selectors:\n                                                          - platform: ios\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: ios\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: android\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: false\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#FFFFFF\"\n                                                              alpha: 1\n                                                          - platform: web\n                                                            dark_mode: true\n                                                            color:\n                                                              type: hex\n                                                              hex: \"#000000\"\n                                                              alpha: 1\n                                                        alignment: center\n                                                        styles:\n                                                        - bold\n                                                        font_families:\n                                                        - sans-serif\n                                              - size:\n                                                  width: 100%\n                                                  height: auto\n                                                margin:\n                                                  top: 0\n                                                  bottom: 0\n                                                view:\n                                                  type: linear_layout\n                                                  direction: vertical\n                                                  items:\n                                                  - size:\n                                                      width: 100%\n                                                      height: auto\n                                                    view:\n                                                      type: linear_layout\n                                                      direction: horizontal\n                                                      items:\n                                                      - size:\n                                                          width: 50%\n                                                          height: auto\n                                                        margin:\n                                                          end: 4\n                                                          bottom: 4\n                                                        view:\n                                                          type: label\n                                                          text: \"Not Likely\"\n                                                          accessibility:\n                                                            accessibility_hidden: true\n                                                          text_appearance:\n                                                            font_size: 18\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            alignment: start\n                                                            styles: []\n                                                            font_families:\n                                                            - sans-serif\n                                                      - size:\n                                                          width: 50%\n                                                          height: auto\n                                                        margin:\n                                                          start: 4\n                                                          bottom: 4\n                                                        view:\n                                                          type: label\n                                                          text: \"Extremly Likely\"\n                                                          accessibility:\n                                                            accessibility_hidden: true\n                                                          text_appearance:\n                                                            font_size: 18\n                                                            color:\n                                                              default:\n                                                                type: hex\n                                                                hex: \"#FFFFFF\"\n                                                                alpha: 1\n                                                              selectors:\n                                                              - platform: ios\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: ios\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: android\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: false\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#FFFFFF\"\n                                                                  alpha: 1\n                                                              - platform: web\n                                                                dark_mode: true\n                                                                color:\n                                                                  type: hex\n                                                                  hex: \"#000000\"\n                                                                  alpha: 1\n                                                            alignment: end\n                                                            styles: []\n                                                            font_families:\n                                                            - sans-serif\n                                                  - size:\n                                                      width: 100%\n                                                      height: auto\n                                                    view:\n                                                      type: linear_layout\n                                                      direction: horizontal\n                                                      items:\n                                                      - size:\n                                                          height: auto\n                                                          width: 100%\n                                                        view:\n                                                          content_description: \"0 is not at all likely, 10 means extremely likely\"\n                                                          type: score\n                                                          style:\n                                                            type: number_range\n                                                            start: 0\n                                                            end: 10\n                                                            spacing: 2\n                                                            wrapping:\n                                                              line_spacing: 16\n                                                              max_items_per_line: 6\n                                                            bindings:\n                                                              selected:\n                                                                shapes:\n                                                                - type: rectangle\n                                                                  scale: 1\n                                                                  border:\n                                                                    radius: 45\n                                                                    stroke_width: 1\n                                                                    stroke_color:\n                                                                      default:\n                                                                        type: hex\n                                                                        hex: \"#000000\"\n                                                                        alpha: 1\n                                                                      selectors:\n                                                                      - platform: ios\n                                                                        dark_mode: false\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#000000\"\n                                                                          alpha: 1\n                                                                      - platform: ios\n                                                                        dark_mode: true\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#FFFFFF\"\n                                                                          alpha: 1\n                                                                      - platform: android\n                                                                        dark_mode: false\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#000000\"\n                                                                          alpha: 1\n                                                                      - platform: android\n                                                                        dark_mode: true\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#FFFFFF\"\n                                                                          alpha: 1\n                                                                      - platform: web\n                                                                        dark_mode: false\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#000000\"\n                                                                          alpha: 1\n                                                                      - platform: web\n                                                                        dark_mode: true\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#FFFFFF\"\n                                                                          alpha: 1\n                                                                  color:\n                                                                    default:\n                                                                      type: hex\n                                                                      hex: \"#000000\"\n                                                                      alpha: 1\n                                                                    selectors:\n                                                                    - platform: ios\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#000000\"\n                                                                        alpha: 1\n                                                                    - platform: ios\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#000000\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#000000\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                text_appearance:\n                                                                  alignment: center\n                                                                  font_families:\n                                                                  - sans-serif\n                                                                  font_size: 18\n                                                                  color:\n                                                                    default:\n                                                                      type: hex\n                                                                      hex: \"#004BFF\"\n                                                                      alpha: 1\n                                                                    selectors:\n                                                                    - platform: ios\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: ios\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                              unselected:\n                                                                shapes:\n                                                                - type: rectangle\n                                                                  scale: 1\n                                                                  border:\n                                                                    radius: 45\n                                                                    stroke_width: 1\n                                                                    stroke_color:\n                                                                      default:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                      selectors:\n                                                                      - platform: ios\n                                                                        dark_mode: false\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#004BFF\"\n                                                                          alpha: 1\n                                                                      - platform: ios\n                                                                        dark_mode: true\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#004BFF\"\n                                                                          alpha: 1\n                                                                      - platform: android\n                                                                        dark_mode: false\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#004BFF\"\n                                                                          alpha: 1\n                                                                      - platform: android\n                                                                        dark_mode: true\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#004BFF\"\n                                                                          alpha: 1\n                                                                      - platform: web\n                                                                        dark_mode: false\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#004BFF\"\n                                                                          alpha: 1\n                                                                      - platform: web\n                                                                        dark_mode: true\n                                                                        color:\n                                                                          type: hex\n                                                                          hex: \"#004BFF\"\n                                                                          alpha: 1\n                                                                  color:\n                                                                    default:\n                                                                      type: hex\n                                                                      hex: \"#FFFFFF\"\n                                                                      alpha: 1\n                                                                    selectors:\n                                                                    - platform: ios\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: ios\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#FFFFFF\"\n                                                                        alpha: 1\n                                                                text_appearance:\n                                                                  font_size: 18\n                                                                  font_families:\n                                                                  - sans-serif\n                                                                  color:\n                                                                    default:\n                                                                      type: hex\n                                                                      hex: \"#004BFF\"\n                                                                      alpha: 1\n                                                                    selectors:\n                                                                    - platform: ios\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: ios\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: android\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: false\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                                    - platform: web\n                                                                      dark_mode: true\n                                                                      color:\n                                                                        type: hex\n                                                                        hex: \"#004BFF\"\n                                                                        alpha: 1\n                                                          identifier: df83673d-a0a0-4b8c-b307-6d5c676e020e\n                                                          required: false\n                                          - identifier: 2d6a001d-254d-4211-8dd3-074a2d43c7e5\n                                            margin:\n                                              top: 30\n                                              bottom: 8\n                                              start: 16\n                                              end: 16\n                                            size:\n                                              width: 80%\n                                              height: auto\n                                            view:\n                                              type: label_button\n                                              identifier: submit_feedback--Submit\n                                                Rating\n                                              reporting_metadata:\n                                                trigger_link_id: 2d6a001d-254d-4211-8dd3-074a2d43c7e5\n                                              label:\n                                                type: label\n                                                text: Submit Rating\n                                                content_description: Submit Rating\n                                                text_appearance:\n                                                  font_size: 16\n                                                  color:\n                                                    default:\n                                                      type: hex\n                                                      hex: \"#004BFF\"\n                                                      alpha: 1\n                                                    selectors:\n                                                    - platform: ios\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#004BFF\"\n                                                        alpha: 1\n                                                    - platform: ios\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#004BFF\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#004BFF\"\n                                                        alpha: 1\n                                                    - platform: android\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#004BFF\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: false\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#004BFF\"\n                                                        alpha: 1\n                                                    - platform: web\n                                                      dark_mode: true\n                                                      color:\n                                                        type: hex\n                                                        hex: \"#004BFF\"\n                                                        alpha: 1\n                                                  alignment: center\n                                                  styles:\n                                                  - bold\n                                                  font_families:\n                                                  - sans-serif\n                                              actions: {}\n                                              enabled:\n                                              - form_validation\n                                              button_click:\n                                              - form_submit\n                                              - dismiss\n                                              background_color:\n                                                default:\n                                                  type: hex\n                                                  hex: \"#FFFFFF\"\n                                                  alpha: 1\n                                                selectors:\n                                                - platform: ios\n                                                  dark_mode: false\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: ios\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#000000\"\n                                                    alpha: 1\n                                                - platform: android\n                                                  dark_mode: false\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: android\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#000000\"\n                                                    alpha: 1\n                                                - platform: web\n                                                  dark_mode: false\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#FFFFFF\"\n                                                    alpha: 1\n                                                - platform: web\n                                                  dark_mode: true\n                                                  color:\n                                                    type: hex\n                                                    hex: \"#000000\"\n                                                    alpha: 1\n                                              border:\n                                                radius: 3\n                                                stroke_width: 0\n                                                stroke_color:\n                                                  default:\n                                                    type: hex\n                                                    hex: \"#63AFF1\"\n                                                    alpha: 1\n                                                  selectors:\n                                                  - platform: ios\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: ios\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: android\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: false\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                                  - platform: web\n                                                    dark_mode: true\n                                                    color:\n                                                      type: hex\n                                                      hex: \"#63AFF1\"\n                                                      alpha: 1\n                                              event_handlers:\n                                              - type: tap\n                                                state_actions:\n                                                - type: set\n                                                  key: submitted\n                                                  value: true\n                                          - size:\n                                              width: 100%\n                                              height: 100%\n                                            view:\n                                              type: linear_layout\n                                              direction: horizontal\n                                              items: []\n                            background_color:\n                              default:\n                                type: hex\n                                hex: \"#004BFF\"\n                                alpha: 1\n                              selectors:\n                              - platform: ios\n                                dark_mode: false\n                                color:\n                                  type: hex\n                                  hex: \"#004BFF\"\n                                  alpha: 1\n                              - platform: ios\n                                dark_mode: true\n                                color:\n                                  type: hex\n                                  hex: \"#004BFF\"\n                                  alpha: 1\n                              - platform: android\n                                dark_mode: false\n                                color:\n                                  type: hex\n                                  hex: \"#004BFF\"\n                                  alpha: 1\n                              - platform: android\n                                dark_mode: true\n                                color:\n                                  type: hex\n                                  hex: \"#004BFF\"\n                                  alpha: 1\n                              - platform: web\n                                dark_mode: false\n                                color:\n                                  type: hex\n                                  hex: \"#004BFF\"\n                                  alpha: 1\n                              - platform: web\n                                dark_mode: true\n                                color:\n                                  type: hex\n                                  hex: \"#004BFF\"\n                                  alpha: 1\n                      ignore_safe_area: false\n                    - position:\n                        horizontal: end\n                        vertical: top\n                      size:\n                        width: 48\n                        height: 48\n                      view:\n                        type: image_button\n                        image:\n                          scale: 0.4\n                          type: icon\n                          icon: close\n                          color:\n                            default:\n                              type: hex\n                              hex: \"#FFFFFF\"\n                              alpha: 1\n                            selectors:\n                            - platform: ios\n                              dark_mode: false\n                              color:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                            - platform: ios\n                              dark_mode: true\n                              color:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                            - platform: android\n                              dark_mode: false\n                              color:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                            - platform: android\n                              dark_mode: true\n                              color:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                            - platform: web\n                              dark_mode: false\n                              color:\n                                type: hex\n                                hex: \"#FFFFFF\"\n                                alpha: 1\n                            - platform: web\n                              dark_mode: true\n                              color:\n                                type: hex\n                                hex: \"#000000\"\n                                alpha: 1\n                        identifier: dismiss_button\n                        button_click:\n                        - dismiss\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/Scenes/Modal/wrapping-nps-test.yml",
    "content": "---\nversion: 1\npresentation:\n  type: modal\n  default_placement:\n    size:\n      width: 90%\n      height: 80%\n    shade_color:\n      default:\n        hex: '#000000'\n        alpha: 0.6\nview:\n  type: pager_controller\n  identifier: \"pager-controller-id\"\n  view:\n    type: container\n    border:\n      radius: 30\n      stroke_width: 2\n      stroke_color:\n        default:\n          hex: \"#333333\"\n          alpha: 0.8\n    background_color:\n      default:\n        hex: \"#ffffff\"\n        alpha: 1\n    items:\n      - position:\n          vertical: center\n          horizontal: center\n        size:\n          height: 100%\n          width: 100%\n        border:\n          radius: 25\n        view:\n          type: pager\n          items:\n            - identifier: \"pager-page-1-id\"\n              # display_actions:\n              #   add_tags_action: 'pager-page-1x'\n              accessibility_actions:\n                - type: default\n                  reporting_metadata:\n                    key: \"page_1_next\"\n                  localized_content_description:\n                    fallback: \"Next Page\"\n                    ref: \"ua_next\"\n                  actions:\n                    - add_tags_action: \"page_1_next_action\"\n                  behaviors:\n                    - pager_next\n                - type: escape\n                  reporting_metadata:\n                    key: \"page_1_escape\"\n                  localized_content_description:\n                    fallback: \"Dismiss\"\n                    ref: \"ua_escape\"\n                  actions:\n                    - add_tags_action: \"page_1_dismiss_action\"\n                  behaviors:\n                    - dismiss\n#              automated_actions:\n#                - identifier: \"auto_announce_page_1\"\n#                  delay: 0.5\n#                  behaviors: [\"pager_pause\"]\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#FF0000\"\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_1_nps_form\n                      nps_identifier: score_identifier_1\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier_1\n                              required: true\n                              style:\n                                type: number_range\n                                start: 990\n                                end: 1000\n                                spacing: 120\n                                wrapping:\n                                  line_spacing: 10\n                                  max_items_per_line: 11\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#FFFFFF\"\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 10\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 10\n                                      color:\n                                        default:\n                                          hex: \"#ffffff\"\n                                          alpha: 1\n                              content_description: \"Rate your experience from 0 to 10\"\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              button_click: [\"form_submit\", \"cancel\"]\n                              enabled: [\"form_validation\"]\n                              # display_actions:\n                              #   add_tags_action: 'pager-page-1-form-submit'\n                              label:\n                                type: label\n                                text: width:auto height:auto\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: \"#000000\"\n                                      alpha: 1\n                              content_description: \"Submit button\"\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_1\n                      button_click: [ dismiss ]\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: \"#000000\"\n                            alpha: 1\n                      content_description: \"Close button\"\n            - identifier: \"pager-page-2-id\"\n              # display_actions:\n              #   add_tags_action: 'pager-page-2x'\n              accessibility_actions:\n                - type: default\n                  reporting_metadata:\n                    key: \"page_2_previous\"\n                  localized_content_description:\n                    fallback: \"Previous Page\"\n                    ref: \"ua_previous\"\n                  actions:\n                    - add_tags_action: \"page_2_previous_action\"\n                  behaviors:\n                    - pager_previous\n                - type: escape\n                  reporting_metadata:\n                    key: \"page_2_escape\"\n                  localized_content_description:\n                    fallback: \"Dismiss\"\n                    ref: \"ua_escape\"\n                  actions:\n                    - add_tags_action: \"page_2_dismiss_action\"\n                  behaviors:\n                    - dismiss\n#              automated_actions:\n#                - identifier: \"auto_announce_page_2\"\n#                  delay: 0.5\n#                  behaviors: [\"pager_pause\"]\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FF00\"\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_2_nps_form\n                      nps_identifier: score_identifier_2\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 200\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier_2\n                              required: true\n                              style:\n                                type: number_range\n                                start: 0\n                                end: 10\n                                spacing: 0\n                                wrapping:\n                                  line_spacing: 0\n                                  max_items_per_line: 11\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#FFFFFF\"\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 88\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 88\n                                      color:\n                                        default:\n                                          hex: \"#ffffff\"\n                                          alpha: 1\n                              content_description: \"Rate your experience from 0 to 10\"\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              button_click: [\"form_submit\", \"cancel\"]\n                              enabled: [\"form_validation\"]\n                              # display_actions:\n                              #   add_tags_action: 'pager-page-2-form-submit'\n                              label:\n                                type: label\n                                text: width:200 height:auto\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: \"#000000\"\n                                      alpha: 1\n                              content_description: \"Submit button\"\n            - identifier: \"pager-page-3-id\"\n              # display_actions:\n              #   add_tags_action: 'pager-page-3x'\n              accessibility_actions:\n                - type: default\n                  reporting_metadata:\n                    key: \"page_3_previous\"\n                  localized_content_description:\n                    fallback: \"Previous Page\"\n                    ref: \"ua_previous\"\n                  actions:\n                    - add_tags_action: \"page_3_previous_action\"\n                  behaviors:\n                    - pager_previous\n                - type: escape\n                  reporting_metadata:\n                    key: \"page_3_escape\"\n                  localized_content_description:\n                    fallback: \"Dismiss\"\n                    ref: \"ua_escape\"\n                  actions:\n                    - add_tags_action: \"page_3_dismiss_action\"\n                  behaviors:\n                    - dismiss\n#              automated_actions:\n#                - identifier: \"auto_announce_page_3\"\n#                  delay: 0.5\n#                  behaviors: [\"pager_pause\"]\n              view:\n                type: container\n                background_color:\n                  default:\n                    hex: \"#00FFF0\"\n                    alpha: 0.5\n                items:\n                  - position:\n                      vertical: center\n                      horizontal: center\n                    size:\n                      height: auto\n                      width: auto\n                    view:\n                      type: nps_form_controller\n                      identifier: page_2_nps_form\n                      nps_identifier: score_identifier_2\n                      submit: submit_event\n                      view:\n                        type: linear_layout\n                        direction: vertical\n                        items:\n                          - size:\n                              width: 50\n                              height: auto\n                            margin:\n                              start: 8\n                              end: 8\n                            view:\n                              type: score\n                              identifier: score_identifier_2\n                              required: true\n                              style:\n                                type: number_range\n                                start: 0\n                                end: 10\n                                spacing: 0\n                                wrapping:\n                                  line_spacing: 0\n                                  max_items_per_line: 11\n                                bindings:\n                                  selected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#FFFFFF\"\n                                            alpha: 0\n                                    text_appearance:\n                                      font_size: 42\n                                      color:\n                                        default:\n                                          hex: \"#000000\"\n                                          alpha: 1\n                                  unselected:\n                                    shapes:\n                                      - type: ellipse\n                                        color:\n                                          default:\n                                            hex: \"#000000\"\n                                            alpha: 1\n                                    text_appearance:\n                                      font_size: 42\n                                      color:\n                                        default:\n                                          hex: \"#ffffff\"\n                                          alpha: 1\n                              content_description: \"Rate your experience from 0 to 10\"\n                          - size:\n                              width: auto\n                              height: auto\n                            margin:\n                              start: 16\n                              end: 16\n                            view:\n                              type: label_button\n                              identifier: submit_button\n                              background_color:\n                                default:\n                                  hex: \"#ffffff\"\n                                  alpha: 1\n                              button_click: [\"form_submit\", \"cancel\"]\n                              enabled: [\"form_validation\"]\n                              # display_actions:\n                              #   add_tags_action: 'pager-page-2-form-submit'\n                              label:\n                                type: label\n                                text: width:50 height:auto\n                                text_appearance:\n                                  font_size: 14\n                                  alignment: center\n                                  color:\n                                    default:\n                                      hex: \"#000000\"\n                                      alpha: 1\n                              content_description: \"Submit button\"\n                  - position:\n                      horizontal: end\n                      vertical: top\n                    size:\n                      height: 24\n                      width: 24\n                    margin:\n                      top: 8\n                      end: 8\n                    view:\n                      type: image_button\n                      identifier: close_button_2\n                      button_click: [ dismiss ]\n                      image:\n                        type: icon\n                        icon: close\n                        color:\n                          default:\n                            hex: \"#000000\"\n                            alpha: 1\n                      content_description: \"Close button\"\n          gestures:\n            - type: swipe\n              identifier: \"swipe_next\"\n              direction: up\n              behavior:\n                behaviors: [\"pager_next\"]\n            - type: tap\n              identifier: \"tap_next\"\n              location: end\n              behavior:\n                behaviors: [\"pager_next\"]\n      - size:\n          height: 16\n          width: auto\n        position:\n          vertical: bottom\n          horizontal: center\n        margin:\n          bottom: 8\n        view:\n          type: pager_indicator\n          spacing: 4\n          bindings:\n            selected:\n              shapes:\n                - type: rectangle\n                  aspect_ratio: 2.25\n                  scale: 0.9\n                  border:\n                    radius: 3\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 0.7\n                  color:\n                    default:\n                      hex: \"#ffffff\"\n                      alpha: 1\n            unselected:\n              shapes:\n                - type: rectangle\n                  aspect_ratio: 2.25\n                  scale: .9\n                  border:\n                    radius: 3\n                    stroke_width: 1\n                    stroke_color:\n                      default:\n                        hex: \"#ffffff\"\n                        alpha: 0.7\n                  color:\n                    default:\n                      hex: \"#000000\"\n                      alpha: 0\n          automated_accessibility_actions:\n            - type: announce\n          background_color:\n            default:\n              hex: \"#333333\"\n              alpha: 0.7\n          border:\n            radius: 8\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/23GrandeAccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"0x49\",\n          \"green\" : \"0x0D\",\n          \"red\" : \"0xFF\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/23GrandeAllFashion.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"all_fashion.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/23GrandeAppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"40.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"60.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"29.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"58.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"87.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"80.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"57.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"1x\",\n      \"size\" : \"57x57\"\n    },\n    {\n      \"filename\" : \"114.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"57x57\"\n    },\n    {\n      \"filename\" : \"120.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"2x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"180.png\",\n      \"idiom\" : \"iphone\",\n      \"scale\" : \"3x\",\n      \"size\" : \"60x60\"\n    },\n    {\n      \"filename\" : \"20.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"40.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"20x20\"\n    },\n    {\n      \"filename\" : \"29.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"58.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"40.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"80.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\"\n    },\n    {\n      \"filename\" : \"50.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"50x50\"\n    },\n    {\n      \"filename\" : \"100.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"50x50\"\n    },\n    {\n      \"filename\" : \"72.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"72x72\"\n    },\n    {\n      \"filename\" : \"144.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"72x72\"\n    },\n    {\n      \"filename\" : \"76.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"1x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"152.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"76x76\"\n    },\n    {\n      \"filename\" : \"167.png\",\n      \"idiom\" : \"ipad\",\n      \"scale\" : \"2x\",\n      \"size\" : \"83.5x83.5\"\n    },\n    {\n      \"filename\" : \"1024.png\",\n      \"idiom\" : \"ios-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    },\n    {\n      \"filename\" : \"16.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"16x16\"\n    },\n    {\n      \"filename\" : \"32.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"64.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"32x32\"\n    },\n    {\n      \"filename\" : \"128.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"128x128\"\n    },\n    {\n      \"filename\" : \"256.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"256x256\"\n    },\n    {\n      \"filename\" : \"512.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"1x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"1024.png\",\n      \"idiom\" : \"mac\",\n      \"scale\" : \"2x\",\n      \"size\" : \"512x512\"\n    },\n    {\n      \"filename\" : \"48.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"notificationCenter\",\n      \"scale\" : \"2x\",\n      \"size\" : \"24x24\",\n      \"subtype\" : \"38mm\"\n    },\n    {\n      \"filename\" : \"55.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"notificationCenter\",\n      \"scale\" : \"2x\",\n      \"size\" : \"27.5x27.5\",\n      \"subtype\" : \"42mm\"\n    },\n    {\n      \"filename\" : \"58.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"companionSettings\",\n      \"scale\" : \"2x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"filename\" : \"87.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"companionSettings\",\n      \"scale\" : \"3x\",\n      \"size\" : \"29x29\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"notificationCenter\",\n      \"scale\" : \"2x\",\n      \"size\" : \"33x33\",\n      \"subtype\" : \"45mm\"\n    },\n    {\n      \"filename\" : \"80.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"40x40\",\n      \"subtype\" : \"38mm\"\n    },\n    {\n      \"filename\" : \"88.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"44x44\",\n      \"subtype\" : \"40mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"46x46\",\n      \"subtype\" : \"41mm\"\n    },\n    {\n      \"filename\" : \"100.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"50x50\",\n      \"subtype\" : \"44mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"51x51\",\n      \"subtype\" : \"45mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"appLauncher\",\n      \"scale\" : \"2x\",\n      \"size\" : \"54x54\",\n      \"subtype\" : \"49mm\"\n    },\n    {\n      \"filename\" : \"172.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"86x86\",\n      \"subtype\" : \"38mm\"\n    },\n    {\n      \"filename\" : \"196.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"98x98\",\n      \"subtype\" : \"42mm\"\n    },\n    {\n      \"filename\" : \"216.png\",\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"108x108\",\n      \"subtype\" : \"44mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"117x117\",\n      \"subtype\" : \"45mm\"\n    },\n    {\n      \"idiom\" : \"watch\",\n      \"role\" : \"quickLook\",\n      \"scale\" : \"2x\",\n      \"size\" : \"129x129\",\n      \"subtype\" : \"49mm\"\n    },\n    {\n      \"filename\" : \"1024.png\",\n      \"idiom\" : \"watch-marketing\",\n      \"scale\" : \"1x\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/23GrandeArrival1.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"new_arrival_1.jpg\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/23GrandeArrival2.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"new_arrival_2.jpg\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/23GrandeHomeBanner.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"homeBanner.svg\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/Resources/SharedAssets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/ThomasLayoutListView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport SwiftUI\n\n// MARK: - AppView\n\nstruct ThomasLayoutListView: View {\n    @StateObject\n    private var viewModel = ThomasLayoutViewModel()\n\n    private enum SceneRoutes: Hashable, CaseIterable {\n        case embedded\n        case modal\n        case banner\n    }\n\n    private enum InAppAutomationRoutes: Hashable, CaseIterable {\n        case modal\n        case banner\n        case fullscreen\n        case html\n    }\n\n    @State var errorMessage: String?\n    @State var showError: Bool = false\n\n    func open(_ layout: LayoutFile, addToRecents: Bool = true) {\n        do {\n            try viewModel.openLayout(layout, addToRecents: addToRecents)\n        } catch {\n            self.showError = true\n            self.errorMessage = \"Failed to open layout \\(error)\"\n        }\n    }\n\n    private var layoutsView: some View {\n        Form {\n            Section(\"Recent\") {\n                ForEach(viewModel.recentLayouts) { layout in\n                    Button(layout.fileName) {\n                       open(layout, addToRecents: false)\n                    }\n                }\n            }\n\n            Section(\"Scenes\") {\n                ForEach(SceneRoutes.allCases, id: \\.self) { route in\n                    switch route {\n                    case .embedded:\n                        NavigationLink(value: AppRouter.HomeRoute.thomas(.layoutList(.sceneEmbedded))) {\n                            Label(\"Embedded\", systemImage: \"rectangle.portrait.topleft.inset.filled\")\n                        }\n                    case .modal: makeDestination(type: .sceneModal)\n                    case .banner: makeDestination(type: .sceneBanner)\n                    }\n                }\n            }\n\n            Section(\"In-App Automations\") {\n                ForEach(InAppAutomationRoutes.allCases, id: \\.self) { route in\n                    let type: LayoutType = switch route {\n                    case .modal: .messageModal\n                    case .banner: .messageBanner\n                    case .fullscreen: .messageFullscreen\n                    case .html: .messageHTML\n                    }\n                    \n                    makeDestination(type: type)\n                }\n            }\n        }\n        .navigationTitle(\"Layout Viewer\")\n        .alert(isPresented: $showError) {\n            Alert(\n                title: Text(\"Error\"),\n                message: Text(self.errorMessage ?? \"error\"),\n                dismissButton: .default(Text(\"OK\"))\n            )\n        }\n        .onAppear { self.viewModel.refreshRecent() }\n    }\n    \n    var body: some View {\n        layoutsView\n    }\n    \n    @ViewBuilder\n    private func makeDestination(type: LayoutType) -> some View {\n        let (label, icon) = switch(type) {\n        case .sceneEmbedded: (\"\", \"\")\n        case .sceneModal: (\"Modal\", \"rectangle.portrait.center.inset.filled\")\n        case .sceneBanner: (\"Banner\", \"rectangle.portrait.topthird.inset.filled\")\n        case .messageModal: (\"Modal\", \"rectangle.portrait.center.inset.filled\")\n        case .messageBanner: (\"Banner\", \"rectangle.portrait.topthird.inset.filled\")\n        case .messageFullscreen: (\"Fullscreen\", \"rectangle.portrait.inset.filled\")\n        case .messageHTML: (\"HTML\", \"safari.fill\")\n        }\n        \n        NavigationLink(value: AppRouter.HomeRoute.thomas(.layoutList(type))) {\n            Label(label, systemImage: icon)\n        }\n    }\n}\n\n#Preview {\n    ThomasLayoutListView()\n}\n\n"
  },
  {
    "path": "DevApp/Dev App/Thomas/ThomasLayoutViewModel.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\ninternal import Yams\n\n@MainActor\nfinal class ThomasLayoutViewModel: ObservableObject {\n\n    private let layoutLoader: LayoutLoader = .init()\n\n    @Published\n    public private(set) var recentLayouts: [LayoutFile] = []\n\n    init() {\n        self.recentLayouts = ThomasUserDefaults.shared.recentLayouts\n    }\n\n    func openLayout(_ layout: LayoutFile, addToRecents: Bool = true) throws {\n        try layout.open()\n        if addToRecents {\n            Self.saveToRecent(layout)\n            self.recentLayouts = ThomasUserDefaults.shared.recentLayouts\n        }\n    }\n\n    func layouts(type: LayoutType) -> [LayoutFile] {\n        return layoutLoader.load(type: type)\n    }\n    \n    func refreshRecent() {\n        self.recentLayouts = ThomasUserDefaults.shared.recentLayouts\n    }\n    \n    static func saveToRecent(_ layout: LayoutFile) {\n        ThomasUserDefaults.shared.addRecentLayout(layout)\n    }\n}\n\n@MainActor\nfileprivate final class ThomasUserDefaults {\n    static let shared: ThomasUserDefaults = ThomasUserDefaults()\n\n    var recentLayouts: [LayoutFile] {\n        get { readCodable(\"recentLayouts\") ?? [] }\n        set { write(\"recentLayouts\", codable: newValue) }\n    }\n\n    let defaults: UserDefaults\n\n    private init() {\n        self.defaults = UserDefaults(suiteName: \"airship.layout\")!\n    }\n\n    private func write<T>(_ key: String, codable: T) where T: Codable {\n        let data = try? JSONEncoder().encode(codable)\n        write(key, value: data)\n    }\n\n    private func readCodable<T>(_ key: String) -> T? where T: Codable {\n        guard let value: Data = read(key) else {\n            return nil\n        }\n        return try? JSONDecoder().decode(T.self, from: value)\n    }\n\n\n    private func write(_ key: String, value: Any?) {\n        if let value = value {\n            defaults.set(value, forKey: key)\n        } else {\n            defaults.removeObject(forKey: key)\n        }\n    }\n\n    private func read<T>(_ key: String) -> T? {\n        return defaults.value(forKey: key) as? T\n    }\n\n    private func readString(_ key: String, trimmingCharacters: CharacterSet? = nil) -> String? {\n        guard let value: String = read(key) else {\n            return nil\n        }\n\n        return if let trimmingCharacters {\n            value.trimmingCharacters(in: trimmingCharacters)\n        } else {\n            value\n        }\n    }\n\n}\n\nextension ThomasUserDefaults {\n    func addRecentLayout(_ layout: LayoutFile) {\n        var current = recentLayouts\n        // Remove duplicate if exists\n        current.removeAll(where: { $0 == layout })\n        // Insert new layout at the beginning\n        current.insert(layout, at: 0)\n        // Keep only the last 5 items\n        if current.count > 5 {\n            current = Array(current.prefix(5))\n        }\n        self.recentLayouts = current\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/Toast.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport Combine\nimport Foundation\n\n@MainActor\nfinal class Toast: ObservableObject {\n    struct Message: Equatable, Sendable, Hashable {\n        let id: String = UUID().uuidString\n        let text: String\n        let duration: TimeInterval\n    }\n\n    @Published\n    var message: Message?\n}\n"
  },
  {
    "path": "DevApp/Dev App/View/AppView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport AirshipMessageCenter\nimport AirshipPreferenceCenter\nimport SwiftUI\n\nstruct AppView: View {\n    \n    @EnvironmentObject\n    private var toast: Toast\n    \n    @EnvironmentObject\n    private var router: AppRouter\n    \n    @StateObject\n    private var viewModel: ViewModel = ViewModel()\n    \n    var body: some View {\n        TabView(selection: $router.selectedTab) {\n            HomeView()\n                .tabItem {\n                    Label(\n                        \"Home\",\n                        systemImage: \"house.fill\"\n                    )\n                }\n                .onAppear {\n                    Airship.analytics.trackScreen(\"home\")\n                }\n                .tag(AppRouter.Tabs.home)\n            \n            MessageCenterView(\n                controller: router.messageCenterController\n            )\n            .tabItem {\n                Label(\n                    \"Message Center\",\n                    systemImage: \"tray.fill\"\n                )\n            }\n#if !os(tvOS)\n            .badge(self.viewModel.messageCenterUnreadcount)\n#endif\n            .onAppear {\n                Airship.analytics.trackScreen(\"message_center\")\n            }\n            .tag(AppRouter.Tabs.messageCenter)\n            \n            PreferenceCenterView(\n                preferenceCenterID: router.preferenceCenterID\n            )\n#if !os(macOS)\n            .navigationViewStyle(.stack)\n#endif\n            .tabItem {\n                Label(\n                    \"Preferences\",\n                    systemImage: \"person.fill\"\n                )\n            }\n            .onAppear {\n                Airship.analytics.trackScreen(\"preference_center\")\n            }\n            .tag(AppRouter.Tabs.preferenceCenter)\n        }\n        .overlay {\n            ToastView(toast: toast).padding()\n        }\n    }\n    \n    @MainActor\n    final class ViewModel: ObservableObject {\n        \n        @Published\n        var messageCenterUnreadcount: Int\n        \n        private var task: Task<Void, Never>? = nil\n        \n        @MainActor\n        init() {\n            self.messageCenterUnreadcount = 0\n            self.task = Task { [weak self] in\n                await Airship.waitForReady()\n                for await unreadCount in Airship.messageCenter.inbox.unreadCountUpdates {\n                    self?.messageCenterUnreadcount = unreadCount\n                }\n            }\n        }\n        \n        deinit {\n            task?.cancel()\n        }\n    }\n}\n\n#Preview {\n    AppView()\n        .environmentObject(AppRouter())\n        .environmentObject(Toast())\n}\n"
  },
  {
    "path": "DevApp/Dev App/View/HomeView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Combine\nimport SwiftUI\n\n#if canImport(ActivityKit)\nimport ActivityKit\n#endif\n\nstruct HomeView: View {\n\n    @StateObject\n    private var viewModel: ViewModel = ViewModel()\n\n    @Environment(\\.verticalSizeClass)\n    private var verticalSizeClass\n\n    @EnvironmentObject\n    private var toast: Toast\n\n    @EnvironmentObject\n    private var appRouter: AppRouter\n\n    @ViewBuilder\n    private func makeQuickSettingItem(\n        title: String,\n        value: String? = nil\n    ) -> some View {\n        VStack(alignment: .leading) {\n            Text(title)\n                .foregroundColor(.accentColor)\n                .multilineTextAlignment(.leading)\n                .frame(maxWidth: .infinity, alignment: .leading)\n\n            if let value {\n                Text(value)\n                    .foregroundColor(Color.secondary)\n                    .multilineTextAlignment(.leading)\n                    .frame(maxWidth: .infinity, alignment: .leading)\n            }\n        }\n    }\n\n    @ViewBuilder\n    private var quickSettings: some View {\n        ScrollView {\n            VStack {\n                Button(\n                    action: {\n                        #if !os(tvOS)\n                        self.viewModel.copyChannel()\n                        self.toast.message = Toast.Message(\n                            text: \"Channel copied to pasteboard\",\n                            duration: 2.0\n                        )\n                        #endif\n                    }\n                ) {\n                    makeQuickSettingItem(\n                        title: \"Channel ID\",\n                        value: self.viewModel.channelID ?? \"Unavailable\"\n                    )\n                }\n\n                Divider()\n\n                NavigationLink(\n                    value: AppRouter.HomeRoute.namedUser\n                ) {\n                    makeQuickSettingItem(\n                        title: \"Named User\",\n                        value: self.viewModel.namedUserID ?? \"Not Set\"\n                    )\n                }\n\n#if canImport(ActivityKit) && !os(macOS)\n                Divider()\n\n                Button(action: {\n                    do {\n                        try viewModel.startLiveActivity()\n                        toast.message = Toast.Message(\n                            text: \"Live Activity started!\",\n                            duration: 2.0\n                        )\n                    } catch {\n                        toast.message = Toast.Message(\n                            text: \"Failed to start live activity \\(error)\",\n                            duration: 2.0\n                        )\n                    }\n                }) {\n                    makeQuickSettingItem(\n                        title: \"Start Live Activity\",\n                        value: \"Tap to create delivery\"\n                    )\n                }\n#endif\n                \n                Divider()\n                \n                NavigationLink(value: AppRouter.HomeRoute.thomas(.home)) {\n                    makeQuickSettingItem(\n                        title: \"Thomas Layouts\",\n                        value: \"Tap to preview layouts\"\n                    )\n                }\n                \n            }\n        }\n    }\n\n    @ViewBuilder\n    private var hero: some View {\n        AirshipEmbeddedView(embeddedID: \"test\") {\n            Image(\"HomeHeroImage\")\n                .resizable()\n                .scaledToFit()\n        }\n    }\n\n    @ViewBuilder\n    private var pushButton: some View {\n        Button(action: { viewModel.toggleNotifications() }) {\n            if let optedIn = viewModel.notificationStatus?.isUserOptedIn {\n                Text(optedIn ? \"Disable Notifications\" : \"Enable Notifications\")\n                    .fontWeight(.semibold)\n                    .frame(maxWidth: .infinity)\n                    .padding(.vertical, 12)\n            } else {\n                ProgressView()\n                    .frame(maxWidth: .infinity)\n                    .padding(.vertical, 12)\n            }\n        }\n        .controlSize(.large)\n        .buttonStyle(.bordered)\n        .buttonBorderShape(.capsule)\n    }\n\n    @ViewBuilder\n    private var content: some View {\n        if self.verticalSizeClass == .compact {\n            HStack(spacing: 16) {\n                VStack(spacing: 16) {\n                    hero.frame(maxHeight: .infinity)\n                    pushButton\n                }\n                .frame(maxHeight: .infinity)\n                quickSettings.frame(maxHeight: .infinity)\n            }\n        } else {\n            VStack(spacing: 16) {\n                VStack(spacing: 16) {\n                    hero.frame(maxHeight: .infinity)\n                    pushButton\n                }\n                .frame(maxHeight: .infinity)\n                quickSettings.frame(maxHeight: .infinity)\n            }\n        }\n    }\n\n    var body: some View {\n        NavigationStack(path: self.$appRouter.homePath) {\n            content\n                .padding()\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n#if !os(macOS)\n                .navigationBarTitle(\"\")\n                .navigationBarHidden(true)\n#endif\n                .navigationDestination(for: AppRouter.HomeRoute.self) { $0.destination() }\n        }\n    }\n\n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published\n        var notificationStatus: AirshipNotificationStatus?\n\n        @Published\n        var channelID: String?\n\n        @Published\n        var namedUserID: String?\n\n        private var task: Task<Void, Never>? = nil\n\n        @MainActor\n        init() {\n            self.task = Task { [weak self] in\n                await Airship.waitForReady()\n\n                // Get initial values\n                self?.namedUserID = await Airship.contact.namedUserID\n                self?.notificationStatus = await Airship.push.notificationStatus\n                self?.channelID = Airship.channel.identifier\n\n                // Listen for changes\n                self?.listenForChanges()\n            }\n        }\n        private func listenForChanges() {\n            // Named User changes\n            Task { @MainActor [weak self] in\n                for await update in Airship.contact.namedUserIDPublisher.values {\n                    self?.namedUserID = update\n                }\n            }\n\n            // Push notification changes\n            Task { @MainActor [weak self] in\n                for await update in await Airship.push.notificationStatusUpdates {\n                    self?.notificationStatus = update\n                }\n            }\n\n            // Channel ID changes\n            Task { @MainActor [weak self] in\n                for await update in Airship.channel.identifierUpdates {\n                    self?.channelID = update\n                }\n            }\n        }\n\n        deinit {\n            task?.cancel()\n        }\n        \n#if !os(tvOS)\n        func copyChannel() {\n            guard let channelID = Airship.channel.identifier else { return }\n            \n#if os(macOS)\n            let pasteboard = NSPasteboard.general\n            pasteboard.declareTypes([.string], owner: nil)\n            pasteboard.setString(channelID, forType: .string)\n#else\n            UIPasteboard.general.string = channelID\n#endif\n        }\n#endif\n\n#if canImport(ActivityKit) && !os(macOS)\n        func startLiveActivity() throws {\n            let state = DeliveryAttributes.ContentState(\n                stopsAway: 10\n            )\n\n            let attributes = DeliveryAttributes(\n                orderNumber: generateOrderNumber()\n            )\n\n            let activity = try Activity.request(\n                attributes: attributes,\n                content: .init(state: state, staleDate: nil),\n                pushType: .token\n            )\n\n            Airship.channel.trackLiveActivity(\n                activity,\n                name: attributes.orderNumber\n            )\n        }\n#endif\n\n        @MainActor\n        func toggleNotifications() {\n            if self.notificationStatus?.isUserOptedIn != true {\n                Task {\n                    Airship.privacyManager.enableFeatures(.push)\n                    await Airship.push.enableUserPushNotifications(fallback: .systemSettings)\n                }\n            } else {\n                Airship.push.userPushNotificationsEnabled = false\n            }\n        }\n\n        private func generateOrderNumber() -> String {\n            var number = \"#\"\n            for _ in 1...6 {\n                number += \"\\(Int.random(in: 1...9))\"\n            }\n            return number\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/View/NamedUserView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\nimport SwiftUI\n\n@MainActor\nstruct NamedUserView: View {\n\n    @StateObject\n    private var viewModel: ViewModel = ViewModel()\n\n    @ViewBuilder\n    private func makeTextInput() -> some View {\n        TextField(\"Named User\", text: self.$viewModel.namedUserID)\n            .onSubmit {\n                viewModel.apply()\n            }\n#if !os(macOS)\n            .textInputAutocapitalization(.never)\n#endif\n            .disableAutocorrection(true)\n    }\n    \n    var body: some View {\n        VStack(alignment: .leading, spacing: 24) {\n            Text(\n                \"A named user is an identifier that maps multiple devices and channels to a specific individual.\"\n            )\n            .multilineTextAlignment(.leading)\n\n            makeTextInput()\n                .padding()\n                .overlay(\n                    RoundedRectangle(cornerRadius: 4)\n                        .strokeBorder(Color.secondary, lineWidth: 1)\n                )\n        }\n        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)\n        .navigationTitle(\"Named User\")\n        .padding()\n    }\n\n    @MainActor\n    private class ViewModel: ObservableObject {\n\n        @Published\n        public var namedUserID: String = \"\"\n\n        init() {\n            Airship.onReady {\n                Task { @MainActor [weak self] in\n                    self?.namedUserID = await Airship.contact.namedUserID ?? \"\"\n                }\n            }\n        }\n\n        func apply() {\n            let normalized = self.namedUserID.trimmingCharacters(\n                in: .whitespacesAndNewlines\n            )\n\n            if !normalized.isEmpty {\n                Airship.contact.identify(normalized)\n            } else {\n                Airship.contact.reset()\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev App/View/ToastView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\nstruct ToastView: View {\n    @ObservedObject\n    private var toast: Toast\n\n    init(toast: Toast) {\n        self.toast = toast\n    }\n    \n    @State\n    private var toastTask: Task<(), Never>? = nil\n\n    @State\n    private var toastVisible: Bool = false\n\n    @ViewBuilder\n    private func makeView() -> some View {\n        Text(toast.message?.text ?? \"\")\n            .padding()\n            .background(\n                .ultraThinMaterial,\n                in: RoundedRectangle(cornerRadius: 16, style: .continuous)\n            )\n    }\n\n    @ViewBuilder\n    var body: some View {\n        makeView()\n            .airshipOnChangeOf(self.toast.message) { incoming in\n                if incoming != nil {\n                    showToast()\n                }\n            }\n            .hideOpt(self.toastVisible == false || self.toast.message == nil)\n    }\n\n    private func showToast() {\n        self.toastTask?.cancel()\n\n        guard let message = toast.message else {\n            return\n        }\n\n        let waitTask = Task {\n            try? await Task.sleep(\n                nanoseconds: UInt64(message.duration * 1_000_000_000)\n            )\n            return\n        }\n\n        Task {\n            let _ = await waitTask.result\n            await MainActor.run {\n                if !waitTask.isCancelled {\n                    withAnimation {\n                        self.toastVisible = false\n                        self.toast.message = nil\n                    }\n                }\n            }\n        }\n\n        self.toastTask = waitTask\n\n        withAnimation {\n            self.toastVisible = true\n        }\n    }\n}\n\nextension View {\n    @ViewBuilder\n    func hideOpt(_ shouldHide: Bool) -> some View {\n        if shouldHide {\n            self.hidden()\n        } else {\n            self\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp/Dev-App-Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>GADApplicationIdentifier</key>\n\t<string>ca-app-pub-9716462883842968~5417675763</string>\n\t<key>SKAdNetworkItems</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>cstr6suwn9.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>4fzdc2evr5.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>4pfyvq9l8r.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>2fnua5tdw4.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>ydx93a7ass.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>5a6flpkh64.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>p78axxw29g.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>v72qych5uu.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>ludvb6z3bs.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>cp8zw746q7.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>3sh42y64q3.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>c6k4g5qg8m.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>s39g8k73mm.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>3qy4746246.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>f38h382jlk.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>hs6bdukanm.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>v4nxqhlyqp.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>wzmmz9fp6w.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>yclnxrl5pm.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>t38b2kh725.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>7ug5zh24hu.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>gta9lk7p23.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>vutu7akeur.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>y5ghdn5j9k.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>n6fk4nfna4.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>v9wttpbfk9.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>n38lu8286q.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>47vhws6wlr.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>kbd757ywx3.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>9t245vhmpl.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>eh6m2bh4zr.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>a2p9lx4jpn.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>22mmun2rn5.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>4468km3ulz.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>2u9pt9hc89.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>8s468mfl3y.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>klf5c3l5u5.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>ppxm28t8ap.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>ecpz2srf59.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>uw77j35x4d.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>pwa73g5rt2.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>mlmmfzh3r3.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>578prtvx9j.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>4dzt52r2t5.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>e5fvkxwrpn.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>8c4e2ghe7u.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>zq492l623r.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>3rd42ekr43.skadnetwork</string>\n\t\t</dict>\n\t\t<dict>\n\t\t\t<key>SKAdNetworkIdentifier</key>\n\t\t\t<string>3qcr597p9d.skadnetwork</string>\n\t\t</dict>\n\t</array>\n\t<key>UIAppFonts</key>\n\t<array>\n\t\t<string>JurassicPark.otf</string>\n\t</array>\n\t<key>UIBackgroundModes</key>\n\t<array>\n\t\t<string>fetch</string>\n\t\t<string>remote-notification</string>\n\t</array>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/DevApp App Clip/Airship_Sample_App_Clip.entitlements",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>com.apple.developer.parent-application-identifiers</key>\n    <array>\n        <string>$(AppIdentifierPrefix)com.urbanairship.richpush</string>\n    </array>\n</dict>\n</plist>"
  },
  {
    "path": "DevApp/DevApp App Clip/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSAppClip</key>\n\t<dict>\n\t\t<key>NSAppClipRequestEphemeralUserNotification</key>\n\t\t<true/>\n\t\t<key>NSAppClipRequestLocationConfirmation</key>\n\t\t<false/>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/DevApp Service Extension/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSExtension</key>\n\t<dict>\n\t\t<key>NSExtensionPointIdentifier</key>\n\t\t<string>com.apple.usernotifications.service</string>\n\t\t<key>NSExtensionPrincipalClass</key>\n\t\t<string>$(PRODUCT_MODULE_NAME).NotificationService</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/DevApp Service Extension/NotificationService.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipNotificationServiceExtension\n\nclass NotificationService: UANotificationServiceExtension {\n\n    /// Overrides config to log everyting publically\n    override var airshipConfig: AirshipExtensionConfig  {\n        AirshipExtensionConfig(\n            logLevel: .verbose,\n            logHandler: .publicLogger\n        )\n    }\n    \n}\n"
  },
  {
    "path": "DevApp/DevApp.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 55;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t279DCED42E85A544008B5542 /* BiometricLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECE2E85A544008B5542 /* BiometricLoginView.swift */; };\n\t\t279DCED52E85A544008B5542 /* WeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECF2E85A544008B5542 /* WeatherView.swift */; };\n\t\t279DCED62E85A544008B5542 /* MapRouteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECB2E85A544008B5542 /* MapRouteView.swift */; };\n\t\t279DCED72E85A544008B5542 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECC2E85A544008B5542 /* CameraView.swift */; };\n\t\t279DCED82E85A544008B5542 /* AdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECD2E85A544008B5542 /* AdView.swift */; };\n\t\t279DCED92E85A544008B5542 /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCED02E85A544008B5542 /* WeatherViewModel.swift */; };\n\t\t279DCEDA2E85A544008B5542 /* BiometricLoginView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECE2E85A544008B5542 /* BiometricLoginView.swift */; };\n\t\t279DCEDB2E85A544008B5542 /* WeatherView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECF2E85A544008B5542 /* WeatherView.swift */; };\n\t\t279DCEDC2E85A544008B5542 /* MapRouteView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECB2E85A544008B5542 /* MapRouteView.swift */; };\n\t\t279DCEDD2E85A544008B5542 /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECC2E85A544008B5542 /* CameraView.swift */; };\n\t\t279DCEDE2E85A544008B5542 /* AdView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCECD2E85A544008B5542 /* AdView.swift */; };\n\t\t279DCEDF2E85A544008B5542 /* WeatherViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCED02E85A544008B5542 /* WeatherViewModel.swift */; };\n\t\t279DCEE12E85A558008B5542 /* ThomasLayoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEE02E85A558008B5542 /* ThomasLayoutViewModel.swift */; };\n\t\t279DCEE22E85A558008B5542 /* ThomasLayoutViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEE02E85A558008B5542 /* ThomasLayoutViewModel.swift */; };\n\t\t279DCEE62E85A5BD008B5542 /* Scenes in Resources */ = {isa = PBXBuildFile; fileRef = 279DCEE42E85A5BD008B5542 /* Scenes */; };\n\t\t279DCEE72E85A5BD008B5542 /* Messages in Resources */ = {isa = PBXBuildFile; fileRef = 279DCEE32E85A5BD008B5542 /* Messages */; };\n\t\t279DCEE82E85A5BD008B5542 /* Scenes in Resources */ = {isa = PBXBuildFile; fileRef = 279DCEE42E85A5BD008B5542 /* Scenes */; };\n\t\t279DCEE92E85A5BD008B5542 /* Messages in Resources */ = {isa = PBXBuildFile; fileRef = 279DCEE32E85A5BD008B5542 /* Messages */; };\n\t\t279DCEF92E85A6A7008B5542 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 279DCEF82E85A6A7008B5542 /* Yams */; };\n\t\t279DCEFB2E85A6C6008B5542 /* JurassicPark.otf in Resources */ = {isa = PBXBuildFile; fileRef = 279DCEFA2E85A6C6008B5542 /* JurassicPark.otf */; };\n\t\t279DCEFC2E85A6C6008B5542 /* JurassicPark.otf in Resources */ = {isa = PBXBuildFile; fileRef = 279DCEFA2E85A6C6008B5542 /* JurassicPark.otf */; };\n\t\t279DCF032E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEFE2E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift */; };\n\t\t279DCF042E85A75C008B5542 /* PlaceholderToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF012E85A75C008B5542 /* PlaceholderToggleView.swift */; };\n\t\t279DCF052E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEFD2E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift */; };\n\t\t279DCF062E85A75C008B5542 /* KeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF002E85A75C008B5542 /* KeyView.swift */; };\n\t\t279DCF072E85A75C008B5542 /* EmbeddedPlaygroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEFF2E85A75C008B5542 /* EmbeddedPlaygroundView.swift */; };\n\t\t279DCF082E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEFE2E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift */; };\n\t\t279DCF092E85A75C008B5542 /* PlaceholderToggleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF012E85A75C008B5542 /* PlaceholderToggleView.swift */; };\n\t\t279DCF0A2E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEFD2E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift */; };\n\t\t279DCF0B2E85A75C008B5542 /* KeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF002E85A75C008B5542 /* KeyView.swift */; };\n\t\t279DCF0C2E85A75C008B5542 /* EmbeddedPlaygroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCEFF2E85A75C008B5542 /* EmbeddedPlaygroundView.swift */; };\n\t\t279DCF0E2E85A766008B5542 /* Layouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF0D2E85A766008B5542 /* Layouts.swift */; };\n\t\t279DCF0F2E85A766008B5542 /* Layouts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF0D2E85A766008B5542 /* Layouts.swift */; };\n\t\t279DCF112E85A826008B5542 /* ThomasLayoutListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF102E85A826008B5542 /* ThomasLayoutListView.swift */; };\n\t\t279DCF122E85A826008B5542 /* ThomasLayoutListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF102E85A826008B5542 /* ThomasLayoutListView.swift */; };\n\t\t279DCF142E85A9DE008B5542 /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = 279DCF132E85A9DE008B5542 /* Yams */; };\n\t\t279DCF162E85AA02008B5542 /* LayoutsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF152E85AA02008B5542 /* LayoutsList.swift */; };\n\t\t279DCF172E85AA02008B5542 /* LayoutsList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 279DCF152E85AA02008B5542 /* LayoutsList.swift */; };\n\t\t32B5BE4B28F8BFBA00F2254B /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE4A28F8BFBA00F2254B /* Toast.swift */; };\n\t\t32B5BE4C28F8BFBA00F2254B /* Toast.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE4A28F8BFBA00F2254B /* Toast.swift */; };\n\t\t32B5BE4E28F94FD200F2254B /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE4D28F94FD200F2254B /* ToastView.swift */; };\n\t\t32B5BE4F28F94FD200F2254B /* ToastView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 32B5BE4D28F94FD200F2254B /* ToastView.swift */; };\n\t\t45AA81872F4A697800B81FBC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = 45AA81862F4A697800B81FBC /* AirshipConfig.plist */; };\n\t\t45AA81882F4A697800B81FBC /* AirshipConfig.plist in Resources */ = {isa = PBXBuildFile; fileRef = 45AA81862F4A697800B81FBC /* AirshipConfig.plist */; };\n\t\t6E7DB38F28ED0AF3002725F6 /* DeliveryAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DB37B28ECA830002725F6 /* DeliveryAttributes.swift */; };\n\t\t6E7DB39028ED0AF4002725F6 /* DeliveryAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DB37B28ECA830002725F6 /* DeliveryAttributes.swift */; };\n\t\t6E7DB39128ED0AF5002725F6 /* DeliveryAttributes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E7DB37B28ECA830002725F6 /* DeliveryAttributes.swift */; };\n\t\t6E938DAC2AC35F2600F691D9 /* AirshipFeatureFlags.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E2F5AA82A67330900CABD3D /* AirshipFeatureFlags.framework */; };\n\t\t6E938DAD2AC35F2600F691D9 /* AirshipFeatureFlags.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E2F5AA82A67330900CABD3D /* AirshipFeatureFlags.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EAAE97F28C7E18C003CAE53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6EAAE97E28C7E18C003CAE53 /* Assets.xcassets */; };\n\t\t6EAAE98728C7E1A1003CAE53 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8B828C7C9A3003CAE53 /* HomeView.swift */; };\n\t\t6EAAE98828C7E1A1003CAE53 /* NamedUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8BB28C7C9A3003CAE53 /* NamedUserView.swift */; };\n\t\t6EAAE98A28C7E1A1003CAE53 /* MainApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8BC28C7C9A3003CAE53 /* MainApp.swift */; };\n\t\t6EAAE98C28C7E1A1003CAE53 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8C028C7C9A3003CAE53 /* AppView.swift */; };\n\t\t6EAAE9AB28C7E309003CAE53 /* DevApp App Clip.app in Embed App Clips */ = {isa = PBXBuildFile; fileRef = 6EAAE99C28C7E308003CAE53 /* DevApp App Clip.app */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n\t\t6EAAE9B128C7E31E003CAE53 /* MainApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8BC28C7C9A3003CAE53 /* MainApp.swift */; };\n\t\t6EAAE9B228C7E31E003CAE53 /* NamedUserView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8BB28C7C9A3003CAE53 /* NamedUserView.swift */; };\n\t\t6EAAE9B928C7E31E003CAE53 /* AppView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8C028C7C9A3003CAE53 /* AppView.swift */; };\n\t\t6EAAE9BD28C7E31E003CAE53 /* HomeView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE8B828C7C9A3003CAE53 /* HomeView.swift */; };\n\t\t6EAAE9BE28C7E31E003CAE53 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6EAAE97E28C7E18C003CAE53 /* Assets.xcassets */; };\n\t\t6EAAE9F228C7E417003CAE53 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EAAE9F128C7E417003CAE53 /* NotificationService.swift */; };\n\t\t6EAAE9F628C7E417003CAE53 /* DevApp Service Extension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6EAAE9EF28C7E417003CAE53 /* DevApp Service Extension.appex */; platformFilters = (ios, xros, ); settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n\t\t6EAAE9FA28C7E438003CAE53 /* AirshipNotificationServiceExtension.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D5912396E9A300AF2D1A /* AirshipNotificationServiceExtension.framework */; };\n\t\t6EAAE9FB28C7E438003CAE53 /* AirshipNotificationServiceExtension.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D5912396E9A300AF2D1A /* AirshipNotificationServiceExtension.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EAAE9FF28C7E4B4003CAE53 /* AirshipBasement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EB1B41326EAA531000421B9 /* AirshipBasement.framework */; };\n\t\t6EAAEA0028C7E4B4003CAE53 /* AirshipBasement.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6EB1B41326EAA531000421B9 /* AirshipBasement.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EAAEA0128C7E4B4003CAE53 /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D57C2396E99D00AF2D1A /* AirshipCore.framework */; };\n\t\t6EAAEA0228C7E4B4003CAE53 /* AirshipCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D57C2396E99D00AF2D1A /* AirshipCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EAAEA0328C7E4B4003CAE53 /* AirshipDebug.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D5842396E99D00AF2D1A /* AirshipDebug.framework */; };\n\t\t6EAAEA0428C7E4B4003CAE53 /* AirshipDebug.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D5842396E99D00AF2D1A /* AirshipDebug.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EAAEA0528C7E4B4003CAE53 /* AirshipMessageCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D5882396E99D00AF2D1A /* AirshipMessageCenter.framework */; };\n\t\t6EAAEA0628C7E4B4003CAE53 /* AirshipMessageCenter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6E65D5882396E99D00AF2D1A /* AirshipMessageCenter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EAAEA0728C7E4B4003CAE53 /* AirshipPreferenceCenter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EAD7CDF26B2003700B88EA7 /* AirshipPreferenceCenter.framework */; };\n\t\t6EAAEA0828C7E4B4003CAE53 /* AirshipPreferenceCenter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 6EAD7CDF26B2003700B88EA7 /* AirshipPreferenceCenter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t6EB21AA02E82008A001A5660 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A962E820088001A5660 /* AppRouter.swift */; };\n\t\t6EB21AA12E82008A001A5660 /* AppRouter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21A962E820088001A5660 /* AppRouter.swift */; };\n\t\t6EB21AA32E820C5A001A5660 /* AirshipInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21AA22E820C55001A5660 /* AirshipInitializer.swift */; };\n\t\t6EB21AA42E820C5A001A5660 /* AirshipInitializer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21AA22E820C55001A5660 /* AirshipInitializer.swift */; };\n\t\t6EB21AFE2E821E01001A5660 /* LiveActivityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21AFD2E821DFD001A5660 /* LiveActivityHandler.swift */; };\n\t\t6EB21AFF2E821E01001A5660 /* LiveActivityHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21AFD2E821DFD001A5660 /* LiveActivityHandler.swift */; };\n\t\t6EB21B022E821E34001A5660 /* DeepLinkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21B012E821E30001A5660 /* DeepLinkHandler.swift */; };\n\t\t6EB21B032E821E34001A5660 /* DeepLinkHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21B012E821E30001A5660 /* DeepLinkHandler.swift */; };\n\t\t6EB21B052E821E50001A5660 /* PushNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21B042E821E4C001A5660 /* PushNotificationHandler.swift */; };\n\t\t6EB21B062E821E50001A5660 /* PushNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EB21B042E821E4C001A5660 /* PushNotificationHandler.swift */; };\n\t\t6EDB2B0628D4E24F00A01377 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EDB2B0528D4E24F00A01377 /* WidgetKit.framework */; };\n\t\t6EDB2B0828D4E24F00A01377 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 6EDB2B0728D4E24F00A01377 /* SwiftUI.framework */; };\n\t\t6EDB2B0B28D4E24F00A01377 /* DeliveryActivityWidget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDB2B0A28D4E24F00A01377 /* DeliveryActivityWidget.swift */; };\n\t\t6EDB2B0E28D4E25100A01377 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 6EDB2B0D28D4E25100A01377 /* Assets.xcassets */; };\n\t\t6EDB2B1028D4E25100A01377 /* LiveActivity.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 6EDB2B0C28D4E24F00A01377 /* LiveActivity.intentdefinition */; };\n\t\t6EDB2B1128D4E25100A01377 /* LiveActivity.intentdefinition in Sources */ = {isa = PBXBuildFile; fileRef = 6EDB2B0C28D4E24F00A01377 /* LiveActivity.intentdefinition */; };\n\t\t6EDB2B1428D4E25100A01377 /* LiveActivityExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6EDB2B0428D4E24F00A01377 /* LiveActivityExtension.appex */; platformFilter = ios; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n\t\t6EDB2B2E28D4F97F00A01377 /* Widgets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6EDB2B2A28D4F27800A01377 /* Widgets.swift */; };\n\t\t6EDFBAF42F5660C40043D9EF /* AirshipAutomation.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 84A565712A3D01A100F3A345 /* AirshipAutomation.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t99E8D8072B57A5BA0099B6F3 /* AirshipAutomation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84A565712A3D01A100F3A345 /* AirshipAutomation.framework */; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t1BEF603A24CB3DB60039091F /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D58B2396E9A300AF2D1A /* AirshipExtensions.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = DF0C1B14244E483C0011ACCA;\n\t\t\tremoteInfo = AirshipNotificationServiceExtensionTests;\n\t\t};\n\t\t32F68D0528F07C7300F7F52A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 6E4A466E28EF44F600A25617;\n\t\t\tremoteInfo = AirshipMessageCenterTests;\n\t\t};\n\t\t6E1B7AD92B6DBE3A00695561 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E0B8729294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomation;\n\t\t};\n\t\t6E1B7AEA2B6DBE4A00695561 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E0B8729294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomation;\n\t\t};\n\t\t6E2F5A982A67330900CABD3D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6E2F5AA72A67330900CABD3D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = A62058692A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6E2F5AA92A67330900CABD3D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = A62058702A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlagsTests;\n\t\t};\n\t\t6E2F5AAE2A67331500CABD3D /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = A62058682A5841330041FBF9;\n\t\t\tremoteInfo = AirshipFeatureFlags;\n\t\t};\n\t\t6E65D57B2396E99D00AF2D1A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 494DD9571B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6E65D57F2396E99D00AF2D1A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = CC64F0541D8B77E3009CEF27;\n\t\t\tremoteInfo = AirshipTests;\n\t\t};\n\t\t6E65D5832396E99D00AF2D1A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 3CA0E2AC237CCE2600EE76CF;\n\t\t\tremoteInfo = AirshipDebug;\n\t\t};\n\t\t6E65D5872396E99D00AF2D1A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 3CA0E423237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\t6E65D5902396E9A300AF2D1A /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D58B2396E9A300AF2D1A /* AirshipExtensions.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 49AA00FC1D65158C0081989A;\n\t\t\tremoteInfo = AirshipNotificationServiceExtension;\n\t\t};\n\t\t6EAAE9A928C7E309003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = CCDA359B1B8D233A00950AD5 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6EAAE99B28C7E308003CAE53;\n\t\t\tremoteInfo = \"Airship Sample App Clip\";\n\t\t};\n\t\t6EAAE9F428C7E417003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = CCDA359B1B8D233A00950AD5 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6EAAE9EE28C7E417003CAE53;\n\t\t\tremoteInfo = \"Airship Sample Service Extension\";\n\t\t};\n\t\t6EAAEA0C28C7E4DB003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E431F6B26EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\t6EAAEA0E28C7E4DB003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6EAAEA1028C7E4DB003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E298237CCE2600EE76CF;\n\t\t\tremoteInfo = AirshipDebug;\n\t\t};\n\t\t6EAAEA1228C7E4DB003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E346237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\t6EAAEA1428C7E4DB003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\t6EAAEA1628C7E4E3003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D58B2396E9A300AF2D1A /* AirshipExtensions.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 49AA00FB1D65158C0081989A;\n\t\t\tremoteInfo = AirshipNotificationServiceExtension;\n\t\t};\n\t\t6EAAEA1A28C7E4ED003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6E431F6B26EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\t6EAAEA1C28C7E4ED003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 494DD9561B0EB677009C134E;\n\t\t\tremoteInfo = AirshipCore;\n\t\t};\n\t\t6EAAEA1E28C7E4ED003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E298237CCE2600EE76CF;\n\t\t\tremoteInfo = AirshipDebug;\n\t\t};\n\t\t6EAAEA2028C7E4ED003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 3CA0E346237E4A7B00EE76CF;\n\t\t\tremoteInfo = AirshipMessageCenter;\n\t\t};\n\t\t6EAAEA2228C7E4ED003CAE53 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 847BFFF3267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\t6EAD7CDE26B2003700B88EA7 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 847BFFF4267CD739007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenter;\n\t\t};\n\t\t6EAD7CE026B2003700B88EA7 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 847BFFFC267CD73A007CD249;\n\t\t\tremoteInfo = AirshipPreferenceCenterTests;\n\t\t};\n\t\t6EAF57662D35E2B700DF01BB /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = A641E1472BDBBDB400DE6FAA;\n\t\t\tremoteInfo = AirshipObjectiveC;\n\t\t};\n\t\t6EB1B41226EAA531000421B9 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 6E43204826EA814F009228AB;\n\t\t\tremoteInfo = AirshipBasement;\n\t\t};\n\t\t6EDB2B1228D4E25100A01377 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = CCDA359B1B8D233A00950AD5 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 6EDB2B0328D4E24F00A01377;\n\t\t\tremoteInfo = LiveActivityExtension;\n\t\t};\n\t\t84A565702A3D01A100F3A345 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 6E0B872A294A9C120064B7BD;\n\t\t\tremoteInfo = AirshipAutomationSwift;\n\t\t};\n\t\t84A565722A3D01A100F3A345 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\tproxyType = 2;\n\t\t\tremoteGlobalIDString = 6E0B8731294A9C130064B7BD;\n\t\t\tremoteInfo = AirshipAutomationSwiftTests;\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t6EAAE9AF28C7E309003CAE53 /* Embed App Clips */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"$(CONTENTS_FOLDER_PATH)/AppClips\";\n\t\t\tdstSubfolderSpec = 16;\n\t\t\tfiles = (\n\t\t\t\t6EAAE9AB28C7E309003CAE53 /* DevApp App Clip.app in Embed App Clips */,\n\t\t\t);\n\t\t\tname = \"Embed App Clips\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE9D128C7E393003CAE53 /* Embed Foundation Extensions */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 13;\n\t\t\tfiles = (\n\t\t\t\t6EAAE9F628C7E417003CAE53 /* DevApp Service Extension.appex in Embed Foundation Extensions */,\n\t\t\t\t6EDB2B1428D4E25100A01377 /* LiveActivityExtension.appex in Embed Foundation Extensions */,\n\t\t\t);\n\t\t\tname = \"Embed Foundation Extensions\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE9FC28C7E438003CAE53 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\t6EAAE9FB28C7E438003CAE53 /* AirshipNotificationServiceExtension.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAEA0928C7E4B4003CAE53 /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\t6EDFBAF42F5660C40043D9EF /* AirshipAutomation.framework in Embed Frameworks */,\n\t\t\t\t6EAAEA0028C7E4B4003CAE53 /* AirshipBasement.framework in Embed Frameworks */,\n\t\t\t\t6EAAEA0428C7E4B4003CAE53 /* AirshipDebug.framework in Embed Frameworks */,\n\t\t\t\t6EAAEA0628C7E4B4003CAE53 /* AirshipMessageCenter.framework in Embed Frameworks */,\n\t\t\t\t6EAAEA0828C7E4B4003CAE53 /* AirshipPreferenceCenter.framework in Embed Frameworks */,\n\t\t\t\t6E938DAD2AC35F2600F691D9 /* AirshipFeatureFlags.framework in Embed Frameworks */,\n\t\t\t\t6EAAEA0228C7E4B4003CAE53 /* AirshipCore.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t1BEF603F24CB3FF10039091F /* AppClip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AppClip.framework; path = System/Library/Frameworks/AppClip.framework; sourceTree = SDKROOT; };\n\t\t1BFB70562388302000BF7F07 /* UserNotifications.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotifications.framework; path = System/Library/Frameworks/UserNotifications.framework; sourceTree = SDKROOT; };\n\t\t1BFB70582388302000BF7F07 /* UserNotificationsUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UserNotificationsUI.framework; path = System/Library/Frameworks/UserNotificationsUI.framework; sourceTree = SDKROOT; };\n\t\t279DCECB2E85A544008B5542 /* MapRouteView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MapRouteView.swift; sourceTree = \"<group>\"; };\n\t\t279DCECC2E85A544008B5542 /* CameraView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = \"<group>\"; };\n\t\t279DCECD2E85A544008B5542 /* AdView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AdView.swift; sourceTree = \"<group>\"; };\n\t\t279DCECE2E85A544008B5542 /* BiometricLoginView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BiometricLoginView.swift; sourceTree = \"<group>\"; };\n\t\t279DCECF2E85A544008B5542 /* WeatherView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherView.swift; sourceTree = \"<group>\"; };\n\t\t279DCED02E85A544008B5542 /* WeatherViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WeatherViewModel.swift; sourceTree = \"<group>\"; };\n\t\t279DCEE02E85A558008B5542 /* ThomasLayoutViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutViewModel.swift; sourceTree = \"<group>\"; };\n\t\t279DCEE32E85A5BD008B5542 /* Messages */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Messages; sourceTree = \"<group>\"; };\n\t\t279DCEE42E85A5BD008B5542 /* Scenes */ = {isa = PBXFileReference; lastKnownFileType = folder; path = Scenes; sourceTree = \"<group>\"; };\n\t\t279DCEFA2E85A6C6008B5542 /* JurassicPark.otf */ = {isa = PBXFileReference; lastKnownFileType = file; path = JurassicPark.otf; sourceTree = \"<group>\"; };\n\t\t279DCEFD2E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPlaygroundMenuView.swift; sourceTree = \"<group>\"; };\n\t\t279DCEFE2E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPlaygroundPicker.swift; sourceTree = \"<group>\"; };\n\t\t279DCEFF2E85A75C008B5542 /* EmbeddedPlaygroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmbeddedPlaygroundView.swift; sourceTree = \"<group>\"; };\n\t\t279DCF002E85A75C008B5542 /* KeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyView.swift; sourceTree = \"<group>\"; };\n\t\t279DCF012E85A75C008B5542 /* PlaceholderToggleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlaceholderToggleView.swift; sourceTree = \"<group>\"; };\n\t\t279DCF0D2E85A766008B5542 /* Layouts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Layouts.swift; sourceTree = \"<group>\"; };\n\t\t279DCF102E85A826008B5542 /* ThomasLayoutListView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThomasLayoutListView.swift; sourceTree = \"<group>\"; };\n\t\t279DCF152E85AA02008B5542 /* LayoutsList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutsList.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE4A28F8BFBA00F2254B /* Toast.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Toast.swift; sourceTree = \"<group>\"; };\n\t\t32B5BE4D28F94FD200F2254B /* ToastView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ToastView.swift; sourceTree = \"<group>\"; };\n\t\t45AA81862F4A697800B81FBC /* AirshipConfig.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = AirshipConfig.plist; sourceTree = \"<group>\"; };\n\t\t6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = \"wrapper.pb-project\"; name = Airship.xcodeproj; path = ../Airship/Airship.xcodeproj; sourceTree = \"<group>\"; };\n\t\t6E65D58B2396E9A300AF2D1A /* AirshipExtensions.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = \"wrapper.pb-project\"; name = AirshipExtensions.xcodeproj; path = ../AirshipExtensions/AirshipExtensions.xcodeproj; sourceTree = \"<group>\"; };\n\t\t6E7DB37B28ECA830002725F6 /* DeliveryAttributes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeliveryAttributes.swift; sourceTree = \"<group>\"; };\n\t\t6EAAE8B828C7C9A3003CAE53 /* HomeView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeView.swift; sourceTree = \"<group>\"; };\n\t\t6EAAE8BB28C7C9A3003CAE53 /* NamedUserView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NamedUserView.swift; sourceTree = \"<group>\"; };\n\t\t6EAAE8BC28C7C9A3003CAE53 /* MainApp.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MainApp.swift; sourceTree = \"<group>\"; };\n\t\t6EAAE8C028C7C9A3003CAE53 /* AppView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppView.swift; sourceTree = \"<group>\"; };\n\t\t6EAAE97828C7E18B003CAE53 /* DevApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = DevApp.app; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6EAAE97E28C7E18C003CAE53 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t6EAAE99528C7E254003CAE53 /* DevApp.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DevApp.entitlements; sourceTree = \"<group>\"; };\n\t\t6EAAE99628C7E257003CAE53 /* Dev-App-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = \"Dev-App-Info.plist\"; sourceTree = SOURCE_ROOT; };\n\t\t6EAAE99C28C7E308003CAE53 /* DevApp App Clip.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = \"DevApp App Clip.app\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6EAAE9A728C7E309003CAE53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6EAAE9A828C7E309003CAE53 /* Airship_Sample_App_Clip.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Airship_Sample_App_Clip.entitlements; sourceTree = \"<group>\"; };\n\t\t6EAAE9EF28C7E417003CAE53 /* DevApp Service Extension.appex */ = {isa = PBXFileReference; explicitFileType = \"wrapper.app-extension\"; includeInIndex = 0; path = \"DevApp Service Extension.appex\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6EAAE9F128C7E417003CAE53 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = \"<group>\"; };\n\t\t6EAAE9F328C7E417003CAE53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6EB21A962E820088001A5660 /* AppRouter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppRouter.swift; sourceTree = \"<group>\"; };\n\t\t6EB21AA22E820C55001A5660 /* AirshipInitializer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirshipInitializer.swift; sourceTree = \"<group>\"; };\n\t\t6EB21AFD2E821DFD001A5660 /* LiveActivityHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityHandler.swift; sourceTree = \"<group>\"; };\n\t\t6EB21B012E821E30001A5660 /* DeepLinkHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeepLinkHandler.swift; sourceTree = \"<group>\"; };\n\t\t6EB21B042E821E4C001A5660 /* PushNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotificationHandler.swift; sourceTree = \"<group>\"; };\n\t\t6EDB2B0428D4E24F00A01377 /* LiveActivityExtension.appex */ = {isa = PBXFileReference; explicitFileType = \"wrapper.app-extension\"; includeInIndex = 0; path = LiveActivityExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t6EDB2B0528D4E24F00A01377 /* WidgetKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = WidgetKit.framework; path = System/Library/Frameworks/WidgetKit.framework; sourceTree = SDKROOT; };\n\t\t6EDB2B0728D4E24F00A01377 /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };\n\t\t6EDB2B0A28D4E24F00A01377 /* DeliveryActivityWidget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeliveryActivityWidget.swift; sourceTree = \"<group>\"; };\n\t\t6EDB2B0C28D4E24F00A01377 /* LiveActivity.intentdefinition */ = {isa = PBXFileReference; lastKnownFileType = file.intentdefinition; path = LiveActivity.intentdefinition; sourceTree = \"<group>\"; };\n\t\t6EDB2B0D28D4E25100A01377 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = \"<group>\"; };\n\t\t6EDB2B0F28D4E25100A01377 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = \"<group>\"; };\n\t\t6EDB2B2A28D4F27800A01377 /* Widgets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Widgets.swift; sourceTree = \"<group>\"; };\n/* End PBXFileReference section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t6EAAE97528C7E18B003CAE53 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t99E8D8072B57A5BA0099B6F3 /* AirshipAutomation.framework in Frameworks */,\n\t\t\t\t6EAAE9FF28C7E4B4003CAE53 /* AirshipBasement.framework in Frameworks */,\n\t\t\t\t6EAAEA0328C7E4B4003CAE53 /* AirshipDebug.framework in Frameworks */,\n\t\t\t\t6EAAEA0528C7E4B4003CAE53 /* AirshipMessageCenter.framework in Frameworks */,\n\t\t\t\t6EAAEA0728C7E4B4003CAE53 /* AirshipPreferenceCenter.framework in Frameworks */,\n\t\t\t\t6E938DAC2AC35F2600F691D9 /* AirshipFeatureFlags.framework in Frameworks */,\n\t\t\t\t279DCEF92E85A6A7008B5542 /* Yams in Frameworks */,\n\t\t\t\t6EAAEA0128C7E4B4003CAE53 /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE99928C7E308003CAE53 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t279DCF142E85A9DE008B5542 /* Yams in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE9EC28C7E417003CAE53 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EAAE9FA28C7E438003CAE53 /* AirshipNotificationServiceExtension.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EDB2B0128D4E24F00A01377 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EDB2B0828D4E24F00A01377 /* SwiftUI.framework in Frameworks */,\n\t\t\t\t6EDB2B0628D4E24F00A01377 /* WidgetKit.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t279DCECA2E85A530008B5542 /* Thomas */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCEFA2E85A6C6008B5542 /* JurassicPark.otf */,\n\t\t\t\t279DCED32E85A544008B5542 /* CustomView */,\n\t\t\t\t279DCEE02E85A558008B5542 /* ThomasLayoutViewModel.swift */,\n\t\t\t\t279DCF022E85A75C008B5542 /* Embedded Playground View */,\n\t\t\t\t279DCF0D2E85A766008B5542 /* Layouts.swift */,\n\t\t\t\t279DCEE52E85A5BD008B5542 /* Resources */,\n\t\t\t\t279DCF102E85A826008B5542 /* ThomasLayoutListView.swift */,\n\t\t\t\t279DCF152E85AA02008B5542 /* LayoutsList.swift */,\n\t\t\t);\n\t\t\tpath = Thomas;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t279DCED12E85A544008B5542 /* Weather */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCECF2E85A544008B5542 /* WeatherView.swift */,\n\t\t\t\t279DCED02E85A544008B5542 /* WeatherViewModel.swift */,\n\t\t\t);\n\t\t\tpath = Weather;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t279DCED22E85A544008B5542 /* Examples */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCECB2E85A544008B5542 /* MapRouteView.swift */,\n\t\t\t\t279DCECC2E85A544008B5542 /* CameraView.swift */,\n\t\t\t\t279DCECD2E85A544008B5542 /* AdView.swift */,\n\t\t\t\t279DCECE2E85A544008B5542 /* BiometricLoginView.swift */,\n\t\t\t\t279DCED12E85A544008B5542 /* Weather */,\n\t\t\t);\n\t\t\tpath = Examples;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t279DCED32E85A544008B5542 /* CustomView */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCED22E85A544008B5542 /* Examples */,\n\t\t\t);\n\t\t\tpath = CustomView;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t279DCEE52E85A5BD008B5542 /* Resources */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCEE32E85A5BD008B5542 /* Messages */,\n\t\t\t\t279DCEE42E85A5BD008B5542 /* Scenes */,\n\t\t\t);\n\t\t\tpath = Resources;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t279DCF022E85A75C008B5542 /* Embedded Playground View */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCEFD2E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift */,\n\t\t\t\t279DCEFE2E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift */,\n\t\t\t\t279DCEFF2E85A75C008B5542 /* EmbeddedPlaygroundView.swift */,\n\t\t\t\t279DCF002E85A75C008B5542 /* KeyView.swift */,\n\t\t\t\t279DCF012E85A75C008B5542 /* PlaceholderToggleView.swift */,\n\t\t\t);\n\t\t\tpath = \"Embedded Playground View\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E65D56B2396E99D00AF2D1A /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB1B41326EAA531000421B9 /* AirshipBasement.framework */,\n\t\t\t\t6E65D57C2396E99D00AF2D1A /* AirshipCore.framework */,\n\t\t\t\t84A565712A3D01A100F3A345 /* AirshipAutomation.framework */,\n\t\t\t\t6E2F5AA82A67330900CABD3D /* AirshipFeatureFlags.framework */,\n\t\t\t\t6E65D5882396E99D00AF2D1A /* AirshipMessageCenter.framework */,\n\t\t\t\t6EAD7CDF26B2003700B88EA7 /* AirshipPreferenceCenter.framework */,\n\t\t\t\t6EAF57672D35E2B700DF01BB /* AirshipObjectiveC.framework */,\n\t\t\t\t6E65D5842396E99D00AF2D1A /* AirshipDebug.framework */,\n\t\t\t\t6E65D5802396E99D00AF2D1A /* AirshipTests.xctest */,\n\t\t\t\t84A565732A3D01A100F3A345 /* AirshipAutomationTests.xctest */,\n\t\t\t\t6E2F5AAA2A67330900CABD3D /* AirshipFeatureFlagsTests.xctest */,\n\t\t\t\t32F68D0628F07C7300F7F52A /* AirshipMessageCenterTests.xctest */,\n\t\t\t\t6EAD7CE126B2003700B88EA7 /* AirshipPreferenceCenterTests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6E65D58C2396E9A300AF2D1A /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E65D5912396E9A300AF2D1A /* AirshipNotificationServiceExtension.framework */,\n\t\t\t\t1BEF603B24CB3DB60039091F /* AirshipNotificationServiceExtensionTests.xctest */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EAAE97928C7E18B003CAE53 /* Dev App */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t279DCECA2E85A530008B5542 /* Thomas */,\n\t\t\t\t6EB21B002E821E21001A5660 /* Setup */,\n\t\t\t\t32B5BE4A28F8BFBA00F2254B /* Toast.swift */,\n\t\t\t\t6EB21A962E820088001A5660 /* AppRouter.swift */,\n\t\t\t\t6EAAE8BC28C7C9A3003CAE53 /* MainApp.swift */,\n\t\t\t\t6EB21AA52E821509001A5660 /* View */,\n\t\t\t\t6EAAE99628C7E257003CAE53 /* Dev-App-Info.plist */,\n\t\t\t\t6EAAE99528C7E254003CAE53 /* DevApp.entitlements */,\n\t\t\t\t6EAAE97E28C7E18C003CAE53 /* Assets.xcassets */,\n\t\t\t);\n\t\t\tpath = \"Dev App\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EAAE99D28C7E308003CAE53 /* DevApp App Clip */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EAAE9A728C7E309003CAE53 /* Info.plist */,\n\t\t\t\t6EAAE9A828C7E309003CAE53 /* Airship_Sample_App_Clip.entitlements */,\n\t\t\t);\n\t\t\tpath = \"DevApp App Clip\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EAAE9F028C7E417003CAE53 /* DevApp Service Extension */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EAAE9F128C7E417003CAE53 /* NotificationService.swift */,\n\t\t\t\t6EAAE9F328C7E417003CAE53 /* Info.plist */,\n\t\t\t);\n\t\t\tpath = \"DevApp Service Extension\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21AA52E821509001A5660 /* View */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32B5BE4D28F94FD200F2254B /* ToastView.swift */,\n\t\t\t\t6EAAE8C028C7C9A3003CAE53 /* AppView.swift */,\n\t\t\t\t6EAAE8B828C7C9A3003CAE53 /* HomeView.swift */,\n\t\t\t\t6EAAE8BB28C7C9A3003CAE53 /* NamedUserView.swift */,\n\t\t\t);\n\t\t\tpath = View;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EB21B002E821E21001A5660 /* Setup */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EB21AA22E820C55001A5660 /* AirshipInitializer.swift */,\n\t\t\t\t6EB21B042E821E4C001A5660 /* PushNotificationHandler.swift */,\n\t\t\t\t6EB21B012E821E30001A5660 /* DeepLinkHandler.swift */,\n\t\t\t\t6EB21AFD2E821DFD001A5660 /* LiveActivityHandler.swift */,\n\t\t\t);\n\t\t\tpath = Setup;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t6EDB2B0928D4E24F00A01377 /* LiveActivity */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6E7DB37B28ECA830002725F6 /* DeliveryAttributes.swift */,\n\t\t\t\t6EDB2B0A28D4E24F00A01377 /* DeliveryActivityWidget.swift */,\n\t\t\t\t6EDB2B0C28D4E24F00A01377 /* LiveActivity.intentdefinition */,\n\t\t\t\t6EDB2B0D28D4E25100A01377 /* Assets.xcassets */,\n\t\t\t\t6EDB2B0F28D4E25100A01377 /* Info.plist */,\n\t\t\t\t6EDB2B2A28D4F27800A01377 /* Widgets.swift */,\n\t\t\t);\n\t\t\tpath = LiveActivity;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCCDA359A1B8D233A00950AD5 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t45AA81862F4A697800B81FBC /* AirshipConfig.plist */,\n\t\t\t\t6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */,\n\t\t\t\t6E65D58B2396E9A300AF2D1A /* AirshipExtensions.xcodeproj */,\n\t\t\t\t6EAAE97928C7E18B003CAE53 /* Dev App */,\n\t\t\t\t6EAAE99D28C7E308003CAE53 /* DevApp App Clip */,\n\t\t\t\t6EAAE9F028C7E417003CAE53 /* DevApp Service Extension */,\n\t\t\t\t6EDB2B0928D4E24F00A01377 /* LiveActivity */,\n\t\t\t\tCCDA35A41B8D233A00950AD5 /* Products */,\n\t\t\t\tDF4E494E221D96CB00F306A5 /* Frameworks */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tCCDA35A41B8D233A00950AD5 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t6EAAE97828C7E18B003CAE53 /* DevApp.app */,\n\t\t\t\t6EAAE99C28C7E308003CAE53 /* DevApp App Clip.app */,\n\t\t\t\t6EAAE9EF28C7E417003CAE53 /* DevApp Service Extension.appex */,\n\t\t\t\t6EDB2B0428D4E24F00A01377 /* LiveActivityExtension.appex */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\tDF4E494E221D96CB00F306A5 /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t1BEF603F24CB3FF10039091F /* AppClip.framework */,\n\t\t\t\t1BFB70562388302000BF7F07 /* UserNotifications.framework */,\n\t\t\t\t1BFB70582388302000BF7F07 /* UserNotificationsUI.framework */,\n\t\t\t\t6EDB2B0528D4E24F00A01377 /* WidgetKit.framework */,\n\t\t\t\t6EDB2B0728D4E24F00A01377 /* SwiftUI.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t6EAAE97728C7E18B003CAE53 /* DevApp */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6EAAE98328C7E18C003CAE53 /* Build configuration list for PBXNativeTarget \"DevApp\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6EAAE97428C7E18B003CAE53 /* Sources */,\n\t\t\t\t6EAAE97528C7E18B003CAE53 /* Frameworks */,\n\t\t\t\t6EAAE97628C7E18B003CAE53 /* Resources */,\n\t\t\t\t6EAAE9AF28C7E309003CAE53 /* Embed App Clips */,\n\t\t\t\t6EAAE9D128C7E393003CAE53 /* Embed Foundation Extensions */,\n\t\t\t\t6EAAEA0928C7E4B4003CAE53 /* Embed Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E1B7ADA2B6DBE3A00695561 /* PBXTargetDependency */,\n\t\t\t\t6E2F5A992A67330900CABD3D /* PBXTargetDependency */,\n\t\t\t\t6EAAEA0D28C7E4DB003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA0F28C7E4DB003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA1128C7E4DB003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA1328C7E4DB003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA1528C7E4DB003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAE9AA28C7E309003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAE9F528C7E417003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EDB2B1328D4E25100A01377 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = DevApp;\n\t\t\tproductName = \"Airship Sample\";\n\t\t\tproductReference = 6EAAE97828C7E18B003CAE53 /* DevApp.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n\t\t6EAAE99B28C7E308003CAE53 /* DevApp App Clip */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6EAAE9AC28C7E309003CAE53 /* Build configuration list for PBXNativeTarget \"DevApp App Clip\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6EAAE99828C7E308003CAE53 /* Sources */,\n\t\t\t\t6EAAE99928C7E308003CAE53 /* Frameworks */,\n\t\t\t\t6EAAE99A28C7E308003CAE53 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6E1B7AEB2B6DBE4A00695561 /* PBXTargetDependency */,\n\t\t\t\t6E2F5AAF2A67331500CABD3D /* PBXTargetDependency */,\n\t\t\t\t6EAAEA1B28C7E4ED003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA1D28C7E4ED003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA1F28C7E4ED003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA2128C7E4ED003CAE53 /* PBXTargetDependency */,\n\t\t\t\t6EAAEA2328C7E4ED003CAE53 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = \"DevApp App Clip\";\n\t\t\tproductName = \"Airship Sample App Clip\";\n\t\t\tproductReference = 6EAAE99C28C7E308003CAE53 /* DevApp App Clip.app */;\n\t\t\tproductType = \"com.apple.product-type.application.on-demand-install-capable\";\n\t\t};\n\t\t6EAAE9EE28C7E417003CAE53 /* DevApp Service Extension */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6EAAE9F728C7E417003CAE53 /* Build configuration list for PBXNativeTarget \"DevApp Service Extension\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6EAAE9EB28C7E417003CAE53 /* Sources */,\n\t\t\t\t6EAAE9EC28C7E417003CAE53 /* Frameworks */,\n\t\t\t\t6EAAE9ED28C7E417003CAE53 /* Resources */,\n\t\t\t\t6EAAE9FC28C7E438003CAE53 /* Embed Frameworks */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t6EAAEA1728C7E4E3003CAE53 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = \"DevApp Service Extension\";\n\t\t\tproductName = \"Airship Sample Service Extension\";\n\t\t\tproductReference = 6EAAE9EF28C7E417003CAE53 /* DevApp Service Extension.appex */;\n\t\t\tproductType = \"com.apple.product-type.app-extension\";\n\t\t};\n\t\t6EDB2B0328D4E24F00A01377 /* LiveActivityExtension */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 6EDB2B2228D4E25100A01377 /* Build configuration list for PBXNativeTarget \"LiveActivityExtension\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t6EDB2B0028D4E24F00A01377 /* Sources */,\n\t\t\t\t6EDB2B0128D4E24F00A01377 /* Frameworks */,\n\t\t\t\t6EDB2B0228D4E24F00A01377 /* Resources */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tname = LiveActivityExtension;\n\t\t\tproductName = LiveActivityExtension;\n\t\t\tproductReference = 6EDB2B0428D4E24F00A01377 /* LiveActivityExtension.appex */;\n\t\t\tproductType = \"com.apple.product-type.app-extension\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\tCCDA359B1B8D233A00950AD5 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = YES;\n\t\t\t\tLastSwiftUpdateCheck = 1410;\n\t\t\t\tLastUpgradeCheck = 1600;\n\t\t\t\tORGANIZATIONNAME = \"Urban Airship\";\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t6EAAE97728C7E18B003CAE53 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.4;\n\t\t\t\t\t};\n\t\t\t\t\t6EAAE99B28C7E308003CAE53 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.4;\n\t\t\t\t\t\tDevelopmentTeam = PGJV57GD94;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t\t6EAAE9EE28C7E417003CAE53 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 13.4;\n\t\t\t\t\t\tDevelopmentTeam = PGJV57GD94;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t\t6EDB2B0328D4E24F00A01377 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 14.1;\n\t\t\t\t\t\tDevelopmentTeam = PGJV57GD94;\n\t\t\t\t\t\tProvisioningStyle = Automatic;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = CCDA359E1B8D233A00950AD5 /* Build configuration list for PBXProject \"DevApp\" */;\n\t\t\tcompatibilityVersion = \"Xcode 3.2\";\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t\tde,\n\t\t\t\tes,\n\t\t\t\tfr,\n\t\t\t\tit,\n\t\t\t\tja,\n\t\t\t\tpt,\n\t\t\t\t\"zh-Hans\",\n\t\t\t\t\"zh-Hant\",\n\t\t\t);\n\t\t\tmainGroup = CCDA359A1B8D233A00950AD5;\n\t\t\tpackageReferences = (\n\t\t\t\t279DCEF72E85A6A7008B5542 /* XCRemoteSwiftPackageReference \"Yams\" */,\n\t\t\t);\n\t\t\tproductRefGroup = CCDA35A41B8D233A00950AD5 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectReferences = (\n\t\t\t\t{\n\t\t\t\t\tProductGroup = 6E65D56B2396E99D00AF2D1A /* Products */;\n\t\t\t\t\tProjectRef = 6E65D56A2396E99D00AF2D1A /* Airship.xcodeproj */;\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tProductGroup = 6E65D58C2396E9A300AF2D1A /* Products */;\n\t\t\t\t\tProjectRef = 6E65D58B2396E9A300AF2D1A /* AirshipExtensions.xcodeproj */;\n\t\t\t\t},\n\t\t\t);\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t6EAAE97728C7E18B003CAE53 /* DevApp */,\n\t\t\t\t6EAAE99B28C7E308003CAE53 /* DevApp App Clip */,\n\t\t\t\t6EAAE9EE28C7E417003CAE53 /* DevApp Service Extension */,\n\t\t\t\t6EDB2B0328D4E24F00A01377 /* LiveActivityExtension */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXReferenceProxy section */\n\t\t1BEF603B24CB3DB60039091F /* AirshipNotificationServiceExtensionTests.xctest */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.cfbundle;\n\t\t\tpath = AirshipNotificationServiceExtensionTests.xctest;\n\t\t\tremoteRef = 1BEF603A24CB3DB60039091F /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t32F68D0628F07C7300F7F52A /* AirshipMessageCenterTests.xctest */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.cfbundle;\n\t\t\tpath = AirshipMessageCenterTests.xctest;\n\t\t\tremoteRef = 32F68D0528F07C7300F7F52A /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E2F5AA82A67330900CABD3D /* AirshipFeatureFlags.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipFeatureFlags.framework;\n\t\t\tremoteRef = 6E2F5AA72A67330900CABD3D /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E2F5AAA2A67330900CABD3D /* AirshipFeatureFlagsTests.xctest */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.cfbundle;\n\t\t\tpath = AirshipFeatureFlagsTests.xctest;\n\t\t\tremoteRef = 6E2F5AA92A67330900CABD3D /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E65D57C2396E99D00AF2D1A /* AirshipCore.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipCore.framework;\n\t\t\tremoteRef = 6E65D57B2396E99D00AF2D1A /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E65D5802396E99D00AF2D1A /* AirshipTests.xctest */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.cfbundle;\n\t\t\tpath = AirshipTests.xctest;\n\t\t\tremoteRef = 6E65D57F2396E99D00AF2D1A /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E65D5842396E99D00AF2D1A /* AirshipDebug.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipDebug.framework;\n\t\t\tremoteRef = 6E65D5832396E99D00AF2D1A /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E65D5882396E99D00AF2D1A /* AirshipMessageCenter.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipMessageCenter.framework;\n\t\t\tremoteRef = 6E65D5872396E99D00AF2D1A /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6E65D5912396E9A300AF2D1A /* AirshipNotificationServiceExtension.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipNotificationServiceExtension.framework;\n\t\t\tremoteRef = 6E65D5902396E9A300AF2D1A /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6EAD7CDF26B2003700B88EA7 /* AirshipPreferenceCenter.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipPreferenceCenter.framework;\n\t\t\tremoteRef = 6EAD7CDE26B2003700B88EA7 /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6EAD7CE126B2003700B88EA7 /* AirshipPreferenceCenterTests.xctest */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.cfbundle;\n\t\t\tpath = AirshipPreferenceCenterTests.xctest;\n\t\t\tremoteRef = 6EAD7CE026B2003700B88EA7 /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6EAF57672D35E2B700DF01BB /* AirshipObjectiveC.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipObjectiveC.framework;\n\t\t\tremoteRef = 6EAF57662D35E2B700DF01BB /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t6EB1B41326EAA531000421B9 /* AirshipBasement.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipBasement.framework;\n\t\t\tremoteRef = 6EB1B41226EAA531000421B9 /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t84A565712A3D01A100F3A345 /* AirshipAutomation.framework */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.framework;\n\t\t\tpath = AirshipAutomation.framework;\n\t\t\tremoteRef = 84A565702A3D01A100F3A345 /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n\t\t84A565732A3D01A100F3A345 /* AirshipAutomationTests.xctest */ = {\n\t\t\tisa = PBXReferenceProxy;\n\t\t\tfileType = wrapper.cfbundle;\n\t\t\tpath = AirshipAutomationTests.xctest;\n\t\t\tremoteRef = 84A565722A3D01A100F3A345 /* PBXContainerItemProxy */;\n\t\t\tsourceTree = BUILT_PRODUCTS_DIR;\n\t\t};\n/* End PBXReferenceProxy section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t6EAAE97628C7E18B003CAE53 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t279DCEE82E85A5BD008B5542 /* Scenes in Resources */,\n\t\t\t\t279DCEE92E85A5BD008B5542 /* Messages in Resources */,\n\t\t\t\t279DCEFB2E85A6C6008B5542 /* JurassicPark.otf in Resources */,\n\t\t\t\t45AA81882F4A697800B81FBC /* AirshipConfig.plist in Resources */,\n\t\t\t\t6EAAE97F28C7E18C003CAE53 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE99A28C7E308003CAE53 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t279DCEE62E85A5BD008B5542 /* Scenes in Resources */,\n\t\t\t\t279DCEE72E85A5BD008B5542 /* Messages in Resources */,\n\t\t\t\t279DCEFC2E85A6C6008B5542 /* JurassicPark.otf in Resources */,\n\t\t\t\t45AA81872F4A697800B81FBC /* AirshipConfig.plist in Resources */,\n\t\t\t\t6EAAE9BE28C7E31E003CAE53 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE9ED28C7E417003CAE53 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EDB2B0228D4E24F00A01377 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EDB2B0E28D4E25100A01377 /* Assets.xcassets in Resources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t6EAAE97428C7E18B003CAE53 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EDB2B1128D4E25100A01377 /* LiveActivity.intentdefinition in Sources */,\n\t\t\t\t6EB21AA32E820C5A001A5660 /* AirshipInitializer.swift in Sources */,\n\t\t\t\t279DCEE12E85A558008B5542 /* ThomasLayoutViewModel.swift in Sources */,\n\t\t\t\t32B5BE4B28F8BFBA00F2254B /* Toast.swift in Sources */,\n\t\t\t\t6EAAE98C28C7E1A1003CAE53 /* AppView.swift in Sources */,\n\t\t\t\t6EB21AFF2E821E01001A5660 /* LiveActivityHandler.swift in Sources */,\n\t\t\t\t279DCF032E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift in Sources */,\n\t\t\t\t279DCF042E85A75C008B5542 /* PlaceholderToggleView.swift in Sources */,\n\t\t\t\t279DCF052E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift in Sources */,\n\t\t\t\t279DCF062E85A75C008B5542 /* KeyView.swift in Sources */,\n\t\t\t\t279DCF072E85A75C008B5542 /* EmbeddedPlaygroundView.swift in Sources */,\n\t\t\t\t6EB21B062E821E50001A5660 /* PushNotificationHandler.swift in Sources */,\n\t\t\t\t279DCEDA2E85A544008B5542 /* BiometricLoginView.swift in Sources */,\n\t\t\t\t279DCEDB2E85A544008B5542 /* WeatherView.swift in Sources */,\n\t\t\t\t279DCEDC2E85A544008B5542 /* MapRouteView.swift in Sources */,\n\t\t\t\t279DCEDD2E85A544008B5542 /* CameraView.swift in Sources */,\n\t\t\t\t279DCEDE2E85A544008B5542 /* AdView.swift in Sources */,\n\t\t\t\t279DCF162E85AA02008B5542 /* LayoutsList.swift in Sources */,\n\t\t\t\t279DCEDF2E85A544008B5542 /* WeatherViewModel.swift in Sources */,\n\t\t\t\t6EAAE98A28C7E1A1003CAE53 /* MainApp.swift in Sources */,\n\t\t\t\t6EB21B022E821E34001A5660 /* DeepLinkHandler.swift in Sources */,\n\t\t\t\t279DCF112E85A826008B5542 /* ThomasLayoutListView.swift in Sources */,\n\t\t\t\t6EAAE98828C7E1A1003CAE53 /* NamedUserView.swift in Sources */,\n\t\t\t\t32B5BE4E28F94FD200F2254B /* ToastView.swift in Sources */,\n\t\t\t\t6EB21AA12E82008A001A5660 /* AppRouter.swift in Sources */,\n\t\t\t\t6EAAE98728C7E1A1003CAE53 /* HomeView.swift in Sources */,\n\t\t\t\t6E7DB39128ED0AF5002725F6 /* DeliveryAttributes.swift in Sources */,\n\t\t\t\t279DCF0E2E85A766008B5542 /* Layouts.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE99828C7E308003CAE53 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t279DCF122E85A826008B5542 /* ThomasLayoutListView.swift in Sources */,\n\t\t\t\t6EB21B032E821E34001A5660 /* DeepLinkHandler.swift in Sources */,\n\t\t\t\t6EAAE9B128C7E31E003CAE53 /* MainApp.swift in Sources */,\n\t\t\t\t279DCF0F2E85A766008B5542 /* Layouts.swift in Sources */,\n\t\t\t\t6EAAE9BD28C7E31E003CAE53 /* HomeView.swift in Sources */,\n\t\t\t\t32B5BE4C28F8BFBA00F2254B /* Toast.swift in Sources */,\n\t\t\t\t279DCED42E85A544008B5542 /* BiometricLoginView.swift in Sources */,\n\t\t\t\t279DCED52E85A544008B5542 /* WeatherView.swift in Sources */,\n\t\t\t\t279DCED62E85A544008B5542 /* MapRouteView.swift in Sources */,\n\t\t\t\t279DCED72E85A544008B5542 /* CameraView.swift in Sources */,\n\t\t\t\t279DCED82E85A544008B5542 /* AdView.swift in Sources */,\n\t\t\t\t279DCED92E85A544008B5542 /* WeatherViewModel.swift in Sources */,\n\t\t\t\t6EB21AFE2E821E01001A5660 /* LiveActivityHandler.swift in Sources */,\n\t\t\t\t6EAAE9B928C7E31E003CAE53 /* AppView.swift in Sources */,\n\t\t\t\t6EB21AA02E82008A001A5660 /* AppRouter.swift in Sources */,\n\t\t\t\t279DCF082E85A75C008B5542 /* EmbeddedPlaygroundPicker.swift in Sources */,\n\t\t\t\t279DCF092E85A75C008B5542 /* PlaceholderToggleView.swift in Sources */,\n\t\t\t\t279DCF0A2E85A75C008B5542 /* EmbeddedPlaygroundMenuView.swift in Sources */,\n\t\t\t\t279DCF0B2E85A75C008B5542 /* KeyView.swift in Sources */,\n\t\t\t\t279DCF0C2E85A75C008B5542 /* EmbeddedPlaygroundView.swift in Sources */,\n\t\t\t\t6E7DB39028ED0AF4002725F6 /* DeliveryAttributes.swift in Sources */,\n\t\t\t\t32B5BE4F28F94FD200F2254B /* ToastView.swift in Sources */,\n\t\t\t\t6EB21B052E821E50001A5660 /* PushNotificationHandler.swift in Sources */,\n\t\t\t\t279DCF172E85AA02008B5542 /* LayoutsList.swift in Sources */,\n\t\t\t\t279DCEE22E85A558008B5542 /* ThomasLayoutViewModel.swift in Sources */,\n\t\t\t\t6EAAE9B228C7E31E003CAE53 /* NamedUserView.swift in Sources */,\n\t\t\t\t6EB21AA42E820C5A001A5660 /* AirshipInitializer.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EAAE9EB28C7E417003CAE53 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EAAE9F228C7E417003CAE53 /* NotificationService.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t6EDB2B0028D4E24F00A01377 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t6EDB2B0B28D4E24F00A01377 /* DeliveryActivityWidget.swift in Sources */,\n\t\t\t\t6EDB2B1028D4E25100A01377 /* LiveActivity.intentdefinition in Sources */,\n\t\t\t\t6E7DB38F28ED0AF3002725F6 /* DeliveryAttributes.swift in Sources */,\n\t\t\t\t6EDB2B2E28D4F97F00A01377 /* Widgets.swift in Sources */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t6E1B7ADA2B6DBE3A00695561 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipAutomation;\n\t\t\ttargetProxy = 6E1B7AD92B6DBE3A00695561 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E1B7AEB2B6DBE4A00695561 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipAutomation;\n\t\t\ttargetProxy = 6E1B7AEA2B6DBE4A00695561 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E2F5A992A67330900CABD3D /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipFeatureFlags;\n\t\t\ttargetProxy = 6E2F5A982A67330900CABD3D /* PBXContainerItemProxy */;\n\t\t};\n\t\t6E2F5AAF2A67331500CABD3D /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipFeatureFlags;\n\t\t\ttargetProxy = 6E2F5AAE2A67331500CABD3D /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE9AA28C7E309003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tplatformFilter = ios;\n\t\t\ttarget = 6EAAE99B28C7E308003CAE53 /* DevApp App Clip */;\n\t\t\ttargetProxy = 6EAAE9A928C7E309003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAE9F528C7E417003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tplatformFilters = (\n\t\t\t\tios,\n\t\t\t\txros,\n\t\t\t);\n\t\t\ttarget = 6EAAE9EE28C7E417003CAE53 /* DevApp Service Extension */;\n\t\t\ttargetProxy = 6EAAE9F428C7E417003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA0D28C7E4DB003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipBasement;\n\t\t\ttargetProxy = 6EAAEA0C28C7E4DB003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA0F28C7E4DB003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipCore;\n\t\t\ttargetProxy = 6EAAEA0E28C7E4DB003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1128C7E4DB003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipDebug;\n\t\t\tplatformFilters = (\n\t\t\t\tios,\n\t\t\t\ttvos,\n\t\t\t\txros,\n\t\t\t);\n\t\t\ttargetProxy = 6EAAEA1028C7E4DB003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1328C7E4DB003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipMessageCenter;\n\t\t\ttargetProxy = 6EAAEA1228C7E4DB003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1528C7E4DB003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipPreferenceCenter;\n\t\t\ttargetProxy = 6EAAEA1428C7E4DB003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1728C7E4E3003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipNotificationServiceExtension;\n\t\t\tplatformFilter = ios;\n\t\t\ttargetProxy = 6EAAEA1628C7E4E3003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1B28C7E4ED003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipBasement;\n\t\t\ttargetProxy = 6EAAEA1A28C7E4ED003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1D28C7E4ED003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipCore;\n\t\t\ttargetProxy = 6EAAEA1C28C7E4ED003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA1F28C7E4ED003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipDebug;\n\t\t\ttargetProxy = 6EAAEA1E28C7E4ED003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA2128C7E4ED003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipMessageCenter;\n\t\t\ttargetProxy = 6EAAEA2028C7E4ED003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EAAEA2328C7E4ED003CAE53 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tname = AirshipPreferenceCenter;\n\t\t\ttargetProxy = 6EAAEA2228C7E4ED003CAE53 /* PBXContainerItemProxy */;\n\t\t};\n\t\t6EDB2B1328D4E25100A01377 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\tplatformFilter = ios;\n\t\t\ttarget = 6EDB2B0328D4E24F00A01377 /* LiveActivityExtension */;\n\t\t\ttargetProxy = 6EDB2B1228D4E25100A01377 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\t6EAAE98428C7E18C003CAE53 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tAUTOMATION_APPLE_EVENTS = NO;\n\t\t\t\tBUILD_LIBRARY_FOR_DISTRIBUTION = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"Dev App/DevApp.entitlements\";\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;\n\t\t\t\tENABLE_RESOURCE_ACCESS_CALENDARS = NO;\n\t\t\t\tENABLE_RESOURCE_ACCESS_CAMERA = YES;\n\t\t\t\tENABLE_RESOURCE_ACCESS_CONTACTS = NO;\n\t\t\t\tENABLE_RESOURCE_ACCESS_LOCATION = YES;\n\t\t\t\tENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"Dev-App-Info.plist\";\n\t\t\t\tINFOPLIST_KEY_NSCameraUsageDescription = \"Camera App needs to use your camera to submit your ID Photo\";\n\t\t\t\tINFOPLIST_KEY_NSFaceIDUsageDescription = \"Thomas app needs to use Face ID to log you in\";\n\t\t\t\tINFOPLIST_KEY_NSLocationWhenInUseUsageDescription = \"Weather app needs to use your location to route you to your nearest NETs station\";\n\t\t\t\tINFOPLIST_KEY_NSSupportsLiveActivities = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 15.6;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = NO;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;\n\t\t\t\tRUNTIME_EXCEPTION_DEBUGGING_TOOL = NO;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6EAAE98528C7E18C003CAE53 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tAUTOMATION_APPLE_EVENTS = NO;\n\t\t\t\tBUILD_LIBRARY_FOR_DISTRIBUTION = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"Dev App/DevApp.entitlements\";\n\t\t\t\tCODE_SIGN_IDENTITY = \"Apple Development\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_APP_SANDBOX = YES;\n\t\t\t\tENABLE_HARDENED_RUNTIME = YES;\n\t\t\t\tENABLE_OUTGOING_NETWORK_CONNECTIONS = YES;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tENABLE_RESOURCE_ACCESS_AUDIO_INPUT = NO;\n\t\t\t\tENABLE_RESOURCE_ACCESS_CALENDARS = NO;\n\t\t\t\tENABLE_RESOURCE_ACCESS_CAMERA = YES;\n\t\t\t\tENABLE_RESOURCE_ACCESS_CONTACTS = NO;\n\t\t\t\tENABLE_RESOURCE_ACCESS_LOCATION = YES;\n\t\t\t\tENABLE_RESOURCE_ACCESS_PHOTO_LIBRARY = NO;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"Dev-App-Info.plist\";\n\t\t\t\tINFOPLIST_KEY_NSCameraUsageDescription = \"Camera App needs to use your camera to submit your ID Photo\";\n\t\t\t\tINFOPLIST_KEY_NSFaceIDUsageDescription = \"Thomas app needs to use Face ID to log you in\";\n\t\t\t\tINFOPLIST_KEY_NSLocationWhenInUseUsageDescription = \"Weather app needs to use your location to route you to your nearest NETs station\";\n\t\t\t\tINFOPLIST_KEY_NSSupportsLiveActivities = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMACOSX_DEPLOYMENT_TARGET = 15.6;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tPROVISIONING_PROFILE_SPECIFIER = \"\";\n\t\t\t\tREGISTER_APP_GROUPS = YES;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_DYLD_ENVIRONMENT_VARIABLES = NO;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_JIT = NO;\n\t\t\t\tRUNTIME_EXCEPTION_ALLOW_UNSIGNED_EXECUTABLE_MEMORY = NO;\n\t\t\t\tRUNTIME_EXCEPTION_DEBUGGING_TOOL = NO;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_EXECUTABLE_PAGE_PROTECTION = NO;\n\t\t\t\tRUNTIME_EXCEPTION_DISABLE_LIBRARY_VALIDATION = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"appletvos appletvsimulator iphoneos iphonesimulator macosx xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,3,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6EAAE9AD28C7E309003CAE53 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"DevApp App Clip/Airship_Sample_App_Clip.entitlements\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"DevApp App Clip/Info.plist\";\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = DevApp;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.Clip;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6EAAE9AE28C7E309003CAE53 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_ENTITLEMENTS = \"DevApp App Clip/Airship_Sample_App_Clip.entitlements\";\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"DevApp App Clip/Info.plist\";\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = DevApp;\n\t\t\t\tINFOPLIST_KEY_LSApplicationCategoryType = \"\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = \"UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight\";\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.Clip;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6EAAE9F828C7E417003CAE53 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"DevApp Service Extension/Info.plist\";\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"DevApp Service Extension\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022 Urban Airship. All rights reserved.\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@executable_path/../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.ServiceExtension;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6EAAE9F928C7E417003CAE53 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++17\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = \"DevApp Service Extension/Info.plist\";\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"DevApp Service Extension\";\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022 Urban Airship. All rights reserved.\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@executable_path/../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.ServiceExtension;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator xros xrsimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2,7\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t6EDB2B1528D4E25100A01377 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = LiveActivity/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = LiveActivity;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022 Urban Airship. All rights reserved.\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@executable_path/../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.LiveActivity;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t6EDB2B1628D4E25100A01377 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu11;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_FILE = LiveActivity/Info.plist;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = LiveActivity;\n\t\t\t\tINFOPLIST_KEY_NSHumanReadableCopyright = \"Copyright © 2022 Urban Airship. All rights reserved.\";\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t\t\"@executable_path/../../Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush.LiveActivity;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSUPPORTED_PLATFORMS = \"iphoneos iphonesimulator\";\n\t\t\t\tSUPPORTS_MACCATALYST = NO;\n\t\t\t\tSUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\tCCDA35B51B8D233A00950AD5 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALID_ARCHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\tCCDA35B61B8D233A00950AD5 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tCLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++0x\";\n\t\t\t\tCLANG_CXX_LIBRARY = \"libc++\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\t\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\" = \"iPhone Developer\";\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu99;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tINFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES;\n\t\t\t\tINFOPLIST_KEY_UILaunchScreen_Generation = YES;\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-O\";\n\t\t\t\tSWIFT_VERSION = 6.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = \"1,2\";\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t\tVALID_ARCHS = \"$(inherited)\";\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t6EAAE98328C7E18C003CAE53 /* Build configuration list for PBXNativeTarget \"DevApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6EAAE98428C7E18C003CAE53 /* Debug */,\n\t\t\t\t6EAAE98528C7E18C003CAE53 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6EAAE9AC28C7E309003CAE53 /* Build configuration list for PBXNativeTarget \"DevApp App Clip\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6EAAE9AD28C7E309003CAE53 /* Debug */,\n\t\t\t\t6EAAE9AE28C7E309003CAE53 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6EAAE9F728C7E417003CAE53 /* Build configuration list for PBXNativeTarget \"DevApp Service Extension\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6EAAE9F828C7E417003CAE53 /* Debug */,\n\t\t\t\t6EAAE9F928C7E417003CAE53 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t6EDB2B2228D4E25100A01377 /* Build configuration list for PBXNativeTarget \"LiveActivityExtension\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t6EDB2B1528D4E25100A01377 /* Debug */,\n\t\t\t\t6EDB2B1628D4E25100A01377 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\tCCDA359E1B8D233A00950AD5 /* Build configuration list for PBXProject \"DevApp\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\tCCDA35B51B8D233A00950AD5 /* Debug */,\n\t\t\t\tCCDA35B61B8D233A00950AD5 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\n/* Begin XCRemoteSwiftPackageReference section */\n\t\t279DCEF72E85A6A7008B5542 /* XCRemoteSwiftPackageReference \"Yams\" */ = {\n\t\t\tisa = XCRemoteSwiftPackageReference;\n\t\t\trepositoryURL = \"https://github.com/jpsim/Yams.git\";\n\t\t\trequirement = {\n\t\t\t\tkind = upToNextMajorVersion;\n\t\t\t\tminimumVersion = 6.1.0;\n\t\t\t};\n\t\t};\n/* End XCRemoteSwiftPackageReference section */\n\n/* Begin XCSwiftPackageProductDependency section */\n\t\t279DCEF82E85A6A7008B5542 /* Yams */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 279DCEF72E85A6A7008B5542 /* XCRemoteSwiftPackageReference \"Yams\" */;\n\t\t\tproductName = Yams;\n\t\t};\n\t\t279DCF132E85A9DE008B5542 /* Yams */ = {\n\t\t\tisa = XCSwiftPackageProductDependency;\n\t\t\tpackage = 279DCEF72E85A6A7008B5542 /* XCRemoteSwiftPackageReference \"Yams\" */;\n\t\t\tproductName = Yams;\n\t\t};\n/* End XCSwiftPackageProductDependency section */\n\t};\n\trootObject = CCDA359B1B8D233A00950AD5 /* Project object */;\n}\n"
  },
  {
    "path": "DevApp/DevApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "DevApp/DevApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>IDEDidComputeMac32BitWarning</key>\n\t<true/>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/DevApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved",
    "content": "{\n  \"originHash\" : \"00fc82b0de716b0b6fae8646fae1c289e5b4679f93a4b0a9aca7e47cd71f9315\",\n  \"pins\" : [\n    {\n      \"identity\" : \"yams\",\n      \"kind\" : \"remoteSourceControl\",\n      \"location\" : \"https://github.com/jpsim/Yams.git\",\n      \"state\" : {\n        \"revision\" : \"deaf82e867fa2cbd3cd865978b079bfcf384ac28\",\n        \"version\" : \"6.2.1\"\n      }\n    }\n  ],\n  \"version\" : 3\n}\n"
  },
  {
    "path": "DevApp/LiveActivity/Assets.xcassets/23Grande.imageset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"1024.png\",\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/LiveActivity/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/LiveActivity/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"idiom\" : \"universal\",\n      \"platform\" : \"ios\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/LiveActivity/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/LiveActivity/Assets.xcassets/WidgetBackground.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp/LiveActivity/DeliveryActivityWidget.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(ActivityKit)\n\nimport Foundation\nimport ActivityKit\nimport SwiftUI\nimport WidgetKit\n\nstruct DeliveryActivityWidget: Widget {\n    var body: some WidgetConfiguration {\n        ActivityConfiguration(for: DeliveryAttributes.self) { context in\n            LockScreenLiveActivityView(context: context)\n                .widgetURL(URL(string: \"cool\"))\n        } dynamicIsland: { context in\n            DynamicIsland {\n                DynamicIslandExpandedRegion(.leading) {\n                    Image(systemName: \"box.truck.fill\")\n                        .resizable()\n                        .scaledToFit()\n                        .frame(width: 30, height: 30)\n                }\n                DynamicIslandExpandedRegion(.trailing) {\n                    Label {\n                        Text(\"\\(context.attributes.orderNumber)\")\n                    } icon: {\n                        Image(systemName: \"shippingbox.fill\")\n                            .foregroundColor(.primary)\n                    }\n                }\n\n                DynamicIslandExpandedRegion(.center) {\n                    if context.state.stopsAway > 0 {\n                        Text(\"\\(context.state.stopsAway) stops away\")\n                    } else {\n                        Text(\"Delivered\")\n                    }\n                }\n                DynamicIslandExpandedRegion(.bottom) {\n                    DeliveryStatusView(stopsAway: context.state.stopsAway)\n                }\n            } compactLeading: {\n                Image(systemName: \"box.truck.fill\")\n                    .resizable()\n                    .scaledToFit()\n                    .padding(2)\n            } compactTrailing: {\n                if context.state.stopsAway > 0 {\n                    Text(\"En Route\")\n                } else {\n                    Text(\"Delivered\")\n                }\n            } minimal: {\n                Image(systemName: \"box.truck.fill\")\n                    .resizable()\n                    .scaledToFit()\n                    .padding(2)\n            }\n            .keylineTint(.red)\n        }\n    }\n}\n\nstruct LockScreenLiveActivityView: View {\n    let context: ActivityViewContext<DeliveryAttributes>\n\n    var body: some View {\n        VStack(alignment: .leading) {\n            HStack(alignment: .top) {\n                VStack(alignment: .leading) {\n                    Image(systemName: \"box.truck.fill\")\n                        .resizable()\n                        .scaledToFit()\n                        .frame(width: 48, height: 48)\n                    Text(\"Shipping Status\")\n\n                    if context.state.stopsAway <= 0 {\n                        Text(\"Delivered!\")\n                            .font(.footnote)\n                    } else if context.state.stopsAway <= 10 {\n                        Text(\"\\(context.state.stopsAway) stops away\")\n                            .font(.footnote)\n                    } else {\n                        Text(\"En Route...\")\n                            .font(.footnote)\n                    }\n                }\n\n                Spacer()\n\n                VStack {\n                    Image(systemName: \"shippingbox.fill\")\n                        .resizable()\n                        .scaledToFit()\n                        .foregroundColor(.primary)\n                        .frame(width: 48, height: 48)\n                    Text(\"Order: \\(context.attributes.orderNumber)\")\n                }\n            }\n\n            DeliveryStatusView(stopsAway: self.context.state.stopsAway)\n\n        }\n        .padding(16)\n\n    }\n}\n\nstruct DeliveryStatusView: View {\n    let stopsAway: Int\n\n    @ViewBuilder\n    var body: some View {\n        VStack {\n            HStack(spacing: 0) {\n                Circle()\n                    .foregroundColor(.red)\n                    .frame(width: 16, height: 16)\n                    .offset(x: 1)\n\n                Rectangle()\n                    .foregroundColor(.red)\n                    .frame(height: 6)\n\n                Circle()\n                    .foregroundColor(.red)\n                    .frame(width: 16, height: 16)\n                    .offset(x: -1)\n\n                if self.stopsAway > 0 {\n                    ForEach(1..<10, id: \\.self) { index in\n                        Rectangle()\n                            .foregroundColor(.primary)\n                            .frame(height: 6)\n                            .padding(.horizontal, 2)\n                    }\n\n                    Circle()\n                        .strokeBorder(.primary, lineWidth: 2)\n                        .frame(width: 16, height: 16)\n                }\n            }\n        }\n\n    }\n}\n\nstruct Line: Shape {\n    func path(in rect: CGRect) -> Path {\n        var path = Path()\n        path.move(to: CGPoint(x: 0, y: 0))\n        path.addLine(to: CGPoint(x: rect.width, y: 0))\n        return path\n    }\n}\n\n#endif\n"
  },
  {
    "path": "DevApp/LiveActivity/DeliveryAttributes.swift",
    "content": "/* Copyright Airship and Contributors */\n\n#if canImport(ActivityKit) && !os(macOS)\n\nimport ActivityKit\nimport Foundation\n\nstruct DeliveryAttributes: ActivityAttributes {\n    public typealias PizzaDeliveryStatus = ContentState\n\n    public struct ContentState: Codable, Hashable {\n        var stopsAway: Int\n    }\n\n    var orderNumber: String\n}\n\n#endif\n"
  },
  {
    "path": "DevApp/LiveActivity/Info.plist",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>NSExtension</key>\n\t<dict>\n\t\t<key>NSExtensionPointIdentifier</key>\n\t\t<string>com.apple.widgetkit-extension</string>\n\t</dict>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/LiveActivity/LiveActivity.intentdefinition",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n\t<key>INEnums</key>\n\t<array/>\n\t<key>INIntentDefinitionModelVersion</key>\n\t<string>1.2</string>\n\t<key>INIntentDefinitionNamespace</key>\n\t<string>88xZPY</string>\n\t<key>INIntentDefinitionSystemVersion</key>\n\t<string>20A294</string>\n\t<key>INIntentDefinitionToolsBuildVersion</key>\n\t<string>12A6144</string>\n\t<key>INIntentDefinitionToolsVersion</key>\n\t<string>12.0</string>\n\t<key>INIntents</key>\n\t<array>\n\t\t<dict>\n\t\t\t<key>INIntentCategory</key>\n\t\t\t<string>information</string>\n\t\t\t<key>INIntentDescriptionID</key>\n\t\t\t<string>tVvJ9c</string>\n\t\t\t<key>INIntentEligibleForWidgets</key>\n\t\t\t<true/>\n\t\t\t<key>INIntentIneligibleForSuggestions</key>\n\t\t\t<true/>\n\t\t\t<key>INIntentName</key>\n\t\t\t<string>Configuration</string>\n\t\t\t<key>INIntentResponse</key>\n\t\t\t<dict>\n\t\t\t\t<key>INIntentResponseCodes</key>\n\t\t\t\t<array>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>INIntentResponseCodeName</key>\n\t\t\t\t\t\t<string>success</string>\n\t\t\t\t\t\t<key>INIntentResponseCodeSuccess</key>\n\t\t\t\t\t\t<true/>\n\t\t\t\t\t</dict>\n\t\t\t\t\t<dict>\n\t\t\t\t\t\t<key>INIntentResponseCodeName</key>\n\t\t\t\t\t\t<string>failure</string>\n\t\t\t\t\t</dict>\n\t\t\t\t</array>\n\t\t\t</dict>\n\t\t\t<key>INIntentTitle</key>\n\t\t\t<string>Configuration</string>\n\t\t\t<key>INIntentTitleID</key>\n\t\t\t<string>gpCwrM</string>\n\t\t\t<key>INIntentType</key>\n\t\t\t<string>Custom</string>\n\t\t\t<key>INIntentVerb</key>\n\t\t\t<string>View</string>\n\t\t</dict>\n\t</array>\n\t<key>INTypes</key>\n\t<array/>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp/LiveActivity/Widgets.swift",
    "content": "import Foundation\nimport SwiftUI\nimport WidgetKit\n\n#if canImport(ActivityKit)\nimport ActivityKit\n#endif\n\n@main\nstruct Widgets: WidgetBundle {\n    var body: some Widget {\n        #if canImport(ActivityKit)\n        DeliveryActivityWidget()\n        #endif\n    }\n}\n"
  },
  {
    "path": "DevApp/Sample Plist/AirshipConfig.plist.sample",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n<plist version=\"1.0\">\n<dict>\n    <key>detectProvisioningMode</key>\n    <true/>\n    <key>developmentAppKey</key>\n    <string>Your Development App Key</string>\n    <key>developmentAppSecret</key>\n    <string>Your Development App Secret</string>\n    <key>productionAppKey</key>\n    <string>Your Production App Key</string>\n    <key>productionAppSecret</key>\n    <string>Your Production App Secret</string>\n</dict>\n</plist>\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/AppDelegate.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport WatchKit\n\nclass ExtensionDelegate: NSObject, WKExtensionDelegate, DeepLinkDelegate {\n\n    func applicationDidFinishLaunching() {\n\n        // Populate AirshipConfig.plist with your app's info from https://go.urbanairship.com\n        // or set runtime properties here.\n        var config = try! AirshipConfig.default()\n        config.productionLogLevel = .verbose\n        config.developmentLogLevel = .verbose\n\n        // Set log level for debugging config loading (optional)\n        // It will be set to the value in the loaded config upon takeOff\n        config.productionLogLevel = .verbose\n        config.developmentLogLevel = .verbose\n\n        // Print out the application configuration for debugging (optional)\n        print(\"Config:\\n \\(config)\")\n\n        // You can then programmatically override the plist values:\n        // config.developmentAppKey = \"YourKey\"\n        // etc.\n\n        // Call takeOff (which creates the UAirship singleton)\n        try? Airship.takeOff(config)\n\n        Airship.channel.editTags { $0.add([\"watchOs\"]) }\n        Airship.deepLinkDelegate = self\n\n        // User notifications will not be enabled until userPushNotificationsEnabled is\n        // enabled on UAPush. Once enabled, the setting will be persisted and the user\n        // will be prompted to allow notifications. You should wait for a more appropriate\n        // time to enable push to increase the likelihood that the user will accept\n        // notifications.\n\n        Airship.push.notificationOptions = [.alert, .sound, .badge]\n    }\n\n    func applicationDidBecomeActive() {\n        // Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.\n    }\n\n    func applicationWillResignActive() {\n        // Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.\n        // Use this method to pause ongoing tasks, disable timers, etc.\n    }\n    \n    func receivedDeepLink(_ deepLink: URL) async {\n        // Handle deeplink navigation\n        print(\"deeplink received: \\(deepLink)\")\n    }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"1.000\",\n          \"green\" : \"0.294\",\n          \"red\" : \"0.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"watchOSicon@2x.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"watchos\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/HomeView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Combine\nimport SwiftUI\n\nstruct HomeView: View {\n    \n    enum Path: Sendable, Hashable, Equatable, CaseIterable {\n        case namedUser\n    }\n    \n    @State private var path: [Path] = []\n    \n    @StateObject\n    private var viewModel: ViewModel = ViewModel()\n    \n    @ViewBuilder\n    private func makeQuickSettingItem(title: String, value: String) -> some View\n    {\n        VStack(alignment: .leading) {\n            Text(title)\n                .foregroundColor(.accentColor)\n                .multilineTextAlignment(.leading)\n                .frame(maxWidth: .infinity, alignment: .leading)\n            \n            Text(value)\n                .foregroundColor(Color.secondary)\n                .multilineTextAlignment(.leading)\n                .frame(maxWidth: .infinity, alignment: .leading)\n        }\n    }\n    \n    @ViewBuilder\n    private var channelIDView: some View {\n        Group {\n            if let channelID = viewModel.channelID {\n                Text(\"Channel ID:\")\n                Text(channelID)\n                    .font(.system(size: 12))\n                    .fixedSize(horizontal: false, vertical: true)\n            } else {\n                Text(\"Channel ID: Unavailable\")\n            }\n        }\n    }\n    \n    @ViewBuilder\n    private var enablePushButton: some View {\n        Toggle(\"Push Notifications\", isOn: self.$viewModel.pushEnabled)\n    }\n    \n    @ViewBuilder\n    private var namedUserButton: some View {\n        Button(action: {\n            self.path.append(.namedUser)\n        }) {\n            makeQuickSettingItem(\n                title: \"Named User\",\n                value: self.viewModel.namedUserID ?? \"Not Set\"\n            )\n        }\n    }\n    \n    @ViewBuilder\n    private var content: some View {\n        VStack(alignment: .leading) {\n            channelIDView\n            enablePushButton\n            namedUserButton\n        }\n        .padding(.bottom)\n    }\n    \n    var body: some View {\n        NavigationStack(path: $path) {\n            content.padding()\n                .frame(maxWidth: .infinity, maxHeight: .infinity)\n                .navigationBarTitle(\"Home\")\n                .navigationDestination(for: Path.self) { selection in\n                    switch(selection) {\n                    case .namedUser:\n                        NamedUserView()\n                    }\n                }\n        }\n    }\n    \n    @MainActor\n    class ViewModel: ObservableObject {\n        @Published\n        var pushEnabled: Bool = true {\n            didSet {\n                if (pushEnabled) {\n                    Task {\n                        await Airship.push.enableUserPushNotifications(fallback: .systemSettings)\n                    }\n                } else {\n                    Airship.push.userPushNotificationsEnabled = false\n                }\n            }\n        }\n        \n        @Published\n        var channelID: String? = Airship.channel.identifier\n        \n        @Published\n        var namedUserID: String? = \"\"\n        \n        private var subscriptions = Set<AnyCancellable>()\n        \n        @MainActor\n        init() {\n            NotificationCenter.default\n                .publisher(for: AirshipNotifications.ChannelCreated.name)\n                .receive(on: RunLoop.main)\n                .sink { _ in\n                    self.channelID = Airship.channel.identifier\n                }\n                .store(in: &self.subscriptions)\n            \n            Airship.contact.namedUserIDPublisher\n                .receive(on: RunLoop.main)\n                .sink { namedUserID in\n                    self.namedUserID = namedUserID\n                }\n                .store(in: &self.subscriptions)\n            \n            Airship.push.notificationStatusPublisher\n                .map { status in\n                    status.isUserOptedIn\n                }\n                .receive(on: RunLoop.main)\n                .sink { optedIn in\n                    if (self.pushEnabled != optedIn) {\n                        self.pushEnabled = optedIn\n                    }\n                }\n                .store(in: &self.subscriptions)\n        }\n    }\n}\n\n#Preview {\n    HomeView()\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/MainApp.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport SwiftUI\n\n@main\nstruct MainApp: App {\n\n    @WKExtensionDelegateAdaptor(ExtensionDelegate.self) var appDelegate\n    var body: some Scene {\n        WindowGroup {\n            HomeView()\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/NamedUserView.swift",
    "content": "/* Copyright Airship and Contributors */\n\nimport AirshipCore\nimport Foundation\nimport SwiftUI\n\nstruct NamedUserView: View {\n\n    @StateObject\n    private var viewModel: ViewModel = ViewModel()\n\n\n    private func updateNamedUser() {\n        let normalized = self.viewModel.namedUserID.trimmingCharacters(\n            in: .whitespacesAndNewlines\n        )\n\n        if !normalized.isEmpty {\n            Airship.contact.identify(normalized)\n        } else {\n            Airship.contact.reset()\n        }\n    }\n\n    @ViewBuilder\n    private func makeTextInput() -> some View {\n        TextField(\"Named User\", text:self.$viewModel.namedUserID) {\n            updateNamedUser()\n        }\n    }\n\n    var body: some View {\n        VStack() {\n            Text(\"Named User\")\n                .padding(.bottom)\n\n            makeTextInput()\n        }\n    }\n\n    @MainActor\n    private class ViewModel: ObservableObject {\n        @Published\n        public var namedUserID: String = \"\"\n\n        init() {\n            Task { @MainActor in\n                self.namedUserID = await Airship.contact.namedUserID ?? \"\"\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/Preview Content/Preview Assets.xcassets/AccentColor.colorset/Contents.json",
    "content": "{\n  \"colors\" : [\n    {\n      \"color\" : {\n        \"color-space\" : \"srgb\",\n        \"components\" : {\n          \"alpha\" : \"1.000\",\n          \"blue\" : \"1.000\",\n          \"green\" : \"0.294\",\n          \"red\" : \"0.000\"\n        }\n      },\n      \"idiom\" : \"universal\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/Preview Content/Preview Assets.xcassets/AppIcon.appiconset/Contents.json",
    "content": "{\n  \"images\" : [\n    {\n      \"filename\" : \"watchOSicon@2x.png\",\n      \"idiom\" : \"universal\",\n      \"platform\" : \"watchos\",\n      \"size\" : \"1024x1024\"\n    }\n  ],\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/Preview Content/Preview Assets.xcassets/Contents.json",
    "content": "{\n  \"info\" : {\n    \"author\" : \"xcode\",\n    \"version\" : 1\n  }\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS/PushNotificationPayload.apns",
    "content": "{\n    \"aps\": {\n        \"alert\": {\n            \"body\": \"Test message\",\n            \"title\": \"Optional title\",\n            \"subtitle\": \"Optional subtitle\"\n        },\n        \"category\": \"myCategory\",\n        \"thread-id\": \"5280\"\n    },\n    \"Simulator Target Bundle\": \"com.urbanairship.richpush\",\n    \"WatchKit Simulator Actions\": [\n        {\n            \"title\": \"First Button\",\n            \"identifier\": \"firstButtonAction\"\n        }\n    ],\n    \n    \"customKey\": \"Use this file to define a testing payload for your notifications. The aps dictionary specifies the category, alert text and title. The WatchKit Simulator Actions array can provide info for one or more action buttons in addition to the standard Dismiss button. Any other top level keys are custom payload. If you have multiple such JSON files in your project, you'll be able to select them when choosing to debug the notification interface of your Watch App.\"\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS.xcodeproj/project.pbxproj",
    "content": "// !$*UTF8*$!\n{\n\tarchiveVersion = 1;\n\tclasses = {\n\t};\n\tobjectVersion = 77;\n\tobjects = {\n\n/* Begin PBXBuildFile section */\n\t\t327BFC3F2D53C042009A73C6 /* DevApp watchOS Watch App.app in Embed Watch Content */ = {isa = PBXBuildFile; fileRef = 327BFC3E2D53C042009A73C6 /* DevApp watchOS Watch App.app */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };\n\t\t32BC89B52D54CE4F001EAF3D /* AirshipBasement.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32BC89AC2D54CE4F001EAF3D /* AirshipBasement.framework */; };\n\t\t32BC89B62D54CE4F001EAF3D /* AirshipBasement.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 32BC89AC2D54CE4F001EAF3D /* AirshipBasement.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n\t\t32BC89B72D54CE4F001EAF3D /* AirshipCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 32BC89AD2D54CE4F001EAF3D /* AirshipCore.framework */; };\n\t\t32BC89B82D54CE4F001EAF3D /* AirshipCore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 32BC89AD2D54CE4F001EAF3D /* AirshipCore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };\n/* End PBXBuildFile section */\n\n/* Begin PBXContainerItemProxy section */\n\t\t327BFC402D53C042009A73C6 /* PBXContainerItemProxy */ = {\n\t\t\tisa = PBXContainerItemProxy;\n\t\t\tcontainerPortal = 327BFC322D53C042009A73C6 /* Project object */;\n\t\t\tproxyType = 1;\n\t\t\tremoteGlobalIDString = 327BFC3D2D53C042009A73C6;\n\t\t\tremoteInfo = \"AirshipWatchOS Sample Watch App\";\n\t\t};\n/* End PBXContainerItemProxy section */\n\n/* Begin PBXCopyFilesBuildPhase section */\n\t\t327BFC672D53C044009A73C6 /* Embed Watch Content */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"$(CONTENTS_FOLDER_PATH)/Watch\";\n\t\t\tdstSubfolderSpec = 16;\n\t\t\tfiles = (\n\t\t\t\t327BFC3F2D53C042009A73C6 /* DevApp watchOS Watch App.app in Embed Watch Content */,\n\t\t\t);\n\t\t\tname = \"Embed Watch Content\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t32BC89C32D54CE4F001EAF3D /* Embed Frameworks */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 10;\n\t\t\tfiles = (\n\t\t\t\t32BC89B62D54CE4F001EAF3D /* AirshipBasement.framework in Embed Frameworks */,\n\t\t\t\t32BC89B82D54CE4F001EAF3D /* AirshipCore.framework in Embed Frameworks */,\n\t\t\t);\n\t\t\tname = \"Embed Frameworks\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t32BC89F92D55FEB1001EAF3D /* Embed Foundation Extensions */ = {\n\t\t\tisa = PBXCopyFilesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tdstPath = \"\";\n\t\t\tdstSubfolderSpec = 13;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\tname = \"Embed Foundation Extensions\";\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXCopyFilesBuildPhase section */\n\n/* Begin PBXFileReference section */\n\t\t327BFC382D53C042009A73C6 /* DevApp watchOS.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = \"DevApp watchOS.app\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t327BFC3E2D53C042009A73C6 /* DevApp watchOS Watch App.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = \"DevApp watchOS Watch App.app\"; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89922D54CAD5001EAF3D /* Airship.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = \"wrapper.pb-project\"; name = Airship.xcodeproj; path = ../Airship/Airship.xcodeproj; sourceTree = SOURCE_ROOT; };\n\t\t32BC89AB2D54CE4F001EAF3D /* AirshipAutomation.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipAutomation.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89AC2D54CE4F001EAF3D /* AirshipBasement.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipBasement.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89AD2D54CE4F001EAF3D /* AirshipCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89AE2D54CE4F001EAF3D /* AirshipDebug.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipDebug.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89AF2D54CE4F001EAF3D /* AirshipFeatureFlags.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipFeatureFlags.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89B02D54CE4F001EAF3D /* AirshipMessageCenter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipMessageCenter.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89B12D54CE4F001EAF3D /* AirshipObjectiveC.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipObjectiveC.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC89B22D54CE4F001EAF3D /* AirshipPreferenceCenter.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipPreferenceCenter.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC8A0A2D5604A7001EAF3D /* AirshipNotificationServiceExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipNotificationServiceExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n\t\t32BC8A0E2D560CAB001EAF3D /* AirshipCore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = AirshipCore.framework; sourceTree = BUILT_PRODUCTS_DIR; };\n/* End PBXFileReference section */\n\n/* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\t\t32BC89A82D54CDDF001EAF3D /* Exceptions for \"DevApp watchOS\" folder in \"DevApp watchOS\" target */ = {\n\t\t\tisa = PBXFileSystemSynchronizedBuildFileExceptionSet;\n\t\t\tmembershipExceptions = (\n\t\t\t\tPushNotificationPayload.apns,\n\t\t\t);\n\t\t\ttarget = 327BFC372D53C042009A73C6 /* DevApp watchOS */;\n\t\t};\n/* End PBXFileSystemSynchronizedBuildFileExceptionSet section */\n\n/* Begin PBXFileSystemSynchronizedRootGroup section */\n\t\t327BFC422D53C042009A73C6 /* DevApp watchOS */ = {\n\t\t\tisa = PBXFileSystemSynchronizedRootGroup;\n\t\t\texceptions = (\n\t\t\t\t32BC89A82D54CDDF001EAF3D /* Exceptions for \"DevApp watchOS\" folder in \"DevApp watchOS\" target */,\n\t\t\t);\n\t\t\tpath = \"DevApp watchOS\";\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXFileSystemSynchronizedRootGroup section */\n\n/* Begin PBXFrameworksBuildPhase section */\n\t\t327BFC3B2D53C042009A73C6 /* Frameworks */ = {\n\t\t\tisa = PBXFrameworksBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t\t32BC89B52D54CE4F001EAF3D /* AirshipBasement.framework in Frameworks */,\n\t\t\t\t32BC89B72D54CE4F001EAF3D /* AirshipCore.framework in Frameworks */,\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXFrameworksBuildPhase section */\n\n/* Begin PBXGroup section */\n\t\t327BFC312D53C042009A73C6 = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32BC89922D54CAD5001EAF3D /* Airship.xcodeproj */,\n\t\t\t\t327BFC422D53C042009A73C6 /* DevApp watchOS */,\n\t\t\t\t32BC89AA2D54CE4F001EAF3D /* Frameworks */,\n\t\t\t\t327BFC392D53C042009A73C6 /* Products */,\n\t\t\t);\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t327BFC392D53C042009A73C6 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t327BFC382D53C042009A73C6 /* DevApp watchOS.app */,\n\t\t\t\t327BFC3E2D53C042009A73C6 /* DevApp watchOS Watch App.app */,\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t32BC89AA2D54CE4F001EAF3D /* Frameworks */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t\t32BC8A0E2D560CAB001EAF3D /* AirshipCore.framework */,\n\t\t\t\t32BC8A0A2D5604A7001EAF3D /* AirshipNotificationServiceExtension.framework */,\n\t\t\t\t32BC89AB2D54CE4F001EAF3D /* AirshipAutomation.framework */,\n\t\t\t\t32BC89AC2D54CE4F001EAF3D /* AirshipBasement.framework */,\n\t\t\t\t32BC89AD2D54CE4F001EAF3D /* AirshipCore.framework */,\n\t\t\t\t32BC89AE2D54CE4F001EAF3D /* AirshipDebug.framework */,\n\t\t\t\t32BC89AF2D54CE4F001EAF3D /* AirshipFeatureFlags.framework */,\n\t\t\t\t32BC89B02D54CE4F001EAF3D /* AirshipMessageCenter.framework */,\n\t\t\t\t32BC89B12D54CE4F001EAF3D /* AirshipObjectiveC.framework */,\n\t\t\t\t32BC89B22D54CE4F001EAF3D /* AirshipPreferenceCenter.framework */,\n\t\t\t);\n\t\t\tname = Frameworks;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n\t\t604C4A692D66505500E58688 /* Products */ = {\n\t\t\tisa = PBXGroup;\n\t\t\tchildren = (\n\t\t\t);\n\t\t\tname = Products;\n\t\t\tsourceTree = \"<group>\";\n\t\t};\n/* End PBXGroup section */\n\n/* Begin PBXNativeTarget section */\n\t\t327BFC372D53C042009A73C6 /* DevApp watchOS */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 327BFC682D53C044009A73C6 /* Build configuration list for PBXNativeTarget \"DevApp watchOS\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t327BFC362D53C042009A73C6 /* Resources */,\n\t\t\t\t327BFC672D53C044009A73C6 /* Embed Watch Content */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t\t327BFC412D53C042009A73C6 /* PBXTargetDependency */,\n\t\t\t);\n\t\t\tname = \"DevApp watchOS\";\n\t\t\tpackageProductDependencies = (\n\t\t\t);\n\t\t\tproductName = \"AirshipWatchOS Sample\";\n\t\t\tproductReference = 327BFC382D53C042009A73C6 /* DevApp watchOS.app */;\n\t\t\tproductType = \"com.apple.product-type.application.watchapp2-container\";\n\t\t};\n\t\t327BFC3D2D53C042009A73C6 /* DevApp watchOS Watch App */ = {\n\t\t\tisa = PBXNativeTarget;\n\t\t\tbuildConfigurationList = 327BFC642D53C044009A73C6 /* Build configuration list for PBXNativeTarget \"DevApp watchOS Watch App\" */;\n\t\t\tbuildPhases = (\n\t\t\t\t327BFC3A2D53C042009A73C6 /* Sources */,\n\t\t\t\t327BFC3B2D53C042009A73C6 /* Frameworks */,\n\t\t\t\t327BFC3C2D53C042009A73C6 /* Resources */,\n\t\t\t\t32BC89C32D54CE4F001EAF3D /* Embed Frameworks */,\n\t\t\t\t32BC89F92D55FEB1001EAF3D /* Embed Foundation Extensions */,\n\t\t\t);\n\t\t\tbuildRules = (\n\t\t\t);\n\t\t\tdependencies = (\n\t\t\t);\n\t\t\tfileSystemSynchronizedGroups = (\n\t\t\t\t327BFC422D53C042009A73C6 /* DevApp watchOS */,\n\t\t\t);\n\t\t\tname = \"DevApp watchOS Watch App\";\n\t\t\tpackageProductDependencies = (\n\t\t\t);\n\t\t\tproductName = \"AirshipWatchOS Sample Watch App\";\n\t\t\tproductReference = 327BFC3E2D53C042009A73C6 /* DevApp watchOS Watch App.app */;\n\t\t\tproductType = \"com.apple.product-type.application\";\n\t\t};\n/* End PBXNativeTarget section */\n\n/* Begin PBXProject section */\n\t\t327BFC322D53C042009A73C6 /* Project object */ = {\n\t\t\tisa = PBXProject;\n\t\t\tattributes = {\n\t\t\t\tBuildIndependentTargetsInParallel = 1;\n\t\t\t\tLastSwiftUpdateCheck = 1620;\n\t\t\t\tLastUpgradeCheck = 1620;\n\t\t\t\tTargetAttributes = {\n\t\t\t\t\t327BFC372D53C042009A73C6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 16.2;\n\t\t\t\t\t\tLastSwiftMigration = 1620;\n\t\t\t\t\t};\n\t\t\t\t\t327BFC3D2D53C042009A73C6 = {\n\t\t\t\t\t\tCreatedOnToolsVersion = 16.2;\n\t\t\t\t\t};\n\t\t\t\t};\n\t\t\t};\n\t\t\tbuildConfigurationList = 327BFC352D53C042009A73C6 /* Build configuration list for PBXProject \"DevApp watchOS\" */;\n\t\t\tdevelopmentRegion = en;\n\t\t\thasScannedForEncodings = 0;\n\t\t\tknownRegions = (\n\t\t\t\ten,\n\t\t\t\tBase,\n\t\t\t);\n\t\t\tmainGroup = 327BFC312D53C042009A73C6;\n\t\t\tminimizedProjectReferenceProxies = 1;\n\t\t\tpreferredProjectObjectVersion = 77;\n\t\t\tproductRefGroup = 327BFC392D53C042009A73C6 /* Products */;\n\t\t\tprojectDirPath = \"\";\n\t\t\tprojectReferences = (\n\t\t\t\t{\n\t\t\t\t\tProductGroup = 604C4A692D66505500E58688 /* Products */;\n\t\t\t\t\tProjectRef = 32BC89922D54CAD5001EAF3D /* Airship.xcodeproj */;\n\t\t\t\t},\n\t\t\t);\n\t\t\tprojectRoot = \"\";\n\t\t\ttargets = (\n\t\t\t\t327BFC372D53C042009A73C6 /* DevApp watchOS */,\n\t\t\t\t327BFC3D2D53C042009A73C6 /* DevApp watchOS Watch App */,\n\t\t\t);\n\t\t};\n/* End PBXProject section */\n\n/* Begin PBXResourcesBuildPhase section */\n\t\t327BFC362D53C042009A73C6 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n\t\t327BFC3C2D53C042009A73C6 /* Resources */ = {\n\t\t\tisa = PBXResourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXResourcesBuildPhase section */\n\n/* Begin PBXSourcesBuildPhase section */\n\t\t327BFC3A2D53C042009A73C6 /* Sources */ = {\n\t\t\tisa = PBXSourcesBuildPhase;\n\t\t\tbuildActionMask = 2147483647;\n\t\t\tfiles = (\n\t\t\t);\n\t\t\trunOnlyForDeploymentPostprocessing = 0;\n\t\t};\n/* End PBXSourcesBuildPhase section */\n\n/* Begin PBXTargetDependency section */\n\t\t327BFC412D53C042009A73C6 /* PBXTargetDependency */ = {\n\t\t\tisa = PBXTargetDependency;\n\t\t\ttarget = 327BFC3D2D53C042009A73C6 /* DevApp watchOS Watch App */;\n\t\t\ttargetProxy = 327BFC402D53C042009A73C6 /* PBXContainerItemProxy */;\n\t\t};\n/* End PBXTargetDependency section */\n\n/* Begin XCBuildConfiguration section */\n\t\t327BFC622D53C044009A73C6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = dwarf;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_TESTABILITY = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_DYNAMIC_NO_PIC = NO;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_OPTIMIZATION_LEVEL = 0;\n\t\t\t\tGCC_PREPROCESSOR_DEFINITIONS = (\n\t\t\t\t\t\"DEBUG=1\",\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t);\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tONLY_ACTIVE_ARCH = YES;\n\t\t\t\tSWIFT_ACTIVE_COMPILATION_CONDITIONS = \"DEBUG $(inherited)\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t327BFC632D53C044009A73C6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tALWAYS_SEARCH_USER_PATHS = NO;\n\t\t\t\tASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;\n\t\t\t\tCLANG_ANALYZER_NONNULL = YES;\n\t\t\t\tCLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;\n\t\t\t\tCLANG_CXX_LANGUAGE_STANDARD = \"gnu++20\";\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_ARC = YES;\n\t\t\t\tCLANG_ENABLE_OBJC_WEAK = YES;\n\t\t\t\tCLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;\n\t\t\t\tCLANG_WARN_BOOL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_COMMA = YES;\n\t\t\t\tCLANG_WARN_CONSTANT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;\n\t\t\t\tCLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;\n\t\t\t\tCLANG_WARN_DOCUMENTATION_COMMENTS = YES;\n\t\t\t\tCLANG_WARN_EMPTY_BODY = YES;\n\t\t\t\tCLANG_WARN_ENUM_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_INFINITE_RECURSION = YES;\n\t\t\t\tCLANG_WARN_INT_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;\n\t\t\t\tCLANG_WARN_OBJC_LITERAL_CONVERSION = YES;\n\t\t\t\tCLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;\n\t\t\t\tCLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES;\n\t\t\t\tCLANG_WARN_RANGE_LOOP_ANALYSIS = YES;\n\t\t\t\tCLANG_WARN_STRICT_PROTOTYPES = YES;\n\t\t\t\tCLANG_WARN_SUSPICIOUS_MOVE = YES;\n\t\t\t\tCLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;\n\t\t\t\tCLANG_WARN_UNREACHABLE_CODE = YES;\n\t\t\t\tCLANG_WARN__DUPLICATE_METHOD_MATCH = YES;\n\t\t\t\tCOPY_PHASE_STRIP = NO;\n\t\t\t\tDEBUG_INFORMATION_FORMAT = \"dwarf-with-dsym\";\n\t\t\t\tENABLE_NS_ASSERTIONS = NO;\n\t\t\t\tENABLE_STRICT_OBJC_MSGSEND = YES;\n\t\t\t\tENABLE_USER_SCRIPT_SANDBOXING = YES;\n\t\t\t\tGCC_C_LANGUAGE_STANDARD = gnu17;\n\t\t\t\tGCC_NO_COMMON_BLOCKS = YES;\n\t\t\t\tGCC_WARN_64_TO_32_BIT_CONVERSION = YES;\n\t\t\t\tGCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;\n\t\t\t\tGCC_WARN_UNDECLARED_SELECTOR = YES;\n\t\t\t\tGCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;\n\t\t\t\tGCC_WARN_UNUSED_FUNCTION = YES;\n\t\t\t\tGCC_WARN_UNUSED_VARIABLE = YES;\n\t\t\t\tLOCALIZATION_PREFERS_STRING_CATALOGS = YES;\n\t\t\t\tMTL_ENABLE_DEBUG_INFO = NO;\n\t\t\t\tMTL_FAST_MATH = YES;\n\t\t\t\tSWIFT_COMPILATION_MODE = wholemodule;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t327BFC652D53C044009A73C6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"AirshipWatchOS Sample Watch App/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"AirshipWatchOS Sample\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown\";\n\t\t\t\tINFOPLIST_KEY_WKWatchOnly = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = watchos;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = 4;\n\t\t\t\tWATCHOS_DEPLOYMENT_TARGET = 11.6;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t327BFC662D53C044009A73C6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;\n\t\t\t\tASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_ASSET_PATHS = \"\\\"AirshipWatchOS Sample Watch App/Preview Content\\\"\";\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tENABLE_PREVIEWS = YES;\n\t\t\t\tGENERATE_INFOPLIST_FILE = YES;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"AirshipWatchOS Sample\";\n\t\t\t\tINFOPLIST_KEY_UISupportedInterfaceOrientations = \"UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown\";\n\t\t\t\tINFOPLIST_KEY_WKWatchOnly = YES;\n\t\t\t\tLD_RUNPATH_SEARCH_PATHS = (\n\t\t\t\t\t\"$(inherited)\",\n\t\t\t\t\t\"@executable_path/Frameworks\",\n\t\t\t\t);\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = watchos;\n\t\t\t\tSKIP_INSTALL = YES;\n\t\t\t\tSWIFT_EMIT_LOC_STRINGS = YES;\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tTARGETED_DEVICE_FAMILY = 4;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t\tWATCHOS_DEPLOYMENT_TARGET = 11.6;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n\t\t327BFC692D53C044009A73C6 /* Debug */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"AirshipWatchOS Sample\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"AirshipWatchOS Sample Watch App/AirshipWatchOS Sample-Bridging-Header.h\";\n\t\t\t\tSWIFT_OPTIMIZATION_LEVEL = \"-Onone\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t};\n\t\t\tname = Debug;\n\t\t};\n\t\t327BFC6A2D53C044009A73C6 /* Release */ = {\n\t\t\tisa = XCBuildConfiguration;\n\t\t\tbuildSettings = {\n\t\t\t\tCLANG_ENABLE_MODULES = YES;\n\t\t\t\tCODE_SIGN_STYLE = Automatic;\n\t\t\t\tCURRENT_PROJECT_VERSION = 1;\n\t\t\t\tDEVELOPMENT_TEAM = PGJV57GD94;\n\t\t\t\tINFOPLIST_KEY_CFBundleDisplayName = \"AirshipWatchOS Sample\";\n\t\t\t\tIPHONEOS_DEPLOYMENT_TARGET = 16.6;\n\t\t\t\tMARKETING_VERSION = 1.0;\n\t\t\t\tPRODUCT_BUNDLE_IDENTIFIER = com.urbanairship.richpush;\n\t\t\t\tPRODUCT_NAME = \"$(TARGET_NAME)\";\n\t\t\t\tSDKROOT = iphoneos;\n\t\t\t\tSWIFT_OBJC_BRIDGING_HEADER = \"AirshipWatchOS Sample Watch App/AirshipWatchOS Sample-Bridging-Header.h\";\n\t\t\t\tSWIFT_VERSION = 5.0;\n\t\t\t\tVALIDATE_PRODUCT = YES;\n\t\t\t};\n\t\t\tname = Release;\n\t\t};\n/* End XCBuildConfiguration section */\n\n/* Begin XCConfigurationList section */\n\t\t327BFC352D53C042009A73C6 /* Build configuration list for PBXProject \"DevApp watchOS\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t327BFC622D53C044009A73C6 /* Debug */,\n\t\t\t\t327BFC632D53C044009A73C6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t327BFC642D53C044009A73C6 /* Build configuration list for PBXNativeTarget \"DevApp watchOS Watch App\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t327BFC652D53C044009A73C6 /* Debug */,\n\t\t\t\t327BFC662D53C044009A73C6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n\t\t327BFC682D53C044009A73C6 /* Build configuration list for PBXNativeTarget \"DevApp watchOS\" */ = {\n\t\t\tisa = XCConfigurationList;\n\t\t\tbuildConfigurations = (\n\t\t\t\t327BFC692D53C044009A73C6 /* Debug */,\n\t\t\t\t327BFC6A2D53C044009A73C6 /* Release */,\n\t\t\t);\n\t\t\tdefaultConfigurationIsVisible = 0;\n\t\t\tdefaultConfigurationName = Release;\n\t\t};\n/* End XCConfigurationList section */\n\t};\n\trootObject = 327BFC322D53C042009A73C6 /* Project object */;\n}\n"
  },
  {
    "path": "DevApp watchOS/DevApp watchOS.xcodeproj/project.xcworkspace/contents.xcworkspacedata",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Workspace\n   version = \"1.0\">\n   <FileRef\n      location = \"self:\">\n   </FileRef>\n</Workspace>\n"
  },
  {
    "path": "Documentation/.jazzy.json",
    "content": "{\n    \"author\":\"Urban Airship\",\n    \"author_url\":\"https://urbanairship.com\",\n    \"github_url\":\"https://github.com/urbanairship/ios-library\",\n    \"readme\":\"readme-for-jazzy.md\",\n    \"documentation\":\"Documentation/Migration/M*md\",\n    \"abstract\":\"Documentation/abstracts/*md\"\n}\n"
  },
  {
    "path": "Documentation/Migration/README.md",
    "content": "\n# Airship iOS SDK Migration Guides\n\nComprehensive migration guides with code examples, troubleshooting, and clear migration paths.\n\n## Migration Guides\n\n- **[SDK 19.x → 20.0](migration-guide-19-20.md)** - Major architectural changes, block-based callbacks, and UI refactors\n- **[SDK 18.x → 19.x](migration-guide-18-19.md)** - SwiftUI improvements and modern APIs\n- **[SDK 17.x → 18.x](migration-guide-17-18.md)** - Enhanced messaging and automation features\n- **[SDK 16.x → 17.x](migration-guide-16-17.md)** - Core platform updates and new capabilities\n\n## Older Migration Guides\n\nMigration guides for SDK versions 9-15 are available in the [16.0.0 release](https://github.com/urbanairship/ios-library/tree/16.0.0/Documentation/Migration).\n\n## Support\n\n- [Full Documentation](https://docs.airship.com/)\n- [Report Issues](https://github.com/urbanairship/ios-library/issues)\n"
  },
  {
    "path": "Documentation/Migration/migration-guide-16-17.md",
    "content": "# Airship iOS SDK 16.x to 17.0 Migration Guide\n\n## Xcode requirements\n\nSDK 17.x now requires Xcode 14.3 or newer.\n\n## Minimum deployment version\n\nSDK 17.x is compatible with iOS 14+. Apps using Airship will need to update the minimum deployment version.\n\n## Removed modules\n\nThe following modules are no longer supported and have been removed from the SDK:\n\n### `AirshipAccengage`\n\nUsers of Accengage should remove the `AirshipAccengage` module from their project after completing the migration process.\nFor further information about migration and removal, see the [Accengage Migration guide](https://docs.airship.com/platform/mobile/accengage-migration/migration/ios/index.html#remove-airship-accengage-module).\n\n### `AirshipChat`\n\nThe Airship Chat module is no longer supported and has been removed from the SDK.\n\n### `AirshipLocation`\n\nThe Airship Location module is no longer supported and has been removed from the SDK. If you want to continue prompting users for location permissions, you must update your integration to set a location permission delegate on the `PermissionsManager`:\n\n```\nimport Foundation\nimport CoreLocation\nimport AirshipCore\nimport Combine\n\nclass LocationPermissionDelegate: AirshipPermissionDelegate {\n    let locationManager = CLLocationManager()\n\n    @MainActor\n    func checkPermissionStatus() async -> AirshipCore.AirshipPermissionStatus {\n        return self.status\n    }\n\n    @MainActor\n    func requestPermission() async -> AirshipCore.AirshipPermissionStatus {\n        guard (self.status == .notDetermined) else {\n            return self.status\n        }\n\n        guard (AppStateTracker.shared.state == .active) else {\n            return .notDetermined\n        }\n\n        locationManager.requestAlwaysAuthorization()\n        await waitActive()\n        return self.status\n    }\n\n\n    var status: AirshipPermissionStatus {\n        switch(locationManager.authorizationStatus) {\n        case .notDetermined:\n            return .notDetermined\n        case .restricted:\n            return .denied\n        case .denied:\n            return .denied\n        case .authorizedAlways:\n            return .granted\n        case .authorizedWhenInUse:\n            return .granted\n        @unknown default:\n            return .notDetermined\n        }\n    }\n}\n\n\n@MainActor\nprivate func waitActive() async {\n    var subscription: AnyCancellable?\n    await withCheckedContinuation { continuation in\n        subscription = NotificationCenter.default.publisher(for: AppStateTracker.didBecomeActiveNotification)\n            .first()\n            .sink { _ in\n                continuation.resume()\n            }\n    }\n\n    subscription?.cancel()\n}\n```\n\nThen after takeOff, register the permission delegate for the location permission:\n```\nAirship.shared.permissionsManager.setDelegate(\n    LocationPermissionDelegate(),\n    permission: .location\n)\n```\n\n### `AirshipExtendedActions`\n\nThe Airship Extended Actions only contained the RateAppAction which is now available in the core module.\n\n## Allowed URLs\n\nThe URL allow list configuration has been changed to an opt-out process, rather than an opt-in process like previous SDK versions.\nBy default, all URLs are allowed by SDK 17, unless explicitly disallowed by the app via the `urlAllowList` or `urlAllowListScopeOpen` config options.\n\nAllow list behavior changes:\n- If neither `urlAllowList` or `urlAllowListScopeOpenURL` are set in your Airship config, the SDK will default to allowing all URLs and an error message will be logged.\n- To suppress the error message, set `urlAllowList` or `urlAllowListScopeOpenURL` to `[*]` to your config to adopt the new allow-all behavior, or customize the allowed URLs as needed.\n- URLs for media displayed within in-app messages will no longer be checked against the URL allow lists.\n- YouTube has been removed from the default allow list. If your application makes use of opening links to YouTube from Airship messaging, you will need to update your allow list to explicitly allow `youtube.com`, or allow all URLs with `[*]`.\n\n## Renamed classes\n\nSome common class names have been renamed to prevent collisions with other libraries/apps:\n\n| Legacy class name  | New class name  |\n| -------------------| ----------------|\n| Config             | AirshipConfig   |\n| Channel            | AirshipChannel  |\n| Contact            | AirshipContact  |\n| Push               | AirshipPush     |\n| PrivacyManager     | AirshipPrivacyManager|\n| Analytics          | AirshipAnalytics|\n| Action             | AirshipAction |\n| Situation          | ActionSituation |\n| Features           | AirshipFeature |\n| Event              | AirshipEvent |\n\n\n## In-App Automation\n\n### Updated the default display interval for In-App Messages\n\nThe new default display interval for in-app messages is now set to 0 seconds. Apps that wish to maintain the previous default display interval of 30 seconds should set the display interval manually, after takeOff:\n\n```\nInAppAutomation.shared.inAppMessageManager.displayInterval = 30.0\n```\n\n### Deep link delegate \n\nDeep link delegate is now async.\n\nSDK 16:\n```\ndeepLinkDelegate.receivedDeepLink(deepLink) {\n    completionHandler(true)\n}\n```\n\nSDK 17:\n```\nawait deepLinkDelegate.receivedDeepLink(deepLink) {\n    completionHandler(true)\n}  \n```\n\n## Contacts\n\n### Contact conflict listener interface updated\n\nContact conflict event is now available as a NSNotification or using `Airship.contact.conflictEventPublisher` to listen for events:\n\nSDK 16:\n```\nconflictDelegate?.onConflict(anonymousContactData: anonData, namedUserID: namedUserID)\n```\n\nSDK 17:\n```\nAirship.contact.conflictEventPublisher.sink { event in\n    // ...\n}\n    \nNotificationCenter.default.addObserver(\n    self,\n    selector: #selector(conflictEventReceived),\n    name: AirshipContact.contactConflictEvent\n)\n```\n\n### Async Named User ID access\n\nNamed User ID access is now an async property:\n\nSDK 16:\n```\nlet namedUserID = Airship.contact.namedUserID\n```\n\nSDK 17:\n```\nlet namedUserID = await Airship.contact.namedUserID\n```\n\n### Async Subscription lists access \n\nSubscription list is now an async method:\n\nSDK 16:\n```\nAirship.contact.fetchSubscriptionLists { contactSubscriptionLists, error in\n    // Use the contactSubscriptionLists\n}\n```\n\nSDK 17:\n```\nlet contactSubscriptions = try await Airship.contact.fetchSubscriptionLists\n```\n\n## Channels\n\n### Async Subscription lists access \n\nSubscription list is now an async method:\n\nSDK 16:\n```\nAirship.channel.fetchSubscriptionLists { channelSubscriptionLists, error in\n    // Use the channelSubscriptionLists\n}\n```\n\nSDK 17:\n```\nlet channelSubscriptions = try await Airship.channel.fetchSubscriptionLists\n```\n\n### Live Activities\n\nThe API's to track and restore live activity tracking have been updated to no longer require a Task:\n\nSDK 16:\n```\nTask {\n    await Airship.channel.trackLiveActivity(\n    activity,\n    name: \"order-1234\"\n)\n\nTask {\n    await Airship.channel.restoreLiveActivityTracking { restorer in\n        await restorer.restore(\n            forType: Activity<DeliveryAttributes>.self\n        )\n        await restorer.restore(\n            forType: Activity<SomeOtherAttributes>.self\n        )\n    }\n}\n```\n\nSDK 17:\n```\nAirship.channel.trackLiveActivity(\n    activity,\n    name: \"order-1234\"\n)\n\nAirship.channel.restoreLiveActivityTracking { restorer in\n    await restorer.restore(\n        forType: Activity<DeliveryAttributes>.self\n    )\n    await restorer.restore(\n        forType: Activity<SomeOtherAttributes>.self\n    )\n}\n```\n\n## Message Center\n\nThe MessageCenter module has been rewritten in Swift and the OOTB UI in SwiftUI. With the rewrite, we are providing a new set of APIs that take advantage of Swift's structured concurrency.\n\n### Message listing API Changes\n\nAll methods on the Message Center for listing are now async, and the listing is no longer stored in memory.\n\n#### Accessing message list\n\nSDK 16:\n```\nlet messages = MessageCenter.shared.messageList.messages\n```\n\nSDK 17:\n```\nlet messages = await MessageCenter.shared.inbox.messages\n```\n\n#### Deleting a message\n\nSDK 16:\n```\nMessageCenter.shared.messageList.markMessagesDeleted([message]) { // completed }\n```\n\nSDK 17:\n```\n// by message ID\nawait MessageCenter.shared.inbox.delete(messageIDs: [\"messageID\"])\n\n// by message\nawait MessageCenter.shared.inbox.delete(messages: [message])\n```\n\n#### Marking a message as read\n\nSDK 16:\n```\nMessageCenter.shared.messageList.markMessagesRead([message]) { // completed }\n```\n\nSDK 17:\n```\n// by message ID\nawait MessageCenter.shared.inbox.markRead(messageIDs: [\"messageID\"])\n\n// by message\nawait MessageCenter.shared.inbox.markRead(messages: [message])\n```\n\n#### Refreshing the message listing\n\nSDK 16:\n```\nMessageCenter.shared.messageList.retrieveMessageList(successBlock: {\n   // handle success\n}, withFailureBlock: {\n    // handle failure\n})\n```\n\nSDK 17:\n```\nawait MessageCenter.shared.inbox.refreshMessages()\n```\n\n#### Listening to message listing updates\n\nSDK 16:\n```\n        NotificationCenter.default.addObserver(self,\n                                            selector: #selector(messageListWillUpdate),\n                                            name: UAInboxMessageListWillUpdateNotification,\n                                            object: nil)\n        NotificationCenter.default.addObserver(self,\n                                            selector: #selector(messageListUpdated),\n                                            name: UAInboxMessageListUpdatedNotification,\n                                            object: nil)\n```\n\nSDK 17:\n```\nMessageCenter.shared.inbox.messagePublisher\n    .receive(on: RunLoop.main)\n    .sink(receiveValue: { messages in\n        // Latest messages\n    })\n    .store(in: &self.subscriptions)\n```\n\n### Message Center UI\n\nNow our Message Center View has been rewritten using SwiftUI. The UIKit based views have been removed.\n\n#### Embedding Message Center in SwiftUI\n\n```\nstruct CustomMessageCenter: View {\n    let controller = MessageCenterController()\n    \n    var body: some View {\n        MessageCenterView(controller: controller)\n    }\n}\n```\n\n#### Embedding Message Center in UIKit\n\nSDK 16:\n\n```\nimport AirshipKit\n\nclass MessageCenterViewController : DefaultMessageCenterSplitViewController {\n\n}\n```\n\nSDK 17:\n```\n// 1\nlet messageCenterviewController = MessageCenterViewControllerFactory.make(\ncontroller: controller\n)\n\nif let messageCenterView = messageCenterviewController.view {\n    // 2\n    // Add the message center view controller to the destination view controller.\n    addChild(messageCenterviewController)\n    view.addSubview(messageCenterView)\n\n    // 3\n    // Create and activate the constraints.\n    messageCenterView.translatesAutoresizingMaskIntoConstraints = false\n    NSLayoutConstraint.activate([\n        messageCenterView.topAnchor.constraint(equalTo: view.topAnchor),\n        messageCenterView.bottomAnchor.constraint(equalTo: view.bottomAnchor),\n        messageCenterView.leftAnchor.constraint(equalTo: view.leftAnchor),\n        messageCenterView.rightAnchor.constraint(equalTo: view.rightAnchor),\n    ])\n}\n```\n\n#### Theming\n\n`MessageCenterStyle` has been renamed to `MessageCenterTheme` to avoid confusing with SwiftUI style patterns.\n\nSDK 16:\n```\nlet style = MessageCenterStyle()\n\nMessageCenter.shared.defaultUI.style = style\n```\n\nSDK 17:\n```\nvar messageCenterTheme = MessageCenterTheme()\n\nMessageCenter.shared.theme = messageCenterTheme\n```\n\nYou can also set the theme on the MessageCenterView directly:\n\nExample:\n\n```\nMessageCenterView(controller: controller)\n    .messageCenterTheme(CustomMessageCenter.messageCenterTheme)\n```\n\n### Load custom message center message view\n\n#### Fetch user credentials:\n\nSDK 16:\n```\nMessageCenter.shared.user.getData { user in\n    // ...\n}\n```\n\nSDK 17:\n```\nlet user = await MessageCenter.shared.inbox.user\n```\n\n#### Load  webView:\n\nThe example below shows how to fetch the credentials, set auth on the request, and load a message into the webview. \nThis code assumes a custom view controller with an embedded WKWebView, as well as a `MessageCenterMessage` ready to be loaded.\n\nSDK 16:\n```\nlet requestObj = NSMutableURLRequest(url:message.messageURL) \nMessageCenter.shared.user.getData { data in \n\n    // set the auth \n    let auth = Utils.authHeaderString(withName: data.username, password: data.password) \n    requestObj.setValue(auth, forHTTPHeaderField:\"Authorization\")\n\n    // load the request \n    self.webView.load(requestObj) \n\n}, queue: DispatchQueue.main)\n```\n\nSDK 17:\n\n```\nvar request = URLRequest(url: message.bodyURL)\nlet user = await MessageCenter.shared.inbox.user\n                \n// set the auth\nrequest.setValue(user.basicAuthString, forHTTPHeaderField: \"Authorization\")\n                \n// load the request\nself.webView.load(request)\n```\n\n## Preference Center\n\n### Preference Center UI\n\nThe Preference Center UI has been rewritten in SwiftUI.\n\n\n#### Embedding Preference Center in SwiftUI\n\n```\nPreferenceCenterView(preferenceCenterID: \"preferenceCenter-ID\")\n```\n\n#### Embedding Preference Center in UIKit\n\n```\n// 1\nlet preferenceCenterviewController = PreferenceCenterViewControllerFactory.makeViewController(preferenceCenterID: \"neat\")\n\nif let preferenceCenterView = preferenceCenterviewController.view {\n\n    // 2\n    // Add the prefrence center view controller to the destination view controller.\n    addChild(preferenceCenterviewController)\n    view.addSubview(preferenceCenterView)\n\n    // 3\n    // Create and activate the constraints.\n    preferenceCenterView.translatesAutoresizingMaskIntoConstraints = false\n    NSLayoutConstraint.activate([\n        preferenceCenterView.topAnchor.constraint(equalTo: view.topAnchor),\n        preferenceCenterView.bottomAnchor.constraint(equalTo: view.bottomAnchor),\n        preferenceCenterView.leftAnchor.constraint(equalTo: view.leftAnchor),\n        preferenceCenterView.rightAnchor.constraint(equalTo: view.rightAnchor),\n    ])\n}\n```\n\n### Theme\n\n`PreferenceCenterStyle` has been replaced by `PreferenceCenterTheme`.\n\nSDK 16:\n```\nlet style = PreferenceCenterStyle()\n\nPreferenceCenter.shared.style = style\n```\n\nSDK 17:\n```\nvar theme = PreferenceCenterTheme()\n\nPreferenceCenter.shared.theme = theme\n```\n\nA theme can also be set directly on the PreferenceCenterView:\n\n```\n PreferenceCenterView(preferenceCenterID: \"preferenceCenterID\")\n    .preferenceCenterTheme(theme)\n```\n\n## Actions\n\nActions have been rewritten to use async/await and be Sendable.\n\n\n### Registering actions\n\nSDK 16:\n```\nAirship.shared.actionRegistry.register(action, names: [\"action_name\", \"action_alias\"])\n```\n\nSDK 17:\n```\nAirship.shared.actionRegistry.registerEntry(names: [\"action_name\", \"action_alias\"]) {\n    return ActionEntry(action: action)\n}\n```\n\n### Defining actions\n\nSDK 16:\n```\nlet customAction = BlockAction { args, completionHandler in\n    print(\"Action is performing with args: \\(args)\")\n    completionHandler(ActionResult.empty())\n}\n```\n\nSDK 17:\n```\nlet customAction = BlockAction { args in\n    print(\"Action is performing with args: \\(args)\")\n    return nil\n}\n```\n\n### Running actions\n\nSDK 16:\n```\n// Run an action by name\nActionRunner.run(\"action_name\", value: \"action_value\", situation: .manualInvocation) { result in\n    print(\"Action finished!\")\n}\n\n// Run an action directly\nActionRunner.run(action, value: \"action_value\", situation: .manualInvocation) { result in\n    print(\"Action finished!\")\n}\n```\n\nSDK 17:\n```\n// Run an action by name\nlet result = await ActionRunner.run(\n    actionName: \"action_name\",\n    arguments: ActionArguments(\n        string: \"action_value\",\n        situation: .manualInvocation\n    )\n)\n\n// Run an action directly\nlet result = await ActionRunner.run(\n    action: action,\n    arguments:ActionArguments(\n        string: \"action_value\",\n        situation: .manualInvocation\n    )\n)\n            \n```\n\n"
  },
  {
    "path": "Documentation/Migration/migration-guide-17-18.md",
    "content": "# Airship iOS SDK 17.x to 18.0 Migration Guide\n\n## Xcode requirements\n\nSDK 18.x now requires Xcode 15.2 or newer.\n\n## Airship Components\n\nInstead of a mix of class vars and instance vars to access various components on the `Airship` instance, they have been normalized to just class vars.\n\n| SDK 17.x                                 | SDK 18.x                           |\n| -----------------------------------------|------------------------------------|\n| Airship.shared.config                    | Airship.config                     |\n| Airship.shared.actionRegistry            | Airship.actionRegistry             |\n| Airship.shared.permissionsManager        | Airship.permissionsManager         |\n| Airship.shared.javaScriptCommandDelegate | Airship.javaScriptCommandDelegate  |\n| Airship.shared.channelCapture            | Airship.channelCapture             |\n| Airship.shared.deepLinkDelegate          | Airship.deepLinkDelegate           |\n| Airship.shared.urlAllowList              | Airship.urlAllowList               |\n| Airship.shared.localeManager             | Airship.localeManager              |\n| Airship.shared.privacyManager            | Airship.privacyManager             |\n| Airship.shared.applicationMetrics        | Removed, this is internal only now |\n\n\nProtocols are exposed instead of concrete classes on Airship to better hide implementation details.\n\n| SDK 17.x                                 | SDK 18.x                           |\n|------------------------------------------|------------------------------------|\n| URLAllowList                             | URLAllowListProtocol               |\n| AirshipLocaleManager                     | AirshipLocaleManagerProtocol       |\n| AirshipPush                              | AirshipPushProtocol                |\n| AirshipContact                           | AirshipContactProtocol             |\n| AirshipAnalytics                         | AirshipAnalyticsProtocol           |\n| AirshipChannel                           | AirshipChannelProtocol             |\n\n`AirshipPush`, `AirshipContact`, `AirshipAnalytics`, and `AirshipChannel` are all internal classes now, the shared methods on those classes have been removed. Instead,\nuse the `Airship.push`, `Airship.contact`, `Airship.analytics`, and `Airship.channel` class vars instead.\n\n## NotificationCenter (NSNotificationCenter)\n\nNotification Center events emitted by the Airship SDK have been updated. Most the notifications are still available, except channel updated. The constants for the rest have been moved.\n\n#### Airship Ready Event\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: Airship.airshipReadyNotification,\n        object: nil,\n        queue: nil\n    ) { notification in\n        /// Following values are only available if `extendedBroadcastEnabled` is true in config.\n        let appKey = notification.userInfo?[Airship.airshipReadyAppKey] as? String\n        let payloadVersion = notification.userInfo?[Airship.airshipReadyPayloadVersion] as? Int\n        let channelID = notification.userInfo?[Airship.airshipReadyChannelIdentifier] as? String\n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.AirshipReady.name,\n        object: nil,\n        queue: nil\n    ) { notification in\n        /// Following values are only available if `extendedBroadcastEnabled` is true in config.\n        let appKey = notification.userInfo?[AirshipNotifications.AirshipReady.appKey] as? String\n        let payloadVersion = notification.userInfo?[AirshipNotifications.AirshipReady.payloadVersionKey] as? Int\n        let channelID = notification.userInfo?[AirshipNotifications.AirshipReady.channelIDKey] as? String\n    }\n```\n\n#### Channel Created\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipChannel.channelCreatedEvent,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let channelID = notification.userInfo?[AirshipChannel.channelIdentifierKey] as? String\n        let isExisting = notification.userInfo?[AirshipChannel.channelExistingKey] as? Bool ?? false\n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.ChannelCreated.name,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let channelID = notification.userInfo?[AirshipNotifications.ChannelCreated.channelIDKey] as? String\n        let isExisting = notification.userInfo?[AirshipNotifications.ChannelCreated.isExistingChannelKey] as? Bool ?? false\n    }\n```\n\n#### Channel Updated\n\nChannel updated has been removed. For the most part apps should not need that and most likely are trying to listen for opt-in status. For that, see `notificationStatus` on the `PushProtocol`.\n\n\n#### Received Notifications\n\nThe foreground and background notifications have been collapsed into a single event with a userInfo key indicating foreground vs background.\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipPush.receivedForegroundNotificationEvent,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let isForeground = true\n        let receivedNotification = notification.userInfo\n    }\n\n    NotificationCenter.default.addObserver(\n        forName: AirshipPush.receivedBackgrounddNotificationEvent,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let isForeground = false\n        let receivedNotification = notification.userInfo\n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.RecievedNotification.name,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let isForeground = notification.userInfo?[AirshipNotifications.RecievedNotification.isForegroundKey] as? Bool\n        let receivedNotification = notification.userInfo?[AirshipNotifications.RecievedNotification.notificationKey] as? [AnyHashable: Any]\n    }\n```\n\n#### Received Notification Response\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipPush.receivedNotificationResponseEvent,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let response = notification.userInfo?[AirshipPush.receivedNotificationResponseEventResponseKey]\n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.ReceivedNotificationResponse.name,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let response = notification.userInfo?[AirshipNotifications.ReceivedNotificationResponse.responseKey]\n    }\n```\n\n#### Locale Updated\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipLocaleManager.localeUpdatedEvent,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let locale = notification.userInfo?[AirshipLocaleManager.localeEventKey] as? Locale\n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.LocaleUpdated.name,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let response = notification.userInfo?[AirshipNotifications.LocaleUpdated.localeKey] as? Locale\n    }\n```\n\n#### Privacy Manager Updated\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipPrivacyManager.localechangeEventUpdatedEvent,\n        object: nil,\n        queue: nil\n    ) { _ in\n        \n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.PrivacyManagerUpdated.name,\n        object: nil,\n        queue: nil\n    ) { _ in\n        \n    }\n```\n\n#### Contact Conflict Event\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipContact.contactConflictEvent,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let conflictEvent = notification.userInfo?[AirshipContact.contactConflictEventKey] as? ContactConflictEvent\n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.ContactConflict.name,\n        object: nil,\n        queue: nil\n    ) { notification in\n        let conflictEvent = notification.userInfo?[AirshipNotifications.ContactConflict.eventKey] as? ContactConflictEvent\n    }\n```\n\n#### Message Center Updated\n\n17.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: MessageCenterInbox.messageListUpdatedEvent,\n        object: nil,\n        queue: nil\n    ) { _ in\n        \n    }\n```\n\n18.x:\n```\n    NotificationCenter.default.addObserver(\n        forName: AirshipNotifications.MessageCenterListUpdated.name,\n        object: nil,\n        queue: nil\n    ) { _ in\n        \n    }\n```\n\n\n\n## Adding Events\n\nThe Analytics method `addEvent(_)` has been removed and replaced with `recordRegionEvent(_)` and `recordCustomEvent(_)`\n\n| SDK 17.x                                | SDK 18.x\n| ----------------------------------------|---------------------------------------------------|\n| Airship.analytics.addEvent(customEvent) | Airship.analytics.recordCustomEvent(customEvent) |\n| Airship.analytics.addEvent(regionEvent) | Airship.analytics.recordRegionEvent(regionEvent) |\n\n\n## AirshipAutomation\n\nThe `AirshipAutomation` module has been rewritten in swift and no longer supports obj-c bindings. For most apps, this will be a trivial update, but if you are using custom display adapters the update will be more extensive. See below for more on custom display adapters.\n\n### Accessors\n\nThe accessors for `InAppMessaging` and `LegacyInAppMessaging` have moved.\n\n| SDK 17.x                                    | SDK 18.x                                    |\n|---------------------------------------------|---------------------------------------------|\n| InAppAutomation.shared.inAppMessageManager  | InAppAutomation.shared.inAppMessaging       |\n| LegacyInAppMessaging.shared                 | InAppAutomation.shared.legacyInAppMessaging |\n\n### Cache Management\n\n`InAppMessagePrepareAssetsDelegate`, `InAppMessageCachePolicyDelegate`, `InAppMessageAssetManager` have been removed and is no longer available to extend. These APIs were difficult to use and often times lead to unintended consequences. The Airship SDK will now manage its own assets.  External assets required by the App that need to be fetched before hand should happen outside of Airship. If assets are needed and can be fetched at display time, use the `CustomDisplayAdapter.waitForReady()` method as a hook to fetch those assets.\n\n### Display Coordinators\n\nDisplay coordinators was another difficult to use API that has been removed. Instead, use the `InAppMessageDisplayDelegate.isMessageReadyToDisplay(_:scheduleID:)` method to prevent messages from displaying, and `InAppAutomation.shared.inAppMessaging.notifyDisplayConditionsChanged()` to notify when the message should be tried again. If a use case is not able to be solved with the replacement methods, please file a Github issue with your use case.\n\n### Extending messages\n\nInAppMessages are no longer extendable when displaying. If this is needed in your application, please file a Github issue with your use case.\n\n### Custom Display Adapter\n\n`InAppMessageAdapterProtocol` has been replaced with `CustomDisplayAdapter`. The new protocol has changed, but it roughly provides the same functionality as before just with a different interface.\n\n\n| SDK 17.x   `InAppMessageAdapterProtocol`                                           | SDK 18.x `CustomDisplayAdapter`                                                        |\n| -----------------------------------------------------------------------------------|----------------------------------------------------------------------------------------|\n| adapter(for:)                                                                      | No mapping, no required factory method                                                 |\n| display() async -> InAppMessageResolution                                          | display(scene: UIWindowScene) async -> CustomDisplayResolution                         |\n| func prepare(with assets: InAppMessageAssets) async -> InAppMessagePrepareResult   | use isReady and func waitForReady() async. Asset are available in the factory callback |\n| isReadyToDisplay                                                                   | isReady                                                                                |\n\n\nExample:\n\n```\nfinal class MyCustomDisplayAdapter : CustomDisplayAdapter {\n\n    @MainActor\n    static func register() {\n        InAppAutomation.shared.inAppMessaging.setAdapterFactoryBlock(forType: .banner) { message, assets in\n            return MyCustomDisplayAdapter(message: message, assets: assets)\n        }\n    }\n\n    let message: InAppMessage\n    let assets: AirshipCachedAssetsProtocol\n\n    init(message: InAppMessage, assets: AirshipCachedAssetsProtocol) {\n        self.message = message\n        self.assets = assets\n    }\n\n    @MainActor\n    var isReady: Bool {\n        // This is called before the message is displayed. If `false`, `waitForReady()` will\n        // be called before this is checked again. If `true`, `display` will  be called\n        // on the same run loop\n        return true\n    }\n\n    @MainActor\n    func waitForReady() async {\n        /// If `isReady` is false, this method should wait for whatever conditions are required to make `isReady` true.\n    }\n\n    @MainActor\n    func display(scene: UIWindowScene) async -> CustomDisplayResolution {\n        /// Most apps will probably need a continuation\n        return await withCheckedContinuation { continuation in\n\n            /// Display the message\n\n\n            /// Resume with the results after its been displayed. Failing to resume will block other messages\n            /// from displaying\n            continuation.resume(returning: CustomDisplayResolution.userDismissed)\n        }\n    }\n}\n```\n\nThen, after takeOff:\n```\n    func application(\n        _ application: UIApplication,\n        didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil\n    ) -> Bool {\n\n        ...\n\n        Airship.takeOff(config, launchOptions: launchOptions)\n        MyCustomDisplayAdapter.register()\n\n        ...\n    }\n```\n\n\n"
  },
  {
    "path": "Documentation/Migration/migration-guide-18-19.md",
    "content": "# Airship iOS SDK 18.x to 19.0 Migration Guide\n\nThe Airship SDK 19.0 introduces significant updates to improve Swift API support and adopts Swift 6. Major changes include transitioning Objective-C support to a separate framework, converting many classes to structs, and making most public APIs `Sendable`. This guide outlines the major updates and non-obvious changes for migrating from SDK 18.x to SDK 19.0.\n\n---\n\n## SDK 19 requirements\n\n- **Xcode 16.2 or newer**\n- iOS 15+\n- tvOS 18+\n- visionOS 1+\n\n---\n\n## Objective-C Support\n\nObjective-C support has been removed from the core Airship frameworks to leverage Swift APIs fully. A new framework, `AirshipObjectiveC`, provides bindings for apps still using Objective-C. For this first release only the most common APIs will have bindings.\n\n- **Missing Bindings?** Open a GitHub issue to request additional bindings\n- Rewrite parts of your application in Swift\n- Provide your own Objective-C bindings\n\n---\n\n## Airship Config\n\nThe `AirshipConfig` class has been converted to a struct. Key updates include:\n\n1. **Property Changes**:\n   - Properties like `appKey` and `appSecret` are now determined during `takeOff` based on the `inProduction` flag.\n   - `inProduction` is now an optional Bool. If set the value will be used. If not, it will be inferred by inspecting the APNS environment.\n\n2. **API Updates**:\n   - APIs that could fail now throw errors instead of silently failing\n\n| SDK 18.x AirshipConfig API                                  | SDK 19.x AirshipConfig API                                  |\n| ----------------------------------------------------------- | ----------------------------------------------------------- |\n| class func AirshipConfig.default() -> AirshipConfig         | static func AirshipConfig.default() throws -> AirshipConfig |\n| class func AirshipConfig.config() -> AirshipConfig          | init()                                                      |\n| class func config(contentsOfFile: String?) -> AirshipConfig | init(fromPlist: String) throws                              |\n| init(contentsOfFile: String?) -> AirshipConfig              | init(fromPlist: String) throws                              |\n| init(contentsOfFile: String?) -> AirshipConfig              | init(fromPlist: String) throws                              |\n| var inProduction: Bool { get set }                          | var inProduction: Bool? { get set }                         |\n| var detectProvisioningMode: Bool { get set }                | _REMOVED_                                                   |\n| var appKey: String { get }                                  | _REMOVED: determined during takeOff based on inProduction_  |\n| var appSecret: String { get }                               | _REMOVED: determined during takeOff based on inProduction_  |\n| var logLevel: AirshipLogLevel { get }                       | _REMOVED: determined during takeOff based on inProduction_  |\n| var logPrivacyLevel: AirshipLogPrivacyLevel { get }         | _REMOVED: determined during takeOff based on inProduction_  |\n| func validate() -> Bool                                     | func validateCredentials(inProduction: Bool) throws         |\n| func validate(logIssues: Bool) -> Bool                      | func validateCredentials(inProduction: Bool) throws         |\n\n---\n\n## Changes to `Airship.takeOff`\n\nThe `takeOff` methods now throw errors for better error handling. `takeOff` will throw in the following conditions:\n\n- `takeOff` already successfully called.\n- `takeOff` was called without an `AirshipConfig` instance and it fails to parse `AirshipConfig.plist`\n- `takeOff` was called without an `AirshipConfig` instance and the parsed `AirshipConfig.plist` is invalid (missing credentials)\n- `takeOff` was called with an `AirshipConfig` instance with invalid config (missing credentials)\n\nNo error will be thrown if the config is properly setup and Airship is only called once during `application(_:didFinishLaunchingWithOptions:)`.\n\nExample error handling:\n\n**Crash on Startup**:\n\n```swift\nlet config = try! AirshipConfig.default()\ntry! Airship.takeOff(config, launchOptions: launchOptions)\n```\n\n**Log Misconfiguration (SDK 18.x behavior)**:\n\n```swift\ndo {\n    let config = try AirshipConfig.default()\n    try Airship.takeOff(config, launchOptions: launchOptions)\n} catch {\n    print(\"Airship.takeOff failed: \\(error)\")\n}\n```\n\nThe absence of an error does not guarantee that the provided app credentials are valid. Airship only verifies that the credentials exist for the specified production mode and conform to the expected length and character set. For new integrations, review the logs for any warnings or errors to ensure a proper setup.\n\n---\n\n## Logging\n\nLogger configuration before `takeOff` is no longer needed. All logging config has moved to `AirshipConfig`.\n\nExample:\n\n```swift\nvar config = AirshipConfig()\n\n// Log everything publicly to the console for development\nconfig.developmentLogLevel = .verbose\nconfig.developmentLogPrivacyLevel = .public\n\n// Custom log handler\nconfig.logHandler = MyCustomLogHandler()\n```\n\n---\n\n## Module component accessors\n\nAccessors for module components have been standardized:\n\n| SDK 18.x Accessors        | SDK 19.x Accessors         |\n| ------------------------- | -------------------------- |\n| MessageCenter.shared      | Airship.messageCenter      |\n| PreferenceCenter.shared   | Airship.preferenceCenter   |\n| FeatureFlagManager.shared | Airship.featureFlagManager |\n| InAppAutomation.shared    | Airship.inAppAutomation    |\n\n---\n\n## Push options\n\n`UANotificationOptions` and `UAAuthorizationStatus` have been removed. Use Apple's equivalents instead:\n\n| SDK 18.x Type         | SDK 19.x Replacement   |\n| --------------------- | ---------------------- |\n| UANotificationOptions | UNAuthorizationOptions |\n| UAAuthorizationStatus | UNAuthorizationStatus  |\n\n`UAAuthorizedNotificationSettings` has been ported to Swift and is now named `AirshipAuthorizedNotificationSettings`:\n\n| SDK 18.x Type                    | SDK 19.x Replacement                  |\n| -------------------------------- | ------------------------------------- |\n| UAAuthorizedNotificationSettings | AirshipAuthorizedNotificationSettings |\n\n---\n\n## Changes to `PushNotificationDelegate`\n\nThe `PushNotificationDelegate` methods are now asynchronous. Update your implementations to match the new async methods.\n\n---\n\n## Changes to `AppIntegration`\n\nFor apps disabling automatic integration, methods in `AppIntegration` are now async and decorated with `@MainActor`. Update your implementation to use the async equivalent methods:\n\n```swift\n    func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {\n        AppIntegration.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken)\n    }\n    func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {\n        AppIntegration.application(application, didFailToRegisterForRemoteNotificationsWithError: error)\n    }\n    func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any]) async -> UIBackgroundFetchResult {\n        return await AppIntegration.application(application, didReceiveRemoteNotification: userInfo)\n    }\n    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse) async {\n        await AppIntegration.userNotificationCenter(center, didReceive: response)\n    }\n    func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification) async -> UNNotificationPresentationOptions {\n        return await AppIntegration.userNotificationCenter(center, willPresent: notification)\n    }\n```\n\nIf you are running into sendable issues with any of the above methods, you should decorate the AppDelegate class with `@MainActor`.\n\n---\n\n## Changes to `AirshipJSON`\n\n`AirshipJSON.wrap` will no longer special case a `Date` by formatting it as an ISO date string. Instead it will use the date formatting strategy defined in the encoder/decoder. `Airship.defaultEncoder` and\n`Airship.defaultDecoder` now use the `.iso8601` date strategy. If you are using `Airship.defaultEncoder` or `Airship.defaultDecoder`, you may want to use a default instance instead.\n\n---\n\n## Changes to `CustomEvent`\n\nCustom event property is now a `Decimal` instead of an `NSNumber`. The init methods for the value now only accepts `Double` and `Decimal`. String values are no longer accepted in the init methods so the app\ncan detect parse failures instead of it silently failing. The property `eventValue` is no longer optional and defaults to 1.0. The default value did not change, just the interface.\n\nProperty values are now able to be set with mutating functions on the custom event. These functions will wrap the value as an `AirshipJSON` to make the event `Sendable`. The `JSONEncoder` can now be specified in the function,\nthe static mutable property `CustomEvent.defaultEncoder` has been replaced by a factory method `CustomEvent.defaultEncoder()` that provides the default encoder for the property mutators if one is not provided.\n\n**API Changes**\n\n| SDK 18.x CustomEvent API                                        | SDK 19.x CustomEvent API                                                       |\n| --------------------------------------------------------------- | ------------------------------------------------------------------------------ |\n| var eventValue: NSNumber? { get set }                           | var eventValue: Decimal { get set }                                            |\n| var properties: [String: Any] { get set }                       | var properties: [String: AirshipJSON] { get }                                  |\n| var properties: [String: Any] { get set }                       | var properties: [String: AirshipJSON] { get }                                  |\n| init(name: String, value: NSNumber?)                            | init(name: String, value: Double) or init(name: String, decimalValue: Decimal) |\n| init(name: String, stringValue: String?)                        | _REMOVED: parse the value as a Double first_                                   |\n| class func event(name: String) -> CustomEvent                   | init(name: String)                                                             |\n| class func event(name: String, string: String?) -> CustomEvent  | _REMOVED: parse the value as a Double first, then use init(name:value:)_       |\n| class func event(name: String, value: NSNumber?) -> CustomEvent | init(name: String, value: Double) or init(name: String, decimalValue: Decimal) |\n\n**Property Mutators**\n\n| SDK 19.x Custom Event property functions                                               | Description                                                    |\n| -------------------------------------------------------------------------------------- | -------------------------------------------------------------- |\n| mutating func setProperty(string: String, forKey: String)                              | Sets a string value in the property map                        |\n| mutating func setProperty(bool: Bool, forKey: String)                                  | Sets a bool value in the property map                          |\n| mutating func setProperty(double: Double, forKey: String)                              | Sets a double value in the property map                        |\n| mutating func setProperty(value: Any?, forKey: String, encoder: JSONEncoder) -> throws | Sets a value (wrapped by AirshipJSON) in the property map      |\n| mutating func removeProperty(forKey: String)                                           | Removes a property in the property map                         |\n| mutating func setProperties(object: Any?, encoder: JSONEncoder) -> throws              | Sets the properties object. The value must result in an object |\n\n---\n\n## Custom Event Templates\n\nThe custom event templates classes have been removed and replaced with new `CustomEvent` init methods.\n\n| SDK 18.x template class | SDK 19.x replacement                          |\n| ----------------------- | --------------------------------------------- |\n| AccountEventTemplate    | CustomEvent.init(accountTemplate:properties:) |\n| RetailEventTemplate     | CustomEvent.init(retailTemplate:properties:)  |\n| SearchEventTemplate     | CustomEvent.init(searchTemplate:properties:)  |\n| MediaEventTemplate      | CustomEvent.init(mediaTemplate:properties:)   |\n\nThe LTV (life time value) property use to be set if the template defined a value. This would lead to inconsistent results depending on if the value was set on the template vs\nsetting the value on the generated custom event. The SDK will no longer automatically set the `ltv` property, it now can be set in the template properties:\n\n```\n    var event = CustomEvent(\n        searchTemplate: .search,\n        properties: CustomEvent.SearchProperties(\n            isLTV: true\n        )\n    )\n    event.eventValue = 100.0\n```\n"
  },
  {
    "path": "Documentation/Migration/migration-guide-19-20.md",
    "content": "# Airship iOS SDK 19.x to 20.0 Migration Guide\n\nThe Airship SDK 20.0 introduces major architectural changes including UI refactors for Message Center and Preference Center, a protocol-first architecture for core components, and modern block-based callback alternatives to delegate patterns. The minimum deployment target is raised to iOS 16+. This guide outlines the necessary changes for migrating your app from SDK 19.x to SDK 20.0.\n\n**Required Migration Tasks:**\n- Update Xcode to 26+\n- Update deployment target to iOS 16+\n- Update SwiftUI view calls for Message Center/Preference Center\n\n**Optional Migration Tasks:**\n- Migrate delegate patterns to block-based callbacks\n- Update deprecated API calls to new APIs\n\n## Table of Contents\n\n- [Breaking Changes](#breaking-changes)\n  - [Preference Center Refactor](#preference-center-refactor)\n  - [Message Center Refactor](#message-center-refactor)\n  - [Protocol Architecture Changes](#protocol-architecture-changes)\n- [Deprecated APIs](#deprecated-apis)\n  - [Attribute Management](#attribute-management)\n  - [Preference Center Display](#preference-center-display)\n- [Block-Based Callbacks](#block-based-callbacks)\n  - [Push Notifications](#push-notifications)\n  - [Registration](#registration)\n  - [Deep Links](#deep-links)\n  - [URL Allow List](#url-allow-list)\n  - [Displaying the Message Center](#displaying-the-message-center)\n  - [Displaying the Preference Center](#displaying-the-preference-center)\n  - [In-App Messaging Display Control](#in-app-messaging-display-control)\n- [Troubleshooting](#troubleshooting)\n\n## Breaking Changes\n\n### Preference Center Refactor\n\nThe Preference Center has been refactored to provide clearer separation between content and navigation, and to simplify customization.\n\n#### View Hierarchy Changes\n\nThe Preference Center now follows a \"Container vs. Content\" architecture that separates navigation from content. The main view `PreferenceCenterView` is now a wrapper that provides a `NavigationStack`. The core content, previously known as `PreferenceCenterList`, has been renamed to `PreferenceCenterContent`.\n\n- **`PreferenceCenterView`**: This is the container view. It sets up the `NavigationStack` and is responsible for the navigation bar's title and back button. Use this view for a standard Preference Center implementation with navigation.\n- **`PreferenceCenterContent`**: This is the content view. It loads and displays the list of preferences. Use this view if you want to provide your own navigation or embed the Preference Center within another view.\n\n#### API Updates\n\nSeveral types and protocols have been renamed for clarity:\n\n- `PreferenceCenterList` → `PreferenceCenterContent`\n- `PreferenceCenterViewPhase` → `PreferenceCenterContentPhase`\n- `PreferenceCenterViewLoader` → `PreferenceCenterContentLoader`\n- `PreferenceCenterViewStyle` → `PreferenceCenterContentStyle`\n- `PreferenceCenterViewStyleConfiguration` → `PreferenceCenterContentStyleConfiguration`\n\n\n#### Navigation Changes\n\nThe `PreferenceCenterNavigationStack` enum and the `preferenceCenterNavigationStack()` view modifier have been removed. `PreferenceCenterView` now always uses a `NavigationStack`. If you were previously using `.none`, you should switch to using `PreferenceCenterContent` directly.\n\n**Before:**\n```swift\n// To provide custom navigation\nPreferenceCenterView(preferenceCenterID: \"your_id\")\n    .preferenceCenterNavigationStack(.none)\n```\n\n**After:**\n```swift\n// Use PreferenceCenterContent directly\nPreferenceCenterContent(preferenceCenterID: \"your_id\")\n```\n\n### Message Center Refactor\n\nThe Message Center UI has been refactored for greater flexibility and clearer API boundaries, separating navigation from content.\n\n#### View Hierarchy Changes\n\nThe Message Center now follows a \"Container vs. Content\" architecture that separates navigation from content. The top-level `MessageCenterView` is now a navigation container. The actual content is rendered by `MessageCenterContent`.\n\nThe Message Center UI is broken down into several public components that can be used to build a custom experience:\n\n- **`MessageCenterView`**: The top-level container that provides a `NavigationStack` or `NavigationSplitView`. Use this view for a standard Message Center implementation. It provides either a `NavigationStack` or a `NavigationSplitView`, which can be controlled via the new `navigationStyle` parameter.\n- **`MessageCenterContent`**: The core content view that coordinates the message list. Use this view if you need to provide your own navigation or embed the Message Center within a custom view hierarchy.\n- **`MessageCenterListViewWithNavigation`**: This view displays the list of messages and **is responsible for the navigation bar content**, including the title and the edit/toolbar buttons.\n- **`MessageCenterListView`**: A simpler view that only displays the list of messages, without any navigation bar items.\n- **`MessageCenterMessageViewWithNavigation`**: Displays a single message and manages its navigation bar.\n- **`MessageCenterMessageView`**: Displays a single message without a navigation bar.\n\n#### API Updates\n\n- `MessageCenterViewStyle` → `MessageCenterContentStyle`\n- `messageCenterViewStyle()` → `messageCenterContentStyle()`\n- `MessageCenterStyleConfiguration` → `MessageCenterContentStyleConfiguration`\n\n#### Navigation Changes\n\nThe `MessageCenterNavigationStack` enum and the `messageCenterNavigationStack()` view modifier have been removed. Navigation is now controlled by the `navigationStyle` parameter on `MessageCenterView`.\n\n**Before:**\n```swift\n// Basic Message Center\nMessageCenterView()\n\n// With custom navigation\nMessageCenterView()\n    .messageCenterNavigationStack(.none)\n```\n\n**After:**\n```swift\n// Stack-based navigation (default on iPhone)\nMessageCenterView(navigationStyle: .stack)\n\n// Split-view navigation (default on iPad)\nMessageCenterView(navigationStyle: .split)\n\n// To provide custom navigation, use MessageCenterContent\nMessageCenterContent()\n```\n\n### Protocol Architecture Changes\n\nSDK 20.0 refactors core Airship components to use protocols instead of concrete classes. The existing functionality remains the same, but the implementation is now hidden behind protocol interfaces. This change provides better testability, modularity, and allows for easier customization and mocking.\n\n#### Class-to-Protocol Conversions\n\nSeveral core Airship classes have been converted to protocols (with the same functionality):\n\n- `AirshipPrivacyManager`\n- `AirshipPermissionsManager`\n- `MessageCenter`\n- `InAppAutomation`\n- `PreferenceCenter`\n- `InAppMessaging`\n- `LegacyInAppMessaging`\n- `AirshipActionRegistry`\n- `AirshipChannelCapture`\n- `FeatureFlagManager`\n\n#### Protocol Renames\n\nSeveral protocols have been renamed to remove the \"Protocol\" suffix:\n\n- `AirshipAnalyticsProtocol` → `AirshipAnalytics`\n- `AirshipChannelProtocol` → `AirshipChannel`\n- `AirshipContactProtocol` → `AirshipContact`\n- `AirshipPushProtocol` → `AirshipPush`\n- `PrivacyManagerProtocol` → `AirshipPrivacyManager`\n- `URLAllowListProtocol` → `AirshipURLAllowList`\n- `AirshipLocaleManagerProtocol` → `AirshipLocaleManager`\n- `InAppMessagingProtocol` → `InAppMessaging`\n- `LegacyInAppMessagingProtocol` → `LegacyInAppMessaging`\n\n#### Migration Impact\n\n**For most developers:** These changes are primarily internal and won't affect your code. The public APIs remain the same - you can continue using `Airship.contact`, `Airship.privacyManager`, `Airship.messageCenter`, `Airship.inAppAutomation`, `Airship.preferenceCenter`, etc. as before.\n\n---\n\n## Deprecated APIs\n\nSeveral APIs have been deprecated in SDK 20.0 and will be removed in future versions. Update your code to use the recommended alternatives.\n\n### Attribute Management\n\nThe `set(number:attribute:)` method now accepts Swift numeric types directly instead of `NSNumber`. This change is **backward compatible** - existing code using `Int` or `UInt` literals will continue to work without modification.\n\n**Before:**\n```swift\n// Old method using NSNumber\nAirship.contact.editAttributes { editor in\n    editor.set(number: NSNumber(value: 42), attribute: \"age\")\n}\n```\n\n**After:**\n```swift\n// Use Swift types - Int, Uint, or Double\nAirship.contact.editAttributes { editor in\n    editor.set(number: 42, attribute: \"age\")\n    editor.set(number: 42.0, attribute: \"age\")\n    editor.set(number: UInt(42), attribute: \"age\")\n}\n```\n\n### Preference Center Display\n\n**Before:**\n```swift\n// Old method\nAirship.preferenceCenter.openPreferenceCenter(preferenceCenterID: \"my_id\")\n```\n\n**After:**\n```swift\n// New method\nAirship.preferenceCenter.display(\"my_id\")\n```\n\n---\n\n## Block-Based Callbacks\n\nTo provide a more modern and convenient Swift API, SDK 20 introduces block-based (closure) callbacks as an alternative to several common delegate protocols. These new callbacks improve code locality and can reduce boilerplate for simple event handling.\n\nThe delegate-based approach is still fully supported, but we recommend adopting the new block-based callbacks for new implementations. **The delegate patterns will be deprecated in a future 20.x release and removed in SDK 21.0.0**\n\nAll new callback closures are `@MainActor` and `@Sendable` to ensure thread safety and simplify UI updates.\n\n### Push Notifications\n\nInstead of conforming to `PushNotificationDelegate`, you can now set individual closures on `Airship.push`. If a block is provided for a specific event, the corresponding `PushNotificationDelegate` method will be ignored.\n\n**Before:**\n```swift\n// A class that implements the delegate\nclass MyPushDelegate: PushNotificationDelegate {\n    func receivedNotificationResponse(_ response: UNNotificationResponse) async {\n        // Handle response asynchronously\n        await someAsyncTask()\n    }\n    // ... other delegate methods\n}\n\n// In your app, store a strong reference to the delegate\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    private let pushDelegate = MyPushDelegate()\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        ...\n        \n        // After takeOff\n        Airship.push.pushNotificationDelegate = pushDelegate\n        return true\n    }\n}\n```\n\n**After:**\n```swift\n// In your app's startup code\nAirship.push.onReceivedNotificationResponse = { response in\n    // Handle response asynchronously\n    await someAsyncTask()\n}\n```\n\n**New APIs on `Airship.push`:**\n- `onReceivedForegroundNotification`\n- `onReceivedBackgroundNotification`\n- `onReceivedNotificationResponse`\n- `onExtendPresentationOptions`\n\n### Registration\n\nThe `RegistrationDelegate` has also been broken down into more granular, event-specific closures. If a block is provided, it will be used instead of the corresponding delegate method.\n\n**Before:**\n```swift\n// A class that implements the delegate\nclass MyRegistrationDelegate: RegistrationDelegate {\n    func apnsRegistrationSucceeded(withDeviceToken deviceToken: Data) {\n        print(\"APNs registration succeeded\")\n    }\n\n    func notificationRegistrationFinished(\n        withAuthorizedSettings authorizedSettings: AirshipAuthorizedNotificationSettings,\n        status: UNAuthorizationStatus\n    ) {\n        print(\"Notification registration finished with status: \\(status)\")\n    }\n}\n\n// In your app, store a strong reference to the delegate\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    private let registrationDelegate = MyRegistrationDelegate()\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        ...\n        \n        // After takeOff\n        Airship.push.registrationDelegate = registrationDelegate\n        return true\n    }\n}\n```\n\n**After:**\n```swift\n// In your app's startup code\n\n// Handle APNS registration updates\nAirship.push.onAPNSRegistrationFinished = { result in\n    switch result {\n    case .success(let deviceToken):\n        print(\"APNs registration succeeded: \\(deviceToken)\")\n    case .failure(let error):\n        print(\"APNs registration failed: \\(error)\")\n    }\n}\n\n// Handle user notification registration updates\nAirship.push.onNotificationRegistrationFinished = { result in\n    print(\"Notification registration finished with status: \\(result.status)\")\n}\n\n// Handle changes to authorized notification settings\nAirship.push.onNotificationAuthorizedSettingsDidChange = { settings in\n    print(\"Authorized settings changed: \\(settings)\")\n}\n```\n\n**New APIs on `Airship.push`:**\n- `onAPNSRegistrationFinished` with `APNSRegistrationResult`\n- `onNotificationRegistrationFinished` with `NotificationRegistrationResult`\n- `onNotificationAuthorizedSettingsDidChange`\n\nThe new callbacks use the following data structures:\n\n```swift\n/// The result of an APNs registration.\npublic enum APNSRegistrationResult: Sendable {\n    /// Registration was successful and a new device token was received.\n    case success(deviceToken: String)\n\n    /// Registration failed.\n    case failure(error: any Error)\n}\n\n/// The result of the initial notification registration prompt.\npublic struct NotificationRegistrationResult: Sendable {\n    /// The settings that were authorized at the time of registration.\n    public let authorizedSettings: AirshipAuthorizedNotificationSettings\n\n    /// The authorization status.\n    public let status: UNAuthorizationStatus\n\n    #if !os(tvOS)\n    /// Set of the categories that were most recently registered.\n    public let categories: Set<UNNotificationCategory>\n    #endif\n}\n```\n\n### Deep Links\n\nInstead of conforming to `DeepLinkDelegate`, you can now set closures on `Airship`. If the `onDeepLink` block is set, the `DeepLinkDelegate` will be ignored.\n\n**Before:**\n```swift\nclass MyDeepLinkDelegate: DeepLinkDelegate {\n    func receivedDeepLink(_ deepLink: URL) async {\n        // Handle deep link asynchronously\n        await someNavigationTask(url)\n    }\n}\n\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    private let deepLinkDelegate = MyDeepLinkDelegate()\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        ...\n        \n        // After takeOff\n        Airship.deepLinkDelegate = deepLinkDelegate\n        return true\n    }\n}\n```\n\n**After:**\n```swift\nAirship.onDeepLink = { url in\n    // Handle deep link asynchronously\n    await someNavigationTask(url)\n}\n```\n\n**New API on `Airship`:**\n- `onDeepLink`\n\n### URL Allow List\n\nInstead of conforming to `URLAllowListDelegate`, you can now set the `onAllowURL` closure on `Airship.urlAllowList`. If the `onAllowURL` block is set, the `URLAllowListDelegate` will be ignored.\n\n**Before:**\n```swift\nclass MyURLDelegate: URLAllowListDelegate {\n    func allowURL(_ url: URL, scope: URLAllowListScope) -> Bool {\n        // Custom URL validation logic\n        return url.host?.contains(\"trusted-domain.com\") == true\n    }\n}\n\nclass AppDelegate: UIResponder, UIApplicationDelegate {\n    private let urlDelegate = MyURLDelegate()\n\n    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n        ...\n        \n        // After takeOff\n        Airship.urlAllowList.delegate = urlDelegate\n        return true\n    }\n}\n```\n\n**After:**\n```swift\nAirship.urlAllowList.onAllowURL = { url, scope in\n    // Custom URL validation logic\n    return url.host?.contains(\"trusted-domain.com\") == true\n}\n```\n\n**New API on `Airship.urlAllowList`:**\n- `onAllowURL`\n\n### Displaying the Message Center\n\nInstead of `MessageCenterDisplayDelegate`, you can now use the `onDisplay` and `onDismissDisplay` closures on `Airship.messageCenter`. The `onDisplay` closure should return `true` if the display was handled, or `false` to let the SDK fall back to its default UI. If the `onDisplay` block is set, the delegate will be ignored.\n\n**Before:**\n```swift\nclass MyDisplayDelegate: MessageCenterDisplayDelegate {\n    func displayMessageCenter(messageID: String?) {\n        // Display Message Center UI\n    }\n\n    func dismissMessageCenter() {\n        // Dismiss Message Center UI\n    }\n}\n\n// Store a strong reference\nprivate let displayDelegate = MyDisplayDelegate()\n\n// In your app's startup code\nAirship.messageCenter.displayDelegate = displayDelegate\n```\n\n**After:**\n```swift\nAirship.messageCenter.onDisplay = { messageID in\n    // Display custom Message Center UI\n    // Return true to prevent the default SDK display behavior.\n    return true\n}\n\nAirship.messageCenter.onDismissDisplay = {\n    // Dismiss Message Center UI\n}\n```\n\n### Displaying the Preference Center\n\nInstead of `PreferenceCenterOpenDelegate`, you can use the new `onDisplay` closure on `Airship.preferenceCenter`. The closure should return `true` if the display was handled, or `false` to let the SDK fall back to its default UI. If the `onDisplay` block is set, the delegate will be ignored.\n\n**Before:**\n```swift\nclass MyOpenDelegate: PreferenceCenterOpenDelegate {\n    func openPreferenceCenter(preferenceCenterID: String) {\n        // Display Preference Center UI\n    }\n}\n\n// Store a strong reference\nprivate let openDelegate = MyOpenDelegate()\n\n// In your app's startup code\nAirship.preferenceCenter.openDelegate = openDelegate\n```\n\n**After:**\n```swift\nAirship.preferenceCenter.onDisplay = { preferenceCenterID in\n    // Display custom Preference Center UI\n    // Return true to prevent the default SDK display behavior.\n    return true\n}\n```\n\n**New API on `Airship.preferenceCenter`:**\n- `onDisplay`\n\n### In-App Messaging Display Control\n\nInstead of implementing `InAppMessagingDisplayDelegate`, you can now use the `onIsReadyToDisplay` closure on `Airship.inAppMessaging`. This closure allows you to control when in-app messages are ready to be displayed. If the `onIsReadyToDisplay` block is set, the delegate will be ignored.\n\n**Before:**\n```swift\nclass MyDisplayDelegate: InAppMessagingDisplayDelegate {\n    func isMessageReadyToDisplay(_ message: InAppMessage, scheduleID: String) -> Bool {\n        // Custom logic to determine if message should be displayed\n        return someCondition\n    }\n}\n\n// Store a strong reference\nprivate let displayDelegate = MyDisplayDelegate()\n\n// In your app's startup code\nAirship.inAppMessaging.displayDelegate = displayDelegate\n```\n\n**After:**\n```swift\nAirship.inAppMessaging.onIsReadyToDisplay = { message, scheduleID in\n    // Custom logic to determine if message should be displayed\n    return someCondition\n}\n```\n\n**New API on `Airship.inAppMessaging`:**\n- `onIsReadyToDisplay`\n\n---\n\n## Troubleshooting\n\n### Common Issues\n\n**Build Errors After Migration**\n- Ensure you're using Xcode 26+ and have updated your deployment target to iOS 16+\n- Clean your build folder (Product → Clean Build Folder) and rebuild\n- Check that all SwiftUI view calls have been updated to use the new API names\n\n**Message Center/Preference Center Not Displaying**\n- Verify you're using the correct view names (`MessageCenterView` vs `MessageCenterContent`)\n- Check that navigation style parameters are set correctly\n- Ensure you're not mixing old and new API calls\n\n**Delegate Methods Not Being Called**\n- If you've migrated to block-based callbacks, ensure you're not setting both delegates and blocks\n- Block-based callbacks take precedence over delegate methods\n- Check that your delegate objects are retained (not deallocated)\n\n**Attribute Setting Issues**\n- The `set(number:attribute:)` method now accepts Swift types directly\n- `Int` and `UInt` values are automatically bridged to `Double`\n- If you're still using `NSNumber`, consider migrating to Swift types\n\n### Getting Help\n\nIf you encounter issues not covered in this guide:\n- Check the [Airship Documentation](https://docs.airship.com/)\n- Review the [SDK API Reference](https://docs.airship.com/reference/libraries/ios/)\n- Contact [Airship Support](https://support.airship.com/)"
  },
  {
    "path": "Documentation/abstracts/Guides.md",
    "content": "##### [Migration Guide](migration-guide.html)\n##### [Migration Guide (Legacy)](migration-guide-legacy.html)\n"
  },
  {
    "path": "Documentation/readme-for-jazzy.md",
    "content": "# Airship iOS SDK\n\nThe Airship SDK for iOS provides a simple way to integrate Airship services into your iOS applications.\n\n## Resources\n- [AirshipCore Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipCore)\n- [AirshipAutomation Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipAutomation)\n- [AirshipMessageCenter Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipMessageCenter)\n- [AirshipPreferenceCenter Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipPreferenceCenter)\n- [AirshipFeatureFlags Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipFeatureFlags)\n- [AirshipObjectiveC Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipObjectiveC)\n- [AirshipNotificationServiceExtension Docs](https://docs.airship.com/reference/libraries/ios/latest/AirshipNotificationServiceExtension)\n- [Getting started guide](https://docs.airship.com/platform/mobile/setup/sdk/ios)\n- [Migration Guides](https://github.com/urbanairship/ios-library/tree/main/Documentation/Migration)\n"
  },
  {
    "path": "Gemfile",
    "content": "source 'https://rubygems.org'\n\ngem \"cocoapods\"\ngem \"jazzy\""
  },
  {
    "path": "LICENSE",
    "content": "\n                                 Apache License\n                           Version 2.0, January 2004\n                        http://www.apache.org/licenses/\n\n   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION\n\n   1. Definitions.\n\n      \"License\" shall mean the terms and conditions for use, reproduction,\n      and distribution as defined by Sections 1 through 9 of this document.\n\n      \"Licensor\" shall mean the copyright owner or entity authorized by\n      the copyright owner that is granting the License.\n\n      \"Legal Entity\" shall mean the union of the acting entity and all\n      other entities that control, are controlled by, or are under common\n      control with that entity. For the purposes of this definition,\n      \"control\" means (i) the power, direct or indirect, to cause the\n      direction or management of such entity, whether by contract or\n      otherwise, or (ii) ownership of fifty percent (50%) or more of the\n      outstanding shares, or (iii) beneficial ownership of such entity.\n\n      \"You\" (or \"Your\") shall mean an individual or Legal Entity\n      exercising permissions granted by this License.\n\n      \"Source\" form shall mean the preferred form for making modifications,\n      including but not limited to software source code, documentation\n      source, and configuration files.\n\n      \"Object\" form shall mean any form resulting from mechanical\n      transformation or translation of a Source form, including but\n      not limited to compiled object code, generated documentation,\n      and conversions to other media types.\n\n      \"Work\" shall mean the work of authorship, whether in Source or\n      Object form, made available under the License, as indicated by a\n      copyright notice that is included in or attached to the work\n      (an example is provided in the Appendix below).\n\n      \"Derivative Works\" shall mean any work, whether in Source or Object\n      form, that is based on (or derived from) the Work and for which the\n      editorial revisions, annotations, elaborations, or other modifications\n      represent, as a whole, an original work of authorship. For the purposes\n      of this License, Derivative Works shall not include works that remain\n      separable from, or merely link (or bind by name) to the interfaces of,\n      the Work and Derivative Works thereof.\n\n      \"Contribution\" shall mean any work of authorship, including\n      the original version of the Work and any modifications or additions\n      to that Work or Derivative Works thereof, that is intentionally\n      submitted to Licensor for inclusion in the Work by the copyright owner\n      or by an individual or Legal Entity authorized to submit on behalf of\n      the copyright owner. For the purposes of this definition, \"submitted\"\n      means any form of electronic, verbal, or written communication sent\n      to the Licensor or its representatives, including but not limited to\n      communication on electronic mailing lists, source code control systems,\n      and issue tracking systems that are managed by, or on behalf of, the\n      Licensor for the purpose of discussing and improving the Work, but\n      excluding communication that is conspicuously marked or otherwise\n      designated in writing by the copyright owner as \"Not a Contribution.\"\n\n      \"Contributor\" shall mean Licensor and any individual or Legal Entity\n      on behalf of whom a Contribution has been received by Licensor and\n      subsequently incorporated within the Work.\n\n   2. Grant of Copyright License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      copyright license to reproduce, prepare Derivative Works of,\n      publicly display, publicly perform, sublicense, and distribute the\n      Work and such Derivative Works in Source or Object form.\n\n   3. Grant of Patent License. Subject to the terms and conditions of\n      this License, each Contributor hereby grants to You a perpetual,\n      worldwide, non-exclusive, no-charge, royalty-free, irrevocable\n      (except as stated in this section) patent license to make, have made,\n      use, offer to sell, sell, import, and otherwise transfer the Work,\n      where such license applies only to those patent claims licensable\n      by such Contributor that are necessarily infringed by their\n      Contribution(s) alone or by combination of their Contribution(s)\n      with the Work to which such Contribution(s) was submitted. If You\n      institute patent litigation against any entity (including a\n      cross-claim or counterclaim in a lawsuit) alleging that the Work\n      or a Contribution incorporated within the Work constitutes direct\n      or contributory patent infringement, then any patent licenses\n      granted to You under this License for that Work shall terminate\n      as of the date such litigation is filed.\n\n   4. Redistribution. You may reproduce and distribute copies of the\n      Work or Derivative Works thereof in any medium, with or without\n      modifications, and in Source or Object form, provided that You\n      meet the following conditions:\n\n      (a) You must give any other recipients of the Work or\n          Derivative Works a copy of this License; and\n\n      (b) You must cause any modified files to carry prominent notices\n          stating that You changed the files; and\n\n      (c) You must retain, in the Source form of any Derivative Works\n          that You distribute, all copyright, patent, trademark, and\n          attribution notices from the Source form of the Work,\n          excluding those notices that do not pertain to any part of\n          the Derivative Works; and\n\n      (d) If the Work includes a \"NOTICE\" text file as part of its\n          distribution, then any Derivative Works that You distribute must\n          include a readable copy of the attribution notices contained\n          within such NOTICE file, excluding those notices that do not\n          pertain to any part of the Derivative Works, in at least one\n          of the following places: within a NOTICE text file distributed\n          as part of the Derivative Works; within the Source form or\n          documentation, if provided along with the Derivative Works; or,\n          within a display generated by the Derivative Works, if and\n          wherever such third-party notices normally appear. The contents\n          of the NOTICE file are for informational purposes only and\n          do not modify the License. You may add Your own attribution\n          notices within Derivative Works that You distribute, alongside\n          or as an addendum to the NOTICE text from the Work, provided\n          that such additional attribution notices cannot be construed\n          as modifying the License.\n\n      You may add Your own copyright statement to Your modifications and\n      may provide additional or different license terms and conditions\n      for use, reproduction, or distribution of Your modifications, or\n      for any such Derivative Works as a whole, provided Your use,\n      reproduction, and distribution of the Work otherwise complies with\n      the conditions stated in this License.\n\n   5. Submission of Contributions. Unless You explicitly state otherwise,\n      any Contribution intentionally submitted for inclusion in the Work\n      by You to the Licensor shall be under the terms and conditions of\n      this License, without any additional terms or conditions.\n      Notwithstanding the above, nothing herein shall supersede or modify\n      the terms of any separate license agreement you may have executed\n      with Licensor regarding such Contributions.\n\n   6. Trademarks. This License does not grant permission to use the trade\n      names, trademarks, service marks, or product names of the Licensor,\n      except as required for reasonable and customary use in describing the\n      origin of the Work and reproducing the content of the NOTICE file.\n\n   7. Disclaimer of Warranty. Unless required by applicable law or\n      agreed to in writing, Licensor provides the Work (and each\n      Contributor provides its Contributions) on an \"AS IS\" BASIS,\n      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or\n      implied, including, without limitation, any warranties or conditions\n      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A\n      PARTICULAR PURPOSE. You are solely responsible for determining the\n      appropriateness of using or redistributing the Work and assume any\n      risks associated with Your exercise of permissions under this License.\n\n   8. Limitation of Liability. In no event and under no legal theory,\n      whether in tort (including negligence), contract, or otherwise,\n      unless required by applicable law (such as deliberate and grossly\n      negligent acts) or agreed to in writing, shall any Contributor be\n      liable to You for damages, including any direct, indirect, special,\n      incidental, or consequential damages of any character arising as a\n      result of this License or out of the use or inability to use the\n      Work (including but not limited to damages for loss of goodwill,\n      work stoppage, computer failure or malfunction, or any and all\n      other commercial damages or losses), even if such Contributor\n      has been advised of the possibility of such damages.\n\n   9. Accepting Warranty or Additional Liability. While redistributing\n      the Work or Derivative Works thereof, You may choose to offer,\n      and charge a fee for, acceptance of support, warranty, indemnity,\n      or other liability obligations and/or rights consistent with this\n      License. However, in accepting such obligations, You may act only\n      on Your own behalf and on Your sole responsibility, not on behalf\n      of any other Contributor, and only if You agree to indemnify,\n      defend, and hold each Contributor harmless for any liability\n      incurred by, or claims asserted against, such Contributor by reason\n      of your accepting any such warranty or additional liability.\n\n   END OF TERMS AND CONDITIONS\n\n   APPENDIX: How to apply the Apache License to your work.\n\n      To apply the Apache License to your work, attach the following\n      boilerplate notice, with the fields enclosed by brackets \"[]\"\n      replaced with your own identifying information. (Don't include\n      the brackets!)  The text should be enclosed in the appropriate\n      comment syntax for the file format. We also recommend that a\n      file or class name and description of purpose be included on the\n      same \"printed page\" as the copyright notice for easier\n      identification within third-party archives.\n\n   Copyright [yyyy] [name of copyright owner]\n\n   Licensed under the Apache License, Version 2.0 (the \"License\");\n   you may not use this file except in compliance with the License.\n   You may obtain a copy of the License at\n\n       http://www.apache.org/licenses/LICENSE-2.0\n\n   Unless required by applicable law or agreed to in writing, software\n   distributed under the License is distributed on an \"AS IS\" BASIS,\n   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n   See the License for the specific language governing permissions and\n   limitations under the License."
  },
  {
    "path": "Makefile",
    "content": "\nXCODE ?= 26.2\n\nexport XCBEAUTIFY_RENDERER ?= github-actions\nexport TEST_DESTINATION ?= platform=iOS Simulator,OS=latest,name=iPhone 17 Pro Max\nexport TEST_DESTINATION_TVOS ?= platform=tvOS Simulator,OS=latest,name=Apple TV\nexport TEST_DESTINATION_VISIONOS ?= platform=visionOS Simulator,OS=latest,name=Apple Vision Pro\n\nexport DEVELOPER_DIR = $(shell bash ./scripts/get_xcode_path.sh ${XCODE} $(XCODE_PATH))\nexport AIRSHIP_VERSION = $(shell bash \"./scripts/airship_version.sh\")\n\n\nbuild_path = build\nderived_data_path = ${build_path}/derived_data\narchive_path = ${build_path}/archive\n\nxcframeworks_path = ${build_path}/xcframeworks\nxcframeworks_full_path = ${xcframeworks_path}/full\nxcframeworks_dotnet_path = ${xcframeworks_path}/dotnet\npackage_zip_path = ${build_path}/Airship.zip\npackage_xcframeworks_zip_path = ${build_path}/Airship.xcframeworks.zip\npackage_dotnet_xcframeworks_zip_path = ${build_path}/Airship.dotnet.xcframeworks.zip\nfile_size=${build_path}/size.txt\nprevious_file_size=${build_path}/previous-size.txt\n\n.PHONY: setup\nsetup:\n\ttest ${DEVELOPER_DIR}\n\tbundle install --quiet\n\tbash ./scripts/check_xcbeautify.sh\n\n.PHONY: all\nall: setup build test pod-lint\n\n.PHONY: build\nbuild: build-package build-samples\n\n.PHONY: build-package\nbuild-package: clean-package build-xcframeworks\n\tbash ./scripts/package.sh \\\n\t \"${package_zip_path}\" \\\n\t \"${xcframeworks_full_path}/*.xcframework\" \\\n\t CHANGELOG.md \\\n\t README.md \\\n\t LICENSE\n\tbash ./scripts/package_xcframeworks.sh \"${package_xcframeworks_zip_path}\" \"${xcframeworks_full_path}/\" \"Carthage/build\"\n\tbash ./scripts/package_xcframeworks.sh \"${package_dotnet_xcframeworks_zip_path}\" \"${xcframeworks_dotnet_path}/\" \"xcframeworks\"\n\n.PHONY: build-docC\nbuild-docC:\n\tbash ./scripts/build_docCs.sh $(version)\n\t\n\n.PHONY: build-xcframeworks\nbuild-xcframeworks: setup clean-xcframeworks\n\tbash ./scripts/build_xcframeworks.sh \"${xcframeworks_path}\" \"${derived_data_path}\" \"${archive_path}\"\n\n.PHONY: build-xcframeworks-no-sign\nbuild-xcframeworks-no-sign: setup clean-xcframeworks\n\tbash ./scripts/build_xcframeworks.sh \"${xcframeworks_path}\" \"${derived_data_path}\" \"${archive_path}\" \"true\"\n\n.PHONY: build-samples\nbuild-samples: build-sample-ios\n\n.PHONY: build-sample-ios\nbuild-sample-ios: setup\n\tbash ./scripts/build_sample.sh \"DevApp\" \"${derived_data_path}\"\n\t\n.PHONY: build-sample-watchos\nbuild-sample-watchos: setup\n\tbash ./scripts/build_sample_watchos.sh \"watchOSSample_WatchKit_Extension\" \"${derived_data_path}\"\n\t\n.PHONY: build-airship-objectiveC\nbuild-airship-objectiveC: setup\n\tbash ./scripts/run_xcodebuild.sh \"AirshipObjectiveC\" \"${derived_data_path}\" build\n\n.PHONY: test\ntest: setup test-core test-preference-center test-message-center test-automation test-feature-flags test-service-extension\n\n.PHONY: test-core\ntest-core: setup\n\tbash ./scripts/run_xcodebuild.sh AirshipCore \"${derived_data_path}\" test\n\n.PHONY: test-message-center\ntest-message-center: setup\n\tbash ./scripts/run_xcodebuild.sh AirshipMessageCenter \"${derived_data_path}\" test\n\n.PHONY: test-preference-center\ntest-preference-center: setup\n\tbash ./scripts/run_xcodebuild.sh AirshipPreferenceCenter \"${derived_data_path}\" test\n\n.PHONY: test-automation\ntest-automation: setup\n\tbash ./scripts/run_xcodebuild.sh AirshipAutomation \"${derived_data_path}\" test\n\n.PHONY: test-feature-flags\ntest-feature-flags: setup\n\tbash ./scripts/run_xcodebuild.sh AirshipFeatureFlags \"${derived_data_path}\" test\n\n.PHONY: test-service-extension\ntest-service-extension: setup\n\tbash ./scripts/run_xcodebuild.sh AirshipNotificationServiceExtension \"${derived_data_path}\" test\n\n.PHONY: pod-publish\npod-publish: setup\n\tbundle exec pod trunk push Airship.podspec --allow-warnings\n\tbundle exec pod trunk push AirshipServiceExtension.podspec --allow-warnings\n\n.PHONY: pod-lint\npod-lint: pod-lint-tvos pod-lint-ios pod-lint-extensions\n\n.PHONY: pod-lint-tvos\npod-lint-tvos: setup\n\tbundle exec pod lib lint Airship.podspec --verbose --platforms=tvos --fail-fast --skip-tests --no-subspecs --allow-warnings\n\n.PHONY: pod-lint-watchos\npod-lint-watchos: setup\n\tbundle exec pod lib lint Airship.podspec --verbose --platforms=watchos --subspec=Core --fail-fast --skip-tests --no-clean --allow-warnings\n\n.PHONY: pod-lint-ios\npod-lint-ios: setup\n\tbundle exec pod lib lint Airship.podspec --verbose --platforms=ios  --fail-fast --skip-tests --no-subspecs --allow-warnings\n\n.PHONY: pod-lint-visionos\npod-lint-visionos: setup\n\tbundle exec pod lib lint Airship.podspec --verbose --platforms=visionOS  --fail-fast --skip-tests --no-subspecs --allow-warnings\n\n.PHONY: pod-lint-extensions\npod-lint-extensions: setup\n\tbundle exec pod lib lint AirshipServiceExtension.podspec --verbose --platforms=ios  --fail-fast --skip-tests --allow-warnings\n\n.PHONY: clean\nclean:\n\trm -rf \"${build_path}\"\n\n.PHONY: clean-package\nclean-package:\n\trm -rf \"${package_zip_path}\"\n\trm -rf \"${package_xcframeworks_zip_path}\"\n\trm -rf \"${package_dotnet_xcframeworks_zip_path}\"\n\n.PHONY: clean-xcframeworks\nclean-xcframeworks:\n\t# rm -rf \"${xcframeworks_path}\"\n\t\n.PHONY: compare-framework-size\ncompare-framework-size: build-xcframeworks-no-sign check-size\n\n.PHONY: check-size\ncheck-size:\n\tbash ./scripts/check_size.sh \"${xcframeworks_path}\" \"${file_size}\" \"${previous_file_size}\"\n\t\n"
  },
  {
    "path": "Package.swift",
    "content": "// swift-tools-version:6.0\n\n// Copyright Airship and Contributors\n\nimport PackageDescription\n\nlet package = Package(\n    name: \"Airship\",\n    defaultLocalization: \"en\",\n    platforms: [.iOS(.v16), .tvOS(.v18), .visionOS(.v1)],\n    products: [\n        .library(\n            name: \"AirshipCore\",\n            targets: [\"AirshipCore\"]\n        ),\n        .library(\n            name: \"AirshipAutomation\",\n            targets: [\"AirshipAutomation\"]\n        ),\n        .library(\n            name: \"AirshipMessageCenter\",\n            targets: [\"AirshipMessageCenter\"]\n        ),\n        .library(\n            name: \"AirshipNotificationServiceExtension\",\n            targets: [\"AirshipNotificationServiceExtension\"]\n        ),\n        .library(\n            name: \"AirshipPreferenceCenter\",\n            targets: [\"AirshipPreferenceCenter\"]\n        ),\n        .library(\n            name: \"AirshipFeatureFlags\",\n            targets: [\"AirshipFeatureFlags\"]\n        ),\n        .library(\n            name: \"AirshipObjectiveC\",\n            targets: [\"AirshipObjectiveC\"]\n        ),\n        .library(\n            name: \"AirshipDebug\",\n            targets: [\"AirshipDebug\"]\n        ),\n    ],\n    targets: [\n        .target(\n            name: \"AirshipBasement\",\n            path: \"Airship/AirshipBasement\",\n            exclude: [\n                \"Info.plist\",\n            ],\n            sources: [\"Source\"]\n        ),\n        .target(\n            name: \"AirshipCore\",\n            dependencies: [.target(name: \"AirshipBasement\")],\n            path: \"Airship/AirshipCore\",\n            exclude: [\n                \"Info.plist\",\n                \"Tests\",\n            ],\n            sources: [\"Source\"],\n            resources: [\n                .process(\"Resources\")\n            ]\n        ),\n        .target(\n            name: \"AirshipAutomation\",\n            dependencies: [.target(name: \"AirshipCore\")],\n            path: \"Airship/AirshipAutomation\",\n            exclude: [\n                \"Info.plist\",\n                \"Tests\"\n            ],\n            sources: [\"Source\"],\n            resources: [\n                .process(\"Resources\")\n            ]\n        ),\n        .target(\n            name: \"AirshipMessageCenter\",\n            dependencies: [.target(name: \"AirshipCore\")],\n            path: \"Airship/AirshipMessageCenter\",\n            exclude: [\n                \"Info.plist\",\n                \"Tests\"\n            ],\n            sources: [\"Source\"],\n            resources: [\n                .process(\"Resources\")\n            ]\n        ),\n        .target(\n            name: \"AirshipNotificationServiceExtension\",\n            path: \"AirshipExtensions/AirshipNotificationServiceExtension\",\n            exclude: [\n                \"Info.plist\",\n                \"Tests\"\n            ],\n            sources: [\"Source\"]\n        ),\n        .target(\n            name: \"AirshipPreferenceCenter\",\n            dependencies: [.target(name: \"AirshipCore\")],\n            path: \"Airship/AirshipPreferenceCenter\",\n            exclude: [\n                \"Info.plist\",\n                \"Tests\",\n            ],\n            sources: [\"Source\"]\n        ),\n        .target(\n            name: \"AirshipFeatureFlags\",\n            dependencies: [.target(name: \"AirshipCore\")],\n            path: \"Airship/AirshipFeatureFlags\",\n            exclude: [\n                \"Info.plist\",\n                \"Tests\",\n            ],\n            sources: [\"Source\"]\n        ),\n        .target(\n            name: \"AirshipObjectiveC\",\n            dependencies: [\n                .target(name: \"AirshipBasement\"),\n                .target(name: \"AirshipCore\"),\n                .target(name: \"AirshipPreferenceCenter\"),\n                .target(name: \"AirshipMessageCenter\"),\n                .target(name: \"AirshipAutomation\"),\n                .target(name: \"AirshipFeatureFlags\")\n            ],\n            path: \"Airship/AirshipObjectiveC\",\n            sources: [\"Source\"]\n        ),\n        .target(\n            name: \"AirshipDebug\",\n            dependencies: [\n                .target(name: \"AirshipCore\"),\n                .target(name: \"AirshipPreferenceCenter\"),\n                .target(name: \"AirshipMessageCenter\"),\n                .target(name: \"AirshipAutomation\"),\n                .target(name: \"AirshipFeatureFlags\")\n            ],\n            path: \"Airship/AirshipDebug\",\n            exclude: [\n                \"Info.plist\",\n            ],\n            sources: [\"Source\"],\n            resources: [\n                .process(\"Resources\")\n            ]\n        ),\n    ]\n)\n"
  },
  {
    "path": "README.md",
    "content": "# Airship SDK for Apple\n\n[![Swift Package Manager](https://img.shields.io/badge/SPM-supported-DE5C43.svg)](https://swift.org/package-manager/)\n[![CocoaPods](https://img.shields.io/cocoapods/v/Airship.svg)](https://cocoapods.org/pods/Airship)\n[![Carthage](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg)](https://github.com/Carthage/Carthage)\n[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)\n\nThe Airship SDK for Apple provides a comprehensive way to integrate Airship's customer experience platform into your iOS, tvOS, and visionOS applications.\n\n## Features\n- **Push Notifications** - Rich, interactive push notifications with deep linking\n- **Live Activities** - Real-time updates for iOS 16.1+ Dynamic Island and Lock Screen widgets\n- **In-App Experiences** - Contextual messaging and automation\n- **Message Center** - Inbox for push notifications and messages\n- **Preference Center** - User preference management\n- **Feature Flags** - Dynamic feature toggles and experimentation\n- **Analytics** - Comprehensive user behavior tracking\n- **Contacts** - User identification and contact management\n- **Tags, Attributes & Subscription Lists** - User segmentation, personalization, and subscription management\n- **Privacy Controls** - Granular data collection and feature management\n- **SwiftUI Support** - Modern SwiftUI components and views\n- **Swift 6** - Fully adopted Swift 6 with strict concurrency safety\n\n## Platform Support\n\n| Feature                                | iOS | tvOS          | visionOS |\n|----------------------------------------|-----|---------------|----------|\n| Push Notifications                     | ✅  | ✅             | ✅       |\n| Live Activities                        | ✅  | ❌             | ❌       |\n| In-App Experiences                     | ✅  | ✅<sup>1</sup> | ✅       |\n| Message Center                         | ✅  | ✅<sup>2</sup> | ✅       |\n| Preference Center                      | ✅  | ✅             | ✅       |\n| Feature Flags                          | ✅  | ✅             | ✅       |\n| Analytics                              | ✅  | ✅             | ✅       |\n| Contacts                               | ✅  | ✅             | ✅       |\n| Tags, Attributes & Subscription Lists  | ✅  | ✅             | ✅       |\n| Privacy Controls                       | ✅  | ✅             | ✅       |\n| SwiftUI Support                        | ✅  | ✅             | ✅       |\n\n<sup>1</sup> tvOS In-App Experiences: Scenes, Banners, and non-HTML In-App Automations are supported. However, scheduled In-App Experiences will no longer display if the app’s cache is wiped due to tvOS storage limitations.\n<sup>2</sup> tvOS Message Center: Supports Native Message Center.\n\n## Installation\n\nAdd the Airship SDK to your project using Swift Package Manager:\n\n```swift\ndependencies: [\n    .package(url: \"https://github.com/urbanairship/ios-library.git\", from: \"20.4.0\")\n]\n```\n\nIn Xcode, add the following products to your target dependencies:\n- `AirshipCore` (required)\n- `AirshipMessageCenter` (for Message Center)\n- `AirshipPreferenceCenter` (for Preference Center)\n- `AirshipAutomation` (for In-App Experiences, including Scenes, In-App Automation, and Landing Pages)\n- `AirshipFeatureFlags` (for Feature Flags)\n- `AirshipNotificationServiceExtension` (for rich push notifications)\n- `AirshipObjectiveC` (for Objective-C compatibility)\n- `AirshipDebug` (for debugging tools)\n\nFor other installation methods (CocoaPods, Carthage, xcframeworks), please see the [getting started guide](https://www.airship.com/docs/developer/sdk-integration/apple/installation/getting-started/).\n\n## Quick Start\n\n1. **Configure and Initialize Airship** in your `AppDelegate` or `App`:\n```swift\nimport AirshipCore\n\n// In AppDelegate\nfunc application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {\n    var config = AirshipConfig()\n    config.defaultAppKey = \"YOUR_APP_KEY\"\n    config.defaultAppSecret = \"YOUR_APP_SECRET\"\n    \n    try! Airship.takeOff(config)\n    return true\n}\n\n// Or in SwiftUI App\n@main\nstruct MyApp: App {\n    init() {\n        var config = AirshipConfig()\n        config.defaultAppKey = \"YOUR_APP_KEY\"\n        config.defaultAppSecret = \"YOUR_APP_SECRET\"\n        \n        try! Airship.takeOff(config)\n    }\n    \n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n    }\n}\n```\n\n> **Note**: `Airship.takeOff` should only be called once.\n\n2. **Enable & Request User Notifications**:\n```swift\nawait Airship.push.enableUserPushNotifications()\n```\n\n## Requirements\n\n- iOS 16.0+\n- tvOS 18.0+\n- visionOS 1.0+\n- Xcode 26.0+\n\n## Documentation\n\n- **[Getting Started](https://docs.airship.com/platform/mobile/setup/sdk/ios/)** - Complete setup guide\n- **[API Reference](https://urbanairship.github.io/ios-library/)** - Full API documentation\n- **[Migration Guides](Documentation/Migration/README.md)** - Comprehensive migration documentation\n- **[Sample Apps](https://github.com/urbanairship/apple-sample-apps)** - Example implementations\n\n## Support\n\n- 📚 [Documentation](https://docs.airship.com/)\n- 🐛 [Report Issues](https://github.com/urbanairship/ios-library/issues)\n\n## License\n\nThis project is licensed under the Apache License 2.0 - see the [LICENSE](LICENSE) file for details.\n"
  },
  {
    "path": "scripts/airship_version.sh",
    "content": "#!/bin/bash\n\nset -o pipefail\nset -e\n\nROOT_PATH=`dirname \"${0}\"`/..\necho $(awk <\"$ROOT_PATH/Airship/AirshipConfig.xcconfig\" \"\\$1 == \\\"CURRENT_PROJECT_VERSION\\\" { print \\$3 }\")\n"
  },
  {
    "path": "scripts/build_docCs.sh",
    "content": "#!/bin/bash\n# build_docs.sh CURRENT_VERSION\n#  - CURRENT_VERSION: The SDK current version.\n# Adaptive DocC build script that works for both private and public repos\n\nset -o pipefail\nset -e\n\nCURRENT_VERSION=\"$1\"\n\n# 🔧 CONFIG\nSCHEMES=(\n  \"AirshipCore\"\n  \"AirshipPreferenceCenter\"\n  \"AirshipMessageCenter\"\n  \"AirshipAutomation\"\n  \"AirshipFeatureFlags\"\n  \"AirshipObjectiveC\"\n  \"AirshipNotificationServiceExtension\"\n)\n\nBUILD=\"build\"\nDOCS_DIR=\"docs\"\n\n# 🔍 Detect repository context\nif [ -n \"$GITHUB_REPOSITORY\" ]; then\n    # Running in GitHub Actions\n    REPO_NAME=$(basename \"$GITHUB_REPOSITORY\")\nelse\n    # Running locally - try to detect from git remote\n    REPO_URL=$(git remote get-url origin 2>/dev/null || echo \"\")\n    if [[ \"$REPO_URL\" == *\"ios-library.git\"* ]] || [[ \"$REPO_URL\" == *\"ios-library\"* ]]; then\n        REPO_NAME=\"ios-library\"\n    else\n        REPO_NAME=\"ios-library-dev\"\n    fi\nfi\n\necho \"📘 Building DocC for repository: $REPO_NAME\"\necho \"📘 Version: $CURRENT_VERSION\"\n\n# 🧼 Clean up\nrm -rf $BUILD\nrm -rf $DOCS_DIR\nmkdir -p \"$DOCS_DIR\"\n\n# 📘 Generate DocC for each scheme\necho \"📘 Building DocC for schemes: ${SCHEMES[*]}\"\n\nfor SCHEME in \"${SCHEMES[@]}\"; do\n    echo \"📘 Building DocC for $SCHEME ...\"\n    \n    DERIVED_DATA=\"$BUILD/$SCHEME\"\n    xcodebuild docbuild \\\n        -scheme \"$SCHEME\" \\\n        -destination 'platform=macOS' \\\n        -derivedDataPath \"$DERIVED_DATA\"\n    \n    ARCHIVE_PATH=$(find \"$DERIVED_DATA\" -name \"$SCHEME.doccarchive\" | head -n 1)\n        \n    if [ -z \"$ARCHIVE_PATH\" ]; then\n        echo \"❌ No doccarchive for $SCHEME in $CURRENT_VERSION\"\n        exit 1\n    fi\n        \n    OUTPUT_PATH=\"$DOCS_DIR/$SCHEME\"\n    mkdir -p \"$OUTPUT_PATH\"\n    \n    # 🔧 Set hosting base path based on repository\n    if [ \"$REPO_NAME\" = \"ios-library\" ]; then\n        HOSTING_BASE_PATH=\"/ios-library/$CURRENT_VERSION/$SCHEME\"\n    else\n        HOSTING_BASE_PATH=\"/$CURRENT_VERSION/$SCHEME\"\n    fi\n    \n    echo \"📘 Using hosting base path: $HOSTING_BASE_PATH\"\n    \n    $(xcrun --find docc) process-archive \\\n        transform-for-static-hosting \\\n        \"$ARCHIVE_PATH\" \\\n        --output-path \"$OUTPUT_PATH\" \\\n        --hosting-base-path \"$HOSTING_BASE_PATH\"\n    \n    echo \"✅ $SCHEME docs ready at $OUTPUT_PATH\"\ndone\n\necho \"🎉 Docs generated for $REPO_NAME\"\n"
  },
  {
    "path": "scripts/build_sample.sh",
    "content": "#!/bin/bash\n\nset -o pipefail\nset -e\n\nROOT_PATH=`dirname \"${0}\"`/..\n\nSAMPLE=\"$1\"\nDERIVED_DATA=\"$2\"\n\nif [ $SAMPLE == \"tvOSSample\" ]\nthen\n  TARGET_SDK='appletvsimulator'\nelse\n  TARGET_SDK='iphonesimulator'\nfi\n\necho -ne \"\\n\\n *********** BUILDING SAMPLE $SAMPLE *********** \\n\\n\"\n\n# Make sure AirshipConfig.plist exists\ncp -np \"${ROOT_PATH}/$SAMPLE/AirshipConfig.plist.sample\" \"${ROOT_PATH}/$SAMPLE/AirshipConfig.plist\" || true\n\n# Use Debug configurations and a simulator SDK so the build process doesn't attempt to sign the output\nxcrun xcodebuild \\\n-configuration Debug \\\n-workspace \"${ROOT_PATH}/Airship.xcworkspace\" \\\n-scheme \"${SAMPLE}\" \\\n-sdk $TARGET_SDK \\\n-derivedDataPath \"$DERIVED_DATA\" | xcbeautify --renderer $XCBEAUTIFY_RENDERER\n"
  },
  {
    "path": "scripts/build_sample_watchos.sh",
    "content": "#!/bin/bash\n\nset -o pipefail\nset -e\n\nROOT_PATH=`dirname \"${0}\"`/..\n\nSAMPLE=\"$1\"\nDERIVED_DATA=\"$2\"\n\necho -ne \"\\n\\n *********** BUILDING SAMPLE $SAMPLE *********** \\n\\n\"\n\n# Make sure AirshipConfig.plist exists\ncp -np \"${ROOT_PATH}/$SAMPLE/AirshipConfig.plist.sample\" \"${ROOT_PATH}/$SAMPLE/AirshipConfig.plist\" || true\n\n# Use Debug configurations and a simulator SDK so the build process doesn't attempt to sign the output\nxcrun xcodebuild \\\n-configuration Debug \\\n-workspace \"${ROOT_PATH}/Airship.xcworkspace\" \\\n-scheme $SAMPLE \\\n-derivedDataPath \"$DERIVED_DATA\" | xcbeautify --renderer $XCBEAUTIFY_RENDERER\n"
  },
  {
    "path": "scripts/build_xcframeworks.sh",
    "content": "#!/bin/bash\n# build_xcframeworks.sh OUTPUT DERIVED_DATA_PATH ARCHIVE_PATH [SKIP_SIGNING]\n#  - OUTPUT: The output directory.\n#  - DERIVED_DATA_PATH: The derived data path\n#  - ARCHIVE_PATH: The archive path\n#  - SKIP_SIGNING: Optional. Set to \"true\" to skip code signing (useful for size checks)\n\nset -o pipefail\nset -ex\n\n\nROOT_PATH=`dirname \"${0}\"`/..\nOUTPUT=\"$1\"\nDERIVED_DATA=\"$2\"\nARCHIVE_PATH=\"$3\"\nSKIP_SIGNING=\"$4\"\nFULL_ARCHIVE_PATH=\"$(pwd -P)/$ARCHIVE_PATH\"\n\nOUTPUT_FULL=\"$OUTPUT/full\"\nOUTPUT_DOTNET=\"$OUTPUT/dotnet\"\n\nmkdir -p \"$OUTPUT_FULL\"\nmkdir -p \"$OUTPUT_DOTNET\"\n\n##################\n# Build Frameworks\n##################\n\nfunction build_archive {\n  # $1 Project\n  # $2 iOS or tvOS\n\n  local scheme=$1\n  local sdk=\"\"\n  local simulatorSdk=\"\"\n  local destination=\"\"\n  local simulatorDestination=\"\"\n\n  if [ $2 == \"iOS\" ]\n  then\n    sdk=\"iphoneos\"\n    destination=\"generic/platform=iOS\"\n    simulatorSdk=\"iphonesimulator\"\n    simulatorDestination=\"generic/platform=iOS Simulator\"\n  elif [ $2 == \"visionOS\" ]\n  then\n    sdk=\"xros\"\n    destination=\"generic/platform=visionOS\"\n    simulatorSdk=\"xrsimulator\"\n    simulatorDestination=\"generic/platform=visionOS Simulator\"\n  elif [ $2 == \"maccatalyst\" ]\n  then\n    destination=\"generic/platform=macOS,variant=Mac Catalyst,name=Any Mac\"\n  else\n    sdk=\"appletvos\"\n    simulatorSdk=\"appletvsimulator\"\n    destination=\"generic/platform=tvOS\"\n    simulatorDestination=\"generic/platform=tvOS Simulator\"\n  fi\n\n  if [ $2 == \"maccatalyst\" ]\n  then\n    xcrun xcodebuild archive -quiet \\\n    -workspace \"$ROOT_PATH/Airship.xcworkspace\" \\\n    -scheme \"$scheme\" \\\n    -destination \"$destination\" \\\n    -archivePath \"$ARCHIVE_PATH/xcarchive/$scheme/mac.xcarchive\" \\\n    -derivedDataPath \"$DERIVED_DATA\" \\\n    SKIP_INSTALL=NO \\\n    BUILD_LIBRARIES_FOR_DISTRIBUTION=YES | xcbeautify --renderer $XCBEAUTIFY_RENDERER\n  else\n    xcrun xcodebuild archive -quiet \\\n    -workspace \"$ROOT_PATH/Airship.xcworkspace\" \\\n    -scheme \"$scheme\" \\\n    -sdk \"$sdk\" \\\n    -destination \"$destination\" \\\n    -archivePath \"$ARCHIVE_PATH/xcarchive/$scheme/$sdk.xcarchive\" \\\n    -derivedDataPath \"$DERIVED_DATA\" \\\n    SKIP_INSTALL=NO \\\n    BUILD_LIBRARIES_FOR_DISTRIBUTION=YES\n\n    xcrun xcodebuild archive -quiet \\\n    -workspace \"$ROOT_PATH/Airship.xcworkspace\" \\\n    -scheme \"$scheme\" \\\n    -sdk \"$simulatorSdk\" \\\n    -destination \"$simulatorDestination\" \\\n    -archivePath \"$ARCHIVE_PATH/xcarchive/$scheme/$simulatorSdk.xcarchive\" \\\n    -derivedDataPath \"$DERIVED_DATA\" \\\n    SKIP_INSTALL=NO \\\n    BUILD_LIBRARIES_FOR_DISTRIBUTION=YES | xcbeautify --renderer $XCBEAUTIFY_RENDERER\n  fi\n}\n\necho -ne \"\\n\\n *********** BUILDING XCFRAMEWORKS *********** \\n\\n\"\n\n# tvOS\nbuild_archive \"AirshipRelease tvOS\" \"tvOS\"\n\n# iOS\nbuild_archive \"AirshipRelease\" \"iOS\"\nbuild_archive \"AirshipNotificationServiceExtension\" \"iOS\"\n\n# Catalyst\nbuild_archive \"AirshipRelease\" \"maccatalyst\"\nbuild_archive \"AirshipNotificationServiceExtension\" \"maccatalyst\"\n\n# visionOS\nbuild_archive \"AirshipRelease\" \"visionOS\"\n\n# Package AirshipBasement\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/dSYMs/AirshipBasement.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipBasement.xcframework\"\n\n# Package AirshipCore\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/dSYMs/AirshipCore.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipCore.xcframework\"\n\n\n  # Package AirshipAutomation\nxcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipAutomation.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipAutomation.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipAutomation.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipAutomation.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipAutomation.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipAutomation.xcframework\"\n\n# Package AirshipMessageCenter\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipMessageCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipMessageCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipMessageCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipMessageCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipMessageCenter.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipMessageCenter.xcframework\"\n\n# Package AirshipPreferenceCenter\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/dSYMs/AirshipPreferenceCenter.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipPreferenceCenter.xcframework\"\n\n# Package AirshipFeatureFlags\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvos.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease tvOS/appletvsimulator.xcarchive/dSYMs/AirshipFeatureFlags.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipFeatureFlags.xcframework\"\n\n# Package AirshipObjectiveC\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipObjectiveC.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipObjectiveC.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipObjectiveC.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipObjectiveC.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipObjectiveC.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipObjectiveC.xcframework\"\n\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/dSYMs/AirshipDebug.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/dSYMs/AirshipDebug.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xros.xcarchive/dSYMs/AirshipDebug.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/xrsimulator.xcarchive/dSYMs/AirshipDebug.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/dSYMs/AirshipDebug.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipDebug.xcframework\"\n  \n# Package AirshipNotificationServiceExtension\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/iphoneos.xcarchive/Products/Library/Frameworks/AirshipNotificationServiceExtension.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/iphoneos.xcarchive/dSYMs/AirshipNotificationServiceExtension.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/mac.xcarchive/Products/Library/Frameworks/AirshipNotificationServiceExtension.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/mac.xcarchive/dSYMs/AirshipNotificationServiceExtension.framework.dSYM\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipNotificationServiceExtension.framework\" \\\n  -debug-symbols \"$FULL_ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/iphonesimulator.xcarchive/dSYMs/AirshipNotificationServiceExtension.framework.dSYM\" \\\n  -output \"$OUTPUT_FULL/AirshipNotificationServiceExtension.xcframework\"\n\n############################\n# Package .NET frameworks\n############################\n\n# .NET does not support tvOS or visionOS\n\n# Package AirshipBasement (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipBasement.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipBasement.xcframework\"\n\n# Package AirshipCore (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipCore.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipCore.xcframework\"\n\n# Package AirshipAutomation (.NET)\nxcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipAutomation.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipAutomation.xcframework\"\n\n# Package AirshipMessageCenter (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipMessageCenter.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipMessageCenter.xcframework\"\n\n# Package AirshipPreferenceCenter (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipPreferenceCenter.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipPreferenceCenter.xcframework\"\n\n# Package AirshipFeatureFlags (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipFeatureFlags.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipFeatureFlags.xcframework\"\n\n# Package AirshipObjectiveC (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipObjectiveC.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipObjectiveC.xcframework\"\n\n# Package AirshipDebug (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphoneos.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipRelease/mac.xcarchive/Products/Library/Frameworks/AirshipDebug.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipDebug.xcframework\"\n\n# Package AirshipNotificationServiceExtension (.NET)\nxcrun xcodebuild -create-xcframework \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/iphoneos.xcarchive/Products/Library/Frameworks/AirshipNotificationServiceExtension.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/iphonesimulator.xcarchive/Products/Library/Frameworks/AirshipNotificationServiceExtension.framework\" \\\n  -framework \"$ARCHIVE_PATH/xcarchive/AirshipNotificationServiceExtension/mac.xcarchive/Products/Library/Frameworks/AirshipNotificationServiceExtension.framework\" \\\n  -output \"$OUTPUT_DOTNET/AirshipNotificationServiceExtension.xcframework\"\n\n\n# Sign the frameworks (unless SKIP_SIGNING is set to \"true\")\nif [ \"$SKIP_SIGNING\" != \"true\" ]; then\n  echo \"Signing frameworks...\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipBasement.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipCore.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipMessageCenter.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipPreferenceCenter.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipNotificationServiceExtension.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipFeatureFlags.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipAutomation.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipObjectiveC.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_FULL/AirshipDebug.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipBasement.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipCore.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipMessageCenter.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipPreferenceCenter.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipNotificationServiceExtension.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipFeatureFlags.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipAutomation.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipObjectiveC.xcframework\"\n  codesign --timestamp -v --sign \"Apple Distribution: Urban Airship Inc. (PGJV57GD94)\" \"$OUTPUT_DOTNET/AirshipDebug.xcframework\"\nelse\n  echo \"Skipping code signing as requested...\"\nfi\n"
  },
  {
    "path": "scripts/check_size.sh",
    "content": "#!/bin/bash\nset -e\nXCFRAMEWORK_PATH=$1\nFILE_SIZE=$2\nPREVIOUS_FILE_SIZE=$3\n\necho \"Checking all XCFrameworks in: $XCFRAMEWORK_PATH\"\necho \"---------------------------------------------\"\n# Ensure required arguments are passed\nif [ -z \"$XCFRAMEWORK_PATH\" ] || [ -z \"$FILE_SIZE\" ] || [ -z \"$PREVIOUS_FILE_SIZE\" ]; then\n  echo \"Usage: $0 <xcframeworks_folder> <current_size_file> <previous_size_file>\"\n  exit 1\nfi\n#  Use a temporary file for safe writing\nTEMP_FILE=$(mktemp /tmp/xcframework_sizes.XXXXXX)\n# Collect current sizes for all frameworks\nfound_any=false\necho \"Current XCFramework sizes:\"\nfor f in \"$XCFRAMEWORK_PATH\"/*.xcframework; do\n  if [ -d \"$f\" ]; then\n    found_any=true\n    size_kb=$(du -sk \"$f\" | cut -f1)\n    name=$(basename \"$f\")\n    echo \"$name=$size_kb\" >> \"$TEMP_FILE\"\n    echo \"  $name → ${size_kb} KB\"\n  fi\ndone\nif [ \"$found_any\" = false ]; then\n  echo \"No .xcframework files found in $XCFRAMEWORK_PATH\"\n  rm -f \"$TEMP_FILE\"\n  exit 1\nfi\n# Compare with previous sizes\nif [ -f \"$PREVIOUS_FILE_SIZE\" ] && [ -s \"$PREVIOUS_FILE_SIZE\" ]; then\n  echo \"\"\n  echo \" Comparing with previous sizes:\"\n  echo \"---------------------------------------------\"\n  # Loop through current sizes\n  while IFS=\"=\" read -r name size; do\n    # Look up previous size by grep\n    if prev=$(grep \"^$name=\" \"$PREVIOUS_FILE_SIZE\" 2>/dev/null | cut -d= -f2); then\n      if [ -z \"$prev\" ]; then\n        echo \"$name is new (${size} KB)\"\n      else\n        diff=$((size - prev))\n        if [ $diff -gt 0 ]; then\n          echo \"$name grew by ${diff} KB (was ${prev} KB)\"\n        elif [ $diff -lt 0 ]; then\n          echo \"$name shrank by $((-diff)) KB (was ${prev} KB)\"\n        else\n          echo \"$name unchanged (${size} KB)\"\n        fi\n      fi\n    else\n      echo \"   $name is new (${size} KB)\"\n    fi\n  done < \"$TEMP_FILE\"\nelse\n  echo \"\"\n  echo \"No previous size file found — creating baseline.\"\nfi\n# Safely move temp file to target FILE_SIZE\nmv \"$TEMP_FILE\" \"$FILE_SIZE\" 2>/dev/null || {\n  echo \"Could not overwrite $FILE_SIZE directly, trying sudo...\"\n  sudo mv \"$TEMP_FILE\" \"$FILE_SIZE\"\n}\n#  Update previous size file\ncp \"$FILE_SIZE\" \"$PREVIOUS_FILE_SIZE\" 2>/dev/null || {\n  echo \"Could not update $PREVIOUS_FILE_SIZE, trying sudo...\"\n  sudo cp \"$FILE_SIZE\" \"$PREVIOUS_FILE_SIZE\"\n}\necho \"\"\necho \"Size check complete. Baseline updated: $PREVIOUS_FILE_SIZE\"\n"
  },
  {
    "path": "scripts/check_version.sh",
    "content": "#!/bin/bash\nset -e\nset -x\n\nROOT_PATH=`dirname \"${0}\"`/..\nAIRSHIP_VERSION=$(bash \"$ROOT_PATH/scripts/airship_version.sh\")\n\nif [ $1 = $AIRSHIP_VERSION ]; then\n exit 0\nelse\n exit 1\nfi\n"
  },
  {
    "path": "scripts/check_xcbeautify.sh",
    "content": "#!/bin/bash\n\nset -o pipefail\nset -e\n\nif ! which xcbeautify > /dev/null; then\necho \"Missing xcbeautify!\"\nexit 1\nfi"
  },
  {
    "path": "scripts/gemini-review-prompt.md",
    "content": "# Code Review Prompt\n\n## Your Role\nYou review code like your life depends on it. Your main goal is to offer helpful quick GitHub suggestions for:\n1. **Spelling errors** in ANY file type - CHECK EVERY WORD including:\n   - Comments (// comments, /* */ comments, # comments)\n   - String literals\n   - Variable names, function names, class names\n   - Documentation\n   - ANY text in the diff\n2. **Style violations** in Swift files following the Airship iOS Swift Style Guide below\n\nThe suggestions should be easy to implement using GitHub suggestion syntax. Check ALL files in the diff, not just Swift files. Even small typos like \"Promp\" instead of \"Prompt\" must be caught.\n\n### EXACT Format (DO NOT DEVIATE):\n```\nFile: <exact file path from diff>\nLine: <exact line number from diff>\nComment: <one-line explanation>\n```suggestion\n<exact replacement code that is syntactically valid>\n```\n\n**CRITICAL SUGGESTION RULES**:\n1. The suggestion block must contain ONLY the single line being replaced\n2. NEVER include multiple lines in a suggestion\n3. NEVER include partial lines or fragments\n4. The suggestion must be the COMPLETE line including all indentation\n5. If fixing a comment, include the ENTIRE comment line\n6. NEVER remove or add braces, brackets, or parentheses unless they are part of the single line being fixed\n\n**EXAMPLE - CORRECT**:\nIf line 28 is: `    // ─── Promp ───────────────────────────────────────────`\nThe suggestion should be: `    // ─── Prompt ───────────────────────────────────────────`\n\n**EXAMPLE - WRONG**:\nNever suggest partial replacements like just `Prompt` or include multiple lines like:\n```\n    }\n    // ─── Prompt ───────────────────────────────────────────\n```\n\n**RESPONSE LIMITS**: Maximum 10 suggestions. Be concise.\n\n**OUTPUT FORMAT**: Respond with either:\n1. `LGTM 🤖👍` if no issues found\n2. Or suggestions using the format below:\n\n**WARNING**: The code in the suggestion block MUST be valid, compilable code. Never suggest partial code or code with syntax errors.\n\n**BEFORE MAKING ANY SUGGESTION**:\n1. Mentally trace through the code execution\n2. Verify all referenced variables/functions exist at that point\n3. Ensure the suggestion doesn't break the surrounding code\n4. Test that the file would still compile/run after applying your change\n5. If there's doubt, do not make the suggestion\n\n### Important Rules:\n- The suggestion block must contain EXACTLY ONE LINE - the complete line being replaced\n- Include ALL indentation/spacing from the beginning of the line\n- NEVER suggest multi-line replacements\n- NEVER suggest partial line replacements\n- The line number in \"Line: X\" must match exactly one line in the diff\n\n## Example Output\n\n### Code Suggestions\n\nFile: Views/ProfileView.swift\nFile: Views/ProfileView.swift\nLine: 42\nComment: Add [weak self] capture to prevent retain cycle in async closure\n```suggestion\nTask { [weak self] in\n    await self?.updateUser()\n}\n```\n\n---\n\nFile: ViewModels/DataManager.swift  \nLine: 78\nComment: Mark method as @MainActor since it updates @Published UI state\n```suggestion\n@MainActor\nfunc updateLoadingState() async {\n    self.isLoading = false\n}\n```\n\n---\n\nFile: Models/UserSession.swift\nLine: 34\nComment: UserSession contains mutable reference types - needs @unchecked Sendable with synchronization\n```suggestion\nfinal class UserSession: Codable, @unchecked Sendable {\n    private let lock = NSLock()\n```\n\n---\n\nFile: ViewModels/DataManager.swift\nLine: 23\nComment: Potential race condition - async sequence should be created once and stored\n```suggestion\nprivate let updateStream = AsyncStream<Update>.makeStream()\nfunc observeUpdates() -> AsyncStream<Update> {\n    return updateStream.stream\n}\n```\n\n---\n\nFile: Services/APIClient.swift\nLine: 56  \nComment: URLSession task not retained - will be deallocated immediately causing request to fail\n```suggestion\nlet task = session.dataTask(with: request) { data, response, error in\n    // handle response\n}\ntask.resume()\nreturn task\n```\n\n---\n\nFile: Views/ListView.swift\nLine: 89\nComment: ForEach with index binding causes O(n) lookups - use enumerated() for better performance\n```suggestion\nForEach(Array(items.enumerated()), id: \\.element.id) { index, item in\n    ItemView(item: $items[index])\n}\n```\n\n## Airship iOS Swift Style Guide\n\n### File Structure\n```swift\n/* Copyright Airship and Contributors */\n\n// 1. Imports (grouped and ordered)\nimport Foundation\n#if canImport(UIKit)\nimport UIKit\n#endif\n\n// 2. Type documentation\n/// Main type description\npublic final class ClassName: Protocol, @unchecked Sendable {\n    // 3. Static properties\n    private static let constantName = \"value\"\n    \n    // 4. Instance properties (ordered by: visibility then mutability)\n    private let immutableProperty: Type\n    private var mutableProperty: Type\n    public private(set) var readOnlyProperty: Type\n    \n    // 5. Computed properties\n    public var computedProperty: Type {\n        return someValue\n    }\n    \n    // 6. Initializers\n    // 7. Instance methods\n    // 8. Static methods\n}\n\n// 9. Extensions (one per protocol for complex conformances)\nextension ClassName: ProtocolName { }\n```\n\n### Formatting Rules\n- **Indentation**: 4 spaces (never tabs)\n- **Line length**: Maximum 100-120 characters\n- **Braces**: Opening brace on same line, closing brace on new line\n- **Spacing**: \n  - Spaces around operators: `x + y`, `a = b`\n  - No space before colon in type declarations: `let name: String`\n  - Space after colon: `name: String`\n  - One blank line between methods\n  - Two blank lines between major sections\n\n### Function Style\n```swift\n// Multi-line parameters aligned with opening parenthesis\n@MainActor\ninit(\n    dataStore: PreferenceDataStore,\n    config: RuntimeConfig,\n    privacyManager: any PrivacyManagerProtocol\n) {\n    self.dataStore = dataStore\n    self.config = config\n    self.privacyManager = privacyManager\n}\n\n// Function calls align continuations with first parameter\nlet channel = ChannelRegistrar(\n    config: config,\n    dataStore: dataStore,\n    privacyManager: privacyManager\n)\n\n// Chained methods each on new line\npublisher\n    .compactMap { $0 }\n    .removeDuplicates()\n    .sink { value in\n        // Handle value\n    }\n```\n\n### Naming Conventions\n- **Types**: `PascalCase` (e.g., `ChannelManager`, `ContactAPIClient`)\n- **Protocols**: Often end with `Protocol` suffix or describe capability\n- **Properties/Methods**: `camelCase` (e.g., `channelID`, `enableChannelCreation()`)\n- **Constants**: `camelCase` for instance, descriptive names for static\n- **No abbreviations**: Use `identifier` not `id`, `configuration` not `config` (unless established pattern)\n- **Boolean properties**: Use `is`, `has`, `should` prefixes (e.g., `isEnabled`, `hasChanges`)\n\n### Access Control\n- **Always explicit** for all top-level declarations\n- **Order declarations**: public → internal → private\n- **Use `private(set)`** for read-only public properties\n- **Use `fileprivate`** sparingly, only when needed across extensions in same file\n\n### Property Patterns\n```swift\n// Thread-safe wrappers for Sendable conformance\nprivate let lock = AirshipLock()\nprivate let wrapper: AirshipUnsafeSendableWrapper<Type>\n\n// Lazy initialization for expensive objects\nprivate lazy var expensiveObject: Type = {\n    return Type()\n}()\n\n// Property observers on single line when simple\nvar property: Type { didSet { update() } }\n```\n\n### Error Handling\n```swift\n// Guard for early returns\nguard let value = optionalValue else {\n    AirshipLogger.warn(\"Missing required value\")\n    return\n}\n\n// Throwing functions\nfunc performOperation() async throws -> Result {\n    guard isValid else {\n        throw AirshipErrors.error(\"Invalid state\")\n    }\n    return result\n}\n```\n\n### Async/Await Patterns\n```swift\n// Async properties\npublic var updates: AsyncStream<Update> {\n    return channel.makeStream()\n}\n\n// Task creation with weak self\nTask { [weak self] in\n    guard let self else { return }\n    await self.performWork()\n}\n\n// MainActor isolation\n@MainActor\npublic func updateUI() {\n    // UI updates\n}\n```\n\n### Protocol Conformance\n```swift\n// Separate extension per protocol for complex conformances\nextension Type: Equatable {\n    static func == (lhs: Type, rhs: Type) -> Bool {\n        return lhs.id == rhs.id\n    }\n}\n\n// Conditional conformance\nextension Type: Codable where T: Codable {\n    // Implementation\n}\n```\n\n### Documentation\n```swift\n/// Brief description of the type or method.\n/// \n/// Detailed explanation if needed.\n///\n/// - Parameters:\n///   - parameter1: Description of first parameter\n///   - parameter2: Description of second parameter\n/// - Returns: Description of return value\n/// - Throws: Description of errors thrown\npublic func documentedMethod(parameter1: Type, parameter2: Type) throws -> ReturnType {\n    // Implementation\n}\n\n// MARK: - Section Headers\n// Use MARK comments to organize code sections\n\n// Inline comments for clarification\nlet result = complexCalculation() // Explain why if not obvious\n```\n\n### Common Patterns\n- **Avoid force unwraps**: Use `guard`, `if let`, or `??` instead\n- **Prefer `final class`** unless subclassing is explicitly needed\n- **Use `@unchecked Sendable`** with proper synchronization for reference types\n- **Platform-specific code**: Use `#if canImport()` for conditional compilation\n- **Notification names**: Nest in type-specific extensions\n- **Static factory methods**: Use `` `default` `` syntax when needed\n- **Type inference**: Omit type when obvious, include when clarifies intent\n\n### Switch Statements\n```swift\nswitch value {\ncase .case1(let associated):\n    handleCase1(associated)\ncase .case2:\n    handleCase2()\ndefault:\n    handleDefault()\n}\n```\n\n### Closure Style\n```swift\n// Trailing closure for last parameter\nitems.map { item in\n    return item.transformed\n}\n\n// Explicit closure parameters for clarity in complex cases\nitems.reduce(into: [:]) { (result: inout [String: Int], item: Item) in\n    result[item.key] = item.value\n}\n\n// Capture lists\n{ [weak self, strong dependency] in\n    guard let self else { return }\n    // Use self and dependency\n}\n```"
  },
  {
    "path": "scripts/get_xcode_path.sh",
    "content": "#!/bin/bash\n# get_xcode_path.sh ARG\n#  - ARG: The version number or path\n\nset -o pipefail\nset -e\n\nROOT_PATH=`dirname \"${0}\"`/..\n\nXCODE_APPS_FINDER=$(mdfind \"kMDItemCFBundleIdentifier == 'com.apple.dt.Xcode'\")\nXCODE_APPS_FALLBACK=$(find /Applications -iname 'Xcode*.app' -maxdepth 1)\nXCODE_APPS=$(echo -e \"$XCODE_APPS_FINDER\\n$XCODE_APPS_FALLBACK\" | sort | uniq)\nPLIST_BUDDY=\"/usr/libexec/PlistBuddy\"\nXCODE_ARG=$1\n\nfunction get_plist_value() {\n  \"$PLIST_BUDDY\" -c \"Print :$2\" \"$1/Contents/Info.plist\"\n}\n\nfunction get_version() {\n  APP_NAME=$(get_plist_value \"$1\" \"CFBundleName\")\n  if [[ \"$APP_NAME\" == \"Xcode\" ]]; then\n    echo $(get_plist_value \"$1\" \"CFBundleShortVersionString\")\n  else\n    echo \"\"\n  fi\n}\n\nif [ -d \"$XCODE_ARG\" ]\nthen\n  echo $2\n  exit 0\nfi\n\nfor APP in $XCODE_APPS; do\n  APP_VERSION=$(get_version $APP)\n  if [ $XCODE_ARG = $APP_VERSION ]; then\n    echo $APP\n    exit 0\n  fi\ndone\n\necho \"Failed to find $XCODE_ARG. Available versions: \" 1>&2\n\nfor APP in $XCODE_APPS; do\n  APP_VERSION=$(get_version $APP)\n  echo \"$APP_VERSION: $APP\" 1>&2\ndone\n\nexit 1"
  },
  {
    "path": "scripts/package.sh",
    "content": "#!/bin/bash\n# build_docs.sh OUTPUT [PATHS...]\n#  - OUTPUT: The output zip.\n#  - PATHS: A list of directories or files to be included in the zip\n\n\nset -o pipefail\nset -e\n\n\nZIP=\"$(cd \"$(dirname \"$1\")\" && pwd)/$(basename \"$1\")\"\n\npackage() {\n  if [ -d \"$1\" ]\n  then\n    pushd \"${1}/..\"\n    zip -r --symlinks \"${ZIP}\" \"./$(basename $1)\"\n    popd\n  else\n    if [ -f \"$1\" ]\n    then\n      echo \"file: $1\"\n      zip -j \"${ZIP}\" \"$1\"\n    else\n      for file in $1\n      do\n        package \"$file\"\n      done\n    fi\n  fi\n}\n\nBUILD_INFO=$(mktemp -d /tmp/build-XXXXX)/BUILD_INFO\necho \"Airship SDK v${AIRSHIP_VERSION}\" >> ${BUILD_INFO}\necho \"Build time: `date`\" >> ${BUILD_INFO}\necho \"SDK commit: `git log -n 1 --format='%h'`\" >> ${BUILD_INFO}\necho \"Xcode version: $(xcrun xcodebuild -version | tr '\\r\\n' ' ')\" >> ${BUILD_INFO}\n\npackage $BUILD_INFO\n\nfor var in \"${@:2}\"\ndo\n  package \"$var\"\ndone"
  },
  {
    "path": "scripts/package_xcframeworks.sh",
    "content": "#!/bin/bash\n# package_xcframeworks.sh OUTPUT INPUT_DIR [ZIP_ROOT]\n#  - OUTPUT: The output zip.\n#  - INPUT_DIR: A path to the xcframeworks directory to package\n#  - ZIP_ROOT: Optional root folder inside the zip (default: xcframeworks)\n\nset -o pipefail\nset -e\n\nZIP_ROOT=\"${3:-xcframeworks}\"\nTEMP=\"$(mktemp -d)\"\nmkdir -p \"$TEMP/$ZIP_ROOT\"\n\ncp -R \"$2\" \"$TEMP/$ZIP_ROOT\"\ncd \"$TEMP\"\n\nzip -r --symlinks xcframeworks.zip \"${ZIP_ROOT%%/}\" -x \"*.DS_Store\"\ncd -\n\ncp \"$TEMP/xcframeworks.zip\" \"$1\"\n"
  },
  {
    "path": "scripts/pr-review.mjs",
    "content": "// Node ≥20 ESM – no external dependencies\nimport fs from 'fs/promises';\nimport path from 'path';\nimport process from 'process';\n\nconst MAX_DIFF_BYTES = 400_000;                         // ~100 k tokens\nconst MODEL_ID_FROM_ENV = process.env.MODEL_ID;\nconst MODEL_ID = MODEL_ID_FROM_ENV || 'gemini-pro';\nif (!MODEL_ID_FROM_ENV) {\n    warn(`MODEL_ID environment variable not set, falling back to default: ${MODEL_ID}. Ensure this is intended and configured in the workflow.`);\n}\nconst DEBUG = process.env.DEBUG === 'true';\nconst root = process.cwd();\nconst log = (m) => console.log(`🪄 ${m}`);\nconst warn = (m) => console.warn(`⚠️  ${m}`);\nconst die = (m) => { console.error(`💥 ${m}`); process.exit(1); };\n\n(async () => {\n    // ─── Diff ─────────────────────────────────────────────\n    const diff = await fs.readFile(path.join(root, 'diff.patch'), 'utf8');\n    log(`Diff loaded (${(diff.length / 1024).toFixed(1)} KB)`);\n\n    if (diff.length > MAX_DIFF_BYTES) {\n        warn(`Diff > ${(MAX_DIFF_BYTES / 1024)} KB ⇒ skipping AI review`);\n        return;\n    }\n    \n    // ─── Prompt ───────────────────────────────────────────\n    const promptPath = path.join(root, 'scripts', 'gemini-review-prompt.md');\n    const prompt = await fs.readFile(promptPath, 'utf8');\n\n    // ─── Validate API key ───────────────────────────────────\n    if (!process.env.GEMINI_API_KEY) {\n        die('GEMINI_API_KEY environment variable is not set');\n    }\n\n    // ─── Gemini call ─────────────────────────────────────\n    const endpoint = `https://generativelanguage.googleapis.com/v1beta/models/${MODEL_ID}:generateContent?key=${process.env.GEMINI_API_KEY}`;\n    log(`Calling Gemini (${MODEL_ID}) …`);\n\n    const gemRes = await fetch(endpoint, {\n        method: 'POST',\n        headers: { 'Content-Type': 'application/json' },\n        body: JSON.stringify({\n            contents: [{\n                parts: [{ text: `${prompt}\\n\\n---\\n\\n${diff}` }]\n            }],\n            generationConfig: {\n                temperature: 0.2,\n                topK: 1,\n                topP: 0.95\n            }\n        })\n    });\n\n    if (!gemRes.ok) {\n        // Extra diagnostics for the common 403\n        if (gemRes.status === 403) {\n            die(`Gemini HTTP 403 ➜ Check that:\n • Generative Language API is **enabled** in your Google Cloud project\n • The API key has **no application restrictions** (or allows server IPs)\n • MODEL_ID (“${MODEL_ID}”) is available to your key`);\n        }\n        die(`Gemini HTTP ${gemRes.status}`);\n    }\n\n    const gemJson = await gemRes.json();\n    \n    // Better error handling for Gemini response\n    if (gemJson.error) {\n        die(`Gemini API error: ${gemJson.error.message || JSON.stringify(gemJson.error)}`);\n    }\n    \n    if (!gemJson.candidates || !gemJson.candidates.length) {\n        if (DEBUG) {\n            console.log('Full Gemini response:', JSON.stringify(gemJson, null, 2));\n        }\n        die('Gemini returned no candidates');\n    }\n    \n    // Check for blocked or filtered responses\n    const candidate = gemJson.candidates[0];\n    if (candidate.finishReason && candidate.finishReason !== 'STOP') {\n        warn(`Response finish reason: ${candidate.finishReason}`);\n        if (candidate.finishReason === 'SAFETY') {\n            warn('Response blocked by safety filters');\n            if (DEBUG && candidate.safetyRatings) {\n                console.log('Safety ratings:', JSON.stringify(candidate.safetyRatings, null, 2));\n            }\n        }\n    }\n    \n    // Log the structure to debug\n    if (DEBUG) {\n        console.log('Candidate structure:', JSON.stringify(candidate, null, 2));\n    }\n    \n    const gemText = candidate?.content?.parts?.[0]?.text ?? '';\n    log(`Gemini returned ${(gemText.length / 1024).toFixed(1)} KB`);\n\n    if (!gemText.trim()) {\n        warn('Gemini response empty – nothing to post');\n        if (DEBUG) {\n            console.log('Full candidate:', JSON.stringify(candidate, null, 2));\n        }\n        return;\n    }\n\n    if (DEBUG) {\n        console.log('=== GEMINI RESPONSE START ===');\n        console.log(gemText);\n        console.log('=== GEMINI RESPONSE END ===');\n    }\n\n    // ─── Handle LGTM response ─────────────────────────────\n    if (gemText.trim() === 'LGTM 🤖👍') {\n        log('No issues found - posting LGTM');\n        const ghRes = await fetch(\n            `https://api.github.com/repos/${process.env.REPO}/pulls/${process.env.PR_NUMBER}/reviews`,\n            {\n                method: 'POST',\n                headers: {\n                    'Authorization': `token ${process.env.GITHUB_TOKEN}`,\n                    'Accept': 'application/vnd.github+json'\n                },\n                body: JSON.stringify({\n                    body: 'LGTM 🤖👍',\n                    event: 'COMMENT'\n                })\n            }\n        );\n        if (!ghRes.ok) die(`GitHub HTTP ${ghRes.status}`);\n        log('LGTM posted successfully');\n        return;\n    }\n\n    // ─── Parse suggestions ───────────────────────────────\n    const comments = [];\n    \n    // First check if response contains \"Code Suggestions\" header\n    const suggestionsSection = gemText.match(/###?\\s*Code Suggestions\\s*\\n([\\s\\S]*)/)?.[1] || gemText;\n    const blocks = suggestionsSection.split(/^-{3,}$/m).map(s => s.trim()).filter(Boolean);\n\n    for (const b of blocks) {\n        // Skip if block is too short or looks like a header\n        if (b.length < 20 || b.match(/^#+\\s/)) continue;\n        \n        // Clean up duplicate File: lines (from examples in prompt)\n        const cleanedBlock = b.replace(/^File:.*\\nFile:/m, 'File:');\n        \n        // More flexible regex patterns for parsing\n        const fileMatch = cleanedBlock.match(/^File:\\s*(.+?)$/m);\n        const lineMatch = cleanedBlock.match(/^Line:\\s*(\\d+)/m);\n        const commentMatch = cleanedBlock.match(/^Comment:\\s*(.+?)$/m);\n        const suggestionMatch = cleanedBlock.match(/```suggestion\\n([\\s\\S]*?)\\n```/);\n        \n        if (fileMatch && lineMatch && commentMatch && suggestionMatch) {\n            const file = fileMatch[1].trim();\n            const line = parseInt(lineMatch[1], 10);\n            const comment = commentMatch[1].trim();\n            const suggestion = suggestionMatch[1];\n            \n            // Validate line number\n            if (isNaN(line) || line < 1) {\n                warn(`Invalid line number ${line} for file ${file}`);\n                continue;\n            }\n            \n            // Clean up file path (remove duplicate \"File:\" prefix if present)\n            const cleanFile = file.replace(/^File:\\s*/, '');\n            \n            // Skip if this looks like an example from the prompt\n            if (cleanFile.includes('ProfileView.swift') && line === 42) {\n                if (DEBUG) console.log('Skipping example suggestion from prompt');\n                continue;\n            }\n            \n            const body = `${comment}\\n\\`\\`\\`suggestion\\n${suggestion}\\n\\`\\`\\``;\n            comments.push({ path: cleanFile, line, side: 'RIGHT', body });\n        } else if (b.includes('File:') || b.includes('Line:')) {\n            // Only warn if it looks like a suggestion block but failed to parse\n            if (DEBUG) {\n                console.log('Failed to parse block:');\n                console.log('File match:', fileMatch);\n                console.log('Line match:', lineMatch);\n                console.log('Comment match:', commentMatch);\n                console.log('Has suggestion:', !!suggestionMatch);\n                console.log('Block content:', b.slice(0, 200));\n            }\n            warn(`Unparsable block (missing ${!fileMatch ? 'file' : !lineMatch ? 'line' : !commentMatch ? 'comment' : 'suggestion'})`);\n        }\n    }\n    log(`Parsed ${comments.length} suggestion(s)`);\n\n    if (!comments.length) {\n        log('No suggestions found after parsing');\n        \n        // Post a comment indicating the review ran but found no specific issues\n        const ghRes = await fetch(\n            `https://api.github.com/repos/${process.env.REPO}/pulls/${process.env.PR_NUMBER}/reviews`,\n            {\n                method: 'POST',\n                headers: {\n                    'Authorization': `token ${process.env.GITHUB_TOKEN}`,\n                    'Accept': 'application/vnd.github+json'\n                },\n                body: JSON.stringify({\n                    body: '🤖 **Code review completed** - No issues found',\n                    event: 'COMMENT'\n                })\n            }\n        );\n        if (!ghRes.ok) warn(`Failed to post completion comment: ${ghRes.status}`);\n        return;\n    }\n\n    // ─── Post review ─────────────────────────────────────\n    log('Posting review to GitHub…');\n    const ghRes = await fetch(\n        `https://api.github.com/repos/${process.env.REPO}/pulls/${process.env.PR_NUMBER}/reviews`,\n        {\n            method: 'POST',\n            headers: {\n                'Authorization': `token ${process.env.GITHUB_TOKEN}`,\n                'Accept': 'application/vnd.github+json'\n            },\n            body: JSON.stringify({\n                body: `🤖 **Code Review** - Found ${comments.length} suggestion${comments.length === 1 ? '' : 's'}`,\n                event: 'COMMENT',\n                comments\n            })\n        }\n    );\n\n    if (!ghRes.ok) die(`GitHub HTTP ${ghRes.status}`);\n    log('Review posted successfully 🎉');\n})().catch((e) => die(e.stack || e));"
  },
  {
    "path": "scripts/run_xcodebuild.sh",
    "content": "#!/bin/bash\n\nset -o pipefail\nset -e\nset -x\n\nROOT_PATH=`dirname \"${0}\"`/..\n\n# Usage: run_xcodebuild.sh <scheme> <derived_data_path> [test|build]\n# If no third parameter is provided, defaults to 'test'\n\nSCHEME=$1\nDERIVED_DATA_PATH=$2\nTARGET_TYPE=${3:-test}\n\n# Validate target type\nif [[ \"$TARGET_TYPE\" != \"test\" && \"$TARGET_TYPE\" != \"build\" ]]; then\n    echo \"Error: Target type must be 'test' or 'build'\"\n    echo \"Usage: run_xcodebuild.sh <scheme> <derived_data_path> [test|build]\"\n    exit 1\nfi\n\n# Validate required parameters\nif [[ -z \"$SCHEME\" || -z \"$DERIVED_DATA_PATH\" ]]; then\n    echo \"Error: Missing required parameters\"\n    echo \"Usage: run_xcodebuild.sh <scheme> <derived_data_path> [test|build]\"\n    exit 1\nfi\n\nif [[ \"$TARGET_TYPE\" == \"test\" ]]; then\n    echo -ne \"\\n\\n *********** RUNNING TESTS $SCHEME *********** \\n\\n\"\n    \n    xcrun xcodebuild \\\n    -destination \"${TEST_DESTINATION}\" \\\n    -workspace \"${ROOT_PATH}/Airship.xcworkspace\" \\\n    -scheme $SCHEME \\\n    -derivedDataPath $DERIVED_DATA_PATH \\\n    test | xcbeautify --renderer $XCBEAUTIFY_RENDERER\nelse\n    echo -ne \"\\n\\n *********** BUILDING $SCHEME *********** \\n\\n\"\n    \n    xcrun xcodebuild \\\n    -destination \"${TEST_DESTINATION}\" \\\n    -workspace \"${ROOT_PATH}/Airship.xcworkspace\" \\\n    -scheme $SCHEME \\\n    -derivedDataPath $DERIVED_DATA_PATH | xcbeautify --renderer $XCBEAUTIFY_RENDERER\nfi\n"
  },
  {
    "path": "scripts/update_version.sh",
    "content": "#!/bin/bash\nVERSION=$1\nROOT_PATH=`dirname \"${0}\"`/../\n\nif [ -z \"$1\" ]\n  then\n    echo \"No version number supplied\"\n    exit 1\nfi\n\n# Initialize counters\nFAILED_COUNT=0\nSUCCESS_COUNT=0\n\necho \"Updating version to $VERSION\"\necho \"\"\n\n# Pods\nif sed -i '' \"s/\\(^AIRSHIP_VERSION *= *\\)\\\".*\\\"/\\1\\\"$VERSION\\\"/g\" $ROOT_PATH/Airship.podspec 2>/dev/null; then\n  echo \"✓ Airship.podspec\"\n  SUCCESS_COUNT=$((SUCCESS_COUNT+1))\nelse\n  echo \"✗ Airship.podspec\"\n  FAILED_COUNT=$((FAILED_COUNT+1))\nfi\n\nif sed -i '' \"s/\\(^AIRSHIP_VERSION *= *\\)\\\".*\\\"/\\1\\\"$VERSION\\\"/g\" $ROOT_PATH/AirshipDebug.podspec 2>/dev/null; then\n  echo \"✓ AirshipDebug.podspec\"\n  SUCCESS_COUNT=$((SUCCESS_COUNT+1))\nelse\n  echo \"✗ AirshipDebug.podspec\"\n  FAILED_COUNT=$((FAILED_COUNT+1))\nfi\n\nif sed -i '' \"s/\\(^AIRSHIP_VERSION *= *\\)\\\".*\\\"/\\1\\\"$VERSION\\\"/g\" $ROOT_PATH/AirshipServiceExtension.podspec 2>/dev/null; then\n  echo \"✓ AirshipServiceExtension.podspec\"\n  SUCCESS_COUNT=$((SUCCESS_COUNT+1))\nelse\n  echo \"✗ AirshipServiceExtension.podspec\"\n  FAILED_COUNT=$((FAILED_COUNT+1))\nfi\n\n# Airship Config\nif sed -i '' \"s/\\CURRENT_PROJECT_VERSION.*/CURRENT_PROJECT_VERSION = $VERSION/g\" $ROOT_PATH/Airship/AirshipConfig.xcconfig 2>/dev/null; then\n  echo \"✓ Airship/AirshipConfig.xcconfig\"\n  SUCCESS_COUNT=$((SUCCESS_COUNT+1))\nelse\n  echo \"✗ Airship/AirshipConfig.xcconfig\"\n  FAILED_COUNT=$((FAILED_COUNT+1))\nfi\n\n# AirshipVersion.swift\nif sed -i '' \"s/\\(public static let version *= *\\)\\\".*\\\"/\\1\\\"$VERSION\\\"/g\" $ROOT_PATH/Airship/AirshipCore/Source/AirshipVersion.swift 2>/dev/null; then\n  echo \"✓ Airship/AirshipCore/Source/AirshipVersion.swift\"\n  SUCCESS_COUNT=$((SUCCESS_COUNT+1))\nelse\n  echo \"✗ Airship/AirshipCore/Source/AirshipVersion.swift\"\n  FAILED_COUNT=$((FAILED_COUNT+1))\nfi\n\n# Summary\necho \"\"\nif [ $FAILED_COUNT -gt 0 ]; then\n  echo \"⚠️  $SUCCESS_COUNT succeeded, $FAILED_COUNT failed\"\n  exit 1\nelse\n  echo \"✓ All $SUCCESS_COUNT files updated successfully\"\n  exit 0\nfi\n"
  }
]